Open PhilippMDoerner opened 1 week ago
The last 2 commits implement an example on how to use the stateRef by implementing a feature we previously didn't have - keyCaptureWidget on search.
It allows "forwarding" keypress-events that happen while another widget is in focus to the SearchEntry Widget. This allows in the example to have the list of items underneath the searchentry in focus, and while typing with it in focus the searchEntry fills with text.
From what I can tell it works correctly.
Some thoughts where I'd like to have your opinion:
Just had a chat with leorize: He suggests using the as
operator for the semantics on assigning a ref. And I very much agree that the semantics look a lot clearer.
This would basically change this:
ScrolledWindow:
stateRef = app.keyCaptureRef
... other stuff....
to this
ScrolledWindow as app.keyCaptureRef:
... other stuff....
It'd require some finageling with the DSL I think but in my eyes this improves clarity on where a ref comes from quite a bit.
Any thoughts on that or hints where I'd best look at for implementing that?
I did some experimenting about how to implement this.
With the newest commit (which can easily be reverted) we can now have the as
Syntax, see search_entry.nim in this PR:
ScrolledWindow as app.keyCaptureRef:
ListBox:
... other stuff ...
I wrote an in-depth explanation of what I did and the reasoning behind it in the commit message if further info is required.
ScrolledWindow as app.keyCaptureRef: ... other stuff....
This looks pretty nice! How does it interact with adders and the insert
statement?
The way it currently is implemented I think it doesn't interact with the insert statement at all.
By which I mean you likely can't use this syntax with insert at the moment, that'd require additional changes to what I did in parseGui
.
As for with adders, in which sense do you mean?
Edit:
Actually, you won't need extra stuff with insert. You just would split it different, as in, you'd never use the as X
syntax together with insert.
Like so:
method view(app: AppState): Widget =
let isInActiveSearch = app.text != ""
let window = gui:
ScrolledWindow as app.keyCaptureRef:
ListBox:
... lots of stuff ...
result = gui:
Window():
defaultSize = (600, 400)
HeaderBar() {.addTitlebar.}:
insert(app.toAutoFormMenu(ignoreFields = @["filteredItems"])) {.addRight.}
SearchEntry() {.addTitle, expand: true.}:
keyCaptureRef = app.keyCaptureRef
...lots of fields and events...
insert window
This works as expected.
As for with adders, in which sense do you mean?
My question is if using as
breaks the adder syntax. Where is the adder specified when using as
? Widget {. ... .} as ...
or Widget as .... {. ... .}
?
I wanted to open up this PR to already show you how I tend to act on every proc I touch when it comes to these to give you the chance to veto any of these habits up front.
I think that such refactoring should be made in a separate PR as they are not really related to adding Refs (or given the current title of this PR, adding refs should be a separate PR).
My question is if using as breaks the adder syntax. Where is the adder specified when using as? Widget {. ... .} as ... or Widget as .... {. ... .}?
Right now this breaks the DSL, should be fixable by adjusting some of the parsing again.
This is going to interact with adder or rather pragmas in general in a really ugly way that I haven't yet found a simple way to solve.
Parsing of pragmas happens in of nnkPragmaExpr:
in line 123.
However, adding the possibility of an infix approach, that means expanding that section because you might be parsing a pragma either as part of a "normal" line such as <Widget> {.addRow.}
or as part of an infix line such as <Widget> as <someRef> {.addRow.}
.
@can-lehmann Fixed the interaction with adders not working, example for that is added to SearchEntry. I likely shouldn't put that there but inspiration didn't hit me on how/where else to demonstrate it.
Other than that I fixed the rest of your comments.
I likely shouldn't put that there but inspiration didn't hit me on how/where else to demonstrate it.
I recently wrote a guide on the GUI DSL. You need to add a short subsection on state refs there anyways, so maybe add it there. Of course add a simple example first and then say "To get the reference of a widget with adders, the following syntax is used: "
Another issue you need to solve is what happens when a widget subsribes to a state ref but is deleted afterwards. Currently the observer still remains registered causing a use after free as far as I can see.
Another issue you need to solve is what happens when a widget subsribes to a state ref but is deleted afterwards. Currently the observer still remains registered causing a use after free as far as I can see.
The obvious and clean way to solve that efficiently architecturally speaking is having a destroy hook. Now how that's supposed to work I don't even have a concept for.
Any thoughts on how a destroy-hook in owlkettle would be implemented?
You might need to attach it to the gtk widget directly (via destroy notify I believe), as the lifetime of the WidgetState
is not necessarily equal to the lifetime of the gtk widget.
Memo to me for later in another PR: I'll need to see if I can create some procs that make performing this setup easier when I implement a second feature that makes use of this.
You might need to attach it to the gtk widget directly (via destroy notify I believe), as the lifetime of the
WidgetState
is not necessarily equal to the lifetime of the gtk widget.
The other option would be making sure that the lifetime of the gtk widget is always at least equal (or longer) than the lifetime of the WidgetState (by using g_object_ref/unref correctly). This might actually be a nice thing to do. Then you could use =destroy
.
The other option would be making sure that the lifetime of the gtk widget is always at least equal (or longer) than the lifetime of the WidgetState (by using g_object_ref/unref correctly). This might actually be a nice thing to do. Then you could use =destroy.
I'll claim incompetence on that one, this goes over my head on how I'd even make sure that is correct. It also conceptually makes no sense to me because in my head WidgetState persists across various iterations of Widgets, it just gets updated. So how a Widget would outlive WidgetState is beyond me. That's one of the reason why the usage of StateRef requires you assigning it to WidgetState.
Edit: Therefore the solution so far that makes the most sense to me is implementing a general onDestroy hook, specifically for renderables (because how would I know when a viewable gets "destroyed" ?). That means on-build of a renderable attaching a callback to the GtkWidget that calls the "onDestroyHook"-proc, or does nothing if no hook proc was defined.
So sth with this syntax:
renderable SomeWidget of BaseWidget:
... some fields ...
hooks:
onDestroy:
... do fascinating things ...
Which means modifying the renderable macro
Okay, so I found it rather easy to add destroy-hooks to be parseable, just add to the HookEnum and to parseHookKind and you're done.
However, generating the necessary code for that I'm massively struggling with. I know I want to execute something along the lines of this:
`state`.connect(`hook`, "destroy", eventCallback)
per destroy hook. But I'm hitting walls on how to do so. I'm getting the feeling doing this would be infinitely easier for you than for me because I'm massively struggling even vaguely getting an idea on where to add things and from where to get things.
This PR is a draft to demonstrate how I plan on acting while in the DSL part of the code.
Given that macros and DSLs in general have a tendency to be pretty mean in terms of complexity, I developed a few habits over the years to structure those well enough so I can more easily get back into it after a while.
I plan on introducing some of those in the owlkettle DSL because I believe them to be beneficial to the overall project and for readability.
I wanted to open up this PR to already show you how I tend to act on every proc I touch when it comes to these to give you the chance to veto any of these habits up front. (Sidenote, the below proc I mostly touched because I originally added a stateRef field to the generated WidgetState, then realized that's unnecessary, but at that point I'd already written myself a doc comment and it felt like a waste to throw that away)
In general: