Workbench
Workbench copied to clipboard
Define Gtk closure expressions in code when the UI file is NOT a template.
Suppose we have a .blp-file that defines a template. Then we can use Gtk closure expression defined in the (say javascript) code like this for example:
UI file main.blp
using Gtk 4.0;
using Adw 1;
template $CustomWidget : Adw.PreferencesGroup {
Adw.ComboRow cb {
title: _("Make a selection:");
model:
StringList {
strings ["One", "Two", "Three", "Four"]
};
}
Adw.SwitchRow sw1 { // should only be visible when option "One", "Two" or "Three" is selected.
title: _("Switch one");
active: true;
visible: bind $onlyOneTwoThree(cb.selected-item) as <bool>;
}
Adw.SwitchRow sw2 { // should only be visible when option "Three" or "Four" is selected
title: _("Switch two");
visible: bind $onlyThreeFour(cb.selected-item) as <bool>;
}
Adw.ActionRow {
title: _("Status:");
Label { // should display some status message depending on sw1.visible, sw2.visible and cb.selected-item
label: bind $msg(sw1.visible, sw2.visible, cb.selected-item) as <string>;
}
}
}
Code file main.js
import GObject from "gi://GObject";
import Gtk from "gi://Gtk";
import Adw from "gi://Adw";
const CustomWidget = GObject.registerClass(
{
GTypeName: "CustomWidget",
Template: workbench.template,
},
class CustomWidget extends Adw.PreferencesGroup {
onlyOneTwoThree(_self, item) {
const str = item.string;
return str === "One" || str === "Two" || str === "Three";
}
onlyThreeFour(_self, item) {
const str = item.string;
return str === "Three" || str === "Four";
}
msg(_self, active1, active2, item) {
const str = item.string;
if (active1 && active2) {
return `"${str}" was selected, 2 visible`;
} else if (active1 || active2) {
return `"${str}" was selected, 1 visible`;
} else {
return "0 active. Discard selection";
}
}
},
);
const box = new Gtk.Box({ orientation: "vertical" });
box.append(new CustomWidget());
workbench.preview(box);
Now suppose I want to have the same behavior in case that the UI file does NOT define a template (remove
template $CustomWidget :
from the fourth line of main.blp)
How can we define the Gtk closure expressions onlyOneTwoThree, onlyThreeFour and msg in the code? There doesn't seem to be an easy way to do so. According to @andyholmes (conversation in the matrix channel) these closures should be added to Workbench's builder scope in some way (compare gjs guide), yet apparently Workbench is not set up to use closures at the moment.
So I'd like to request Workbench to support closures in a way that it is easy to use and documented (maybe in a Library demo).
Thanks for filing a clear issue. This is something I've wanted as well.
Related, during a Workshop someone was confused about the lack of signal handlers. https://floss.social/@sonny/110816126000858204
The main issues are:
- We need a simple API for it and it doesn't exist in GTK currently
- I would like this API to be exactly or as close as possible to what you would use once moving away from Workbench
I've been proposing the following for GJS https://gitlab.gnome.org/GNOME/gjs/-/merge_requests/853
Let's move forward, here is a plan:
- [x] Add the API in Workbench but don't use it in Library and make it log a warning "Experimental stuff"
- [ ] Make the API available in Rust and Vala as well
- [ ] See where we go from there and how we can "upstream" this API
- [ ] Expose the API and use it in Library
@rolandlo are you able to test / use Workbench devel (branch main)?
If you're gonna use it, please do share feedback and thoughts. Thanks!
The experimental workbench.build API can be use like so:
Blueprint
using Gtk 4.0;
using Adw 1;
Adw.PreferencesGroup preferences_group {
Adw.ComboRow cb {
title: _("Make a selection:");
model:
StringList {
strings ["One", "Two", "Three", "Four"]
};
}
Adw.SwitchRow sw1 { // should only be visible when option "One", "Two" or "Three" is selected.
title: _("Switch one");
active: true;
visible: bind $onlyOneTwoThree(cb.selected-item) as <bool>;
}
Adw.SwitchRow sw2 { // should only be visible when option "Three" or "Four" is selected
title: _("Switch two");
visible: bind $onlyThreeFour(cb.selected-item) as <bool>;
}
Adw.ActionRow {
title: _("Status:");
Label { // should display some status message depending on sw1.visible, sw2.visible and cb.selected-item
label: bind $msg(sw1.visible, sw2.visible, cb.selected-item) as <string>;
}
}
Button {
label: "Signal handler";
clicked => $onButtonClicked();
}
}
JavaScript
import GObject from "gi://GObject";
import Gtk from "gi://Gtk";
import Adw from "gi://Adw";
const { preferences_group } = workbench.build({
onlyOneTwoThree(_self, item) {
const str = item.string;
return str === "One" || str === "Two" || str === "Three";
},
onlyThreeFour(_self, item) {
const str = item.string;
return str === "Three" || str === "Four";
},
msg(_self, active1, active2, item) {
log("msg");
const str = item.string;
if (active1 && active2) {
return `"${str}" was selected, 2 visible`;
} else if (active1 || active2) {
return `"${str}" was selected, 1 visible`;
} else {
return "0 active. Discard selection";
}
},
onButtonClicked(...args) {
console.log(...args);
},
});
workbench.preview(preferences_group);
@rolandlo are you able to test / use Workbench devel (branch
main)?
Yes, I can test the main branch. It works great in the example. The syntax is nice. However, how would the JS code look for a Blueprint file like this:
using Gtk 4.0;
using Adw 1;
Adw.PreferencesWindow preferences_window {
Adw.PreferencesGroup preferences_group {
Button {
label: "Signal handler";
clicked => $onButtonClicked();
}
}
}
With the obvious looking
import GObject from "gi://GObject";
import Gtk from "gi://Gtk";
import Adw from "gi://Adw";
const { preferences_window } = workbench.build({
onButtonClicked,
});
function onButtonClicked(...args) {
console.log(...args);
}
workbench.preview(preferences_window);
I get a message
when clicking on the button.
You need to press the "Run" button first. Related https://github.com/sonnyp/Workbench/issues/643
I did, but that doesn't help. Note that when I change Adw.PreferencesWindow to Box it also works.
I'm not sure why that is but preferences_window.present() instead of workbench.preview(preferences_window) seems to work.
Right, it does. I will do further tests then.
Everything works fine now with what I wanted to achieve with this feature request. See the redesigned Xournal++ preferences window code/UI in case you'd like to check it out.
@rolandlo that's great to hear.
I'm glad Workbench is useful to Xournal++
I'll leave this open as there are a couple of things left to do here.