Blog
Want more like this?
Subscribe!

A new way of writing Gtk+ applications

Introducing Pyract - my weekend hack

I love working with Gtk+ - it is a great GUI toolkit with a good developer experience. But React has totally changed how GUI apps are written. Now it is all the rage to use more functional style programming:

Gtk3 counter app

The app very complex, see below

I've always wanted to see a combination of these two things, so this is my weekend hack. It is pyract, a python3 library that merges Gtk+, React and MobX into 1 boiling pot:

from gi.repository import Gtk
from pyract.view import run, Component, Node, load_css
from pyract.model import ObservableModel, ObservableValue, ModelField

# Let's create a model to back our application.  Since it is Observable, it
# will tell pyract to re-render our UI when it changes.
class AppModel(ObservableModel):
    # The ModelField tells the model to create us an ObservableValue and set
    # to 0.  ObservableValues lets us re-render the UI when the value changes.
    counter = ModelField(ObservableValue, 0)

    def increment(self):
        self.counter.value = self.counter.value + 1


# Components are similar to in React.
class AppComponent(Component):
    # Our render method can return a Node or list of Nodes.
    def render(self, model: AppModel):
        # Nodes are a type followed by kwargs.  When a component is
        # re-rendered, then the Node trees get diffed and only the changed
        # Nodes and properties are updated.
        # The type is either a Gtk.Widget or pyract.Component subclass.
        # "signal__" props are the same as connecting a GObject signal
        return Node(Gtk.Window, signal__destroy=Gtk.main_quit,
                    title='My Counter App',
                    children=[
            Node(Gtk.Box, orientation=Gtk.Orientation.VERTICAL,
                 children=[
                # The class_names prop adds the appropriate classes
                Node(Gtk.Label, class_names=['counter-label'],
                     label=str(model.counter.value)),
                Node(Gtk.Button, label='Increment Counter',
                     class_names=['suggested-action', 'bottom-button'],
                     # Hide the button when the counter gets to ten
                     visible=model.counter.value < 10,
                     signal__clicked=self._button_clicked_cb),
                # Add a reset button, but only show it when counter == 10
                Node(Gtk.Button, label='Reset',
                     class_names=['destructive-action', 'bottom-button'],
                     visible=model.counter.value >= 10,
                     signal__clicked=self._reset_clicked_cb)
            ])
        ])

    # Signal handlers are just like in normal Gtk+
    def _button_clicked_cb(self, button):
        # Access the props using self.props
        self.props['model'].increment()

    def _reset_clicked_cb(self, button):
        self.props['model'].counter.value = 0


# Adding CSS is really easy:
load_css('''
.counter-label {
    font-size: 100px;
    padding: 20px;
}
.bottom-button {
    margin: 10px;
}
''')


# The run function works just like constructing a Node, but it enters
# the mainloop and runs the app!
run(AppComponent, model=AppModel())

Be sure to subscribe below so that you get updates on the pyract project. Or check it out on GitHub. And as always, feel free to email me feedback.

Get More Great Content

The intersection of marketing, design and machine learning, delivered straight to your inbox:
😀
Awesome! Please check your inbox and confirm your email.
We'll email you our latest posts plus special past content. Change your settings any time.