Robert Wittams

State in Druid widgets

Overview

First a quick overview - this is not meant to cover the entirety of Druid, just the relevant points

Druid widget methods can be dependent on state from the following places:

PlaceMutable?LimitationsNotes
selfAlwaysNoneMain widget methods are &mut self.
app dataIn eventMust impl DataData values are cloned and compared by WidgetPod. If you want to mutate app data in another method, send self a command that does the mutation (possibly queueing the update in self).
envNeverMust be containable in ValueEnvScope can “port” data into the env of its children.
contextVia negotiationLibrary definede.g size, viewport_offset

The lifecycle of druid methods is as follows:

Event->Update?->Layout?->Paint>?

Lifecycle events also occur but each one is specialised. The most relevant to this discussion is WidgetAdded, which should occur before anything else.

MethodWhy is it called
lifecycle (WidgetAdded)Your widget was added to the hierarchy, and children_changed was called
eventa user action, internal event, or command is probably relevant to this widget or its children
updateYour parent (WidgetPod, or LensWrap, or.. ) thinks your data has changed or request_update was called
layoutlayout has never happened, or request_layout was called
paintpaint has never happened, or request_paint was called

When should you call the various request_* methods? (Just covering the ones that cause your own methods to be called).

Request methodWhen to call
request_updateOnly if synthesizing data , like Scope. Your caller (WidgetPod, LensWrap) should detect other changes
request_layoutWhen some state that you depend on in layout changes.
request_paintWhen some state that you depend on in paint changes.
request_paint_rect(Advanced) When some state that you depend on in paint changes, and you can pinpoint the relevant rect. Useful for containers

Observations

From the widget authors point of view, there are a couple of decisions to be made related to state:

Currently, the general thought process is :

The main issue here is what I call the “single binding problem”.

So the widget author has to make decisions that limit the choices we would like to give Widget consumers:

Possible partial solutions

View switcher

What is it? A wrapper widget will recreate the widget from data and env each time the relevant parts change

What do you get? Constructor args etc can be made reactive

Limitations Any state that you do not or cannot extract from the widget will be lost. Identity will be lost if not manually maintained. A bit more manual dependency tracking is required.

Scope

What is it? Your widget can put more state into data, without polluting app data. A wrapper widget will maintain a two way link.

What do you get? Identity and private state maintained. Multiple sibling widgets can reactively share encapsulated state.

Limitations Your ‘real’ widget(s) will likely need to be wrapped by a scope and another wrapper to set up the scope.

Bindings

What is it? A wrapper widget (BindingHost) that gives you one or two way transfer of information between data and self. Env should be doable. (This can include everything inside a scope, because a scopes state is kept in the self of the Scope widget.) The wrapper reach past lenses and other type preserving wrappers to access the widgets self.

What do you get? Keep everything in self, and your callers can set up any bindings they want. Currently bindings can be between a lens on data and a lens on a widget struct, or a lens on data and a BindableProperty. Critically, BindableProperties can call request_* methods. BindableProperties must be manually implemented currently.

Limitations Up in the air a bit. Bindings is currently only in my bindings-scroll branch and I haven’t pushed it very far. Implementation will likely change. Can only reach past wrappers that do not box, and currently needs impls of traits for wrappers and widgets that need to be bound.

Speculation

I think the combination of Scope + Bindings does give us at least a ‘full reactive’ story :

However, I am not convinced it is the final picture at all - it is likely that this is scaffolding, enabling us to write more complete widgets and reveal fuller solutions that are less noisy and more integrated into the framework.

My current (half baked) thoughts of a possible future state:

← All writing