The previous post looked into installing Kivy and running a simple button-application. This post will look closer at the App() class and its window to see what more can be done with them. NB: in this post and hereafter, all text between quotes "like this" will be drawn from the Kivy API Documentation. Usually I will link to the section it was quoted from." Also this post originally meant to look at the App() class and at the Button() widget class. After consideration I prefer to focus on the Window() class rather than Button().
Review the Basic Application's Code
First we are going to review the Basic Application's code:
import kivy kivy.require('1.0.6') from kivy.app import App from kivy.uix.button import Button class MyApp(App): def build(self): return Button(text='Hello World') if __name__ in ('__android__', '__main__'): MyApp().run()
Line 1: import Kivy (nothing too hard)
Line 2: specify the version of Kivy required for this program to function. "If a user attempts to run your application with a version of Kivy that is older than the version you specified, an Exception will be raised." You can find out the version you are currently using by merely importing Kivy from a Python Console, which will return an [INFO] log with the current version of Kivy, alternatively you can use kivy.__version__ .
Line 4 & 5: we import the bits and pieces we need. Although it's more work to do so, loading the entire library like from kivy import * "would have the disadvantage of cluttering your namespace and making the start of the application potentially much slower. It’s also not as clear what your application uses. The way we do it is faster and cleaner" (see documentation).
Line 7 & 12: the MyApp() class is derived from the App() class of the kivy.app repository. "The App() class is the base for creating Kivy applications. Think of it as your main entry point into the Kivy run loop. In most cases, you subclass this class and make your own app. You create an instance of your specific App() class and then, when you are ready to start the application’s life cycle, you call your instance’s App().run() method" as in line 12 here.
Line 8 & 9: the build() method "initializes the application [and] returns a widget [that] will be used as [root] and added to the window." This method needs not be called, the App().run() will do that for us. In this instance, the build() method returns a Button() widget with a "Hello World" text (label) displayed on it. We'll look at the button() class with more details in a moment, for now let's look at what other things the App() can do.
More on the App() Class
If we look at the documentation on the App() class, we'll find various methods and properties which we can use, some of these methods are automated, as of now I cannot imagine a situation when one would need to call these oneself. This is the case of the build()method, which, as we have seen, will run itself when calling App().run(). It is also the case of the load_kv() method which will be called as the application is launched with App().run(). The load_kv() method will load a .kv file, of which more later.
The App() class offers two attributes called icon and title. The first is a string referring to the relative path+name of the icon file which will be displayed in the task-bar or dock of your OS.
In our MyApp() class definition, which is derived from the bas App() class, we then can modify the default icon property. The default icon tends to render awfully in my dock (I use Cairo-Dock by the way, it's really cool), so I made my own Kivy icon with Inkscape. Now in our class definition we can add the following:
class MyApp(): icon = 'custom-kivy-icon.png'
While doing this I figured out from the log that Kivy does not support SVG format for the App.icon property, PNG worked fine.
Next we want to change the title property. I was wondering why my application was called "My" by default (look at the title bar when launching the application) and I soon figured out that Kivy is sensitive to the spelling of some of its objects and it naturally derived the application's title from the MyApp() class definition by removing the -App suffix. This makes more sense as one discovers that Kivy does a lot of the job for you, by associating the App() class with its .kv file for instance. Now if we change line 7 and 12 to CoolApp() and CoolApp().run() respectively, the title of our application will change to "Cool". That new title however, will be overridden by whatever string will be passed in the App.title so if we add title = 'Basic Application' to the line following our icon property and run the application again, the title should change accordingly.
Furthermore there is the on_start() and on_stop() methods which will be called when the application will be launched and halted, by the one_start and on_stop events respectively. So for instance we could use a simple print function to print something to the console as we launch the application. But Kivy developers wisely introduced a Logger to the library, which logs and prints all kind of information on the console, it's pretty cool for debugging and it's nice to simply know what's happening inside the box. The Logger not only prints to the console but also writes the stuff that goes through it to a log text file in your ~/.kivy folder so you can still access the log even if you closed the terminal. To add a log, we simply need to add a line where/when we need the message to be logged, check the documentation for proper syntax.
Now we've omitted the App().stop() method, but we'll see that one in the next section, together with the Button widget.
The code, now updated should look like this:
Furthermore there is the on_start() and on_stop() methods which will be called when the application will be launched and halted, by the one_start and on_stop events respectively. So for instance we could use a simple print function to print something to the console as we launch the application. But Kivy developers wisely introduced a Logger to the library, which logs and prints all kind of information on the console, it's pretty cool for debugging and it's nice to simply know what's happening inside the box. The Logger not only prints to the console but also writes the stuff that goes through it to a log text file in your ~/.kivy folder so you can still access the log even if you closed the terminal. To add a log, we simply need to add a line where/when we need the message to be logged, check the documentation for proper syntax.
Now we've omitted the App().stop() method, but we'll see that one in the next section, together with the Button widget.
The code, now updated should look like this:
#!/usr/lib/python import kivy kivy.require('1.0.6') from kivy.app import App from kivy.uix.button import Button from kivy.logger import Logger class CoolApp(App): icon = 'custom-kivy-icon.png' title = 'Basic Application' def build(self): return Button(text='Hello World') def on_start(self): Logger.info('App: I\'m alive!') def on_stop(self): Logger.critical('App: Aaaargh I\'m dying!') if __name__ in ('__android__', '__main__'): CoolApp().run()
The Application's Window
At this point I originally thought I'd focus on the Button() widget class and see what could be done with it. But I got confused by the fact that the Button() is the root object returned by App().build() in the example here above. For instance I found that 600x800 px was quite a size for a single button, so I thought I could resize it with Button().size = int, but that didn't work, because the root object returned by the build() method will take on the entire window's space.
But where is this window then in our code? Well, it seems that Kivy does make the work easy for us, in comparison to some other GUI libraries to which every single action must be instructed by the developer.
Let's try a small experiment: if you import time and add time.sleep(2) in your on_start() callback, then run your application, you will see that the application's black window appears first and then the button is drawn onto it. Now we have seen this window on the screen, let's find it in the code.
If we look at the run() method in the app.py code, we see that the method will first get the widget tree and then append it to a Window() instance imported from kivy.core.window. In the documentation, you will find more about this Window() class, and I wanted to look at it closer before I turn to the content of my application. Let's parse it together.
If we look at the run() method in the app.py code, we see that the method will first get the widget tree and then append it to a Window() instance imported from kivy.core.window. In the documentation, you will find more about this Window() class, and I wanted to look at it closer before I turn to the content of my application. Let's parse it together.
Right at the outset of the documentation we are warned: "Kivy support only one window creation. Don’t try to create more than one". To me this begs the question: what if I want my application to have more than one window? I'm thinking of an application like the Gimp for instance, which has its Toolbox in a separate window from that of the drawing. I haven't tried yet, but I guess that one would need to create two instances of the App() class.
I was concerned about the size of my application, and we find the parameters listed in the documentation fullscreen, height and width, but using them in our App() class won't work because the App().run() method doesn't let us pass size parameters. There are then three ways to customise the Window() size of our application:
- change the configuration file's height and width values (or fullscreen values)
- change the same configuration file from code
- launch our application with a --size=WxH tag where W and H are width and height in pixels.
Of the three options I concluded the third was the best. Why? Because the configuration file, which is placed at ~/.kivy/config.ini is shared by all Kivy applications, and the parameters generated by our application would affect that of another application. I am not sure of what the practical use of these parameters are to be honest. The third option, on the contrary, is friendlier because it is passed along as a parameter for our Window() object to feed on. This means we should launch our application using the Terminal:
$ python ./main.py --size=100x50
Now if we want some form of control on the size of our application, we'll have to launch our Kivy GUI from code like this:
import subprocess subprocess.Popen(['pathtogui', 'main.py', '--size=100x50'], shell=True)
... which is not very handy and being able to pass the window geometry directly into the App() for instance or set its size as a property. However, I haven't found parameters to allow or prevent resizing of the window or set the minimum or maximum window size parameters. There may be a way though, since the Window() class of Kivy is derived from PyGame's. I'll look into that.
Positioning the window on the screen is possible through the configuration file only: set the position parameter to custom and the top and / or left values to the coordinate in pixels where you want the top-left corner of the window to be. Again, be careful while using the configuration file since it is shared by all Kivy applications.
To prevent window decoration from within Kivy's API, is possible through the configuration file and flags. Flags again are preferable since they affect the application's instance only. To prevent decoration can set the flag -k (i.e. 'fake fullscreen'). When the window is undecorated, it cannot me moved or resized with the mouse.
To prevent window decoration from within Kivy's API, is possible through the configuration file and flags. Flags again are preferable since they affect the application's instance only. To prevent decoration can set the flag -k (i.e. 'fake fullscreen'). When the window is undecorated, it cannot me moved or resized with the mouse.
The window background can be changed with the Window().clearcolor property to which colour information should be passed in a tuple containing (r, g, b, a) information. Although I managed to change the background colour of the window, setting alpha to 0 did not make the window transparent as I had expected.
Although I am quite enthusiast about Kivy, I feel quite disappointed by its window management at API level. This is because Kivy was developed for touch-phones in first instance, from what I understood. From the point of view of desktop GUI development however, I want to be able to place my window where I want, with the default, min and max size I want and I want it to be able to talk to the Window Manager (skip the window manager, provide window hints (to tell if the window is a dock or pop-up or splash screen), stay on top or below, sticky, reserved space etc.)
I am looking forward to make a dock/panel-like application, for which such features are indispensable. Perhaps there are work-arounds for that, and Kivy may still offer such features in the future, and I've already been told these features should be considered for further development :)
dont require 1.0.0. at least, require the current version youre using. because you dont know if previous will work :-)
ReplyDeleteHa yes, sure :) I just copy paste the code of the doc. I'll change that. Thanks.
ReplyDeleteHey, cool article. I'd love to see more coming.
ReplyDeleteJust two thoughts:
a) You repeatedly refer to App.run() when it should be App().run() for clarity IMHO, as that is an instance method.
b) The Logger not only prints to the console but also writes the stuff that goes through it to a log text file in your ~/.kivy folder so you can still access the log even if you closed the terminal.
Good job!
Thanks for the feedback Dennda, imma correct this.
ReplyDeleteI added the subprocess import and additional line of code (to change screen size on desktop), and then ran the code with a command specifying window size (as suggested above), but it didnt change anything....every app I run on desktop goes fullscreen with no clickable border, no mouse cursor, and some wont close with CTRL-C or ESC. I know that for an android app it doesn't really matter as it will fill the screen nicely (And as a workaround I am now adding exit buttons) but for testing Apps I would like to be able to simply resize the screen. With Tk GUI development it was so simple!
ReplyDelete