I am trying to create a clock with 3 arcs, one for hours, minutes, and seconds. The app currently draws each of the arcs, with different colors and radii as expected. The arcs also fill according to the time at application launch. The red arc represents hours, the green arc represents minutes, and the blue arc represents seconds. From here, I am trying to update the canvas so that the arcs change according to the time (once every second). I would also like a label that displays the time in the center of the clock. Can anyone help me with this problem? I am relatively new to Kivy so I'm not really sure what I should be doing.
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.graphics import Color, Line
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from datetime import datetime
# update canvas every second
# display time in center of clock
class drawClock(FloatLayout):
def __init__(self, **kwargs):
super(drawClock, self).__init__(**kwargs)
self.bind(pos=self.updateClock)
self.bind(size=self.updateClock)
def updateClock(self, *kwargs):
with self.canvas:
now = datetime.now()
hour = now.hour
minute = now.minute
second = now.second
hourAngle = (hour%12) * 30
minuteAngle = minute * 6
secondAngle = second * 6
Line(circle = (self.center_x, self.center_y, 80, 0, hourAngle), width = 2, color = Color(1,0,0))
Line(circle = (self.center_x, self.center_y, 65, 0, minuteAngle), width = 2, color = Color(0,1,0))
Line(circle = (self.center_x, self.center_y, 50, 0, secondAngle), width = 2, color = Color(0,0,1))
class mainApp(App):
def build(self):
return drawClock()
if __name__ == '__main__':
mainApp().run()
You have to use the Clock.schedule_interval() to update the painting, but instead of creating new Lines you must reuse them so you save resources:
from kivy.app import App
from kivy.graphics import Color, Line
from kivy.uix.floatlayout import FloatLayout
from kivy.clock import Clock
from datetime import datetime
class drawClock(FloatLayout):
def __init__(self, **kwargs):
super(drawClock, self).__init__(**kwargs)
self.bind(pos=self.updateClock)
self.bind(size=self.updateClock)
Clock.schedule_interval(self.updateClock, 1)
self.create_canvas()
def create_canvas(self):
with self.canvas:
self.hour_line = Line(width = 2, color = Color(1,0,0))
self.minute_line = Line(width = 2, color = Color(0,1,0))
self.second_line = Line(width = 2, color = Color(0,0,1))
def updateClock(self, *kwargs):
now = datetime.now()
hour = now.hour
minute = now.minute
second = now.second
hourAngle = (hour%12) * 30
minuteAngle = minute * 6
secondAngle = second * 6
self.hour_line.circle = self.center_x, self.center_y, 80, 0, hourAngle
self.minute_line.circle = self.center_x, self.center_y, 65, 0, minuteAngle
self.second_line.circle = self.center_x, self.center_y, 50, 0, secondAngle
class mainApp(App):
def build(self):
return drawClock()
if __name__ == '__main__':
mainApp().run()
Use the update() method. This is a crude way, but it will give you what you want. The update method basically updates the window.
A more efficient way to do it is use the after() method.
The after basically tells what to do after, so figure it out.
window.after(delay_ms, callback=None, *args)
Those are the paramters for after().
For example, this would be to wait one second then carry out func up
window.after(1000, up)
Related
I want to make an internet speed test application for Android with Python.
I have done the back-end side but I have a hard time with the front-end.
After research, I decided to use the Kivy framework, however, I need guidance on how to create a gauge like this.
There are two ways to create a gauge. The first one is by using code with mathematics and the second one is by using graphic files for cadran and needle. Below, you will find the implementation of the second way, with the help of the original code of the gauge widget of Kivy Garden project, in which you will be able to understand how the gauge works more easily.
import kivy
kivy.require('1.6.0')
from kivy.app import App
from kivy.clock import Clock
from kivy.properties import NumericProperty
from kivy.properties import StringProperty
from kivy.properties import BoundedNumericProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.progressbar import ProgressBar
from os.path import join, dirname, abspath
class Gauge(Widget):
'''
Gauge class
'''
unit = NumericProperty(1.8)
value = BoundedNumericProperty(0, min=0, max=100, errorvalue=0)
path = dirname(abspath(__file__))
file_gauge = StringProperty(join(path, "cadran.png"))
file_needle = StringProperty(join(path, "needle.png"))
size_gauge = BoundedNumericProperty(128, min=128, max=256, errorvalue=128)
size_text = NumericProperty(10)
def __init__(self, **kwargs):
super(Gauge, self).__init__(**kwargs)
self._gauge = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_gauge = Image(
source=self.file_gauge,
size=(self.size_gauge, self.size_gauge)
)
self._needle = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_needle = Image(
source=self.file_needle,
size=(self.size_gauge, self.size_gauge)
)
self._glab = Label(font_size=self.size_text, markup=True)
self._progress = ProgressBar(max=100, height=20, value=self.value)
self._gauge.add_widget(_img_gauge)
self._needle.add_widget(_img_needle)
self.add_widget(self._gauge)
self.add_widget(self._needle)
self.add_widget(self._glab)
self.add_widget(self._progress)
self.bind(pos=self._update)
self.bind(size=self._update)
self.bind(value=self._turn)
def _update(self, *args):
'''
Update gauge and needle positions after sizing or positioning.
'''
self._gauge.pos = self.pos
self._needle.pos = (self.x, self.y)
self._needle.center = self._gauge.center
self._glab.center_x = self._gauge.center_x
self._glab.center_y = self._gauge.center_y + (self.size_gauge / 4)
self._progress.x = self._gauge.x
self._progress.y = self._gauge.y + (self.size_gauge / 4)
self._progress.width = self.size_gauge
def _turn(self, *args):
'''
Turn needle, 1 degree = 1 unit, 0 degree point start on 50 value.
'''
self._needle.center_x = self._gauge.center_x
self._needle.center_y = self._gauge.center_y
self._needle.rotation = (50 * self.unit) - (self.value * self.unit)
self._glab.text = "[b]{0:.0f}[/b]".format(self.value)
self._progress.value = self.value
if __name__ == '__main__':
from kivy.uix.slider import Slider
class GaugeApp(App):
increasing = NumericProperty(1)
begin = NumericProperty(50)
step = NumericProperty(1)
def build(self):
box = BoxLayout(orientation='horizontal', padding=5)
self.gauge = Gauge(value=50, size_gauge=256, size_text=25)
self.slider = Slider(orientation='vertical')
stepper = Slider(min=1, max=25)
stepper.bind(
value=lambda instance, value: setattr(self, 'step', value)
)
box.add_widget(self.gauge)
box.add_widget(stepper)
box.add_widget(self.slider)
Clock.schedule_interval(lambda *t: self.gauge_increment(), 0.03)
return box
def gauge_increment(self):
begin = self.begin
begin += self.step * self.increasing
if 0 < begin < 100:
self.gauge.value = self.slider.value = begin
else:
self.increasing *= -1
self.begin = begin
GaugeApp().run()
Of course, if you don't want to use the default cadran and needle, you will have to design your own, using a vector graphics editor.
import kivy
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.clock import Clock
from kivy.graphics import Rectangle
from kivy.core.image import Image as CoreImage
from kivy.core.window import Window
class Background(Widget):
def __init__(self, **kw):
super(Background, self).__init__(**kw)
with self.canvas:
texture = CoreImage('space.png').texture
texture.wrap = 'repeat'
self.rect_1 = Rectangle(texture=texture, size=self.size, pos=self.pos)
Clock.schedule_interval(self.txupdate, 0)
def txupdate(self, *l):
t = Clock.get_boottime()
self.rect_1.tex_coords = -(t * 0.001), 0, -(t * 0.001 + 10), 0, -(t * 0.001 + 10), -10, -(t * 0.001), -10
class CosmicPolygons(App):
def build(self):
return Background(size=Window.size)
if __name__ == "__main__":
CosmicPolygons().run()
I've tried many different ways and attempts in order to create a scrolling background in Kivy. This was the best method I could find as it was the only one that didn't crash. But I'm pretty sure it's still outdated as it did not work as intended, in addition to distorting my png greatly.
If anyone has any ideas on how to achieve this, let me know. Thanks in advance.
Image of current app:
Space.png:
Here is another approach that just creates a list of Images and moves them across the screen:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.image import Image
from kivy.clock import Clock
from kivy.core.window import Window
class BgImage(Image):
pass
Builder.load_string('''
<BgImage>:
source: 'stars.png'
allow_stretch: True
size_hint: None, 1
width: self.image_ratio * self.height
''')
class Background(FloatLayout):
def __init__(self, **kwargs):
super(Background, self).__init__(**kwargs)
self.deltax = 3 # sets speed of background movement
self.bg_images = [] # list of Images that form the background
Clock.schedule_once(self.set_up_bg)
def set_up_bg(self, dt):
# create BgImages and position them to fill the Background
self.start_x = None
pos_x = -1
while pos_x < self.width:
img = BgImage()
if self.start_x is None:
# starting position of first Image is just off screen
self.start_x = -self.height * img.image_ratio
pos_x = self.start_x
img.pos = (pos_x, 0)
self.bg_images.append(img)
self.add_widget(img)
# calculate starting position of next Image by adding its width
pos_x += self.height * img.image_ratio
# start moving the background
Clock.schedule_interval(self.update, 1.0/30.0)
def update(self, dt):
for img in self.bg_images:
img.x += self.deltax
if img.x > self.width:
# this Image is off screen, move it back to starting position
img.x = self.start_x
class CosmicPolygons(App):
def build(self):
return Background(size=Window.size)
if __name__ == "__main__":
CosmicPolygons().run()
Ok, My question goes this way How Can I Animate a Label in Kivy, My apologies if this question is annoyingly way too easy :)
Here is the Label in which I want animation. Can I have edits of the code in the comments?
Actually, I am obsessed with this script, racking my brains off to know the way to animate this thingy!! Pls Help!..
import kivy
from kivy.app import App
from kivy.uix.label import Label
class MyApp(App):
def build(self):
lu = Label(text = "This is a label! Pls help me with Animation!")
return lu
if __name__ == '__main__':
MyApp().run()
If you want to update text on Label every few seconds then you can use Clock and Clock.schedule_interval(function_name, seconds) to execute function_name(dt) every seconds and in this function you should change text in Label
Minimal example which displays current time.
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
import datetime
def update_label(dt):
new_text = datetime.datetime.now().strftime('%H:%M:%S')
label.text = new_text
#print(new_text)
label = None # create global variable to access the same `label` in two different functions
class MyApp(App):
def build(self):
global label # inform function to assign `Label` to global variable
label = Label(text="???")
Clock.schedule_interval(update_label, 1)
return label
#Clock.schedule_interval(update_label, 1)
if __name__ == '__main__':
MyApp().run()
EDIT:
Another example which scroll text
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
import datetime
label = None
text = 'Hello World of Python!'
text_length = len(text)
index = 0
temp_text = text + ' ' + text
def update_label(dt):
global index
label.text = temp_text[index:index+15]
index += 1
if index >= text_length:
index = 0
class MyApp(App):
def build(self):
global label
label = Label(text="???")
Clock.schedule_interval(update_label, 0.20)
return label
if __name__ == '__main__':
MyApp().run()
EDIT:
For numerical values you can use Animation.
Here is blinking text.
It changes color to black (in 0.2 second) and next it change back to white (in 0.2 second). And it repeats it.
from kivy.app import App
from kivy.uix.label import Label
from kivy.animation import Animation
class MyApp(App):
def build(self):
label = Label(text='Hello World of Python!')
anim = Animation(color=(0, 0, 0, 1), duration=.2) + Animation(color=(1, 1, 1, 1), duration=.2)
anim.repeat = True
anim.start(label)
return label
if __name__ == '__main__':
MyApp().run()
I am trying to dynamically change the background color of a Label in Kivy with this simple program. it is intended to produce a grid of red and black cells. However, all I get is a red cell in position (7,0) (I am printing the positions).
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Color, Rectangle
class LabelX(Label):
def set_bgcolor(self,r,b,g,o,*args):
self.canvas.after.clear()
with self.canvas.after:
Color(r,g,b,o)
Rectangle(pos=self.pos,size=self.size)
class MyGrid(GridLayout):
def __init__(self,cols,rows,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.cols = cols
for i in range(rows):
for j in range(cols):
l = LabelX(text=str(i)+","+str(j))
if (i*rows+j)%2:
l.set_bgcolor(1,0,0,1)
else:
l.set_bgcolor(0,0,0,1)
self.add_widget(l)
class GridApp(App):
def build(self):
g = MyGrid(8,8)
return g
if __name__=="__main__":
GridApp().run()
Any ideas on how to get a red/black checker board?
If you print self.pos using the following:
with self.canvas.after:
[...]
print(self.pos)
[...]
only the values [0, 0] are obtained, and that leads to the conclusion that all the rectangles are drawn in that position, so they are superimposed, and that is what you observe.
For example, to verify this we pass a parameter more to the function set_bgcolor () that will be the index of the loop and we will use it as a parameter to locate the position:
def set_bgcolor(self, r, b, g, o, i):
[..]
Rectangle(pos=[100*i, 100*i],size=self.size)
class MyGrid(GridLayout):
def __init__(self,cols,rows,**kwargs):
[...]
if (i*rows+j)%2:
l.set_bgcolor(1,0,0,1, i)
else:
l.set_bgcolor(0,0,0,1, i)
self.add_widget(l)
We get the following:
also if we change the size of the window the rectangle does not change either:
So if you want the Rectangle to be the bottom of the Label, the position and size of the Rectangle should be the same as the Label, so you have to make a binding with both properties between the Label and Rectangle. You must also use canvas.before instead of canvas.after otherwise the Rectangle will be drawn on top of the Label.
class LabelX(Label):
def set_bgcolor(self,r,b,g,o):
self.canvas.before.clear()
with self.canvas.before:
Color(r,g,b,o)
self.rect = Rectangle(pos=self.pos,size=self.size)
self.bind(pos=self.update_rect,
size=self.update_rect)
def update_rect(self, *args):
self.rect.pos = self.pos
self.rect.size = self.size
The advantage of my solution is that it is independent of the external widget or layout since the position and size of the Rectangle only depends on the Label.
The problem is that each labels position is (0,0) and size is (100,100) until the app is actually built. so all your canvas drawing is done at those positions and sizes. You can get the checkerboard effect by waiting until after the positions and sizes are assigned, then doing the checkerboard effect. I do this with a Clock.schedule_once:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.graphics import Color, Rectangle
from kivy.clock import Clock
from kivy.core.window import Window
class LabelX(Label):
def set_bgcolor(self,r,b,g,o,*args):
self.canvas.after.clear()
with self.canvas.after:
Color(r,g,b,o)
Rectangle(pos=self.pos,size=self.size)
class MyGrid(GridLayout):
def __init__(self,cols,rows,**kwargs):
super(MyGrid,self).__init__(**kwargs)
self.cols = cols
for i in range(rows):
for j in range(cols):
l = LabelX(text=str(i)+","+str(j))
l.rowcol = (i,j)
self.add_widget(l)
class GridApp(App):
def build(self):
self.g = MyGrid(8,8)
Window.bind(size=self.checkerboard)
return self.g
def checkerboard(self, *args):
for l in self.g.children:
count = l.rowcol[0] + l.rowcol[1]
if count % 2:
l.set_bgcolor(1, 0, 0, 1)
else:
l.set_bgcolor(0, 0, 0, 1 )
if __name__=="__main__":
app = GridApp()
Clock.schedule_once(app.checkerboard, 1)
app.run()
I am trying to create a line and update is after a fixed interval of time (say 5 seconds). I wrote the code below but it does not update the line. Can anyone help me figure out who to do it?
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.widget import Widget
from kivy.lang import Builder
from kivy.graphics import Color, Ellipse, Line
import time
from kivy.clock import Clock
class MyWidget(Widget):
def my_callback(dt,ds):
Line(points=[100, 100, 200, 100, 100, 200], width=10)
pass
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
with self.canvas:
self.line = Line(points=[100, 100, 200, 100, 100, 200], width=1)
self.line.width = 2
Clock.schedule_once(self.my_callback, 5)
pass
# add your instruction for main canvas here
with self.canvas.before:
pass
# you can use this to add instructions rendered before
with self.canvas.after:
pass
# you can use this to add instructions rendered after
class LineExtendedApp(App):
def build(self):
root = GridLayout(cols=2, padding=50, spacing=50)
root.add_widget(MyWidget())
return root
if __name__ == '__main__':
LineExtendedApp().run()
my_callback is not actually called within the with statement but rather 5s later when that is long gone, and even it if was it wouldn't do what you want - it would draw a new Line, not modify the existing one.
Instead you can change my_callback to do:
self.line.points = [100, 100, 200, 100, 100, 200]
self.line.width = 10
This will modify the existing line as you want. You can also take the clock scheduling out of the with statement but its position doesn't actually matter as long as it stays in the __init__