This Drawing Application extends Tutorial 34. It shows how we can find child widgets of a class. A class Toolbar is on the left of the screen and the Drawing class represents the rest of the screen. The Drawing class will be the parent of all widgets we draw.
The root class is based on RelativeLayout, but there are Widgets for Toolbar and Drawing, as well as the 4 widgets which will be drawn. These four classes are shown in the next slide. We will use NumericProperty and StringProperty for our variables. Line is also imported, as we need to call some Line canvas instructions from inside this Python file.
These are the 4 widgets which are drawn and dynamically added to the Drawing class. There shapes are defined in the Kivy language.
The Toolbox class uses most of the Kivy language kv file for instructions for Buttons, ToggleButtons and Labels. In the python file, it does only a few things. It defines 3 Properties. Two are for the width and height. Shape is a string which indicates the selected shape. The function on_w, and on_h, are called each time by Kivy whenever the width or height changes, according to user selection in the Toolbar. Whenever the width or height changes, we call a function to erase the box, should it exist. The box is created in the Drawing class.
The Drawing class has to have a lot of code. It is responsible for adding instances of one of the 4 widgets. Besides that we have some functions, like Clear, Undo, and Box. The NumericProperty, sel, indicates if a box has been drawn around the current widget. We show, here, the first half of the function common. It sees if the user has selected one of the 4 shapes. The Drawing class then adds the corresponding widget with the add_widget function. The common function receives the touch argument, so it knows where to place the widget.
This is the second half of the common function. It adds one of the last two shapes depending on the value of shape property of the Toolbox class.
The function common is called from either on_touch_down or on touch move, which Kivy calls if it detects a click or drag. We also pass the touch parameter to the function common.
These functions are called from the Kivy kv file if the corresponding Button is pressed. The function clear will clear all the widgets in the Drawing class. Undo removes the last widget from the Drawing class. Notice the children function will get all the children widgets of the Drawing class. We only remove the top-most widget. There is a try clause, so we will not get an error should there be no widget. In that case, we ask it to do nothing.
The box function is called when the last button in the Toolbar is pressed. The list of widget childs will be in the ch list. Should there be none, the function just exits. But if there are widgets, it sees if the sel property is 0 or 1. If sel is 0, a box is drawn around topmost widget, and then sel is set. If sel is set, and Box button is pressed, it will remove the rectangle, and clear sel, so a new box may be drawn.
The box_resize function is called by on_w and on_h functions. It removes the box should it exist.
The root class is based on RelativeLayout. The application's title is 'Drawing Application 2'. This will be displayed on the window.
# ex36.py from kivy.uix.widget import Widget from kivy.uix.relativelayout import RelativeLayout from kivy.app import App from kivy.properties import (NumericProperty, StringProperty) from kivy.graphics import Line class ColorEllipse(Widget): pass class ColorRectangle(Widget): pass class ColorUpTriangle(Widget): pass class ColorDownTriangle(Widget): pass class ToolBox(Widget): w = NumericProperty(20) h = NumericProperty(20) shape = StringProperty('down:0') def on_w(self,*args): self.drawing.box_resize(*args) def on_h(self,*args): self.drawing.box_resize(*args) class Drawing(Widget): sel = NumericProperty(0) def common(self,touch): if touch.x>150: if self.toolbox.shape=='down:0': ce = ColorEllipse() ce.center = touch.pos self.add_widget(ce) elif self.toolbox.shape=='down:1': cr = ColorRectangle() cr.center = touch.pos self.add_widget(cr) elif self.toolbox.shape=='down:2': cut = ColorUpTriangle() cut.center = touch.pos self.add_widget(cut) elif self.toolbox.shape=='down:3': cdt = ColorDownTriangle() cdt.center = touch.pos self.add_widget(cdt) def on_touch_down(self,touch): self.common(touch) def on_touch_move(self,touch): self.common(touch) def clear(self,*args): self.clear_widgets() def undo(self,*args): try: self.remove_widget(self.children[0]) except: pass def box(self,*args): ch = self.children if len(ch) == 0: return if self.sel==0: with self.canvas: self.select=Line(rectangle=(ch[0].x-5,ch[0].y-5, ch[0].width+10,ch[0].height+10),width=2) self.sel=1 else: self.canvas.remove(self.select) self.sel = 0 def box_resize(self,*args): if self.sel==1: self.canvas.remove(self.select) self.sel = 0 class Ex36(RelativeLayout): pass class Ex36App(App): def build(self): self.title = 'Drawing Application 2' return Ex36() if __name__=='__main__': Ex36App().run()
In the kv file, random() function is imported from the random module with alias of rnd. The random() function returns a number from 0 to 1. Next two variables are set, sz and off. The kv for the first Widget is added. It gets its size from the w and h properties.
This is the kv for the second widget. It is similar to the first except it is based on the Rectangle canvas instruction.
This widget draws a triangle which points upwards.
This widget draws a triangle which points downwards.
In the root class, attributes are created to point to ToolBox and Drawing class. The Toolbox id is set, and it creates it's own drawing attribute. We could have renamed it, if we had wanted, however that adds only confusion. The Toolbox will only use the first 100 pixels of the screen.
In the canvas instructions, we set the color as reddish, which is applied to the entire toolbox. Then three blue lines are drawn. These lines will delimit the width and height sections as we will see.
The first ToggleButton is drawn. It is the ellipse shape. Here, we can see how the shape string is created. The on_state is called whenever the state changes. The state is a string, it can be 'normal' or 'down'. We add a string ':0' to it. Thus, the two possible values are 'normal:0' or 'down:0'. We also set the initial state as 'down'. There are canvas.after instruction to draw over the ToggleButton.
ToggleButton 2 is for the rectangle shape. Now the state variable is set as 'normal:1' or 'down:1', when the ToggleButton is pressed.
ToggleButton 3 is for the triangle up shape.
ToggleButton 4 is for the triangle down shape.
We have a label indicating this section deals with 'width'. There is an up button, which increments the w property.
The down button decrements w, and finally the label indicates the current value of w .
The Label indicates we are in the 'height' section. The up button increments the value of the h property.
There is a button that when pressed, it will decrement the h property. Finally, the Label indicates the current value of the h property. Both w and h are bounded, between 1 and 49.
The buttons for 'Clear', 'Undo', and 'Box' are created and call the appropriate function in the Drawing class.
The Drawing class itself has very little kv code. It has the attribute toolbox. It is sized to fit the rest of screen, and is colored white.
# ex36.kv #:import rnd random.random #:set sz 42 #:set off 4 <ColorEllipse>: size_hint: None, None size: app.root.toolbox.w,app.root.toolbox.h canvas: Color: rgb: rnd(),rnd(),rnd() Ellipse: pos: self.pos size: self.size <ColorRectangle>: size_hint: None, None size: app.root.toolbox.w,app.root.toolbox.h canvas: Color: rgb: rnd(),rnd(),rnd() Rectangle: pos: self.pos size: self.size <ColorUpTriangle>: size_hint: None, None size: app.root.toolbox.w,app.root.toolbox.h canvas: Color: rgb: rnd(),rnd(),rnd() Triangle: points: (self.x,self.y, self.x+self.width,self.y, self.x+self.width/2,self.y+self.height) <ColorDownTriangle>: size_hint: None, None size: app.root.toolbox.w,app.root.toolbox.h canvas: Color: rgb: rnd(),rnd(),rnd() Triangle: points: (self.x,self.y+self.height, self.x+self.width,self.y+self.height, self.x+self.width/2,self.y) <Ex36>: toolbox: toolbox_id drawing: drawing_id ToolBox: id: toolbox_id drawing: drawing_id size_hint_x: None width: 100 pos: 0,0 canvas: Color: rgb: .5,0,0 Rectangle: size: self.size pos: self.pos Color: rgb: 0,0,.5 Line: points: 0,root.height-105, 100,root.height-105 width: 2 Line: points: 0,root.height-245, 100,root.height-245 width: 2 Line: points: 0,root.height-395, 100,root.height-395 width: 2 ToggleButton: pos: off,root.height-45 size: sz,sz group: 'shape' on_state: toolbox_id.shape = self.state + ':0' state: 'down' canvas.after: Line: ellipse: self.x+10,self.y+10,20,20 ToggleButton: pos: off+50, root.height-45 size: sz,sz group: 'shape' on_state: toolbox_id.shape = self.state + ':1' canvas.after: Line: rectangle: self.x+10,self.y+10,20,20 ToggleButton: pos: off,root.height-95 size: sz,sz group: 'shape' on_state: toolbox_id.shape = self.state + ':2' canvas.after: Line: points: (self.x+10,self.y+10, self.x+30,self.y+10, self.x+20,self.y+30) close: True ToggleButton: pos: off+50,root.height-95 size: sz,sz group: 'shape' on_state: toolbox_id.shape = self.state + ':3' canvas.after: Line: points: (self.x+10,self.y+30, self.x+30,self.y+30, self.x+20,self.y+10) close: True Label: pos: off,root.height-145 size: 2*sz,sz text: 'width' Button: pos: off, root.height-195 size: sz,sz on_press: if toolbox_id.w < 49 :toolbox_id.w += 1 canvas.after: Line: points: (self.x+10,self.y+10, self.x+30,self.y+10, self.x+20,self.y+30) close: True Button: pos: off+50,root.height-195 size: sz,sz on_press: if toolbox_id.w > 1: toolbox_id.w -= 1 canvas.after: Line: points: (self.x+10,self.y+30, self.x+30,self.y+30, self.x+20,self.y+10) close: True Label: pos: off, root.height-245 size: 2*sz,sz text: 'w: ' + str(toolbox_id.w) Label: pos: off,root.height-295 size: 2*sz,sz text: 'height' Button: pos: off, root.height-345 size: sz,sz on_press: if toolbox_id.h < 49 :toolbox_id.h += 1 canvas.after: Line: points: (self.x+10,self.y+10, self.x+30,self.y+10, self.x+20,self.y+30) close: True Button: pos: off+50,root.height-345 size: sz,sz on_press: if toolbox_id.h > 1: toolbox_id.h -= 1 canvas.after: Line: points: (self.x+10,self.y+30, self.x+30,self.y+30, self.x+20,self.y+10) close: True Label: pos: off, root.height-395 size: 2*sz,sz text: 'h: ' + str(toolbox_id.h) Button: pos: off, root.height-495 size: 2*sz,sz text: 'Clear' on_press: root.drawing.clear(*args) Button: pos: off, root.height-545 size: 2*sz,sz text: 'Undo' on_press: root.drawing.undo(*args) Button: pos: off, root.height-595 size: 2*sz,sz text: 'Box' on_press: root.drawing.box(*args) Drawing: id: drawing_id toolbox: toolbox_id size_hint_x: None width: root.width-100 pos: 100,0 canvas: Color: rgb: 1,1,1 Rectangle: size: self.size pos: self.pos
This is one result of running the program. A separate video goes over the usage in detail. Because w and h are properties, any change in their values will update all the child widgets. Should we not want this behavior, we will have to make them regular variables.
Demo of App on Youtube.
Python:
ReplyDeleteGood Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.
https://www.emexotechnologies.com/online-courses/python-training-in-electronic-city/
Good Post! Thank you so much for sharing this pretty post, it was so good to read and useful to improve my knowledge as updated one, keep blogging.
ReplyDeletePython Training in electronic city
nice information for beginners.thank you.
ReplyDeletejavacodegeeks
welookups python