raui-core 0.38.4

Renderer Agnostic User Interface
Documentation
//! Application foundation used to drive the RAUI interface
//!
//! An [`Application`] is the struct that pulls together all the pieces of a RAUI ui such as layout,
//! interaction, animations, etc.
//!
//! In most cases users will not need to manually create and manage an [`Application`]. That will
//! usually be handled by renderer integration crates like [`raui_tetra_renderer`].
//!
//! [`raui_tetra_renderer`]: https://docs.rs/raui-tetra-renderer/latest/raui_tetra_renderer/
//!
//! You _will_ need to interact with [`Application`] if you are building your own RAUI integration
//! with another renderer or game engine.
//!
//! # Example
//!
//! ```rust
//! # use raui_core::prelude::*;
//! // Create the application
//! let mut application = Application::new();
//!
//! // We need to run the "setup" functions for the application to register components and
//! // properties if we want to support serialization of the UI. We pass it a function that
//! // will do the actual registration
//! application.setup(setup /* the core setup function from the RAUI prelude */);
//!
//! // If we used RAUI material we would also want to call it's setup ( but we don't need
//! // it here )
//! // application.setup(raui_material::setup);
//!
//! // Create the renderer. In this case we use the raw renderer that will return raw
//! // [`WidgetUnit`]'s, but usually you would have a custom renderer for your game
//! // engine or renderer.
//! let mut renderer = RawRenderer;
//!
//! // Create the interactions engine. The default interactions engine covers typical
//! // pointer + keyboard + gamepad navigation/interactions.
//! let mut interactions = DefaultInteractionsEngine::new();
//!
//! // We create our widget tree
//! let tree = widget! {
//!     (#{"app"} nav_content_box [
//!         (#{"button"} button: {NavItemActive} {
//!             content = (#{"icon"} image_box)
//!         })
//!     ])
//! };
//!
//! // We apply the tree to the application. This must be done again if we wish to change the
//! // tree.
//! application.apply(tree);
//!
//! // This and the following function calls would need to be called every frame
//! loop {
//!     // Telling the app to `process` will make it perform any necessary updates.
//!     //
//!     // We can also pass in a `ProcessContext` which allows us to provide the UI with
//!     // mutable access to application data, but we just pass in a default context in
//!     // this case.
//!     application.process();
//!
//!     // To properly handle layout we need to create a mapping of the screen coordinates to
//!     // the RAUI coordinates. We would update this with the size of the window every frame.
//!     let mapping = CoordsMapping::new(Rect {
//!         left: 0.0,
//!         right: 1024.0,
//!         top: 0.0,
//!         bottom: 576.0,
//!     });
//!
//!     // We apply the application layout
//!     application
//!         // We use the default layout engine, but you could make your own layout engine
//!         .layout(&mapping, &mut DefaultLayoutEngine)
//!         .unwrap();
//!
//!     // we interact with UI by sending interaction messages to the engine. You would hook this
//!     // up to whatever game engine or window event loop to perform the proper interactions when
//!     // different events are emitted.
//!     interactions.interact(Interaction::PointerMove(Vec2 { x: 200.0, y: 100.0 }));
//!     interactions.interact(Interaction::PointerDown(
//!         PointerButton::Trigger,
//!         Vec2 { x: 200.0, y: 100.0 },
//!     ));
//!
//!     // Since interactions engines require constructed layout to process interactions we
//!     // have to process interactions after we layout the UI.
//!     application.interact(&mut interactions).unwrap();
//!
//!     // Now we render the app, printing it's raw widget units
//!     println!("{:?}", application.render(&mapping, &mut renderer).unwrap());
//! #   break;
//! }
//! ```

use crate::{
    animator::{AnimationUpdate, Animator, AnimatorStates},
    interactive::InteractionsEngine,
    layout::{CoordsMapping, Layout, LayoutEngine},
    messenger::{Message, MessageData, MessageSender, Messages, Messenger},
    props::{Props, PropsData, PropsRegistry},
    renderer::Renderer,
    signals::{Signal, SignalSender},
    state::{State, StateUpdate},
    widget::{
        component::{WidgetComponent, WidgetComponentPrefab},
        context::{WidgetContext, WidgetMountOrChangeContext, WidgetUnmountContext},
        node::{WidgetNode, WidgetNodePrefab},
        unit::{
            area::{AreaBoxNode, AreaBoxNodePrefab},
            content::{
                ContentBoxItem, ContentBoxItemNode, ContentBoxItemNodePrefab, ContentBoxNode,
                ContentBoxNodePrefab,
            },
            flex::{
                FlexBoxItem, FlexBoxItemNode, FlexBoxItemNodePrefab, FlexBoxNode, FlexBoxNodePrefab,
            },
            grid::{
                GridBoxItem, GridBoxItemNode, GridBoxItemNodePrefab, GridBoxNode, GridBoxNodePrefab,
            },
            image::{ImageBoxNode, ImageBoxNodePrefab},
            portal::{
                PortalBox, PortalBoxNode, PortalBoxNodePrefab, PortalBoxSlot, PortalBoxSlotNode,
                PortalBoxSlotNodePrefab,
            },
            size::{SizeBoxNode, SizeBoxNodePrefab},
            text::{TextBoxNode, TextBoxNodePrefab},
            WidgetUnit, WidgetUnitNode, WidgetUnitNodePrefab,
        },
        FnWidget, WidgetId, WidgetLifeCycle,
    },
    Prefab, PrefabError, PrefabValue, Scalar,
};
use std::{
    any::{Any, TypeId},
    collections::{HashMap, HashSet},
    convert::TryInto,
    sync::{
        atomic::{AtomicBool, Ordering},
        mpsc::{channel, Sender},
        Arc,
    },
};

/// Allows you to check or indicate that an [`Application`] has changed
///
/// A [`ChangeNotifier`] can be obtained from an application with the
/// [`change_notifier()`][Application::change_notifier] method.
#[derive(Debug, Default, Clone)]
pub struct ChangeNotifier(Arc<AtomicBool>);

impl ChangeNotifier {
    /// Mark the application as having changed, this will force the UI to re-render its components
    pub fn change(&mut self) {
        self.0.store(true, Ordering::Relaxed);
    }

    /// Check whether or not the application has changed
    pub fn has_changed(&self) -> bool {
        self.0.load(Ordering::Relaxed)
    }

    /// Get whether the application has changed and atomically set it's changed state to `false
    pub fn consume_change(&mut self) -> bool {
        self.0.swap(false, Ordering::Relaxed)
    }
}

/// Errors that can occur while interacting with an application
#[derive(Debug, Clone)]
pub enum ApplicationError {
    Prefab(PrefabError),
    ComponentMappingNotFound(String),
}

impl From<PrefabError> for ApplicationError {
    fn from(error: PrefabError) -> Self {
        Self::Prefab(error)
    }
}

/// Indicates the reason that an [`Application`] state was invalidated and had to be re-rendered
///
/// You can get the last invalidation cause of an application using [`last_invalidation_cause`]
///
/// [`last_invalidation_cause`]: Application::last_invalidation_cause
#[derive(Debug, Clone)]
pub enum InvalidationCause {
    /// Application not invalidated
    None,
    /// Application update was forced by calling [`mark_dirty`]
    ///
    /// [`mark_dirty`]: Application::mark_dirty
    Forced,
    /// A widget's state changed
    StateChange(WidgetId),
    /// A message was sent to a widget
    MessageReceived(WidgetId),
    /// An animation is in progress for a widget
    AnimationInProgress(WidgetId),
}

impl Default for InvalidationCause {
    fn default() -> Self {
        Self::None
    }
}

/// Contains and orchestrates application layout, animations, interactions, etc.
///
/// See the [`application`][self] module for more information and examples.
pub struct Application {
    component_mappings: HashMap<String, FnWidget>,
    props_registry: PropsRegistry,
    tree: WidgetNode,
    rendered_tree: WidgetUnit,
    layout: Layout,
    states: HashMap<WidgetId, Props>,
    state_changes: HashMap<WidgetId, Props>,
    animators: HashMap<WidgetId, AnimatorStates>,
    messages: HashMap<WidgetId, Messages>,
    signals: Vec<Signal>,
    #[allow(clippy::type_complexity)]
    unmount_closures: HashMap<WidgetId, Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>>,
    dirty: bool,
    render_changed: bool,
    last_invalidation_cause: InvalidationCause,
    change_notifier: ChangeNotifier,
    /// The amount of time between the last update, used when calculating animation progress
    pub animations_delta_time: Scalar,
}

impl Default for Application {
    fn default() -> Self {
        Self::new()
    }
}

impl Application {
    #[inline]
    pub fn new() -> Self {
        Self {
            component_mappings: Default::default(),
            props_registry: Default::default(),
            tree: Default::default(),
            rendered_tree: Default::default(),
            layout: Default::default(),
            states: Default::default(),
            state_changes: Default::default(),
            animators: Default::default(),
            messages: Default::default(),
            signals: Default::default(),
            unmount_closures: Default::default(),
            dirty: true,
            render_changed: false,
            last_invalidation_cause: Default::default(),
            change_notifier: ChangeNotifier::default(),
            animations_delta_time: 0.0,
        }
    }

    /// Setup the application with a given a setup function
    ///
    /// We need to run the `setup` function for the application to register components and
    /// properties if we want to support serialization of the UI. We pass it a function that will do
    /// the actual registration.
    ///
    /// > **Note:** RAUI will work fine without running any `setup` if UI serialization is not
    /// > required.
    ///
    /// # Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// # let mut application = Application::new();
    /// application.setup(setup /* the core setup function from the RAUI prelude */);
    /// ```
    ///
    /// If you use crates like the `raui_material` crate you will want to call it's setup function
    /// as well.
    ///
    /// ```ignore
    /// application.setup(raui_material::setup);
    /// ```
    #[inline]
    pub fn setup<F>(&mut self, mut f: F)
    where
        F: FnMut(&mut Self),
    {
        (f)(self);
    }

    /// Get the [`ChangeNotifier`] for the [`Application`]
    ///
    /// Having the [`ChangeNotifier`] allows you to check whether the application has changed and
    /// allows you to force application updates by marking the app as changed.
    ///
    /// [`ChangeNotifier`]s are also used to create [data bindingss][crate::data_binding].
    #[inline]
    pub fn change_notifier(&self) -> ChangeNotifier {
        self.change_notifier.clone()
    }

    /// Register's a component under a string name used when serializing the UI
    ///
    /// This function is often used in [`setup`][Self::setup] functions for registering batches of
    /// components.
    ///
    /// # Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// fn my_widget(ctx: WidgetContext) -> WidgetNode {
    ///     todo!("make awesome widget");
    /// }
    ///
    /// fn setup_widgets(app: &mut Application) {
    ///     app.register_component("my_widget", my_widget);
    /// }
    ///
    /// let mut application = Application::new();
    ///
    /// application.setup(setup_widgets);
    /// ```
    #[inline]
    pub fn register_component(&mut self, type_name: &str, processor: FnWidget) {
        self.component_mappings
            .insert(type_name.to_owned(), processor);
    }

    /// Unregisters a component
    ///
    /// See [`register_component`][Self::register_component]
    #[inline]
    pub fn unregister_component(&mut self, type_name: &str) {
        self.component_mappings.remove(type_name);
    }

    /// Register's a property type under a string name used when serializing the UI
    ///
    /// This function is often used in [`setup`][Self::setup] functions for registering batches of
    /// properties.
    ///
    /// # Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// # use serde::{Serialize, Deserialize};
    /// #[derive(PropsData, Debug, Default, Copy, Clone, Serialize, Deserialize)]
    /// struct MyProp {
    ///     awesome: bool,
    /// }
    ///
    /// fn setup_properties(app: &mut Application) {
    ///     app.register_props::<MyProp>("MyProp");
    /// }
    ///
    /// let mut application = Application::new();
    ///
    /// application.setup(setup_properties);
    /// ```
    #[inline]
    pub fn register_props<T>(&mut self, name: &str)
    where
        T: 'static + Prefab + PropsData,
    {
        self.props_registry.register_factory::<T>(name);
    }

    /// Unregisters a property type
    ///
    /// See [`register_props`][Self::register_props]
    #[inline]
    pub fn unregister_props(&mut self, name: &str) {
        self.props_registry.unregister_factory(name);
    }

    /// Serialize the given [`Props`] to a [`PrefabValue`]
    #[inline]
    pub fn serialize_props(&self, props: &Props) -> Result<PrefabValue, PrefabError> {
        self.props_registry.serialize(props)
    }

    /// Deserialize [`Props`] from a [`PrefabValue`]
    #[inline]
    pub fn deserialize_props(&self, data: PrefabValue) -> Result<Props, PrefabError> {
        self.props_registry.deserialize(data)
    }

    /// Serialize a [`WidgetNode`] to a [`PrefabValue`]
    #[inline]
    pub fn serialize_node(&self, data: &WidgetNode) -> Result<PrefabValue, ApplicationError> {
        Ok(self.node_to_prefab(data)?.to_prefab()?)
    }

    /// Deserialize a [`WidgetNode`] from a [`PrefabValue`]
    #[inline]
    pub fn deserialize_node(&self, data: PrefabValue) -> Result<WidgetNode, ApplicationError> {
        self.node_from_prefab(WidgetNodePrefab::from_prefab(data)?)
    }

    /// Get the reason that the application state was last invalidated and caused to re-process
    #[inline]
    pub fn last_invalidation_cause(&self) -> &InvalidationCause {
        &self.last_invalidation_cause
    }

    /// Return's `true` if the application needs to be re-processed
    #[inline]
    pub fn is_dirty(&self) -> bool {
        self.dirty
    }

    /// Force mark the application as needing to re-process
    #[inline]
    pub fn mark_dirty(&mut self) {
        self.dirty = true;
    }

    #[inline]
    pub fn does_render_changed(&self) -> bool {
        self.render_changed
    }

    /// Get the [`WidgetNode`] for the application tree
    #[inline]
    pub fn tree(&self) -> &WidgetNode {
        &self.tree
    }

    /// Get the application widget tree rendered to raw [`WidgetUnit`]'s
    #[inline]
    pub fn rendered_tree(&self) -> &WidgetUnit {
        &self.rendered_tree
    }

    /// Get the application [`Layout`] data
    #[inline]
    pub fn layout_data(&self) -> &Layout {
        &self.layout
    }

    #[inline]
    pub fn has_layout_widget(&self, id: &WidgetId) -> bool {
        self.layout.items.keys().any(|k| k == id)
    }

    /// Update the application widget tree
    #[inline]
    pub fn apply(&mut self, tree: WidgetNode) {
        self.tree = tree;
        self.dirty = true;
    }

    /// Render the application
    #[inline]
    pub fn render<R, T, E>(&self, mapping: &CoordsMapping, renderer: &mut R) -> Result<T, E>
    where
        R: Renderer<T, E>,
    {
        renderer.render(&self.rendered_tree, mapping, &self.layout)
    }

    /// Render the application, but only if something effecting the rendering has changed and it
    /// _needs_ to be re-rendered
    #[inline]
    pub fn render_change<R, T, E>(
        &mut self,
        mapping: &CoordsMapping,
        renderer: &mut R,
    ) -> Result<Option<T>, E>
    where
        R: Renderer<T, E>,
    {
        if self.render_changed {
            Ok(Some(self.render(mapping, renderer)?))
        } else {
            Ok(None)
        }
    }

    /// Calculate application layout
    #[inline]
    pub fn layout<L, E>(&mut self, mapping: &CoordsMapping, layout_engine: &mut L) -> Result<(), E>
    where
        L: LayoutEngine<E>,
    {
        self.layout = layout_engine.layout(mapping, &self.rendered_tree)?;
        Ok(())
    }

    /// Calculate application layout, but only if something effecting application layout has changed
    /// and the layout _needs_ to be re-done
    #[inline]
    pub fn layout_change<L, E>(
        &mut self,
        mapping: &CoordsMapping,
        layout_engine: &mut L,
    ) -> Result<bool, E>
    where
        L: LayoutEngine<E>,
    {
        if self.render_changed {
            self.layout(mapping, layout_engine)?;
            Ok(true)
        } else {
            Ok(false)
        }
    }

    /// Perform interactions on the application using the given interaction engine
    #[inline]
    pub fn interact<I, R, E>(&mut self, interactions_engine: &mut I) -> Result<R, E>
    where
        I: InteractionsEngine<R, E>,
    {
        interactions_engine.perform_interactions(self)
    }

    /// Send a message to the given widget
    #[inline]
    pub fn send_message<T>(&mut self, id: &WidgetId, data: T)
    where
        T: 'static + MessageData,
    {
        self.send_message_raw(id, Box::new(data));
    }

    /// Send raw message data to the given widget
    #[inline]
    pub fn send_message_raw(&mut self, id: &WidgetId, data: Message) {
        if let Some(list) = self.messages.get_mut(id) {
            list.push(data);
        } else {
            self.messages.insert(id.to_owned(), vec![data]);
        }
    }

    /// Get the list of [signals][crate::signals] that have been sent by widgets
    #[inline]
    pub fn signals(&self) -> &[Signal] {
        &self.signals
    }

    /// Get the list of [signals][crate::signals] that have been sent by widgets, consuming the
    /// current list so that further calls will not include previously sent signals
    #[inline]
    pub fn consume_signals(&mut self) -> Vec<Signal> {
        std::mem::take(&mut self.signals)
    }

    /// Read the [`Props`] of a given widget
    #[inline]
    pub fn state_read(&self, id: &WidgetId) -> Option<&Props> {
        self.states.get(id)
    }

    /// Set the props of a given widget
    #[inline]
    pub fn state_write(&mut self, id: &WidgetId, data: Props) {
        if self.states.contains_key(id) {
            self.state_changes.insert(id.to_owned(), data);
        }
    }

    /// Get read access to the given widget's [`Props`] in a closure and update the widget's props
    /// to the props that were returned by the closure
    pub fn state_mutate<F>(&mut self, id: &WidgetId, mut f: F)
    where
        F: FnMut(&Props) -> Props,
    {
        if let Some(state) = self.states.get(id) {
            self.state_changes.insert(id.to_owned(), f(state));
        }
    }

    /// Get mutable access to the cloned [`Props`] of a widget in a closure and update the widget's
    /// props to the value of the clone props after the closure modifies them
    pub fn state_mutate_cloned<F>(&mut self, id: &WidgetId, mut f: F)
    where
        F: FnMut(&mut Props),
    {
        if let Some(mut state) = self.states.get(id).cloned() {
            f(&mut state);
            self.state_changes.insert(id.to_owned(), state);
        }
    }

    /// [`process()`][Self::process] application, even if no changes have been detected
    #[inline]
    pub fn forced_process(&mut self) -> bool {
        self.forced_process_with_context(&mut Default::default())
    }

    /// [`process()`][Self::process] application, even if no changes have been detected
    #[inline]
    pub fn forced_process_with_context<'b>(
        &mut self,
        process_context: &mut ProcessContext<'b>,
    ) -> bool {
        self.dirty = true;
        self.process_with_context(process_context)
    }

    /// Process the application, updating animations, applying state changes, handling widget
    /// messages, etc.
    #[inline]
    pub fn process(&mut self) -> bool {
        self.process_with_context(&mut Default::default())
    }

    /// [Process][Self::process] the application and provide a custom [`ProcessContext`]
    ///
    /// # Process Context
    ///
    /// The `process_context` argument allows you to provide the UI's components with mutable or
    /// immutable access to application data. This grants powerful, direct control over the
    /// application to the UI's widgets, but has some caveats and it is easy to fall into
    /// anti-patterns when using.
    ///
    /// You should consider carefully whether or not a process context is the best way to facilitate
    /// your use-case before using this feature. See [caveats](#caveats) below for more explanation.
    ///
    /// ## Caveats
    ///
    /// RAUI provides other ways to facilitate UI integration with external data that should
    /// generally be preferred over using a process context. The primary mechanisms are:
    ///
    /// - [`DataBinding`]s and widget [messages][crate::messenger], for having the application send
    ///   data to widgets
    /// - [signals][crate::signals] for having widgets send data to the application.
    ///
    /// The main difference between using a [`DataBinding`] and a process context is the fact that
    /// RAUI is able to more granularly update the widget tree in response to data changes when
    /// using [`DataBinding`], but it has no way to know know when data in a process context
    /// changes.
    ///
    /// When you use a process context **you** are now responsible for either running
    /// [`forced_process_with_context`][Self::forced_process_with_context] every frame to make sure
    /// that the UI is always updated when the process context changes, or by manually calling
    /// [`mark_dirty`][Self::mark_dirty] when the process context has changed to make sure that the
    /// next `process_with_context()` call will actually update the application.
    ///
    /// [`DataBinding`]: crate::data_binding::DataBinding
    ///
    /// ## Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// /// Some sort of application data
    /// ///
    /// /// Pretend this data cannot be cloned because it has some special requirements.
    /// struct AppData {
    ///     counter: i32
    /// }
    ///
    /// // Make our data
    /// let mut app_data = AppData {
    ///     counter: 0,
    /// };
    ///
    /// let mut app = Application::new();
    /// // Do application stuff like interactions, layout, etc...
    ///
    /// // Now when it is time to process our application we create a process context and we put
    /// // a _mutable reference_ to our app data in the context. This means we don't have to have
    /// // ownership of our `AppData` struct, which is useful when the UI event loop doesn't
    /// // own the data it needs access to.
    /// // Now we call `process` with our process context
    /// app.process_with_context(ProcessContext::new().insert_mut(&mut app_data));
    /// ```
    ///
    /// Now, in our components we can access the `AppData` through the widget's `WidgetContext`
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// # struct AppData {
    /// #    counter: i32
    /// # }
    /// fn my_component(ctx: WidgetContext) -> WidgetNode {
    ///     let app_data = ctx.process_context.get_mut::<AppData>().unwrap();
    ///     let counter = &mut app_data.counter;
    ///     *counter += 1;
    ///
    ///     // widget stuff...
    /// #    widget!(())
    /// }
    /// ```
    pub fn process_with_context<'a>(&mut self, process_context: &mut ProcessContext<'a>) -> bool {
        if self.change_notifier.consume_change() {
            self.dirty = true;
        }
        self.animations_delta_time = self.animations_delta_time.max(0.0);
        self.last_invalidation_cause = InvalidationCause::None;
        self.render_changed = false;
        let changed_states = std::mem::take(&mut self.state_changes);
        let mut messages = std::mem::take(&mut self.messages);
        let changed_animators = self.animators.values().any(|a| a.in_progress());
        if !self.dirty && changed_states.is_empty() && messages.is_empty() && !changed_animators {
            return false;
        }
        if self.dirty {
            self.last_invalidation_cause = InvalidationCause::Forced;
        }
        if let Some((id, _)) = self.animators.iter().find(|(_, a)| a.in_progress()) {
            self.last_invalidation_cause = InvalidationCause::AnimationInProgress(id.to_owned());
        }
        if let Some((id, _)) = messages.iter().next() {
            self.last_invalidation_cause = InvalidationCause::MessageReceived(id.to_owned());
        }
        if let Some((id, _)) = changed_states.iter().next() {
            self.last_invalidation_cause = InvalidationCause::StateChange(id.to_owned());
        }
        let (message_sender, message_receiver) = channel();
        let message_sender = MessageSender::new(message_sender);
        for (k, a) in &mut self.animators {
            a.process(self.animations_delta_time, k, &message_sender);
        }
        self.dirty = false;
        let old_states = std::mem::take(&mut self.states);
        let states = old_states
            .into_iter()
            .chain(changed_states.into_iter())
            .collect::<HashMap<_, _>>();
        let (signal_sender, signal_receiver) = channel();
        let tree = self.tree.clone();
        let mut used_ids = HashSet::new();
        let mut new_states = HashMap::new();
        let rendered_tree = self.process_node(
            tree,
            &states,
            vec![],
            &mut messages,
            &mut new_states,
            &mut used_ids,
            "<*>".to_string(),
            None,
            &message_sender,
            &signal_sender,
            process_context,
        );
        self.states = states
            .into_iter()
            .chain(new_states.into_iter())
            .filter(|(id, state)| {
                if used_ids.contains(id) {
                    true
                } else {
                    if let Some(closures) = self.unmount_closures.remove(id) {
                        for mut closure in closures {
                            let messenger = &message_sender;
                            let signals = SignalSender::new(id.clone(), signal_sender.clone());
                            let context = WidgetUnmountContext {
                                id,
                                state,
                                messenger,
                                signals,
                                process_context,
                            };
                            (closure)(context);
                        }
                    }
                    self.animators.remove(id);
                    false
                }
            })
            .collect();
        while let Ok((id, message)) = message_receiver.try_recv() {
            if let Some(list) = self.messages.get_mut(&id) {
                list.push(message);
            } else {
                self.messages.insert(id, vec![message]);
            }
        }
        self.signals.clear();
        while let Ok(data) = signal_receiver.try_recv() {
            self.signals.push(data);
        }
        self.animators = std::mem::take(&mut self.animators)
            .into_iter()
            .filter_map(|(k, a)| if a.in_progress() { Some((k, a)) } else { None })
            .collect::<HashMap<_, _>>();
        if let Ok(tree) = rendered_tree.try_into() {
            self.rendered_tree = Self::teleport_portals(tree);
            true
        } else {
            false
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn process_node<'a, 'b>(
        &mut self,
        node: WidgetNode,
        states: &'a HashMap<WidgetId, Props>,
        path: Vec<String>,
        messages: &mut HashMap<WidgetId, Messages>,
        new_states: &mut HashMap<WidgetId, Props>,
        used_ids: &mut HashSet<WidgetId>,
        possible_key: String,
        master_shared_props: Option<Props>,
        message_sender: &MessageSender,
        signal_sender: &Sender<Signal>,
        process_context: &mut ProcessContext<'b>,
    ) -> WidgetNode {
        match node {
            WidgetNode::None | WidgetNode::Tuple(_) => node,
            WidgetNode::Component(component) => self.process_node_component(
                component,
                states,
                path,
                messages,
                new_states,
                used_ids,
                possible_key,
                master_shared_props,
                message_sender,
                signal_sender,
                process_context,
            ),
            WidgetNode::Unit(unit) => self.process_node_unit(
                unit,
                states,
                path,
                messages,
                new_states,
                used_ids,
                master_shared_props,
                message_sender,
                signal_sender,
                process_context,
            ),
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn process_node_component<'a, 'b>(
        &mut self,
        component: WidgetComponent,
        states: &'a HashMap<WidgetId, Props>,
        mut path: Vec<String>,
        messages: &mut HashMap<WidgetId, Messages>,
        new_states: &mut HashMap<WidgetId, Props>,
        used_ids: &mut HashSet<WidgetId>,
        possible_key: String,
        master_shared_props: Option<Props>,
        message_sender: &MessageSender,
        signal_sender: &Sender<Signal>,
        process_context: &mut ProcessContext<'b>,
    ) -> WidgetNode {
        let WidgetComponent {
            processor,
            type_name,
            key,
            mut idref,
            mut props,
            shared_props,
            listed_slots,
            named_slots,
        } = component;
        let mut shared_props = match (master_shared_props, shared_props) {
            (Some(master_shared_props), Some(shared_props)) => {
                master_shared_props.merge(shared_props)
            }
            (None, Some(shared_props)) => shared_props,
            (Some(master_shared_props), None) => master_shared_props,
            _ => Default::default(),
        };
        let key = match &key {
            Some(key) => key.to_owned(),
            None => possible_key.to_owned(),
        };
        path.push(key.clone());
        let id = WidgetId::new(&type_name, &path);
        used_ids.insert(id.clone());
        if let Some(idref) = &mut idref {
            idref.write(id.to_owned());
        }
        let (state_sender, state_receiver) = channel();
        let (animation_sender, animation_receiver) = channel();
        let messages_list = match messages.remove(&id) {
            Some(messages) => messages,
            None => Messages::new(),
        };
        let mut life_cycle = WidgetLifeCycle::default();
        let default_animator_state = AnimatorStates::default();
        let (new_node, mounted) = match states.get(&id) {
            Some(state) => {
                let state = State::new(state, StateUpdate::new(state_sender.clone()));
                let animator = self.animators.get(&id).unwrap_or(&default_animator_state);
                let context = WidgetContext {
                    id: &id,
                    idref: idref.as_ref(),
                    key: &key,
                    props: &mut props,
                    shared_props: &mut shared_props,
                    state,
                    animator,
                    life_cycle: &mut life_cycle,
                    named_slots,
                    listed_slots,
                    process_context,
                };
                ((processor)(context), false)
            }
            None => {
                let state_data = Props::default();
                let state = State::new(&state_data, StateUpdate::new(state_sender.clone()));
                let animator = self.animators.get(&id).unwrap_or(&default_animator_state);
                let context = WidgetContext {
                    id: &id,
                    idref: idref.as_ref(),
                    key: &key,
                    props: &mut props,
                    shared_props: &mut shared_props,
                    state,
                    animator,
                    life_cycle: &mut life_cycle,
                    named_slots,
                    listed_slots,
                    process_context,
                };
                let node = (processor)(context);
                new_states.insert(id.clone(), state_data);
                (node, true)
            }
        };
        let (mount, change, unmount) = life_cycle.unwrap();
        if mounted {
            if !mount.is_empty() {
                if let Some(state) = new_states.get(&id) {
                    for mut closure in mount {
                        let state = State::new(state, StateUpdate::new(state_sender.clone()));
                        let messenger = Messenger::new(message_sender.clone(), &messages_list);
                        let signals = SignalSender::new(id.clone(), signal_sender.clone());
                        let animator = Animator::new(
                            self.animators.get(&id).unwrap_or(&default_animator_state),
                            AnimationUpdate::new(animation_sender.clone()),
                        );
                        let context = WidgetMountOrChangeContext {
                            id: &id,
                            props: &props,
                            shared_props: &shared_props,
                            state,
                            messenger,
                            signals,
                            animator,
                            process_context,
                        };
                        (closure)(context);
                    }
                }
            }
        } else if !change.is_empty() {
            if let Some(state) = states.get(&id) {
                for mut closure in change {
                    let state = State::new(state, StateUpdate::new(state_sender.clone()));
                    let messenger = Messenger::new(message_sender.clone(), &messages_list);
                    let signals = SignalSender::new(id.clone(), signal_sender.clone());
                    let animator = Animator::new(
                        self.animators.get(&id).unwrap_or(&default_animator_state),
                        AnimationUpdate::new(animation_sender.clone()),
                    );
                    let context = WidgetMountOrChangeContext {
                        id: &id,
                        props: &props,
                        shared_props: &shared_props,
                        state,
                        messenger,
                        signals,
                        animator,
                        process_context,
                    };
                    (closure)(context);
                }
            }
        }
        if !unmount.is_empty() {
            self.unmount_closures.insert(id.clone(), unmount);
        }
        while let Ok((name, data)) = animation_receiver.try_recv() {
            if let Some(states) = self.animators.get_mut(&id) {
                states.change(name, data);
            } else if let Some(data) = data {
                self.animators
                    .insert(id.to_owned(), AnimatorStates::new(name, data));
            }
        }
        let new_node = self.process_node(
            new_node,
            states,
            path,
            messages,
            new_states,
            used_ids,
            possible_key,
            Some(shared_props),
            message_sender,
            signal_sender,
            process_context,
        );
        while let Ok(data) = state_receiver.try_recv() {
            self.state_changes.insert(id.to_owned(), data);
        }
        new_node
    }

    #[allow(clippy::too_many_arguments)]
    fn process_node_unit<'a, 'b>(
        &mut self,
        mut unit: WidgetUnitNode,
        states: &'a HashMap<WidgetId, Props>,
        path: Vec<String>,
        messages: &mut HashMap<WidgetId, Messages>,
        new_states: &mut HashMap<WidgetId, Props>,
        used_ids: &mut HashSet<WidgetId>,
        master_shared_props: Option<Props>,
        message_sender: &MessageSender,
        signal_sender: &Sender<Signal>,
        process_context: &mut ProcessContext<'b>,
    ) -> WidgetNode {
        match &mut unit {
            WidgetUnitNode::None | WidgetUnitNode::ImageBox(_) | WidgetUnitNode::TextBox(_) => {}
            WidgetUnitNode::AreaBox(unit) => {
                let slot = *std::mem::take(&mut unit.slot);
                unit.slot = Box::new(self.process_node(
                    slot,
                    states,
                    path,
                    messages,
                    new_states,
                    used_ids,
                    ".".to_owned(),
                    master_shared_props,
                    message_sender,
                    signal_sender,
                    process_context,
                ));
            }
            WidgetUnitNode::PortalBox(unit) => match &mut *unit.slot {
                PortalBoxSlotNode::Slot(data) => {
                    let slot = std::mem::take(data);
                    *data = self.process_node(
                        slot,
                        states,
                        path,
                        messages,
                        new_states,
                        used_ids,
                        ".".to_owned(),
                        master_shared_props,
                        message_sender,
                        signal_sender,
                        process_context,
                    )
                }
                PortalBoxSlotNode::ContentItem(item) => {
                    let slot = std::mem::take(&mut item.slot);
                    item.slot = self.process_node(
                        slot,
                        states,
                        path,
                        messages,
                        new_states,
                        used_ids,
                        ".".to_owned(),
                        master_shared_props,
                        message_sender,
                        signal_sender,
                        process_context,
                    )
                }
                PortalBoxSlotNode::FlexItem(item) => {
                    let slot = std::mem::take(&mut item.slot);
                    item.slot = self.process_node(
                        slot,
                        states,
                        path,
                        messages,
                        new_states,
                        used_ids,
                        ".".to_owned(),
                        master_shared_props,
                        message_sender,
                        signal_sender,
                        process_context,
                    )
                }
                PortalBoxSlotNode::GridItem(item) => {
                    let slot = std::mem::take(&mut item.slot);
                    item.slot = self.process_node(
                        slot,
                        states,
                        path,
                        messages,
                        new_states,
                        used_ids,
                        ".".to_owned(),
                        master_shared_props,
                        message_sender,
                        signal_sender,
                        process_context,
                    )
                }
            },
            WidgetUnitNode::ContentBox(unit) => {
                let items = std::mem::take(&mut unit.items);
                unit.items = items
                    .into_iter()
                    .enumerate()
                    .map(|(i, mut node)| {
                        let slot = std::mem::take(&mut node.slot);
                        node.slot = self.process_node(
                            slot,
                            states,
                            path.clone(),
                            messages,
                            new_states,
                            used_ids,
                            format!("<{}>", i),
                            master_shared_props.clone(),
                            message_sender,
                            signal_sender,
                            process_context,
                        );
                        node
                    })
                    .collect::<Vec<_>>();
            }
            WidgetUnitNode::FlexBox(unit) => {
                let items = std::mem::take(&mut unit.items);
                unit.items = items
                    .into_iter()
                    .enumerate()
                    .map(|(i, mut node)| {
                        let slot = std::mem::take(&mut node.slot);
                        node.slot = self.process_node(
                            slot,
                            states,
                            path.clone(),
                            messages,
                            new_states,
                            used_ids,
                            format!("<{}>", i),
                            master_shared_props.clone(),
                            message_sender,
                            signal_sender,
                            process_context,
                        );
                        node
                    })
                    .collect::<Vec<_>>();
            }
            WidgetUnitNode::GridBox(unit) => {
                let items = std::mem::take(&mut unit.items);
                unit.items = items
                    .into_iter()
                    .enumerate()
                    .map(|(i, mut node)| {
                        let slot = std::mem::take(&mut node.slot);
                        node.slot = self.process_node(
                            slot,
                            states,
                            path.clone(),
                            messages,
                            new_states,
                            used_ids,
                            format!("<{}>", i),
                            master_shared_props.clone(),
                            message_sender,
                            signal_sender,
                            process_context,
                        );
                        node
                    })
                    .collect::<Vec<_>>();
            }
            WidgetUnitNode::SizeBox(unit) => {
                let slot = *std::mem::take(&mut unit.slot);
                unit.slot = Box::new(self.process_node(
                    slot,
                    states,
                    path,
                    messages,
                    new_states,
                    used_ids,
                    ".".to_owned(),
                    master_shared_props,
                    message_sender,
                    signal_sender,
                    process_context,
                ));
            }
        }
        unit.into()
    }

    fn teleport_portals(mut root: WidgetUnit) -> WidgetUnit {
        let count = Self::estimate_portals(&root);
        if count == 0 {
            return root;
        }
        let mut portals = Vec::with_capacity(count);
        Self::consume_portals(&mut root, &mut portals);
        Self::inject_portals(&mut root, &mut portals);
        root
    }

    fn estimate_portals(unit: &WidgetUnit) -> usize {
        let mut count = 0;
        match unit {
            WidgetUnit::None | WidgetUnit::ImageBox(_) | WidgetUnit::TextBox(_) => {}
            WidgetUnit::AreaBox(b) => count += Self::estimate_portals(&b.slot),
            WidgetUnit::PortalBox(b) => {
                count += Self::estimate_portals(match &*b.slot {
                    PortalBoxSlot::Slot(slot) => slot,
                    PortalBoxSlot::ContentItem(item) => &item.slot,
                    PortalBoxSlot::FlexItem(item) => &item.slot,
                    PortalBoxSlot::GridItem(item) => &item.slot,
                }) + 1
            }
            WidgetUnit::ContentBox(b) => {
                for item in &b.items {
                    count += Self::estimate_portals(&item.slot);
                }
            }
            WidgetUnit::FlexBox(b) => {
                for item in &b.items {
                    count += Self::estimate_portals(&item.slot);
                }
            }
            WidgetUnit::GridBox(b) => {
                for item in &b.items {
                    count += Self::estimate_portals(&item.slot);
                }
            }
            WidgetUnit::SizeBox(b) => count += Self::estimate_portals(&b.slot),
        }
        count
    }

    fn consume_portals(unit: &mut WidgetUnit, bucket: &mut Vec<(WidgetId, PortalBoxSlot)>) {
        match unit {
            WidgetUnit::None | WidgetUnit::ImageBox(_) | WidgetUnit::TextBox(_) => {}
            WidgetUnit::AreaBox(b) => Self::consume_portals(&mut b.slot, bucket),
            WidgetUnit::PortalBox(b) => {
                let PortalBox {
                    owner, mut slot, ..
                } = std::mem::take(b);
                Self::consume_portals(
                    match &mut *slot {
                        PortalBoxSlot::Slot(slot) => slot,
                        PortalBoxSlot::ContentItem(item) => &mut item.slot,
                        PortalBoxSlot::FlexItem(item) => &mut item.slot,
                        PortalBoxSlot::GridItem(item) => &mut item.slot,
                    },
                    bucket,
                );
                bucket.push((owner, *slot));
            }
            WidgetUnit::ContentBox(b) => {
                for item in &mut b.items {
                    Self::consume_portals(&mut item.slot, bucket);
                }
            }
            WidgetUnit::FlexBox(b) => {
                for item in &mut b.items {
                    Self::consume_portals(&mut item.slot, bucket);
                }
            }
            WidgetUnit::GridBox(b) => {
                for item in &mut b.items {
                    Self::consume_portals(&mut item.slot, bucket);
                }
            }
            WidgetUnit::SizeBox(b) => Self::consume_portals(&mut b.slot, bucket),
        }
    }

    fn inject_portals(unit: &mut WidgetUnit, portals: &mut Vec<(WidgetId, PortalBoxSlot)>) -> bool {
        if portals.is_empty() {
            return false;
        }
        while let Some(data) = unit.as_data() {
            let found = portals.iter().position(|(id, _)| data.id() == id);
            if let Some(index) = found {
                let slot = portals.swap_remove(index).1;
                match unit {
                    WidgetUnit::None
                    | WidgetUnit::PortalBox(_)
                    | WidgetUnit::ImageBox(_)
                    | WidgetUnit::TextBox(_) => {}
                    WidgetUnit::AreaBox(b) => {
                        match slot {
                            PortalBoxSlot::Slot(slot) => b.slot = Box::new(slot),
                            PortalBoxSlot::ContentItem(item) => b.slot = Box::new(item.slot),
                            PortalBoxSlot::FlexItem(item) => b.slot = Box::new(item.slot),
                            PortalBoxSlot::GridItem(item) => b.slot = Box::new(item.slot),
                        }
                        if !Self::inject_portals(&mut b.slot, portals) {
                            return false;
                        }
                    }
                    WidgetUnit::ContentBox(b) => {
                        b.items.push(match slot {
                            PortalBoxSlot::Slot(slot) => ContentBoxItem {
                                slot,
                                ..Default::default()
                            },
                            PortalBoxSlot::ContentItem(item) => item,
                            PortalBoxSlot::FlexItem(item) => ContentBoxItem {
                                slot: item.slot,
                                ..Default::default()
                            },
                            PortalBoxSlot::GridItem(item) => ContentBoxItem {
                                slot: item.slot,
                                ..Default::default()
                            },
                        });
                        for item in &mut b.items {
                            if !Self::inject_portals(&mut item.slot, portals) {
                                return false;
                            }
                        }
                    }
                    WidgetUnit::FlexBox(b) => {
                        b.items.push(match slot {
                            PortalBoxSlot::Slot(slot) => FlexBoxItem {
                                slot,
                                ..Default::default()
                            },
                            PortalBoxSlot::ContentItem(item) => FlexBoxItem {
                                slot: item.slot,
                                ..Default::default()
                            },
                            PortalBoxSlot::FlexItem(item) => item,
                            PortalBoxSlot::GridItem(item) => FlexBoxItem {
                                slot: item.slot,
                                ..Default::default()
                            },
                        });
                        for item in &mut b.items {
                            if !Self::inject_portals(&mut item.slot, portals) {
                                return false;
                            }
                        }
                    }
                    WidgetUnit::GridBox(b) => {
                        b.items.push(match slot {
                            PortalBoxSlot::Slot(slot) => GridBoxItem {
                                slot,
                                ..Default::default()
                            },
                            PortalBoxSlot::ContentItem(item) => GridBoxItem {
                                slot: item.slot,
                                ..Default::default()
                            },
                            PortalBoxSlot::FlexItem(item) => GridBoxItem {
                                slot: item.slot,
                                ..Default::default()
                            },
                            PortalBoxSlot::GridItem(item) => item,
                        });
                        for item in &mut b.items {
                            if !Self::inject_portals(&mut item.slot, portals) {
                                return false;
                            }
                        }
                    }
                    WidgetUnit::SizeBox(b) => {
                        match slot {
                            PortalBoxSlot::Slot(slot) => b.slot = Box::new(slot),
                            PortalBoxSlot::ContentItem(item) => b.slot = Box::new(item.slot),
                            PortalBoxSlot::FlexItem(item) => b.slot = Box::new(item.slot),
                            PortalBoxSlot::GridItem(item) => b.slot = Box::new(item.slot),
                        }
                        if !Self::inject_portals(&mut b.slot, portals) {
                            return false;
                        }
                    }
                }
            } else {
                break;
            }
        }
        true
    }

    fn node_to_prefab(&self, data: &WidgetNode) -> Result<WidgetNodePrefab, ApplicationError> {
        Ok(match data {
            WidgetNode::None => WidgetNodePrefab::None,
            WidgetNode::Component(data) => {
                WidgetNodePrefab::Component(self.component_to_prefab(data)?)
            }
            WidgetNode::Unit(data) => WidgetNodePrefab::Unit(self.unit_to_prefab(data)?),
            WidgetNode::Tuple(data) => WidgetNodePrefab::Tuple(self.tuple_to_prefab(data)?),
        })
    }

    fn component_to_prefab(
        &self,
        data: &WidgetComponent,
    ) -> Result<WidgetComponentPrefab, ApplicationError> {
        if self.component_mappings.contains_key(&data.type_name) {
            Ok(WidgetComponentPrefab {
                type_name: data.type_name.to_owned(),
                key: data.key.clone(),
                props: self.props_registry.serialize(&data.props)?,
                shared_props: match &data.shared_props {
                    Some(p) => Some(self.props_registry.serialize(p)?),
                    None => None,
                },
                listed_slots: data
                    .listed_slots
                    .iter()
                    .map(|v| self.node_to_prefab(v))
                    .collect::<Result<_, _>>()?,
                named_slots: data
                    .named_slots
                    .iter()
                    .map(|(k, v)| Ok((k.to_owned(), self.node_to_prefab(v)?)))
                    .collect::<Result<_, ApplicationError>>()?,
            })
        } else {
            Err(ApplicationError::ComponentMappingNotFound(
                data.type_name.to_owned(),
            ))
        }
    }

    fn unit_to_prefab(
        &self,
        data: &WidgetUnitNode,
    ) -> Result<WidgetUnitNodePrefab, ApplicationError> {
        Ok(match data {
            WidgetUnitNode::None => WidgetUnitNodePrefab::None,
            WidgetUnitNode::AreaBox(data) => {
                WidgetUnitNodePrefab::AreaBox(self.area_box_to_prefab(data)?)
            }
            WidgetUnitNode::PortalBox(data) => {
                WidgetUnitNodePrefab::PortalBox(self.portal_box_to_prefab(data)?)
            }
            WidgetUnitNode::ContentBox(data) => {
                WidgetUnitNodePrefab::ContentBox(self.content_box_to_prefab(data)?)
            }
            WidgetUnitNode::FlexBox(data) => {
                WidgetUnitNodePrefab::FlexBox(self.flex_box_to_prefab(data)?)
            }
            WidgetUnitNode::GridBox(data) => {
                WidgetUnitNodePrefab::GridBox(self.grid_box_to_prefab(data)?)
            }
            WidgetUnitNode::SizeBox(data) => {
                WidgetUnitNodePrefab::SizeBox(self.size_box_to_prefab(data)?)
            }
            WidgetUnitNode::ImageBox(data) => {
                WidgetUnitNodePrefab::ImageBox(self.image_box_to_prefab(data)?)
            }
            WidgetUnitNode::TextBox(data) => {
                WidgetUnitNodePrefab::TextBox(self.text_box_to_prefab(data)?)
            }
        })
    }

    fn tuple_to_prefab(
        &self,
        data: &[WidgetNode],
    ) -> Result<Vec<WidgetNodePrefab>, ApplicationError> {
        data.iter()
            .map(|node| self.node_to_prefab(node))
            .collect::<Result<_, _>>()
    }

    fn area_box_to_prefab(
        &self,
        data: &AreaBoxNode,
    ) -> Result<AreaBoxNodePrefab, ApplicationError> {
        Ok(AreaBoxNodePrefab {
            id: data.id.to_owned(),
            slot: Box::new(self.node_to_prefab(&data.slot)?),
            renderer_effect: data.renderer_effect.to_owned(),
        })
    }

    fn portal_box_to_prefab(
        &self,
        data: &PortalBoxNode,
    ) -> Result<PortalBoxNodePrefab, ApplicationError> {
        Ok(PortalBoxNodePrefab {
            id: data.id.to_owned(),
            slot: Box::new(match &*data.slot {
                PortalBoxSlotNode::Slot(slot) => {
                    PortalBoxSlotNodePrefab::Slot(self.node_to_prefab(slot)?)
                }
                PortalBoxSlotNode::ContentItem(item) => {
                    PortalBoxSlotNodePrefab::ContentItem(ContentBoxItemNodePrefab {
                        slot: self.node_to_prefab(&item.slot)?,
                        layout: item.layout.clone(),
                    })
                }
                PortalBoxSlotNode::FlexItem(item) => {
                    PortalBoxSlotNodePrefab::FlexItem(FlexBoxItemNodePrefab {
                        slot: self.node_to_prefab(&item.slot)?,
                        layout: item.layout.clone(),
                    })
                }
                PortalBoxSlotNode::GridItem(item) => {
                    PortalBoxSlotNodePrefab::GridItem(GridBoxItemNodePrefab {
                        slot: self.node_to_prefab(&item.slot)?,
                        layout: item.layout.clone(),
                    })
                }
            }),
            owner: data.owner.to_owned(),
        })
    }

    fn content_box_to_prefab(
        &self,
        data: &ContentBoxNode,
    ) -> Result<ContentBoxNodePrefab, ApplicationError> {
        Ok(ContentBoxNodePrefab {
            id: data.id.to_owned(),
            props: self.props_registry.serialize(&data.props)?,
            items: data
                .items
                .iter()
                .map(|v| {
                    Ok(ContentBoxItemNodePrefab {
                        slot: self.node_to_prefab(&v.slot)?,
                        layout: v.layout.clone(),
                    })
                })
                .collect::<Result<_, ApplicationError>>()?,
            clipping: data.clipping,
            transform: data.transform,
        })
    }

    fn flex_box_to_prefab(
        &self,
        data: &FlexBoxNode,
    ) -> Result<FlexBoxNodePrefab, ApplicationError> {
        Ok(FlexBoxNodePrefab {
            id: data.id.to_owned(),
            props: self.props_registry.serialize(&data.props)?,
            items: data
                .items
                .iter()
                .map(|v| {
                    Ok(FlexBoxItemNodePrefab {
                        slot: self.node_to_prefab(&v.slot)?,
                        layout: v.layout.clone(),
                    })
                })
                .collect::<Result<_, ApplicationError>>()?,
            direction: data.direction,
            separation: data.separation,
            wrap: data.wrap,
            transform: data.transform,
        })
    }

    fn grid_box_to_prefab(
        &self,
        data: &GridBoxNode,
    ) -> Result<GridBoxNodePrefab, ApplicationError> {
        Ok(GridBoxNodePrefab {
            id: data.id.to_owned(),
            props: self.props_registry.serialize(&data.props)?,
            items: data
                .items
                .iter()
                .map(|v| {
                    Ok(GridBoxItemNodePrefab {
                        slot: self.node_to_prefab(&v.slot)?,
                        layout: v.layout.clone(),
                    })
                })
                .collect::<Result<_, ApplicationError>>()?,
            cols: data.cols,
            rows: data.rows,
            transform: data.transform,
        })
    }

    fn size_box_to_prefab(
        &self,
        data: &SizeBoxNode,
    ) -> Result<SizeBoxNodePrefab, ApplicationError> {
        Ok(SizeBoxNodePrefab {
            id: data.id.to_owned(),
            props: self.props_registry.serialize(&data.props)?,
            slot: Box::new(self.node_to_prefab(&data.slot)?),
            width: data.width,
            height: data.height,
            margin: data.margin,
            transform: data.transform,
        })
    }

    fn image_box_to_prefab(
        &self,
        data: &ImageBoxNode,
    ) -> Result<ImageBoxNodePrefab, ApplicationError> {
        Ok(ImageBoxNodePrefab {
            id: data.id.to_owned(),
            props: self.props_registry.serialize(&data.props)?,
            width: data.width,
            height: data.height,
            content_keep_aspect_ratio: data.content_keep_aspect_ratio,
            material: data.material.clone(),
            transform: data.transform,
        })
    }

    fn text_box_to_prefab(
        &self,
        data: &TextBoxNode,
    ) -> Result<TextBoxNodePrefab, ApplicationError> {
        Ok(TextBoxNodePrefab {
            id: data.id.to_owned(),
            props: self.props_registry.serialize(&data.props)?,
            text: data.text.clone(),
            width: data.width,
            height: data.height,
            horizontal_align: data.horizontal_align,
            vertical_align: data.vertical_align,
            direction: data.direction,
            font: data.font.clone(),
            color: data.color,
            transform: data.transform,
        })
    }

    fn node_from_prefab(&self, data: WidgetNodePrefab) -> Result<WidgetNode, ApplicationError> {
        Ok(match data {
            WidgetNodePrefab::None => WidgetNode::None,
            WidgetNodePrefab::Component(data) => {
                WidgetNode::Component(self.component_from_prefab(data)?)
            }
            WidgetNodePrefab::Unit(data) => WidgetNode::Unit(self.unit_from_prefab(data)?),
            WidgetNodePrefab::Tuple(data) => WidgetNode::Tuple(self.tuple_from_prefab(data)?),
        })
    }

    fn component_from_prefab(
        &self,
        data: WidgetComponentPrefab,
    ) -> Result<WidgetComponent, ApplicationError> {
        if let Some(processor) = self.component_mappings.get(&data.type_name) {
            Ok(WidgetComponent {
                processor: *processor,
                type_name: data.type_name,
                key: data.key,
                idref: Default::default(),
                props: self.deserialize_props(data.props)?,
                shared_props: match data.shared_props {
                    Some(p) => Some(self.deserialize_props(p)?),
                    None => None,
                },
                listed_slots: data
                    .listed_slots
                    .into_iter()
                    .map(|v| self.node_from_prefab(v))
                    .collect::<Result<_, ApplicationError>>()?,
                named_slots: data
                    .named_slots
                    .into_iter()
                    .map(|(k, v)| Ok((k, self.node_from_prefab(v)?)))
                    .collect::<Result<_, ApplicationError>>()?,
            })
        } else {
            Err(ApplicationError::ComponentMappingNotFound(
                data.type_name.clone(),
            ))
        }
    }

    fn unit_from_prefab(
        &self,
        data: WidgetUnitNodePrefab,
    ) -> Result<WidgetUnitNode, ApplicationError> {
        Ok(match data {
            WidgetUnitNodePrefab::None => WidgetUnitNode::None,
            WidgetUnitNodePrefab::AreaBox(data) => {
                WidgetUnitNode::AreaBox(self.area_box_from_prefab(data)?)
            }
            WidgetUnitNodePrefab::PortalBox(data) => {
                WidgetUnitNode::PortalBox(self.portal_box_from_prefab(data)?)
            }
            WidgetUnitNodePrefab::ContentBox(data) => {
                WidgetUnitNode::ContentBox(self.content_box_from_prefab(data)?)
            }
            WidgetUnitNodePrefab::FlexBox(data) => {
                WidgetUnitNode::FlexBox(self.flex_box_from_prefab(data)?)
            }
            WidgetUnitNodePrefab::GridBox(data) => {
                WidgetUnitNode::GridBox(self.grid_box_from_prefab(data)?)
            }
            WidgetUnitNodePrefab::SizeBox(data) => {
                WidgetUnitNode::SizeBox(self.size_box_from_prefab(data)?)
            }
            WidgetUnitNodePrefab::ImageBox(data) => {
                WidgetUnitNode::ImageBox(self.image_box_from_prefab(data)?)
            }
            WidgetUnitNodePrefab::TextBox(data) => {
                WidgetUnitNode::TextBox(self.text_box_from_prefab(data)?)
            }
        })
    }

    fn tuple_from_prefab(
        &self,
        data: Vec<WidgetNodePrefab>,
    ) -> Result<Vec<WidgetNode>, ApplicationError> {
        data.into_iter()
            .map(|data| self.node_from_prefab(data))
            .collect::<Result<_, _>>()
    }

    fn area_box_from_prefab(
        &self,
        data: AreaBoxNodePrefab,
    ) -> Result<AreaBoxNode, ApplicationError> {
        Ok(AreaBoxNode {
            id: data.id,
            slot: Box::new(self.node_from_prefab(*data.slot)?),
            renderer_effect: data.renderer_effect,
        })
    }

    fn portal_box_from_prefab(
        &self,
        data: PortalBoxNodePrefab,
    ) -> Result<PortalBoxNode, ApplicationError> {
        Ok(PortalBoxNode {
            id: data.id,
            slot: Box::new(match *data.slot {
                PortalBoxSlotNodePrefab::Slot(slot) => {
                    PortalBoxSlotNode::Slot(self.node_from_prefab(slot)?)
                }
                PortalBoxSlotNodePrefab::ContentItem(item) => {
                    PortalBoxSlotNode::ContentItem(ContentBoxItemNode {
                        slot: self.node_from_prefab(item.slot)?,
                        layout: item.layout,
                    })
                }
                PortalBoxSlotNodePrefab::FlexItem(item) => {
                    PortalBoxSlotNode::FlexItem(FlexBoxItemNode {
                        slot: self.node_from_prefab(item.slot)?,
                        layout: item.layout,
                    })
                }
                PortalBoxSlotNodePrefab::GridItem(item) => {
                    PortalBoxSlotNode::GridItem(GridBoxItemNode {
                        slot: self.node_from_prefab(item.slot)?,
                        layout: item.layout,
                    })
                }
            }),
            owner: data.owner,
        })
    }

    fn content_box_from_prefab(
        &self,
        data: ContentBoxNodePrefab,
    ) -> Result<ContentBoxNode, ApplicationError> {
        Ok(ContentBoxNode {
            id: data.id,
            props: self.props_registry.deserialize(data.props)?,
            items: data
                .items
                .into_iter()
                .map(|v| {
                    Ok(ContentBoxItemNode {
                        slot: self.node_from_prefab(v.slot)?,
                        layout: v.layout,
                    })
                })
                .collect::<Result<_, ApplicationError>>()?,
            clipping: data.clipping,
            transform: data.transform,
        })
    }

    fn flex_box_from_prefab(
        &self,
        data: FlexBoxNodePrefab,
    ) -> Result<FlexBoxNode, ApplicationError> {
        Ok(FlexBoxNode {
            id: data.id,
            props: self.props_registry.deserialize(data.props)?,
            items: data
                .items
                .into_iter()
                .map(|v| {
                    Ok(FlexBoxItemNode {
                        slot: self.node_from_prefab(v.slot)?,
                        layout: v.layout,
                    })
                })
                .collect::<Result<_, ApplicationError>>()?,
            direction: data.direction,
            separation: data.separation,
            wrap: data.wrap,
            transform: data.transform,
        })
    }

    fn grid_box_from_prefab(
        &self,
        data: GridBoxNodePrefab,
    ) -> Result<GridBoxNode, ApplicationError> {
        Ok(GridBoxNode {
            id: data.id,
            props: self.props_registry.deserialize(data.props)?,
            items: data
                .items
                .into_iter()
                .map(|v| {
                    Ok(GridBoxItemNode {
                        slot: self.node_from_prefab(v.slot)?,
                        layout: v.layout,
                    })
                })
                .collect::<Result<_, ApplicationError>>()?,
            cols: data.cols,
            rows: data.rows,
            transform: data.transform,
        })
    }

    fn size_box_from_prefab(
        &self,
        data: SizeBoxNodePrefab,
    ) -> Result<SizeBoxNode, ApplicationError> {
        Ok(SizeBoxNode {
            id: data.id,
            props: self.props_registry.deserialize(data.props)?,
            slot: Box::new(self.node_from_prefab(*data.slot)?),
            width: data.width,
            height: data.height,
            margin: data.margin,
            transform: data.transform,
        })
    }

    fn image_box_from_prefab(
        &self,
        data: ImageBoxNodePrefab,
    ) -> Result<ImageBoxNode, ApplicationError> {
        Ok(ImageBoxNode {
            id: data.id,
            props: self.props_registry.deserialize(data.props)?,
            width: data.width,
            height: data.height,
            content_keep_aspect_ratio: data.content_keep_aspect_ratio,
            material: data.material,
            transform: data.transform,
        })
    }

    fn text_box_from_prefab(
        &self,
        data: TextBoxNodePrefab,
    ) -> Result<TextBoxNode, ApplicationError> {
        Ok(TextBoxNode {
            id: data.id,
            props: self.props_registry.deserialize(data.props)?,
            text: data.text,
            width: data.width,
            height: data.height,
            horizontal_align: data.horizontal_align,
            vertical_align: data.vertical_align,
            direction: data.direction,
            font: data.font,
            color: data.color,
            transform: data.transform,
        })
    }
}

/// Allows you to get mutable or immutable references to data exposed by the host of the RAUI
/// application
///
/// This allows RAUI hosts to provide the UI with direct access to application data, if necessary,
/// instead of using [`DataBinding`][crate::data_binding::DataBinding]s.
///
/// See [`Application::process`] for more information.
#[derive(Debug, Default)]
pub struct ProcessContext<'a> {
    owned: HashMap<TypeId, Box<dyn Any>>,
    mutable: HashMap<TypeId, &'a mut dyn Any>,
    immutable: HashMap<TypeId, &'a dyn Any>,
}

impl<'a> ProcessContext<'a> {
    /// Create an empty [`ProcessContext`]
    pub fn new() -> Self {
        Default::default()
    }

    /// Can be used to get mutable access to application data provided by the RAUI host.
    ///
    /// # Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// # struct AppData {
    /// #    counter: i32
    /// # }
    /// fn my_component(ctx: WidgetContext) -> WidgetNode {
    ///     let app_data = ctx.process_context.get_mut::<AppData>().unwrap();
    ///     let counter = &mut app_data.counter;
    ///     *counter += 1;
    ///
    ///     // widget stuff...
    /// #    widget!(())
    /// }
    /// ```
    pub fn get_mut<T: 'static>(&mut self) -> Option<&mut T> {
        self.mutable
            .get_mut(&TypeId::of::<T>())
            .map(|x| x.downcast_mut())
            .flatten()
    }

    /// Allows RAUI hosts to add mutable references to application data to the
    /// [`process_context`][crate::widget::context::WidgetContext::process_context`] that is
    /// available to widget components.
    ///
    /// See [`Application::process`] for more information.
    pub fn insert_mut<T: 'static>(&mut self, item: &'a mut T) -> &mut Self {
        self.mutable.insert(TypeId::of::<T>(), item);
        self
    }

    /// Can be used to get immutable access to application data provided by the RAUI host.
    ///
    /// # Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// # struct AppData {
    /// #    counter: i32
    /// # }
    /// fn my_component(ctx: WidgetContext) -> WidgetNode {
    ///     let app_data = ctx.process_context.get::<AppData>().unwrap();
    ///     let counter = app_data.counter;
    ///
    ///     // widget stuff...
    /// #    widget!(())
    /// }
    /// ```
    pub fn get<T: 'static>(&self) -> Option<&T> {
        self.immutable
            .get(&TypeId::of::<T>())
            .map(|x| x.downcast_ref())
            .flatten()
    }

    /// Allows RAUI hosts to add immutable references to application data to the
    /// [`process_context`][crate::widget::context::WidgetContext::process_context`] that is
    /// available to widget components.
    ///
    /// See [`Application::process`] for more information.
    pub fn insert<T: 'static>(&mut self, item: &'a T) -> &mut Self {
        self.immutable.insert(TypeId::of::<T>(), item);
        self
    }

    /// Can be used to get immutable access to owned objects available for current application
    /// processing run provided by the RAUI host.
    ///
    /// # Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// # struct AppData {
    /// #    counter: i32
    /// # }
    /// fn my_component(ctx: WidgetContext) -> WidgetNode {
    ///     let app_data = ctx.process_context.owned_ref::<AppData>().unwrap();
    ///     let counter = app_data.counter;
    ///
    ///     // widget stuff...
    /// #    widget!(())
    /// }
    /// ```
    pub fn owned_ref<T: 'static>(&self) -> Option<&T> {
        self.owned
            .get(&TypeId::of::<T>())
            .map(|x| x.downcast_ref())
            .flatten()
    }

    /// Can be used to get mutable access to owned objects available for current application
    /// processing run provided by the RAUI host.
    ///
    /// # Example
    ///
    /// ```
    /// # use raui_core::prelude::*;
    /// # struct AppData {
    /// #    counter: i32
    /// # }
    /// fn my_component(ctx: WidgetContext) -> WidgetNode {
    ///     let app_data = ctx.process_context.owned_mut::<AppData>().unwrap();
    ///     let counter = app_data.counter;
    ///
    ///     // widget stuff...
    /// #    widget!(())
    /// }
    /// ```
    pub fn owned_mut<T: 'static>(&mut self) -> Option<&mut T> {
        self.owned
            .get_mut(&TypeId::of::<T>())
            .map(|x| x.downcast_mut())
            .flatten()
    }

    /// Allows RAUI hosts to add owned objects to application data to the
    /// [`process_context`][crate::widget::context::WidgetContext::process_context`] that is
    /// available to widget components.
    pub fn insert_owned<T: 'static>(&mut self, item: T) -> &mut Self {
        self.owned.insert(TypeId::of::<T>(), Box::new(item));
        self
    }

    pub fn has<T: 'static>(&self) -> bool {
        let t = TypeId::of::<T>();
        self.owned.contains_key(&t)
            || self.immutable.contains_key(&t)
            || self.mutable.contains_key(&t)
    }
}