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:
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.
I hope you enjoyed this article. Contact me if you have any thoughts or questions.
© 2015—2024 Sam Parkinson