Today, I set to solve one of the problems that we’ll need to write an Edit Text control amongst many others: transmogrifier storage. The idea is that implementing widgets for various frontends may need storage for state that the cross-platform widget doesn’t need to account for. To handle this, I built a little infrastructure to solve several problems:
- Giving unique IDs to widgets
- Giving storage to transmogrified that vary by size and type
In [the commit], I introduced several types, some which are only relevant if you’re implementing your own frontend. Right now, I mostly want to cover the experience for implementing a transmogrifier for a widget that contains another widget.
To store a child widget in your cross-platform widget type, use the WidgetInstance
type. If you wish to accept a generic type for the widget, you can. But, if you’d like to accept any widget and not require a generic type for your widget, you can use Box<dyn AnyWidgetInstance>
. This is how Container
is implemented..
When implementing a transmogrifier for a frontend, you will almost certainly need to call methods on the child widgets. These implementations will vary for each frontend, but here is how Container
works in gooey-browser
:
impl WebSysTransmogrifier for ContainerTransmogrifier {
fn transmogrify(
&self,
_state: &Self::State,
parent: &web_sys::Node,
widget: &<Self as Transmogrifier<WebSys>>::Widget,
frontend: &WebSys,
) -> Option<web_sys::HtmlElement> {
frontend.ui.with_transmogrifier(
widget.child.as_ref(),
|child_transmogrifier, child_state| {
// ...
if let Some(child) = child_transmogrifier.transmogrify(
child_state,
&container,
widget.child.as_ref(),
frontend,
) {
container
.append_child(&child)
.expect("error appending child");
}
container.unchecked_into()
},
)
}
}
The Gooey::with_transmogrifier()
function makes it easy to look up the registered transmogrifier and the transmogrifier state. These parameters are the type-erased versions since those types are for a potentially “foreign” transmogrifier type. The side effect is that the parent cannot directly access the child’s state. It’s a side effect that happens to enforce good boundaries.
Known areas that I want to reconsider
-
with_transmogrifier
returnsOption<T>
rather than type that indicates an error, likeResult<R, NoTransmogrifierFound>
. This is almost certainly incorrect, but error handling really hasn’t been added to gooey yet. Maybe it’s time. - The parameter order on the Rasterizer crate’s transmogrifier functions isn’t consistent and is weird. I wasn’t sure what order made the most sense, so I just left it as horrible as it was before I started the day.