Transmogrifier State Implementation

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 returns Option<T> rather than type that indicates an error, like Result<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.