Kivy, One of the Best and Powerful GUI Creators

Simulation-building is something I seem to return to over and over again. I really enjoy learning something new each time I witness how small behaviors produce emergent behavior. Graphics are an accessible way to conceptualize the complex nature of multi-variable simulations. In this article, I lay the foundation for a project I hope will serve as the first chapter for a much larger vision.

Let’s explore Kivy, a powerful Python app development library.

Spoiler alert, this has, by far, been my best experience with a Graphical User Interface (GUI) library in Python. For me, it provides the best of two worlds for building a small application for a very basic simulation:

  • Layout management and simple widgets for boiler-plate application objects, like buttons and text inputs.
  • Custom drawing to the widgets’ underlying canvas, allowing for custom shapes and graphics.

A Very Basic Simulation

To get a better feel for this GUI framework, I decided to implement a primitive simulation to display on my application. The rules are simple: You have people and you have food! A person (red square) has a stomach and energy. The person’s stomach is filled with food, and energy is created from the food in their stomach. Food (green square) is a stationary source for a person to fill their stomach.

People move around in search for stationary food supplies, which expends energy and in turn uses the food in their stomach. If a person’s energy and stomach level reach 0, they are no longer considered alive (blue square).

Kivy

All right, with the simulation explained, it’s time to dig into Kivy! Kivy gives you so much to play with, yet has a refreshingly minimal barrier to entry. You can add buttons, text boxes, labels, and so much more! The best part is, through the Kv Language, your GUI can function on its own without Python code driving it. There is a helpful rundown on the Kv language on Kivy’s website here.

Kv Design Language

An example of this can be found in my ‘SimulationController’ widget, defined in SimulationGUI.kv below:

<SimulationController>
    BoxLayout:
        width: root.width
        height: root.height
        orientation: "horizontal"
        BoxLayout:
            orientation: "vertical"

            Slider:
                id: grid_height
                min: 25
                max: int(root.height)
                step: 1
                orientation: "horizontal"
            Label:
                text: str(grid_height.value) + " Map Height"
            
            Slider:
                id: grid_width
                min: 25
                max: int(root.width)
                step: 1
                orientation: "horizontal"
            Label:
                text: str(grid_width.value) + " Map Width"

You can see the ‘SimulationController,’ with two nested ‘BoxLayouts.’

<SimulationController>
    BoxLayout:
        width: root.width
        height: root.height
        orientation: "horizontal"
        BoxLayout:
            orientation: "vertical"

On the outermost ‘BoxLayout,’ I define its width and height to fill the space allotted to it by the element into which it is inserted. It took me a while to wrap my head around this as I attempted bigger layouts. Also note the orientation, which describes the direction things will be stacked.

Inside the innermost ‘BoxLayout,’ you can see Slider and Label pairs:

            Slider:
                id: grid_height
                min: 25
                max: int(root.height)
                step: 1
                orientation: "horizontal"
            Label:
                text: str(grid_height.value) + " Map Height"
            
            Slider:
                id: grid_width
                min: 25
                max: int(root.width)
                step: 1
                orientation: "horizontal"
            Label:
                text: str(grid_width.value) + " Map Width"

The Slider and Label are GUI Widgets built into the Kivy library. For the Slider, I set an ID value (basically a name referenced elsewhere in the code), along with a minimum and maximum value. In the Label Widget, you can see the GUI driving itself a bit – the text of the Label is set by the value currently selected by the Slider. This might be a commonplace functionality for a GUI framework that I just haven’t discovered before, but I really like it. Everything looks a lot cleaner when simple functionalities like this are independent from the Python code.

Invoking Python code directly from this language is pretty easy, too.

        Button:
            id: start_but
            on_press: app.run_sim(root.ids.num_people.value, root.ids.num_food.value, root.ids.grid_height.value, root.ids.grid_width.value)
            text: "Start Simulation"

In the above example, you can see a direct reference to ‘app’ and a call to ‘run_sim.’ This is defined on the Python side in my ‘SimulationGUIApp’ object which inherits from the ‘App’ object from the Kivy library.

The reference to ‘run_sim’ uses information from our widget and calls the function with the values collected as parameters. Awesome!

Kivy and Python

For me, it is often difficult to understand the interface between a design language and a program language (I’m looking at you, Javascript and CSS!). Let’s cover some basics.

Make Kv Definitions Python reference-able

First thing: How do you let Kivy know you are using a Kv file? There are a few ways to do this, but the one I chose was to name the .Kv file the same thing as my app’s object name (e.g., Kv filename is ‘SimulationGUI.kv’ and my Python object is ‘SimulationGUIApp’). An important detail here is to note that my Python object name ends with ‘App,’ while the filename doesn’t.

As for the Widgets defined in the .Kv file, all we have to do to use them on the Python side is create an empty class with the same name, as shown below:

.Kv Definition
<SimulationController>
    BoxLayout:
        width: root.width
        height: root.height
        orientation: "horizontal"
Python Definition
class SimulationController(Widget):
    pass

Even though the class is empty, the layout and widgets are defined in the .Kv file and will be shown when the app loads.

Once you do that, you can add Widgets to an App, creating your interface. In the example below, class ‘MainScreen’ inherits from ‘BoxLayout’ and then adds two Widgets to itself. One of the widgets is our familiar ‘SimulationController.’ Next, a ‘MainScreen’ object is instantiated and added to the app class (‘SimulationGUIApp’) in its build method.

class SimulationViewport(Widget):
    pass

class SimulationController(Widget):
    pass
        
class MainScreen(BoxLayout):
    def __init__(self, **kwargs):
        super(MainScreen, self).__init__(**kwargs)
        self.orientation = "vertical"
        self.size_hint = (1.,1.)

        self.sim_view = SimulationViewport()
        self.sim_cont = SimulationController()

        print(self.sim_cont.ids["grid_height"].value)
        
        self.add_widget(self.sim_view)
        self.add_widget(self.sim_cont)

class SimulationGUIApp(App):
    def build(self):
        main_screen = MainScreen()

        self.sim_view = main_screen.sim_view

        return main_screen

I decided to do it this way so I could access the canvas of our ‘SimulationViewport’ for drawing people and food. Also, in the above snippet, I left in a line of code to show how you can reference the values of the Widgets contained in our Kv-defined widgets using the ‘ids’ dictionary.

A Slightly More Advanced Kivy Concept

Until now, we have focused on the basic uses of Kivy. Now, let’s dive into how to draw on Widgets so that we can see our people as they wander around our world looking for food sources.

Assumptions Kill Me

One frustrating mistake I made was when I assumed that my drawing would be relative to the corner of my Widget. In a multi-widget layout, this would mean that the position of each drawing would be local to its own widget. In reality, the drawings are positioned within the global window’s coordinate grid, regardless of widget. With this mistaken assumption, I thought that the Widgets were overlapping one another. I likely developed this intuition from HTML and CSS, where the flow layout can cause elements to exist over one another.

Finally, I realized that drawing occurs from the window’s origin. This is really simple to do, but figuring it out was difficult. Below is the code snippet that saved me, particularly the self.sim_view.pos[0] and [1].

pos_screen_x = x * block_width + self.sim_view.pos[0]
pos_screen_y = y * block_height + self.sim_view.pos[1]

Rectangle(pos=(pos_screen_x, pos_screen_y), size=(block_width, block_height))

A ‘Rectangle’ is drawn at the x, y coordinates of our Person or Food with an offset of ‘self.sim_view.pos[0]’ and ‘[1],’ which gets it to our desired spot. The challenging part, however, is that you can draw outside the widget and it will still be rendered if it is within the window’s region.

So now we are drawing in the right place, but how do we select a color?

It’s like painting

Right above the lines of code where we select the x and y coordinates, there is an if-else block where the color palette is chosen for the upcoming ‘Rectangle’ draw. This color choice selects a value between 0 and 1 for each color (Red/Green/Blue). Here, we check if our object is ‘Food’ or ‘Person.’ Then, we select red or green of varying intensity, based on energy or food levels:

if isinstance(thing, Food):
    color_strength = thing.Amount / thing.MaxAmount
    color_strength = max([color_floor, color_strength])

    Color(0.,color_strength,0.)
elif isinstance(thing, Person):
    color_strength = thing.Energy / thing.EnergyMax
    color_strength = max(color_floor, color_strength)
    if thing.alive:
        Color(color_strength,0.,0.)
    else:
        Color(0.,0.,1.)

Conclusion

This has been a wonderful learning experience for me, and I hope by posting it here it can be useful to you, too! I look forward to possibly building out the simulation and controls with more options and depth.

If you are interested in further reading on my work in the simulation building realm, check out my wandering development post here, where I attempt to build a balanced simulation of a simple model for populations and reproduction.

There is also an excellent website with many models much better than mine on a website called NetLogo. I found this gem while doing research for my simulation (I also bought a book, Complex Adaptive Systems: An Introduction to Computational Models of Social Life, to add to my collection of unread books!).

Thank you so much for sticking with me, I hope the coming week receives you well 🙂

Code

Get the code for my simulation at my GitHub.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.