Blueprint is my markup language for creating GTK user interfaces. Since the last blog post, I and several contributors have fixed a number of bugs that have come up and added several bits of missing syntax. Thanks to everyone who’s contributed by reporting issues or submitting merge requests!
Now that the basics are done, I’m almost ready to call it 1.0. I still need to figure out how to integrate it with Meson better, because there are issues in some cases with the order that things are compiled. There’s also a couple more features I want to include, and I have a milestone to track them in GitLab.
After the official 1.0 release, I have a number of ideas for where to take Blueprint next.
IDE Integration
IDE integration has been a goal from the beginning, but there’s more to do still. I’d like to add a symbol outline, so you can visualize the widget tree and jump to the right part of a large file. It would also be nice to be able to control-click an identifier and jump to that object.
I want to improve the in-IDE documentation, especially by adding docs to
keywords when you hover over them. For example, when you hover over template
,
it will give you a refresher on how templates work. Finally, I want to add a
source code formatter. Most languages have these, and many developers and
projects rely on them to help keep code readable.
In addition to the GNOME Builder integration, I have an experimental VS Code extension that I need to clean up, make production-ready, and publish. Thanks to the Language Server Protocol, the vast majority of the code is not specific to any one IDE and actually resides in the compiler, not the extension. Still, there is some “glue code” and another syntax highlighter to be written.
Documentation & Tutorials
Right now, Blueprint’s documentation is quite sparse. There’s an overview, a tutorial on porting existing projects, and a long list of examples presented mostly without comment.
I’d like to expand the documentation with a complete explanation of what Blueprint does and how it works. I also want to create walkthrough of how to create a simple app from scratch using Blueprint, without prior knowledge of GtkBuilder required. That way, developers wanting to get into GNOME/GTK programming can start out with Blueprint if they want.
Reactive UI programming
I mentioned reactive programming in my last post about Blueprint, and I want to expand on that idea because I think it’s really powerful.
A bit of theory, and some GObject details
The following is some background on programming language concepts, which I find interesting; you can skip this section if that doesn’t interest you.
In the declarative programming paradigm, you describe the expected result of a computation. This is in contrast to imperative programming, where you outline the exact steps of a computation. For example, a SQL query describes what data to fetch and how to aggregate it, but leaves the loops and table lookups to the database to figure out.
Reactive programming is a type of declarative programming where a value can
depend on other values and be updated automatically when they change. In
reactive programming, a = b + c
is not an assignment that executes at a point
in time, but rather an invariant that holds as long as a
exists. When either
b
or c
changes, a
automatically changes to match.
This paradigm is particularly fitting for user interfaces, which usually need
to continually reflect the state of some underlying data model. When the data
changes, the UI always needs to update with it. Have you ever had a giant,
messy update()
method in your UI code that you call whenever the data
changes? That’s the imperative programming solution to a problem that’s
inherently reactive.
Web frameworks have been using more-or-less reactive approaches for a long time. React (of course), Angular, Vue, and Svelte are well-known examples. Although they work in different ways, all four use templates to generate an HTML page from data, then keep the page updated when the data changes. They’ve managed to make this work, but JavaScript doesn’t always play nice–there are well-known performance issues with recalculating the DOM tree, and nested data structures generally don’t trigger updates.
But it turns out GObject has had a critical reactive programming feature
since before I was born1.
It’s the notify::*
signal, which invokes a callback whenever a GObject
property changes.
Gtk.Expression is new in GTK 4. An expression can be a constant, a property lookup, or a user-defined function, and expressions can be composed with each other. Expressions can be watched, so that a callback is invoked whenever any input of the expression changes. They can also be bound, so that when those inputs change, a property is updated with the new value of the expression.
You can probably see where reactive programming fits here. Instead of updating
the UI by writing tedious imperative code, you can simply declare expressions
that update it automatically. No more giant update()
methods!
Reactive programming with Blueprint
Here’s how it would work in practice. In your code, you’d define properties
for your data. In the matching blueprint file, you’d reference those properties
using the bind
keyword, which creates a reactive expression.
Some use cases are simple.
Spinner {
visible: bind MainWindow.active-http-request.pending;
}
Label status_code {
label: bind MainWindow.active-http-request.status;
}
Some are a little more complex.
Label map_scale_label {
label: bind (string) zoom_level_to_scale_label(viewport.zoom-level);
}
But with an expanded expression system, you could even do this:
/* Works by generating an expression and binding it to main_stack.visible-child */
Stack main_stack {
if MainWindow.content-list.n_items() == 0 {
Adw.StatusPage {
/* ... */
}
} else {
ListView {
model: bind MainWindow.content-list;
/* ... */
}
}
}
Implementation
The simpler expressions–constants, property lookups, and callbacks–already exist in GTK and just need matching Blueprint syntax. I’m hoping to implement them soon.
The more advanced reactive expressions will require a helper library (don’t worry, blueprint files that don’t use features from the library won’t need to link to it). Of course, writing a new expression library is a lot more work than just adding syntax to the compiler, so this is a bit of a “stretch goal”.
Making this happen
These plans will likely take well over 100 hours of work to complete. I’m a college student, so this represents a significant investment of my time. If you’d like to sponsor this work, I’d be incredibly grateful! Sponsorships really do help me justify spending more time on Free Software instead of things that pay by the hour :)
But, sponsorships or not, this is something I’m really excited to work on. Language design fascinates me, and developer experience is something I think is really important for any platform. The GNOME ecosystem is great to work with, and the community is creating some awesome stuff that I’m grateful to be a small part of.
-
Sorry
not sorryto anyone who feels old now. ↩