Hi, I'm Sam Parkinson

A new way of writing Gtk+ applications

Introducing Pyract - my weekend hack

By Sam Parkinson, 05 February 2017; view other posts

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.


Comments, thoughts? Mail them to sam@sam.today. I would love to hear them!

Related posts

View all posts
SELinux Concepts - but for humans
This is your SELinux dictionary!
Read post
Stop Disabling SELinux: A Real-World guide
Be safe from software vulnerabilities AND run your webserver
Read post
Derivations 102 - Learning Nix pt 4
Taking advantage of the fact Nix is a programming language
Read post
Creating a super simple derivation - Learning Nix pt 3
Wrapping some shell scripts
Read post
So Variables are a Thing - Learning Nix pt 2
Taking advantage of the fact Nix is a programming language
Read post
NSDC 2016 Topics
Digitizing the motions from National Schools Debating Championships 2016
Read post