Posted
almost 5 years
ago
by
Alexander Taylor
This ZDNet article
was published a few days ago about how “Python apps might soon be
running on Android”. It summarises some recent developments in Android
support for CPython, but disappointingly it’s highly misleading about
some key points. In particular the article states that “apps written
in Python may …
|
Posted
almost 5 years
ago
by
Alexander Taylor
This ZDNet article
was published a few days ago about how "Python apps might soon be
running on Android". It summarises some recent developments in Android
support for CPython, but disappointingly it’s highly misleading about
some key p...
|
Posted
over 5 years
ago
by
Alexander Taylor
This post collates various resources for getting started with the
Kivy graphical framework for Python.
Installation
Follow the official installation documentation.
Introductory resources
Official Kivy docs
The official Kivy “Getting Started”
... [More]
pages. These
cover general introductory concepts, but are a bit eclectic.
Kivy’s official Pong game tutorial
Kivy’s …
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
This post collates various resources for getting started with the
Kivy graphical framework for Python.
Installation
Follow the official installation documentation.
Introductory resources
Official Kivy docs
The official Kivy "Getting ...
|
Posted
over 5 years
ago
by
Alexander Taylor
This is number 9 in a series of introductory Kivy tutorials.
Central themes: Passing data between widgets, creating Kivy properties
This tutorial directly follows on from the previous, so start by
retrieving the previous code, as below:
main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout …
|
Posted
over 5 years
ago
by
Alexander Taylor
This is number 9 in a series of introductory Kivy tutorials.
Central themes: Passing data between widgets, creating Kivy properties
This tutorial directly follows on from the previous, so start by
retrieving the previous code, as below:
main.py:
from
... [More]
kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Line
from random import random
class DrawingWidget(Widget):
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
if not self.collide_point(*touch.pos):
return
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
class Interface(BoxLayout):
pass
class DrawingApp(App):
def build(self):
root_widget = Interface()
return root_widget
DrawingApp().run()
drawing.kv:
<DrawingWidget>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<ColourSlider@Slider>:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 50
<Interface>:
orientation: 'vertical'
DrawingWidget:
ColourSlider:
id: red_slider
ColourSlider:
id: green_slider
ColourSlider:
id: blue_slider
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 50
Label:
text: 'output colour:'
Widget:
canvas:
Color:
rgb: red_slider.value, green_slider.value, blue_slider.value
Rectangle:
size: self.size
pos: self.pos
With this code, you should still be able to draw in the DrawingWidget
region of the app interface, but the lines still have a random colour
each time. Our final task is to make the lines use the colour selected
via the sliders.
Let’s start by reassessing where the app’s state should be held. For a
start, we need to store somewhere the target colour for the
lines. It’s natural to put this inside the DrawingWidget, since this
class is what does the drawing and needs to know what colour to use.
The best way to store this data is to use a Kivy property of our
own. We’ve made use of many Kivy properties of other widgets already,
but this time there isn’t one already created to hold the colour, so
it’s time to create one.
Change the DrawingWidget code as follows:
from kivy.properties import ListProperty
class DrawingWidget(Widget):
target_colour_rgb = ListProperty([0, 0, 0])
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
if not self.collide_point(*touch.pos):
return
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
That’s all it takes to define a new Kivy property, and it
automatically has all the behaviour you’ve seen so far. For instance,
if you change the target_colour_rgb of a DrawingWidget instance,
an event is automatically dispatched. In fact because this is a
ListProperty an event will be dispatched even if we just change the
value of an item of the list! There are other types of Kivy property
for ensuring correct event dispatching with different types of object
(list, dict, int/float, generic objects etc.), which you can find in
the documentation.
Note: It may look a little strange that we’ve defined the property
at the class level (no reference to self.target_colour_rgb), and
it is clearly a ListProperty and not an actual list so how does
accessing its values work? The answer is that Kivy properties are
descriptors,
which are defined at the class level but here are coded to behave
like normal lists/ints/whatever when accessed from a class
instance. You don’t need to worry about these details, just consider
the properties as normal attributes of your objects when accessing them.
As an example of what that really means, lets hook up the property to
change so that we can respond to these events. Change your kv file
rule to read as follows:
<Interface>:
orientation: 'vertical'
DrawingWidget:
target_colour_rgb: red_slider.value, green_slider.value, blue_slider.value # <- new line
ColourSlider:
id: red_slider
ColourSlider:
id: green_slider
ColourSlider:
id: blue_slider
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 50
Label:
text: 'output colour:'
Widget:
canvas:
Color:
rgb: red_slider.value, green_slider.value, blue_slider.value
Rectangle:
size: self.size
pos: self.pos
Note that the only new line here sets the value of target_colour_rgb
based on the values of the sliders. We’re once again taking advantage
of automatic kv event binding: whenever any of red_slider.value,
green_slider.value or blue_slider.value changes then this line
will be re-evaluated to update target_colour_rgb. We can add
some code to prove that it’s working, via a new method in the
DrawingWidget class:
def on_target_colour_rgb(self, instance, value):
print(f"target_colour_rgb changed to {self.target_colour_rgb}")
A method with this name will be called automatically whenever the
target_colour_rgb property changes - this is another handy feature
of Kivy event dispatching, instead of binding explicitly this default
event method is always available. You can consider that code something
like self.bind(target_colour_rgb=self.on_target_colour_rgb) has
been automatically run to create the event binding.
Now, run the application and move the values of the sliders. You
should see code printed in your terminal every time a slider moves,
because every movement updates the value of target_colour_rgb:
target_colour_rgb changed to [0.20853658536585365, 0.6012195121951219, 0.4573170731707317]
target_colour_rgb changed to [0.20853658536585365, 0.6012195121951219, 0.4585365853658537]
target_colour_rgb changed to [0.20853658536585365, 0.6012195121951219, 0.45975609756097563]
Note: The colour changes in this example are very small because
you’re getting an update every time the slider moves even a single pixel!
The final step is to make the DrawingWidget use this target colour
for the next line it draws. For this we just have to update the
on_touch_down method:
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(*self.target_colour_rgb) # <- this line changed
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
That’s all there is to it! When we make the new Color instruction for
the new line, we pass in the current value of our property instead of
selecting random values.
Run the app now and every line should match your currently selected colour:
For a further example, let’s add a similar method to set the Line
width. Again, we add a Kivy property to DrawingWidget:
from kivy.properties import ListProperty, NumericProperty
class DrawingWidget(Widget):
target_colour_rgb = ListProperty([0, 0, 0])
target_width_px = NumericProperty(0)
Then in the kv rule we add a Slider to select the
width, and connect it to the property:
<Interface>:
orientation: 'vertical'
DrawingWidget:
target_colour_rgb: red_slider.value, green_slider.value, blue_slider.value
target_width_px: width_slider.value
ColourSlider:
id: red_slider
ColourSlider:
id: green_slider
ColourSlider:
id: blue_slider
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 50
Label:
text: 'output colour:'
Widget:
canvas:
Color:
rgb: red_slider.value, green_slider.value, blue_slider.value
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 50
Label:
text: "width: {}".format(width_slider.value)
Slider:
id: width_slider
min: 2
max: 10
value: 2
And finally, update the DrawingWidget.on_touch_down to use the
currently-selected width for the new Line instruction:
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(*self.target_colour_rgb)
self.line = Line(points=[touch.pos[0], touch.pos[1]],
width=self.target_width_px)
Note: I’ve added not just a single Slider, but a new BoxLayout to
the kv rule, in order to display a Label indicating what the slider
is for. Notice how, consistent with everything so far, the Label
automatically updates to always show the current value of the Slider.
Run the app and try the drawing. You should now be able to control
both the colour and width of every line:
With that, the application is fully connected together. We have a UI
element for drawing, alongside extra elements for controlling the
details of the lines, with data passed around using Kivy
properties. These basic ideas are at the heart of all Kivy applications.
This would be a good time to experiment. Try adding or removing
widgets, and maybe adding more customisation to the lines.
Full code
main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Line
from kivy.properties import ListProperty, NumericProperty
from random import random
class DrawingWidget(Widget):
target_colour_rgb = ListProperty([0, 0, 0])
target_width_px = NumericProperty(0)
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(*self.target_colour_rgb)
self.line = Line(points=[touch.pos[0], touch.pos[1]],
width=self.target_width_px)
def on_touch_move(self, touch):
if not self.collide_point(*touch.pos):
return
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
def on_target_colour_rgb(self, instance, value):
print(f"target_colour_rgb changed to {self.target_colour_rgb}")
class Interface(BoxLayout):
pass
class DrawingApp(App):
def build(self):
root_widget = Interface()
return root_widget
DrawingApp().run()
drawing.kv:
<DrawingWidget>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<ColourSlider@Slider>:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 50
<Interface>:
orientation: 'vertical'
DrawingWidget:
target_colour_rgb: red_slider.value, green_slider.value, blue_slider.value
target_width_px: width_slider.value
ColourSlider:
id: red_slider
ColourSlider:
id: green_slider
ColourSlider:
id: blue_slider
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 50
Label:
text: 'output colour:'
Widget:
canvas:
Color:
rgb: red_slider.value, green_slider.value, blue_slider.value
Rectangle:
size: self.size
pos: self.pos
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 50
Label:
text: "width: {:.1f}".format(width_slider.value)
Slider:
id: width_slider
min: 2
max: 10
value: 2
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
Central themes: Event binding and canvas instructions in kv language
This tutorial directly follows on from the previous, so start by
retrieving the previous code, as below:
main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
... [More]
from kivy.uix.slider import Slider
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Line
from random import random
class DrawingWidget(Widget):
def __init__(self):
super(DrawingWidget, self).__init__()
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size,
pos=self.pos)
self.bind(pos=self.update_rectangle,
size=self.update_rectangle)
def update_rectangle(self, instance, value):
self.rect.pos = self.pos
self.rect.size = self.size
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
if not self.collide_point(*touch.pos):
return
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
class Interface(BoxLayout):
pass
class DrawingApp(App):
def build(self):
root_widget = Interface()
return root_widget
DrawingApp().run()
drawing.kv:
<Interface>:
orientation: 'vertical'
DrawingWidget:
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 80
Label:
text: 'output colour:'
Widget:
The first thing to do is draw the coloured Rectangle that the final
Widget uses to display an output colour, and for this we need to know
how to draw canvas instructions in kv language. The syntax is as below:
Widget:
canvas:
Color:
rgb: 0, 1, 0 # using a fixed colour for now
Rectangle:
size: self.size
pos: self.pos
Run the code, and you’ll see another of kv language’s most important
features; automatic event binding. In the original Python code of
tutorial 7 we needed an extra .bind(...) call to make the
be updated to always be placed within its Widget. In kv language this
is not necessary, the dependency on self.size and
self.pos is automatically detected, and a binding
automatically created!
This is also the generic syntax for canvas instructions; first add
canvas: (or canvas.before or canvas.after),
then, indent by 4 spaces, and add canvas instructions much like you
would Widgets. However, note that canvas instructions are not widgets.
The only thing now missing from the original Python interface
implementation in tutorial 7 is having the Sliders automatically
update the output colour rectangle. Change the :
rule to the following:
<Interface>:
orientation: 'vertical'
DrawingWidget:
Slider:
id: red_slider
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
id: green_slider
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
id: blue_slider
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 80
Label:
text: 'output colour:'
Widget:
canvas:
Color:
rgb: red_slider.value, green_slider.value, blue_slider.value
Rectangle:
size: self.size
pos: self.pos
There are actually only two changes here; we gave each Slider an
id declaration, and in the canvas Color referred to the
sliders with this name. Giving a widget an id is just like naming it
in Python so that you can refer to it elsewhere.
Thanks to kv’s automatic binding, this is all we need to do to have
the Color update automatically whenever a slider value changes. Run
the code, and you should see that things work exactly as they did in
the original Python interface.
We can finish this tutorial with a couple of extra kv
conveniences. First, just as we added an automatically updating
Rectangle in the Widget kv, we can do the same for the background of
the DrawingWidget. Delete the __init__ and
update_rectangle methods in the Python DrawingWidget code, and
add a new rule in the kv file:
<DrawingWidget>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
Second, you might have noticed that there’s a lot of code duplication
in each of the Slider rules - we set the same min,
max, initial value`, ``size_hint_y` and
height for every one. As is normal in Python, it would be
natural to abstract this in a new class, so as to set each value only
once. You can probably already see how to do this with what we’ve
learned so far (make a new class YourSlider(Slider): in the
Python and add a new : rule in the kv), but I’ll
note that you can even do this entirely in kv:
<ColourSlider@Slider>:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
<Interface>:
orientation: 'vertical'
DrawingWidget:
ColourSlider:
id: red_slider
ColourSlider:
id: green_slider
ColourSlider:
id: blue_slider
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 80
Label:
text: 'output colour:'
Widget:
canvas:
Color:
rgb: red_slider.value, green_slider.value, blue_slider.value
Rectangle:
size: self.size
pos: self.pos
The new : rule defines a dynamic class,
a Python class kv rule without a corresponding Python code
definition. This is convenient if you want to do something repeatedly
only in kv, and never access it from Python.
At this point, we’ve reached feature parity with the original Python
code, and seen all the basics of kv language. In the next tutorial
we’ll finish off the original purpose of all these sliders; letting
the user set the colour of line that is drawn by the DrawingWidget.
Full code
main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Line
from random import random
class DrawingWidget(Widget):
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
if not self.collide_point(*touch.pos):
return
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
class Interface(BoxLayout):
pass
class DrawingApp(App):
def build(self):
root_widget = Interface()
return root_widget
DrawingApp().run()
drawing.kv:
<DrawingWidget>:
canvas:
Color:
rgba: 1, 1, 1, 1
Rectangle:
pos: self.pos
size: self.size
<ColourSlider@Slider>:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
<Interface>:
orientation: 'vertical'
DrawingWidget:
ColourSlider:
id: red_slider
ColourSlider:
id: green_slider
ColourSlider:
id: blue_slider
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 80
Label:
text: 'output colour:'
Widget:
canvas:
Color:
rgb: red_slider.value, green_slider.value, blue_slider.value
Rectangle:
size: self.size
pos: self.pos
[Less]
|
Posted
over 5 years
ago
by
Alexander Taylor
This is number 8 in a series of introductory Kivy tutorials.
Central themes: Event binding and canvas instructions in kv language
This tutorial directly follows on from the previous, so start by
retrieving the previous code, as below:
main.py:
from kivy.app import App
from kivy.uix.boxlayout import …
|
Posted
over 5 years
ago
by
Alexander Taylor
This is number 7 in a series of introductory Kivy tutorials.
Central themes: kv language, building a gui, integration with Python
The goal of this tutorial will be to build up a simple gui around the
DrawingWidget built in the last two tutorials. A nice simple goal
would be to …
|
Posted
over 5 years
ago
by
Alexander Taylor
Central themes: kv language, building a gui, integration with Python
The goal of this tutorial will be to build up a simple gui around the
DrawingWidget built in the last two tutorials. A nice simple goal
would be to let the user select the colour of
... [More]
the lines. Kivy actually
has a ColorPicker Widget for this purpose (see the documentation), but we’ll
skip that for now in order to continue demonstrating Kivy widget construction.
Note
Since all Kivy widgets are built out of other Widgets and
canvas instructions, you might like to think about how you’d
build the ColorPicker from scratch.
Let’s start with the code from last time, minus the now-unnecessary
red Rectangle:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Line
from random import random
class DrawingWidget(Widget):
def __init__(self):
super(DrawingWidget, self).__init__()
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size,
pos=self.pos)
self.bind(pos=self.update_rectangle,
size=self.update_rectangle)
def update_rectangle(self, instance, value):
self.rect.pos = self.pos
self.rect.size = self.size
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
class DrawingApp(App):
def build(self):
root_widget = DrawingWidget()
return root_widget
DrawingApp().run()
I’ll demonstrate adding the new gui components in two ways; first in
pure Python as has been demonstrated in previous tutorials, and second
using kv language instead. So, here’s a Python implementation of the
new features we want, beginning with importing the Widget classes
we’ll need:
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
Slider is a previously-unseen Widget displaying a draggable marker. We’ll be using a
Slider for each primary colour (red, blue, green), and using this to
set the Color when a Line is drawn.
We can now update the build method of DrawingApp, replacing the root
widget and adding the new gui components:
class DrawingApp(App):
def build(self):
root_widget = BoxLayout(orientation='vertical')
drawing_widget = DrawingWidget()
red_slider = Slider(min=0, max=1, value=0.5,
size_hint_y=None, height=80)
green_slider = Slider(min=0, max=1, value=0.5,
size_hint_y=None, height=80)
blue_slider = Slider(min=0, max=1, value=0.5,
size_hint_y=None, height=80)
colour_row = BoxLayout(orientation='horizontal',
size_hint_y=None, height=80)
colour_label = Label(text='output colour:')
colour_widget = Widget()
# We draw a Rectangle on colour_widget exactly the same way as
# with DrawingWidget, just without making a new class
with colour_widget.canvas:
output_colour = Color(red_slider.value,
green_slider.value,
blue_slider.value)
output_rectangle = Rectangle()
def update_colour_widget_rect(instance, value):
output_rectangle.pos = colour_widget.pos
output_rectangle.size = colour_widget.size
colour_widget.bind(pos=update_colour_widget_rect,
size=update_colour_widget_rect)
def update_colour_widget_colour(instance, value):
output_colour.rgb = (red_slider.value,
green_slider.value,
blue_slider.value)
red_slider.bind(value=update_colour_widget_colour)
green_slider.bind(value=update_colour_widget_colour)
blue_slider.bind(value=update_colour_widget_colour)
root_widget.add_widget(drawing_widget)
root_widget.add_widget(red_slider)
root_widget.add_widget(green_slider)
root_widget.add_widget(blue_slider)
root_widget.add_widget(colour_row)
colour_row.add_widget(colour_label)
colour_row.add_widget(colour_widget)
return root_widget
This is a lot of code to drop all at once, but read it carefully and
you’ll see that it’s only the same concepts already introduced: we
instantiate Widgets, add them to one another, and create bindings so
that things automatically happen when Kivy properties are changed. In
this case, we make use of the value Kivy property of the
Slider widget, which gives its current value (changing automatically
when the slider is moved).
Run the code and you should see something like the image below. You
can update the colour in the bottom right by moving the sliders. Cool.
A problem now becoming obvious is that all this code is kind of
verbose, and also it can be a little unclear what is happening -
Widget instantiation is in a different place to where the Widgets are
added to one another, which is different again to where their events
are bound. You can mitigate this with a careful app structure and
following whatever coding conventions you like, but some of it is
unavoidable given how Python works.
It’s for this reason that Kivy comes with kv language, a simple but
powerful language specifically designed for creating Kivy widget
trees. If learning a new language sounds worrying…don’t be
concerned! Kv doesn’t have much special syntax and is targeted
specifically at Kivy widgets, and much of the code you write is
actually normal Python (we’ll see that soon).
All of the kv language stuff discussed below is documented on the
Kivy website; I’ll cover
the basics, but you can find more information there.
First, get rid of all the Python code from above, and replace the
root widget return with the following:
class Interface(BoxLayout):
pass
class DrawingApp(App):
def build(self):
root_widget = Interface()
return root_widget
kv language works by writing rules for Widget classes, which will be
automatically applied every time you instantiate one. We can use kv
for almost everything added to the app so far, but this time we’ll
construct the gui step by step to see how each part is added with the
new kv syntax. We’ll be writing a kv rule for the new
Interface class.
To start using kv language, write the following code in a file named
drawing.kv. This name comes from the name
of the App class, minus the App at the end if present, and in
lowercase (e.g. if you named your App MySuperKivyApp you’d
need to name the file mysuperkivy.kv). This is only necessary if
you want the file to be automatically loaded, you can also load files
or string manually. Our first
kv code is:
<Interface>:
orientation: 'vertical'
Label:
text: 'label added with kv'
font_size: 50
Run the code again, and you should see the a Label with the given
text, as the kv file is automatically loaded and its
rule applied.
This demonstrates the core rules of kv syntax. A kv rule is created
with the : syntax. You can make a rule for any
widget, including built in ones (Kivy internally has a large kv file), and
if you make multiple rules for the same Widget then all of them are
applied one by one.
Below the rule creation, we indent by 4 spaces and define values for
Kivy properties of the widget, and add child widgets. Lines like
orientation: 'vertical' set Kivy properties just like we did
previously in the Python code. Note that everything to the right of
the colon is normal Python code - that doesn’t matter here, but for
instance we could equally well write orientation: ''.join(['v',
'e', 'r', 't', 'i', 'c', 'a', 'l']) and it would be exactly the
same. You can set any Kivy property of a widget in this way, finding
the available options in the documentation as previously discussed.
We can also add child widgets by writing the widget name with a colon,
then indenting by a further 4 spaces, as is done here with the
Label. After this you can keep going as deep as you like,
setting properties or adding more child widgets.
We can use these pieces of syntax to construct the previous Python
interface entirely in kv:
<Interface>:
orientation: 'vertical'
DrawingWidget:
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 80
Label:
text: 'output colour:'
Widget:
This hasn’t yet set up the event binding, but the full widget tree has
been constructed entirely using the kv syntax described above. The
immediate advantage of this is that kv language directly expresses the
widget tree - there are no longer separate steps for instantiating
Widgets, setting their properties and adding them to one
another. Instead, you get to see everything at once.
This gui doesn’t yet have the behaviour of the Python one (i.e. having
the sliders control output colour), but in the interest of keeping
these tutorials relatively short, I’ll stop here for now. In the next
tutorial will see how kv language also makes event binding very easy.
Full code
main.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.slider import Slider
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.slider import Slider
from kivy.uix.widget import Widget
from kivy.graphics import Rectangle, Color, Line
from random import random
class DrawingWidget(Widget):
def __init__(self):
super(DrawingWidget, self).__init__()
with self.canvas:
Color(1, 1, 1, 1)
self.rect = Rectangle(size=self.size,
pos=self.pos)
self.bind(pos=self.update_rectangle,
size=self.update_rectangle)
def update_rectangle(self, instance, value):
self.rect.pos = self.pos
self.rect.size = self.size
def on_touch_down(self, touch):
super(DrawingWidget, self).on_touch_down(touch)
if not self.collide_point(*touch.pos):
return
with self.canvas:
Color(random(), random(), random())
self.line = Line(points=[touch.pos[0], touch.pos[1]], width=2)
def on_touch_move(self, touch):
if not self.collide_point(*touch.pos):
return
self.line.points = self.line.points + [touch.pos[0], touch.pos[1]]
class Interface(BoxLayout):
pass
class DrawingApp(App):
def build(self):
root_widget = Interface()
return root_widget
DrawingApp().run()
drawing.kv:
<Interface>:
orientation: 'vertical'
DrawingWidget:
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
Slider:
min: 0
max: 1
value: 0.5
size_hint_y: None
height: 80
BoxLayout:
orientation: 'horizontal'
size_hint_y: None
height: 80
Label:
text: 'output colour:'
Widget:
[Less]
|