I am trying to create a simple countdown timer, i want to be able to restart it on the Button click, right now it won't start other, i just can not get it right. What am i missing here?
the KV
<RootWidget>:
#:import randint random.randint
orientation: "vertical"
CountDownLbl:
id: anim_label
text: "{0:.3f}".format(float(self.startCount - self.angle / 360))
font_size: 30
canvas:
Color:
rgb: 0,1,0
Line:
circle:self.center_x, self.center_y, 90, 0, self.angle % 360
width: 30
Button:
size_hint_y: 0.1
text: "Start"
on_press: anim_label.start()
and the code
COUNT=1
class RootWidget(FloatLayout):
pass
class CountDownLbl(Label):
startCount = COUNT
angle = NumericProperty(0)
def __init__(self, **kwargs):
super(CountDownLbl, self).__init__(**kwargs)
def start(self):
self.startCount = COUNT
self.anim = Animation(angle=360 * self.startCount, duration=self.startCount)
self.in_progress = True
self.anim.start(self)
class TestApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
TestApp().run()
The problem is that the first animation animates the angle property to 360, and further animations try to animate the angle property to 360 again, which results in no actual animation. The fix is to increment your COUNT after each animation, like this:
def start(self):
global COUNT
self.startCount = COUNT
self.anim = Animation(angle=360 * self.startCount, duration=self.startCount)
self.in_progress = True
self.anim.start(self)
COUNT += 1
Answering my own quesstion, Kudos to John.
Thank's John Anderson. This is the final version. Most important, i am updating text in a function instead of doing it in the KV, and second the increment has to be by TIMER value and not by 1. Latest example can be found here https://github.com/vmindru/amb_pit_timer/blob/main/simple_timer.py
code
TIMER_DURATION = 5
class CountDownLbl(Label):
angle = NumericProperty(0)
timer_duration = NumericProperty(0)
def __init__(self, **kwargs):
super(CountDownLbl, self).__init__(**kwargs)
self.anim_duration = self.timer_duration
self.in_progress = False
def start(self):
if not self.in_progress:
self.anim = Animation(angle=360 * self.anim_duration, duration=self.timer_duration)
self.in_progress = True
self.anim.bind(on_complete=self.finish, on_progress=self.update_timer)
self.anim.start(self)
self.anim_duration += TIMER_DURATION
def finish(self, animation, widget):
widget.text = "FINISHED"
self.in_progress = False
def update_timer(self, animation, widget, progression):
text = ((self.timer_duration * 60000)*(1-progression))/60000
widget.text = "{0:.3f}".format(float(text))
kv
CountDownLbl:
id: anim_label
timer_duration: 5
font_size: 30
text: "{}.000".format(self.timer_duration)
canvas:
Color:
rgb: 0,1,0
Line:
circle:self.center_x, self.center_y, 90, 0, self.angle % 360
width: 30
Related
Imagine I have two buttons, if I press button1 I earn a point and if I press button2 I lose a point. When I reach 10 points, the game stops. The way I have been doing it right now calls the GameApp().run() every time I click a button to "re-launch" the interface, however if I do it too many times I get a RecursionError. I tried doing it with a while loop but the interface never launches.
Note: this is a simplified version of my program, it has to be done this way (essentially the number of button changes everytime a button is pressed, so the layout updates accordingly, that's why I call GameApp().run() over and over).
n = 0
class Game(GridLayout):
def __init__(self):
super(InterfacesGame, self).__init__()
global n
self.rows = 2
if n < 10:
button1 = Button(text="1")
button2 = Button(text="2")
button1.bind(on_press = self.add)
button2.bind(on_press = self.substract)
self.add_widget(button1)
self.add_widget(button2)
def add(self):
global n
n += 1
GameApp().run()
def substract(self):
global n
n -= 1
GameApp().run()
class GameApp(App):
def build(self):
return Game()
if __name__ == '__main__':
GameApp().run()
So eventually if I do too many clicks without reaching n = 10, I naturally get a
RecursionError: maximum recursion depth exceeded calling class app
error.
I tried doing this:
n = 0
class Game(GridLayout):
def __init__(self):
super(InterfacesGame, self).__init__()
global n
self.rows = 2
while n < 10:
button1 = Button(text="1")
button2 = Button(text="2")
button1.bind(on_press = self.add)
button2.bind(on_press = self.substract)
self.add_widget(button1)
self.add_widget(button2)
def add(self):
global n
n += 1
def substract(self):
global n
n -= 1
class GameApp(App):
def build(self):
return Game()
if __name__ == '__main__':
GameApp().run()
But the GUI doesn't run (it keeps loading and loading).
Based on your question and your following comment :
When 10 points are reached, the layout is remplaced by a single button with the text "You won!", and when clicking on it, the game starts all over again (with n = 0).
I implemented a complete example using Screens and ScreenManager.
from kivy.app import App
from kivy.lang import Builder
kv = ("""
ScreenManager:
id: sm
Screen:
name: "First Screen"
BoxLayout:
orientation: "vertical"
Button:
text: "1"
on_release: app.add_point()
Button:
text: "2"
on_release: app.subtract_point()
Screen:
name: "Second Screen"
Label:
text: "You Won"
font_size: "40sp"
Button:
text: "Play again"
size_hint: 0.75, None
height: "50dp"
pos_hint: {"center_x" : 0.5, "center_y" : 0.25}
on_release: sm.current = "First Screen"
""")
class GameApp(App):
count = 0
def build(self):
self.root = Builder.load_string(kv)
return self.root
def add_point(self):
if self.count < 10:
self.count += 1
else:
self.root.current = "Second Screen"
self.count = 0 # Reset counter.
def subtract_point(self):
if 0 < self.count: # Assuming only positive values.
self.count -= 1
GameApp().run()
I am having a custom widget with a rectangle and horizontal line both created using Vertex Instruction. I want to check whether users is touching within rectangle or horizontal line in my widget. Tried using Group but unable to find whether user touched rectangle or Line. Can you please provide me with clue.
Find below sample code.
from kivy.app import App
from kivy.graphics import Line
from kivy.uix.scatter import Scatter
from kivy.uix.relativelayout import RelativeLayout
from kivy.lang import Builder
KV = '''
<Actor>:
id: Actor
canvas:
Color:
rgba: 0,1,0,1
Rectangle:
group: 'rect'
size: 100, 30
pos: 0, root.height - 30
Line:
group: 'line'
points: 50, root.height - 30, 50, 0
width:2
Label:
id: _actr_lbl
text: 'Hello World'
markup: True
color: 0,0,0,1
size_hint: None, None
size: 100, 30
pos: 0, root.height - 30
'''
class Actor(Scatter):
def __init__(self, **kwargs):
super(Actor, self).__init__(**kwargs)
def on_touch_down(self, touch):
print('Touch location {} Actor location {} Actor Size {}'.format(touch, self.pos, self.size))
if self.collide_point(*touch.pos) :
for aVertex in self.canvas.get_group('rect') :
try:
print ('Vertex size {} and pos'.format(aVertex.size, aVertex.pos))
except:
pass
return True
return super(Actor, self).on_touch_down(touch)
class MyPaintApp(App):
def build(self):
Builder.load_string(KV)
root = RelativeLayout()
root.add_widget(Actor(pos_hint={'center_x':0.5, 'center_y':0.5}, size_hint=(.2, 1.)))
return root
if __name__ == '__main__':
MyPaintApp().run()
Thanks in advance
You can do a simple bounding box check, but you must take into account the fact that the touch is in the parent coordinate system. So you can convert the touch position to local coordinates, then do the test. Here is an example for the Rectangle:
def on_touch_down(self, touch):
print('Touch location {} Actor location {} Actor Size {}'.format(touch, self.pos, self.size))
if self.collide_point(*touch.pos) :
localTouchPos = self.to_local(*touch.pos)
for aVertex in self.canvas.get_group('rect') :
print('\tVertex size {} and pos {}'.format(aVertex.size, aVertex.pos))
intersection = True
if localTouchPos[0] < aVertex.pos[0]:
intersection = False
elif localTouchPos[0] > aVertex.pos[0] + aVertex.size[0]:
intersection = False
if localTouchPos[1] < aVertex.pos[1]:
intersection = False
elif localTouchPos[1] > aVertex.pos[1] + aVertex.size[1]:
intersection = False
print('intersection =', intersection)
return True
return super(Actor, self).on_touch_down(touch)
You can do something similar for the Line, but it may be a bit more complicated if you want to do a general Line. If your Line is always vertical, it hould be very similar.
How do you update a progress bar that is displayed in Kivy. In the following example I get AttributeError: 'super' object has no attribute '__getattr__'. The issue is in the following line
self.ids.progress.value = value
I can see why since the progressBar widget is in <LoadingPopup> and not <MainScreen> but after trying several different things I can't reference the progressBar widget in <LoadingPopup> from the do_update method.
Thanks in advance
import threading
from functools import partial
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import StringProperty
from kivy.uix.spinner import Spinner
from kivy.app import App
from kivy.clock import Clock
from kivy.uix.popup import Popup
Builder.load_string('''
<LoadingPopup>:
title: "Popup"
size_hint: None, None
size: 400, 400
auto_dismiss: False
BoxLayout:
orientation: "vertical"
ProgressBar:
id: progress
size_hint: (1.0, 0.06)
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Spinner:
id: first
text: ' First Number'
values: ['1','2','3','4','5','6','7','8','9']
Spinner:
id: second
text: ' Second Number'
values: ['1','2','3','4','5','6','7','8','9']
Label:
id: result
text: ' Result'
color: 0,0,0,1
Button:
id: res
on_press: root.doit_in_thread(first.text,second.text)
text: 'Multiply'
''')
class LoadingPopup(Popup):
def __init__(self, obj, **kwargs):
super(LoadingPopup, self).__init__(**kwargs)
class MainScreen(FloatLayout):
changet = StringProperty()
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
def doit_in_thread(self, fir, sec):
popup = LoadingPopup(self)
popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, popup)).start()
def do_update(self, value, text, *args):
self.ids.progress.value = value
self.ids.result.text = text
def onMul(self,fir,sec, popup):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
popup.dismiss()
class TestApp(App):
def build(self):
return MainScreen()
if __name__ == "__main__":
TestApp().run()
One way to do it, is to keep a reference of the popup so you can address it later:
def __init__(self, **kwargs):
super(MainScreen, self).__init__(**kwargs)
self.popup = LoadingPopup(self)
def doit_in_thread(self, fir, sec):
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec, self.popup)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
Python Code
Replace all references of popup with self.popup so that you can access it in the methods inside class MainScreen.
Replace self.ids.progress.value with self.popup.ids.progress.value
Since a = (int(fir)*int(sec), replace str(b*(int(fir)*int(sec))) with str(b * a)
Don't need to pass popup to onMul method, access it using self.popup
Snippet
class MainScreen(FloatLayout):
changet = StringProperty()
def doit_in_thread(self, fir, sec):
self.popup = LoadingPopup(self)
self.popup.open()
threading.Thread(target=partial(self.onMul, fir, sec)).start()
def do_update(self, value, text, *args):
self.popup.ids.progress.value = value
self.ids.result.text = text
def onMul(self, fir, sec):
a = (int(fir)*int(sec))
print(a)
b = 0
old_value = 0
endRange = 1000000
for i in range(endRange):
progress = int(((i+1)*100)/endRange)
if progress != old_value and progress % 5 == 0:
text = str(b*(int(fir)*int(sec)))
# text = str(b * a)
print("\ttext=", text)
Clock.schedule_once(partial(self.do_update, progress, text))
old_value = progress
b+=1
self.popup.dismiss()
kv file
The result is not visible because the default background colour of Label widget is black, and the text colour for result is also set to black, color: 0,0,0,1. Therefore, remove color: 0,0,0,1.
Snippet
Label:
id: result
text: ' Result'
Output
When I click on the buttons in the left part, the label "Top 10 Plays of 2015" should be changed with the text of the button that I've clicked. I can see the variable changing text but the label is not changing text.
this is my work: when I click the buttons on left side the title in the middle should change:
python
class MainApp(Screen, EventDispatcher):
vid = StringProperty("Videos/Top 10 Plays of 2015.mp4")
title = StringProperty("Top 10 Plays of 2015")
def __init__(self,*args,**kwargs):
super(MainApp,self).__init__(*args, **kwargs)
pass
class OtherVideos(BoxLayout, EventDispatcher):
root = MainApp
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.loadVideos()
def loadVideos(self):
con = MongoClient()
db = con.nba
vids = db.videos.find()
vidnum = 1
for filename in vids:
myid = "vid" + str(vidnum)
getfilename = filename['filename']
button = Button(id=myid,
text=getfilename,
color=[0,0.7,1],
bold=1)
button.bind(on_release=lambda x:(self.change_Title(getfilename), self.change_Vid(getfilename)))
self.add_widget(button)
vidnum += 1
def change_Title(self, title):
self.root.title = title
def change_Vid(self, myfilename):
con = MongoClient()
db = con.nba
vids = db.videos.find()
for filename in vids:
file = os.path.basename(filename['video_path'])
if myfilename == filename['filename']:
self.root.vid = filename['video_path']
break
pass
kivy
BoxLayout:
orientation: 'vertical'
Label:
id: lblTitle
text: root.title
size_hint_y: None
height: 40
font_size: 25
bold: 1
canvas.before:
Color:
rgba: 1, 0, 0, 0.7
Rectangle:
pos: self.pos
size: self.size
VideoPlayer:
id: activeVid
source: root.vid
state: 'play'
when I'm printing root.title it's changing its text but the label is not changing which it should because it's getting its text from that variable.
But when I'm putting the change_title() method in __init__ of OtherVideos like this, the label is changing:
class OtherVideos(BoxLayout, EventDispatcher):
root = MainApp
def __init__(self, *args, **kwargs):
super(OtherVideos,self).__init__(*args, **kwargs)
self.change_Title("my title")
This is already taking me days of work, anyone can help?
There are several problems with your code:
Every widget inherits from EventDispatcher, so inheriting from it the second time is pointless.
This root = MainApp is not a reference to the MainApp (object), but to its class.
You don't need to specify a StringProperty to achieve your goal.
The easiest way to do this is to add a reference of the label you want to change in the function change_Title:
def change_Title(self, title):
# self.root.title = title
self.ids.my_label_to_change.text = title
Trying to make a brick widget change size each time it respawns.
The py file has
class Game(FloatLayout):
player = ObjectProperty(None)
playbutton = ObjectProperty(None)
ratebutton = ObjectProperty(None)
brickg = ObjectProperty(None)
ballsin = NumericProperty(0)
bricklist = ListProperty([])
score = NumericProperty(0)
switch = NumericProperty(0)
level = NumericProperty(0)
def __init__(self, *args, **kwargs):
super(Game, self).__init__(*args, **kwargs)
Clock.schedule_interval(self.update, 1./60)
def spawn_brick(self, *args):
b2 = BrickGreen(x=randint(50, self.width - 50), \
y=randint(self.height - self.height / 4, self.height - (self.height/13)))
self.bricklist.append(b2)
self.add_widget(b2)
def check_brick_spawn(self, *args):
if len(self.bricklist) == 0:
if self.level == 0:
BrickGreen.brickwidth = 100
self.spawn_brick()
elif self.level == 1:
BrickGreen.brickwidth = 75
self.spawn_brick()
else:
BrickGreen.brickwidth = 50
self.spawn_brick()
class BrickGreen(Widget):
def __init__(self, **kwargs):
super(BrickGreen, self).__init__(**kwargs)
brickwidth = NumericProperty(0)
and the kv file has
<BrickGreen>:
size_hint: None, None
size: self.brickwidth, 25
canvas:
Color:
rgba: 0, 1, 0, 1
Rectangle:
pos: self.pos
size: self.size
Essentially, level 0 should have bricks with length of 100, once it breaks it goes to level 1 and should spawn bricks with lenght of 75, but it's staying at 100
BrickGreen.brickwidth = 100
This replaces the NumericProperty at class level with the number 100. You need to set the value of brickwidth for instances of your objects, not the classes themselves. In this case, you could (for instance) pass the width you want to your spawn_brick method, and use it when instantiating the BrickGreen.
You also need to declare the NumericProperty at class level, not inside __init__.
I suggest reading the kivy documentation on properties and going through the examples to see how these should be used.