Open PhilippMDoerner opened 8 months ago
During my waiting times I'm looking into this and I'm wondering whether the first step for this ticket isn't just that we need the ability to query the gui tree we construct via the gui-dsl. Basically javascripts "getElementByTagName".
That way you could be able to fetch any Widget from a given gui tree. Imo that'd be step 1, step 2 is be able to check if a given Widget already has a corresponding GtkWidget and fetch that if it does.
Particularly the second step I have absolutely 0 idea how we could achieve that.
React solves this issue using "refs": https://react.dev/learn/manipulating-the-dom-with-refs Something similar will work for owlkettle as well.
The basic API might look like this:
let entry = Ref[EntryState]()
gui:
Window:
Box:
Entry:
reference = entry
Button:
proc clicked() =
entry.moveCursor(0)
entry.focus()
I never used react so I'm not fully familiar with the concept. From what I can understand, basically you create a ref first, pass that on to the widget and that "fills" the ref for you?
Edit: You were faster with the example :smile: . But yeah, given the code it's pretty much exactly that. Thanks for the explainer!
Given that BaseWidget is the easiest way to make this accessible for most widgets at once, that'd mean add a field "owlRef" or whatever to it and in the beforebuild hook, if hasOwlRef = true, then fill it with... good question. A ref to widgetState? Would make sense I guess since that nets you the entire widget + access to the associated GtkWidget
I tried to play around a bit with this (basically passing WidgetState into the Ref and not making it a generic). Question: Wouldn't a "Ref" approach like this need to sort of work like an observable in rxjs or the like? By that I mean that you should be able to register callbacks and they fire every time the value of the Ref changes.
I've come to this conclusion mostly because I very quickly run into scenarios with the StackSwitcher/StackSidebar things where I want to use what is inside the Ref, but it does not yet have a value assigned to it, because GtkWidgets/WidgetStates only get instantiated once you insert them.
Even though I did not originally plan this, having it be observable sounds like a good solution to the StackSwitcher
issue.
As a first approach for a solution we could create a type WidgetRef = ref GtkWidget
or type WidgetRef = ref WidgetState
and give BaseWidget a field widgetRef
that will assign the constructed GtkWidget or WidgetState to that ref.
Then you can share that around and unref it for access.
It's a bit clunky and we can see how well we can disguise that, but its the only idea I can come up with (and likely one you already thought of, but I really couldn't come up with anything better). We could look into making that type observable-ish as well. I've already implemented an observable/subject pattern in https://github.com/minamorl/rex , though using a full implementation like that might be overkill, maybe we can get away with something a lot simpler, if at all.
I assume you'd want us not depend on the lib to reduce external dependencies which I'd agree with, given we don't plan on going all in with observable.
I think you proposed the idea of making refs observable to allow them to be used in scenarios where a widget needs to be known by another widget that is not one of its ancestors in the widget tree (e.g. Stack Switcher). For this we do not need any reactive stream logic or combinators.
I think the a ref might end up looking like this:
type
WidgetRef*[T] = ref object
state*: T
observers: HashSet[proc(state: T)]
where T
is some WidgetState
.
I don't think that's possible. The main reason being that you'd want to implement it roughly like this:
#widgetutils.nim
type
StateRef*[T: WidgetState] = ref object
state: T
observers: HashSet[proc(state: T)]
proc hasRef*[T: WidgetState](stateRef: StateRef[T]): bool = not stateRef.state.isNil()
proc newRef*[T: WidgetState](): StateRef[T] =
StateRef[T](observers: initHashSet[proc(state: T)]())
proc unwrap*[T: WidgetState](stateRef: StateRef[T]): T =
stateRef.state
proc setRef*[T: WidgetState](stateRef: StateRef[T], state: T) =
stateRef.state = state
for observer in stateRef.observers:
observer(stateRef.state)
proc widget*[T: WidgetState](stateRef: StateRef[T]): Option[GtkWidget] =
when T is Renderable: # This doesn't work so far for some reason
some stateRef.state.internalWidget
else:
none(GtkWidget)
proc subscribe*[T: WidgetState](stateRef: StateRef[T], observer: proc(state: T)) =
stateRef.observers.incl(observer)
proc unsubscribe*[T: WidgetState](stateRef: StateRef[T], observer: proc(state: T)) =
stateRef.observers.excl(observer)
#widgets.nim
renderable Box of BaseWidget:
...
stateRef: StateRef[BoxWidgetState]
hooks:
...
afterBuild:
if not state.stateRef.isNil():
state.stateRef.setRef(state)
Now this is a problem, BoxState doesn't exist yet as it has not yet been created so it can't be used in a type-declaration.
Therefore it'd have to be Renderable
so you can have access to at least GtkWidget, which is the key.
Edit: Or you generate this field inside the DSL, as well as the code for the afterBuild hook
Edit2: I think generating this as part of the DSL is the way to go after mulling it over a bit.
For Features such as CaptureWidget on
SearchEntry
(as discussed here ) and I think others as well, it is mandatory to be able to have circular references.Without those implemented, the full featureset of all GTK widgets can not be provided.
I'd like to have an issue about this simply so we can keep this in mind and have somewhere to track possible progress against etc.
Though honestly, how this could possibly work is entirely beyond me.