raui_core/
application.rs

1//! Application foundation used to drive the RAUI interface
2//!
3//! An [`Application`] is the struct that pulls together all the pieces of a RAUI ui such as layout,
4//! interaction, animations, etc.
5//!
6//! In most cases users will not need to manually create and manage an [`Application`]. That will
7//! usually be handled by renderer integration crates like [`raui-tesselation-renderer`].
8//!
9//! [`raui-tesselation-renderer`]: https://docs.rs/raui-tesselation-renderer/
10//!
11//! You _will_ need to interact with [`Application`] if you are building your own RAUI integration
12//! with another renderer or game engine.
13//! ```
14
15use crate::{
16    Prefab, PrefabError, PrefabValue, Scalar,
17    animator::{AnimationUpdate, Animator, AnimatorStates},
18    interactive::InteractionsEngine,
19    layout::{CoordsMapping, Layout, LayoutEngine},
20    messenger::{Message, MessageData, MessageSender, Messages, Messenger},
21    props::{Props, PropsData, PropsRegistry},
22    renderer::Renderer,
23    signals::{Signal, SignalSender},
24    state::{State, StateChange, StateUpdate},
25    view_model::{ViewModel, ViewModelCollection, ViewModelCollectionView},
26    widget::{
27        FnWidget, WidgetId, WidgetIdCommon, WidgetLifeCycle,
28        component::{
29            WidgetComponent, WidgetComponentPrefab, containers::responsive_box::MediaQueryViewModel,
30        },
31        context::{WidgetContext, WidgetMountOrChangeContext, WidgetUnmountContext},
32        node::{WidgetNode, WidgetNodePrefab},
33        unit::{
34            WidgetUnit, WidgetUnitNode, WidgetUnitNodePrefab,
35            area::{AreaBoxNode, AreaBoxNodePrefab},
36            content::{
37                ContentBoxItem, ContentBoxItemNode, ContentBoxItemNodePrefab, ContentBoxNode,
38                ContentBoxNodePrefab,
39            },
40            flex::{
41                FlexBoxItem, FlexBoxItemNode, FlexBoxItemNodePrefab, FlexBoxNode, FlexBoxNodePrefab,
42            },
43            grid::{
44                GridBoxItem, GridBoxItemNode, GridBoxItemNodePrefab, GridBoxNode, GridBoxNodePrefab,
45            },
46            image::{ImageBoxNode, ImageBoxNodePrefab},
47            portal::{
48                PortalBox, PortalBoxNode, PortalBoxNodePrefab, PortalBoxSlot, PortalBoxSlotNode,
49                PortalBoxSlotNodePrefab,
50            },
51            size::{SizeBoxNode, SizeBoxNodePrefab},
52            text::{TextBoxNode, TextBoxNodePrefab},
53        },
54    },
55};
56use std::{
57    borrow::Cow,
58    collections::{HashMap, HashSet},
59    convert::TryInto,
60    sync::{
61        Arc, RwLock,
62        mpsc::{Sender, channel},
63    },
64};
65
66/// Errors that can occur while interacting with an application
67#[derive(Debug, Clone)]
68pub enum ApplicationError {
69    Prefab(PrefabError),
70    ComponentMappingNotFound(String),
71}
72
73impl From<PrefabError> for ApplicationError {
74    fn from(error: PrefabError) -> Self {
75        Self::Prefab(error)
76    }
77}
78
79/// Indicates the reason that an [`Application`] state was invalidated and had to be re-rendered
80///
81/// You can get the last invalidation cause of an application using [`last_invalidation_cause`]
82///
83/// [`last_invalidation_cause`]: Application::last_invalidation_cause
84#[derive(Debug, Default, Clone)]
85pub enum InvalidationCause {
86    /// Application not invalidated
87    #[default]
88    None,
89    /// Application update caused by change in widgets common root.
90    CommonRootUpdate(WidgetIdCommon),
91}
92
93#[derive(Clone)]
94pub struct ChangeNotifier(Arc<RwLock<HashSet<WidgetId>>>);
95
96impl ChangeNotifier {
97    pub fn notify(&self, id: WidgetId) {
98        if let Ok(mut ids) = self.0.write() {
99            ids.insert(id);
100        }
101    }
102}
103
104/// Contains and orchestrates application layout, animations, interactions, etc.
105///
106/// See the [`application`][self] module for more information and examples.
107pub struct Application {
108    component_mappings: HashMap<String, FnWidget>,
109    props_registry: PropsRegistry,
110    tree: WidgetNode,
111    rendered_tree: WidgetUnit,
112    layout: Layout,
113    states: HashMap<WidgetId, Props>,
114    state_changes: HashMap<WidgetId, Vec<StateChange>>,
115    animators: HashMap<WidgetId, AnimatorStates>,
116    messages: HashMap<WidgetId, Messages>,
117    pending_stack: Vec<WidgetStackItem>,
118    done_stack: Vec<WidgetNode>,
119    signals: Vec<Signal>,
120    pub view_models: ViewModelCollection,
121    changes: ChangeNotifier,
122    #[allow(clippy::type_complexity)]
123    unmount_closures: HashMap<WidgetId, Vec<Box<dyn FnMut(WidgetUnmountContext) + Send + Sync>>>,
124    dirty: WidgetIdCommon,
125    render_changed: bool,
126    last_invalidation_cause: InvalidationCause,
127    /// The amount of time between the last update, used when calculating animation progress
128    pub animations_delta_time: Scalar,
129}
130
131impl Default for Application {
132    fn default() -> Self {
133        let mut view_models = ViewModelCollection::default();
134        view_models.insert(
135            MediaQueryViewModel::VIEW_MODEL.to_string(),
136            ViewModel::produce(MediaQueryViewModel::new),
137        );
138        Self {
139            component_mappings: Default::default(),
140            props_registry: Default::default(),
141            tree: Default::default(),
142            rendered_tree: Default::default(),
143            layout: Default::default(),
144            states: Default::default(),
145            state_changes: Default::default(),
146            animators: Default::default(),
147            messages: Default::default(),
148            pending_stack: Default::default(),
149            done_stack: Default::default(),
150            signals: Default::default(),
151            view_models,
152            changes: ChangeNotifier(Default::default()),
153            unmount_closures: Default::default(),
154            dirty: Default::default(),
155            render_changed: false,
156            last_invalidation_cause: Default::default(),
157            animations_delta_time: 0.0,
158        }
159    }
160}
161
162impl Application {
163    /// Setup the application with a given a setup function
164    ///
165    /// We need to run the `setup` function for the application to register components and
166    /// properties if we want to support serialization of the UI. We pass it a function that will do
167    /// the actual registration.
168    ///
169    /// > **Note:** RAUI will work fine without running any `setup` if UI serialization is not
170    /// > required.
171    #[inline]
172    pub fn setup<F>(&mut self, mut f: F)
173    where
174        F: FnMut(&mut Self),
175    {
176        (f)(self);
177    }
178
179    pub fn notifier(&self) -> ChangeNotifier {
180        self.changes.clone()
181    }
182
183    /// Register's a component under a string name used when serializing the UI
184    ///
185    /// This function is often used in [`setup`][Self::setup] functions for registering batches of
186    /// components.
187    #[inline]
188    pub fn register_component(&mut self, type_name: &str, processor: FnWidget) {
189        self.component_mappings
190            .insert(type_name.to_owned(), processor);
191    }
192
193    /// Unregisters a component
194    ///
195    /// See [`register_component`][Self::register_component]
196    #[inline]
197    pub fn unregister_component(&mut self, type_name: &str) {
198        self.component_mappings.remove(type_name);
199    }
200
201    /// Register's a property type under a string name used when serializing the UI
202    ///
203    /// This function is often used in [`setup`][Self::setup] functions for registering batches of
204    /// properties.
205    #[inline]
206    pub fn register_props<T>(&mut self, name: &str)
207    where
208        T: 'static + Prefab + PropsData,
209    {
210        self.props_registry.register_factory::<T>(name);
211    }
212
213    /// Unregisters a property type
214    ///
215    /// See [`register_props`][Self::register_props]
216    #[inline]
217    pub fn unregister_props(&mut self, name: &str) {
218        self.props_registry.unregister_factory(name);
219    }
220
221    /// Serialize the given [`Props`] to a [`PrefabValue`]
222    #[inline]
223    pub fn serialize_props(&self, props: &Props) -> Result<PrefabValue, PrefabError> {
224        self.props_registry.serialize(props)
225    }
226
227    /// Deserialize [`Props`] from a [`PrefabValue`]
228    #[inline]
229    pub fn deserialize_props(&self, data: PrefabValue) -> Result<Props, PrefabError> {
230        self.props_registry.deserialize(data)
231    }
232
233    /// Serialize a [`WidgetNode`] to a [`PrefabValue`]
234    #[inline]
235    pub fn serialize_node(&self, data: &WidgetNode) -> Result<PrefabValue, ApplicationError> {
236        Ok(self.node_to_prefab(data)?.to_prefab()?)
237    }
238
239    /// Deserialize a [`WidgetNode`] from a [`PrefabValue`]
240    #[inline]
241    pub fn deserialize_node(&self, data: PrefabValue) -> Result<WidgetNode, ApplicationError> {
242        self.node_from_prefab(WidgetNodePrefab::from_prefab(data)?)
243    }
244
245    /// Get the reason that the application state was last invalidated and caused to re-process
246    #[inline]
247    pub fn last_invalidation_cause(&self) -> &InvalidationCause {
248        &self.last_invalidation_cause
249    }
250
251    /// Return's common root widget ID of widgets that has to be to be re-processed
252    #[inline]
253    pub fn dirty(&self) -> &WidgetIdCommon {
254        &self.dirty
255    }
256
257    /// Force mark the application as needing to re-process its root
258    #[inline]
259    pub fn mark_dirty(&mut self) {
260        self.dirty = WidgetIdCommon::new(WidgetId::empty());
261    }
262
263    #[inline]
264    pub fn does_render_changed(&self) -> bool {
265        self.render_changed
266    }
267
268    /// Get the [`WidgetNode`] for the application tree
269    #[inline]
270    pub fn tree(&self) -> &WidgetNode {
271        &self.tree
272    }
273
274    /// Get the application widget tree rendered to raw [`WidgetUnit`]'s
275    #[inline]
276    pub fn rendered_tree(&self) -> &WidgetUnit {
277        &self.rendered_tree
278    }
279
280    /// Get the application [`Layout`] data
281    #[inline]
282    pub fn layout_data(&self) -> &Layout {
283        &self.layout
284    }
285
286    #[inline]
287    pub fn has_layout_widget(&self, id: &WidgetId) -> bool {
288        self.layout.items.keys().any(|k| k == id)
289    }
290
291    /// Update the application widget tree
292    #[inline]
293    pub fn apply(&mut self, tree: impl Into<WidgetNode>) {
294        self.mark_dirty();
295        self.tree = tree.into();
296    }
297
298    /// Render the application
299    #[inline]
300    pub fn render<R, T, E>(&self, mapping: &CoordsMapping, renderer: &mut R) -> Result<T, E>
301    where
302        R: Renderer<T, E>,
303    {
304        renderer.render(&self.rendered_tree, mapping, &self.layout)
305    }
306
307    /// Render the application, but only if something effecting the rendering has changed and it
308    /// _needs_ to be re-rendered
309    #[inline]
310    pub fn render_change<R, T, E>(
311        &mut self,
312        mapping: &CoordsMapping,
313        renderer: &mut R,
314    ) -> Result<Option<T>, E>
315    where
316        R: Renderer<T, E>,
317    {
318        if self.render_changed {
319            Ok(Some(self.render(mapping, renderer)?))
320        } else {
321            Ok(None)
322        }
323    }
324
325    /// Calculate application layout
326    #[inline]
327    pub fn layout<L, E>(&mut self, mapping: &CoordsMapping, layout_engine: &mut L) -> Result<(), E>
328    where
329        L: LayoutEngine<E>,
330    {
331        self.layout = layout_engine.layout(mapping, &self.rendered_tree)?;
332        if let Some(view_model) = self.view_models.get_mut(MediaQueryViewModel::VIEW_MODEL)
333            && let Some(mut view_model) = view_model.write::<MediaQueryViewModel>()
334        {
335            view_model
336                .screen_size
337                .set_unique_notify(self.layout.ui_space.size());
338        }
339        Ok(())
340    }
341
342    /// Calculate application layout, but only if something effecting application layout has changed
343    /// and the layout _needs_ to be re-done
344    #[inline]
345    pub fn layout_change<L, E>(
346        &mut self,
347        mapping: &CoordsMapping,
348        layout_engine: &mut L,
349    ) -> Result<bool, E>
350    where
351        L: LayoutEngine<E>,
352    {
353        if self.render_changed {
354            self.layout(mapping, layout_engine)?;
355            Ok(true)
356        } else {
357            Ok(false)
358        }
359    }
360
361    /// Perform interactions on the application using the given interaction engine
362    #[inline]
363    pub fn interact<I, R, E>(&mut self, interactions_engine: &mut I) -> Result<R, E>
364    where
365        I: InteractionsEngine<R, E>,
366    {
367        interactions_engine.perform_interactions(self)
368    }
369
370    /// Send a message to the given widget
371    #[inline]
372    pub fn send_message<T>(&mut self, id: &WidgetId, data: T)
373    where
374        T: 'static + MessageData,
375    {
376        self.send_message_raw(id, Box::new(data));
377    }
378
379    /// Send raw message data to the given widget
380    #[inline]
381    pub fn send_message_raw(&mut self, id: &WidgetId, data: Message) {
382        if let Some(list) = self.messages.get_mut(id) {
383            list.push(data);
384        } else {
385            self.messages.insert(id.to_owned(), vec![data]);
386        }
387    }
388
389    /// Get the list of [signals][crate::signals] that have been sent by widgets
390    #[inline]
391    pub fn signals(&self) -> &[Signal] {
392        &self.signals
393    }
394
395    /// Get the list of [signals][crate::signals] that have been sent by widgets, consuming the
396    /// current list so that further calls will not include previously sent signals
397    #[inline]
398    pub fn consume_signals(&mut self) -> Vec<Signal> {
399        std::mem::take(&mut self.signals)
400    }
401
402    /// [`process()`][Self::process] application, even if no changes have been detected
403    #[inline]
404    pub fn forced_process(&mut self) -> bool {
405        self.mark_dirty();
406        self.process()
407    }
408
409    /// [Process][Self::process] the application.
410    pub fn process(&mut self) -> bool {
411        self.dirty
412            .include_other(&self.view_models.consume_notified_common_root());
413        if let Ok(mut ids) = self.changes.0.write() {
414            for id in ids.drain() {
415                self.dirty.include(&id);
416            }
417        }
418        self.animations_delta_time = self.animations_delta_time.max(0.0);
419        self.last_invalidation_cause = InvalidationCause::None;
420        self.render_changed = false;
421        let changed_states = std::mem::take(&mut self.state_changes);
422        for id in changed_states.keys() {
423            self.dirty.include(id);
424        }
425        let mut messages = std::mem::take(&mut self.messages);
426        for id in messages.keys() {
427            self.dirty.include(id);
428        }
429        for (id, animator) in &self.animators {
430            if animator.in_progress() {
431                self.dirty.include(id);
432            }
433        }
434        if !self.dirty.is_valid() {
435            return false;
436        }
437        self.last_invalidation_cause = InvalidationCause::CommonRootUpdate(self.dirty.to_owned());
438        let (message_sender, message_receiver) = channel();
439        let message_sender = MessageSender::new(message_sender);
440        for (k, a) in &mut self.animators {
441            a.process(self.animations_delta_time, k, &message_sender);
442        }
443        let mut states = std::mem::take(&mut self.states);
444        for (id, changes) in changed_states {
445            let state = states.entry(id).or_default();
446            for change in changes {
447                match change {
448                    StateChange::Set(props) => {
449                        *state = props;
450                    }
451                    StateChange::Include(props) => {
452                        state.merge_from(props);
453                    }
454                    StateChange::Exclude(type_id) => unsafe {
455                        state.remove_by_type(type_id);
456                    },
457                }
458            }
459        }
460        let (signal_sender, signal_receiver) = channel();
461        let tree = self.tree.clone();
462        let mut used_ids = HashSet::new();
463        let mut new_states = HashMap::new();
464        let rendered_tree = self.process_nodes_stack(
465            tree,
466            &states,
467            &mut messages,
468            &mut new_states,
469            &mut used_ids,
470            &message_sender,
471            &signal_sender,
472        );
473        self.states = states
474            .into_iter()
475            .chain(new_states)
476            .filter(|(id, state)| {
477                if used_ids.contains(id) {
478                    true
479                } else {
480                    if let Some(closures) = self.unmount_closures.remove(id) {
481                        for mut closure in closures {
482                            let messenger = &message_sender;
483                            let signals = SignalSender::new(id.clone(), signal_sender.clone());
484                            let view_models =
485                                ViewModelCollectionView::new(id, &mut self.view_models);
486                            let context = WidgetUnmountContext {
487                                id,
488                                state,
489                                messenger,
490                                signals,
491                                view_models,
492                            };
493                            (closure)(context);
494                        }
495                    }
496                    self.animators.remove(id);
497                    self.view_models.unbind_all(id);
498                    self.view_models.remove_widget_view_models(id);
499                    false
500                }
501            })
502            .collect();
503        while let Ok((id, message)) = message_receiver.try_recv() {
504            if let Some(list) = self.messages.get_mut(&id) {
505                list.push(message);
506            } else {
507                self.messages.insert(id, vec![message]);
508            }
509        }
510        self.signals.clear();
511        while let Ok(data) = signal_receiver.try_recv() {
512            self.signals.push(data);
513        }
514        self.animators = std::mem::take(&mut self.animators)
515            .into_iter()
516            .filter_map(|(k, a)| if a.in_progress() { Some((k, a)) } else { None })
517            .collect();
518        self.dirty = Default::default();
519        if let Ok(tree) = rendered_tree.try_into() {
520            self.rendered_tree = Self::teleport_portals(tree);
521            true
522        } else {
523            false
524        }
525    }
526
527    #[allow(clippy::too_many_arguments)]
528    fn process_nodes_stack(
529        &mut self,
530        root_node: WidgetNode,
531        states: &HashMap<WidgetId, Props>,
532        messages: &mut HashMap<WidgetId, Messages>,
533        new_states: &mut HashMap<WidgetId, Props>,
534        used_ids: &mut HashSet<WidgetId>,
535        message_sender: &MessageSender,
536        signal_sender: &Sender<Signal>,
537    ) -> WidgetNode {
538        self.pending_stack.clear();
539        self.pending_stack.push(WidgetStackItem::Node {
540            node: root_node,
541            path: vec![],
542            possible_key: "<*>".to_string(),
543            master_shared_props: None,
544        });
545        self.done_stack.clear();
546        while let Some(item) = self.pending_stack.pop() {
547            match item {
548                WidgetStackItem::Node {
549                    node,
550                    mut path,
551                    possible_key,
552                    master_shared_props,
553                } => match node {
554                    WidgetNode::None | WidgetNode::Tuple(_) => {
555                        self.done_stack.push(node);
556                    }
557                    WidgetNode::Component(component) => {
558                        let WidgetComponent {
559                            processor,
560                            type_name,
561                            key,
562                            mut idref,
563                            mut props,
564                            shared_props,
565                            listed_slots,
566                            named_slots,
567                        } = component;
568                        let mut shared_props = match (master_shared_props, shared_props) {
569                            (Some(master_shared_props), Some(shared_props)) => {
570                                master_shared_props.merge(shared_props)
571                            }
572                            (None, Some(shared_props)) => shared_props,
573                            (Some(master_shared_props), None) => master_shared_props,
574                            _ => Default::default(),
575                        };
576                        let key = match &key {
577                            Some(key) => key.to_owned(),
578                            None => possible_key.to_owned(),
579                        };
580                        path.push(key.clone().into());
581                        let id = WidgetId::new(&type_name, &path);
582                        used_ids.insert(id.clone());
583                        if let Some(idref) = &mut idref {
584                            idref.write(id.to_owned());
585                        }
586                        let (state_sender, state_receiver) = channel();
587                        let (animation_sender, animation_receiver) = channel();
588                        let messages_list = messages.remove(&id).unwrap_or_default();
589                        let mut life_cycle = WidgetLifeCycle::default();
590                        let default_animator_state = AnimatorStates::default();
591                        let (new_node, mounted) = match states.get(&id) {
592                            Some(state) => {
593                                let state =
594                                    State::new(state, StateUpdate::new(state_sender.clone()));
595                                let animator =
596                                    self.animators.get(&id).unwrap_or(&default_animator_state);
597                                let view_models =
598                                    ViewModelCollectionView::new(&id, &mut self.view_models);
599                                let context = WidgetContext {
600                                    id: &id,
601                                    idref: idref.as_ref(),
602                                    key: &key,
603                                    props: &mut props,
604                                    shared_props: &mut shared_props,
605                                    state,
606                                    animator,
607                                    life_cycle: &mut life_cycle,
608                                    named_slots,
609                                    listed_slots,
610                                    view_models,
611                                };
612                                (processor.call(context), false)
613                            }
614                            None => {
615                                let state_data = Props::default();
616                                let state =
617                                    State::new(&state_data, StateUpdate::new(state_sender.clone()));
618                                let animator =
619                                    self.animators.get(&id).unwrap_or(&default_animator_state);
620                                let view_models =
621                                    ViewModelCollectionView::new(&id, &mut self.view_models);
622                                let context = WidgetContext {
623                                    id: &id,
624                                    idref: idref.as_ref(),
625                                    key: &key,
626                                    props: &mut props,
627                                    shared_props: &mut shared_props,
628                                    state,
629                                    animator,
630                                    life_cycle: &mut life_cycle,
631                                    named_slots,
632                                    listed_slots,
633                                    view_models,
634                                };
635                                let node = processor.call(context);
636                                new_states.insert(id.clone(), state_data);
637                                (node, true)
638                            }
639                        };
640                        let (mount, change, unmount) = life_cycle.unwrap();
641                        if mounted {
642                            if !mount.is_empty()
643                                && let Some(state) = new_states.get(&id)
644                            {
645                                for mut closure in mount {
646                                    let state =
647                                        State::new(state, StateUpdate::new(state_sender.clone()));
648                                    let messenger =
649                                        Messenger::new(message_sender.clone(), &messages_list);
650                                    let signals =
651                                        SignalSender::new(id.clone(), signal_sender.clone());
652                                    let animator = Animator::new(
653                                        self.animators.get(&id).unwrap_or(&default_animator_state),
654                                        AnimationUpdate::new(animation_sender.clone()),
655                                    );
656                                    let view_models =
657                                        ViewModelCollectionView::new(&id, &mut self.view_models);
658                                    let context = WidgetMountOrChangeContext {
659                                        id: &id,
660                                        props: &props,
661                                        shared_props: &shared_props,
662                                        state,
663                                        messenger,
664                                        signals,
665                                        animator,
666                                        view_models,
667                                    };
668                                    (closure)(context);
669                                }
670                            }
671                        } else if !change.is_empty()
672                            && let Some(state) = states.get(&id)
673                        {
674                            for mut closure in change {
675                                let state =
676                                    State::new(state, StateUpdate::new(state_sender.clone()));
677                                let messenger =
678                                    Messenger::new(message_sender.clone(), &messages_list);
679                                let signals = SignalSender::new(id.clone(), signal_sender.clone());
680                                let animator = Animator::new(
681                                    self.animators.get(&id).unwrap_or(&default_animator_state),
682                                    AnimationUpdate::new(animation_sender.clone()),
683                                );
684                                let view_models =
685                                    ViewModelCollectionView::new(&id, &mut self.view_models);
686                                let context = WidgetMountOrChangeContext {
687                                    id: &id,
688                                    props: &props,
689                                    shared_props: &shared_props,
690                                    state,
691                                    messenger,
692                                    signals,
693                                    animator,
694                                    view_models,
695                                };
696                                (closure)(context);
697                            }
698                        }
699                        if !unmount.is_empty() {
700                            self.unmount_closures.insert(id.clone(), unmount);
701                        }
702                        while let Ok((name, data)) = animation_receiver.try_recv() {
703                            if let Some(states) = self.animators.get_mut(&id) {
704                                states.change(name, data);
705                            } else if let Some(data) = data {
706                                self.animators
707                                    .insert(id.to_owned(), AnimatorStates::new(name, data));
708                            }
709                        }
710                        while let Ok(data) = state_receiver.try_recv() {
711                            self.state_changes
712                                .entry(id.to_owned())
713                                .or_default()
714                                .push(data);
715                        }
716                        self.pending_stack.push(WidgetStackItem::Node {
717                            node: new_node,
718                            path,
719                            possible_key,
720                            master_shared_props: Some(shared_props),
721                        });
722                    }
723                    WidgetNode::Unit(unit) => match unit {
724                        WidgetUnitNode::None
725                        | WidgetUnitNode::ImageBox(_)
726                        | WidgetUnitNode::TextBox(_) => {
727                            self.done_stack.push(WidgetNode::Unit(unit));
728                        }
729                        WidgetUnitNode::AreaBox(mut unit) => {
730                            let slot = *std::mem::take(&mut unit.slot);
731                            self.pending_stack
732                                .push(WidgetStackItem::AreaBox { node: unit });
733                            self.pending_stack.push(WidgetStackItem::Node {
734                                node: slot,
735                                path,
736                                possible_key: ".".to_owned(),
737                                master_shared_props,
738                            });
739                        }
740                        WidgetUnitNode::PortalBox(mut unit) => match &mut *unit.slot {
741                            PortalBoxSlotNode::Slot(data) => {
742                                let slot = std::mem::take(data);
743                                self.pending_stack
744                                    .push(WidgetStackItem::PortalBox { node: unit });
745                                self.pending_stack.push(WidgetStackItem::Node {
746                                    node: slot,
747                                    path,
748                                    possible_key: ".".to_owned(),
749                                    master_shared_props,
750                                });
751                            }
752                            PortalBoxSlotNode::ContentItem(item) => {
753                                let slot = std::mem::take(&mut item.slot);
754                                self.pending_stack
755                                    .push(WidgetStackItem::PortalBox { node: unit });
756                                self.pending_stack.push(WidgetStackItem::Node {
757                                    node: slot,
758                                    path,
759                                    possible_key: ".".to_owned(),
760                                    master_shared_props,
761                                });
762                            }
763                            PortalBoxSlotNode::FlexItem(item) => {
764                                let slot = std::mem::take(&mut item.slot);
765                                self.pending_stack
766                                    .push(WidgetStackItem::PortalBox { node: unit });
767                                self.pending_stack.push(WidgetStackItem::Node {
768                                    node: slot,
769                                    path,
770                                    possible_key: ".".to_owned(),
771                                    master_shared_props,
772                                });
773                            }
774                            PortalBoxSlotNode::GridItem(item) => {
775                                let slot = std::mem::take(&mut item.slot);
776                                self.pending_stack
777                                    .push(WidgetStackItem::PortalBox { node: unit });
778                                self.pending_stack.push(WidgetStackItem::Node {
779                                    node: slot,
780                                    path,
781                                    possible_key: ".".to_owned(),
782                                    master_shared_props,
783                                });
784                            }
785                        },
786                        WidgetUnitNode::ContentBox(mut unit) => {
787                            let items = unit
788                                .items
789                                .iter_mut()
790                                .map(|node| std::mem::take(&mut node.slot))
791                                .collect::<Vec<_>>();
792                            self.pending_stack
793                                .push(WidgetStackItem::ContentBox { node: unit });
794                            for (index, node) in items.into_iter().enumerate() {
795                                self.pending_stack.push(WidgetStackItem::Node {
796                                    node,
797                                    path: path.clone(),
798                                    possible_key: format!("<{index}>"),
799                                    master_shared_props: master_shared_props.clone(),
800                                });
801                            }
802                        }
803                        WidgetUnitNode::FlexBox(mut unit) => {
804                            let items = unit
805                                .items
806                                .iter_mut()
807                                .map(|node| std::mem::take(&mut node.slot))
808                                .collect::<Vec<_>>();
809                            self.pending_stack
810                                .push(WidgetStackItem::FlexBox { node: unit });
811                            for (index, node) in items.into_iter().enumerate() {
812                                self.pending_stack.push(WidgetStackItem::Node {
813                                    node,
814                                    path: path.clone(),
815                                    possible_key: format!("<{index}>"),
816                                    master_shared_props: master_shared_props.clone(),
817                                });
818                            }
819                        }
820                        WidgetUnitNode::GridBox(mut unit) => {
821                            let items = unit
822                                .items
823                                .iter_mut()
824                                .map(|node| std::mem::take(&mut node.slot))
825                                .collect::<Vec<_>>();
826                            self.pending_stack
827                                .push(WidgetStackItem::GridBox { node: unit });
828                            for (index, node) in items.into_iter().enumerate() {
829                                self.pending_stack.push(WidgetStackItem::Node {
830                                    node,
831                                    path: path.clone(),
832                                    possible_key: format!("<{index}>"),
833                                    master_shared_props: master_shared_props.clone(),
834                                });
835                            }
836                        }
837                        WidgetUnitNode::SizeBox(mut unit) => {
838                            let slot = *std::mem::take(&mut unit.slot);
839                            self.pending_stack
840                                .push(WidgetStackItem::SizeBox { node: unit });
841                            self.pending_stack.push(WidgetStackItem::Node {
842                                node: slot,
843                                path,
844                                possible_key: ".".to_owned(),
845                                master_shared_props,
846                            });
847                        }
848                    },
849                },
850                WidgetStackItem::AreaBox { mut node } => {
851                    node.slot = Box::new(self.done_stack.pop().unwrap_or_default());
852                    self.done_stack
853                        .push(WidgetNode::Unit(WidgetUnitNode::AreaBox(node)));
854                }
855                WidgetStackItem::PortalBox { mut node } => {
856                    match &mut *node.slot {
857                        PortalBoxSlotNode::Slot(node) => {
858                            *node = self.done_stack.pop().unwrap_or_default();
859                        }
860                        PortalBoxSlotNode::ContentItem(node) => {
861                            node.slot = self.done_stack.pop().unwrap_or_default();
862                        }
863                        PortalBoxSlotNode::FlexItem(node) => {
864                            node.slot = self.done_stack.pop().unwrap_or_default();
865                        }
866                        PortalBoxSlotNode::GridItem(node) => {
867                            node.slot = self.done_stack.pop().unwrap_or_default();
868                        }
869                    }
870                    self.done_stack
871                        .push(WidgetNode::Unit(WidgetUnitNode::PortalBox(node)));
872                }
873                WidgetStackItem::ContentBox { mut node } => {
874                    for item in node.items.iter_mut() {
875                        item.slot = self.done_stack.pop().unwrap_or_default();
876                    }
877                    self.done_stack
878                        .push(WidgetNode::Unit(WidgetUnitNode::ContentBox(node)));
879                }
880                WidgetStackItem::FlexBox { mut node } => {
881                    for item in node.items.iter_mut() {
882                        item.slot = self.done_stack.pop().unwrap_or_default();
883                    }
884                    self.done_stack
885                        .push(WidgetNode::Unit(WidgetUnitNode::FlexBox(node)));
886                }
887                WidgetStackItem::GridBox { mut node } => {
888                    for item in node.items.iter_mut() {
889                        item.slot = self.done_stack.pop().unwrap_or_default();
890                    }
891                    self.done_stack
892                        .push(WidgetNode::Unit(WidgetUnitNode::GridBox(node)));
893                }
894                WidgetStackItem::SizeBox { mut node } => {
895                    node.slot = Box::new(self.done_stack.pop().unwrap_or_default());
896                    self.done_stack
897                        .push(WidgetNode::Unit(WidgetUnitNode::SizeBox(node)));
898                }
899            }
900        }
901        assert!(
902            self.pending_stack.is_empty(),
903            "Pending stack should be empty after processing"
904        );
905        assert_eq!(
906            self.done_stack.len(),
907            1,
908            "Done stack should have exactly one item after processing"
909        );
910        self.done_stack.pop().unwrap_or_default()
911    }
912
913    fn teleport_portals(mut root: WidgetUnit) -> WidgetUnit {
914        let count = Self::estimate_portals(&root);
915        if count == 0 {
916            return root;
917        }
918        let mut portals = Vec::with_capacity(count);
919        Self::consume_portals(&mut root, &mut portals);
920        Self::inject_portals(&mut root, &mut portals);
921        root
922    }
923
924    fn estimate_portals(unit: &WidgetUnit) -> usize {
925        let mut count = 0;
926        match unit {
927            WidgetUnit::None | WidgetUnit::ImageBox(_) | WidgetUnit::TextBox(_) => {}
928            WidgetUnit::AreaBox(b) => count += Self::estimate_portals(&b.slot),
929            WidgetUnit::PortalBox(b) => {
930                count += Self::estimate_portals(match &*b.slot {
931                    PortalBoxSlot::Slot(slot) => slot,
932                    PortalBoxSlot::ContentItem(item) => &item.slot,
933                    PortalBoxSlot::FlexItem(item) => &item.slot,
934                    PortalBoxSlot::GridItem(item) => &item.slot,
935                }) + 1
936            }
937            WidgetUnit::ContentBox(b) => {
938                for item in &b.items {
939                    count += Self::estimate_portals(&item.slot);
940                }
941            }
942            WidgetUnit::FlexBox(b) => {
943                for item in &b.items {
944                    count += Self::estimate_portals(&item.slot);
945                }
946            }
947            WidgetUnit::GridBox(b) => {
948                for item in &b.items {
949                    count += Self::estimate_portals(&item.slot);
950                }
951            }
952            WidgetUnit::SizeBox(b) => count += Self::estimate_portals(&b.slot),
953        }
954        count
955    }
956
957    fn consume_portals(unit: &mut WidgetUnit, bucket: &mut Vec<(WidgetId, PortalBoxSlot)>) {
958        match unit {
959            WidgetUnit::None | WidgetUnit::ImageBox(_) | WidgetUnit::TextBox(_) => {}
960            WidgetUnit::AreaBox(b) => Self::consume_portals(&mut b.slot, bucket),
961            WidgetUnit::PortalBox(b) => {
962                let PortalBox {
963                    owner, mut slot, ..
964                } = std::mem::take(b);
965                Self::consume_portals(
966                    match &mut *slot {
967                        PortalBoxSlot::Slot(slot) => slot,
968                        PortalBoxSlot::ContentItem(item) => &mut item.slot,
969                        PortalBoxSlot::FlexItem(item) => &mut item.slot,
970                        PortalBoxSlot::GridItem(item) => &mut item.slot,
971                    },
972                    bucket,
973                );
974                bucket.push((owner, *slot));
975            }
976            WidgetUnit::ContentBox(b) => {
977                for item in &mut b.items {
978                    Self::consume_portals(&mut item.slot, bucket);
979                }
980            }
981            WidgetUnit::FlexBox(b) => {
982                for item in &mut b.items {
983                    Self::consume_portals(&mut item.slot, bucket);
984                }
985            }
986            WidgetUnit::GridBox(b) => {
987                for item in &mut b.items {
988                    Self::consume_portals(&mut item.slot, bucket);
989                }
990            }
991            WidgetUnit::SizeBox(b) => Self::consume_portals(&mut b.slot, bucket),
992        }
993    }
994
995    fn inject_portals(unit: &mut WidgetUnit, portals: &mut Vec<(WidgetId, PortalBoxSlot)>) -> bool {
996        if portals.is_empty() {
997            return false;
998        }
999        while let Some(data) = unit.as_data() {
1000            let found = portals.iter().position(|(id, _)| data.id() == id);
1001            if let Some(index) = found {
1002                let slot = portals.swap_remove(index).1;
1003                match unit {
1004                    WidgetUnit::None
1005                    | WidgetUnit::PortalBox(_)
1006                    | WidgetUnit::ImageBox(_)
1007                    | WidgetUnit::TextBox(_) => {}
1008                    WidgetUnit::AreaBox(b) => {
1009                        match slot {
1010                            PortalBoxSlot::Slot(slot) => b.slot = Box::new(slot),
1011                            PortalBoxSlot::ContentItem(item) => b.slot = Box::new(item.slot),
1012                            PortalBoxSlot::FlexItem(item) => b.slot = Box::new(item.slot),
1013                            PortalBoxSlot::GridItem(item) => b.slot = Box::new(item.slot),
1014                        }
1015                        if !Self::inject_portals(&mut b.slot, portals) {
1016                            return false;
1017                        }
1018                    }
1019                    WidgetUnit::ContentBox(b) => {
1020                        b.items.push(match slot {
1021                            PortalBoxSlot::Slot(slot) => ContentBoxItem {
1022                                slot,
1023                                ..Default::default()
1024                            },
1025                            PortalBoxSlot::ContentItem(item) => item,
1026                            PortalBoxSlot::FlexItem(item) => ContentBoxItem {
1027                                slot: item.slot,
1028                                ..Default::default()
1029                            },
1030                            PortalBoxSlot::GridItem(item) => ContentBoxItem {
1031                                slot: item.slot,
1032                                ..Default::default()
1033                            },
1034                        });
1035                        for item in &mut b.items {
1036                            if !Self::inject_portals(&mut item.slot, portals) {
1037                                return false;
1038                            }
1039                        }
1040                    }
1041                    WidgetUnit::FlexBox(b) => {
1042                        b.items.push(match slot {
1043                            PortalBoxSlot::Slot(slot) => FlexBoxItem {
1044                                slot,
1045                                ..Default::default()
1046                            },
1047                            PortalBoxSlot::ContentItem(item) => FlexBoxItem {
1048                                slot: item.slot,
1049                                ..Default::default()
1050                            },
1051                            PortalBoxSlot::FlexItem(item) => item,
1052                            PortalBoxSlot::GridItem(item) => FlexBoxItem {
1053                                slot: item.slot,
1054                                ..Default::default()
1055                            },
1056                        });
1057                        for item in &mut b.items {
1058                            if !Self::inject_portals(&mut item.slot, portals) {
1059                                return false;
1060                            }
1061                        }
1062                    }
1063                    WidgetUnit::GridBox(b) => {
1064                        b.items.push(match slot {
1065                            PortalBoxSlot::Slot(slot) => GridBoxItem {
1066                                slot,
1067                                ..Default::default()
1068                            },
1069                            PortalBoxSlot::ContentItem(item) => GridBoxItem {
1070                                slot: item.slot,
1071                                ..Default::default()
1072                            },
1073                            PortalBoxSlot::FlexItem(item) => GridBoxItem {
1074                                slot: item.slot,
1075                                ..Default::default()
1076                            },
1077                            PortalBoxSlot::GridItem(item) => item,
1078                        });
1079                        for item in &mut b.items {
1080                            if !Self::inject_portals(&mut item.slot, portals) {
1081                                return false;
1082                            }
1083                        }
1084                    }
1085                    WidgetUnit::SizeBox(b) => {
1086                        match slot {
1087                            PortalBoxSlot::Slot(slot) => b.slot = Box::new(slot),
1088                            PortalBoxSlot::ContentItem(item) => b.slot = Box::new(item.slot),
1089                            PortalBoxSlot::FlexItem(item) => b.slot = Box::new(item.slot),
1090                            PortalBoxSlot::GridItem(item) => b.slot = Box::new(item.slot),
1091                        }
1092                        if !Self::inject_portals(&mut b.slot, portals) {
1093                            return false;
1094                        }
1095                    }
1096                }
1097            } else {
1098                break;
1099            }
1100        }
1101        true
1102    }
1103
1104    fn node_to_prefab(&self, data: &WidgetNode) -> Result<WidgetNodePrefab, ApplicationError> {
1105        Ok(match data {
1106            WidgetNode::None => WidgetNodePrefab::None,
1107            WidgetNode::Component(data) => {
1108                WidgetNodePrefab::Component(self.component_to_prefab(data)?)
1109            }
1110            WidgetNode::Unit(data) => WidgetNodePrefab::Unit(self.unit_to_prefab(data)?),
1111            WidgetNode::Tuple(data) => WidgetNodePrefab::Tuple(self.tuple_to_prefab(data)?),
1112        })
1113    }
1114
1115    fn component_to_prefab(
1116        &self,
1117        data: &WidgetComponent,
1118    ) -> Result<WidgetComponentPrefab, ApplicationError> {
1119        if self.component_mappings.contains_key(&data.type_name) {
1120            Ok(WidgetComponentPrefab {
1121                type_name: data.type_name.to_owned(),
1122                key: data.key.clone(),
1123                props: self.props_registry.serialize(&data.props)?,
1124                shared_props: match &data.shared_props {
1125                    Some(p) => Some(self.props_registry.serialize(p)?),
1126                    None => None,
1127                },
1128                listed_slots: data
1129                    .listed_slots
1130                    .iter()
1131                    .map(|v| self.node_to_prefab(v))
1132                    .collect::<Result<_, _>>()?,
1133                named_slots: data
1134                    .named_slots
1135                    .iter()
1136                    .map(|(k, v)| Ok((k.to_owned(), self.node_to_prefab(v)?)))
1137                    .collect::<Result<_, ApplicationError>>()?,
1138            })
1139        } else {
1140            Err(ApplicationError::ComponentMappingNotFound(
1141                data.type_name.to_owned(),
1142            ))
1143        }
1144    }
1145
1146    fn unit_to_prefab(
1147        &self,
1148        data: &WidgetUnitNode,
1149    ) -> Result<WidgetUnitNodePrefab, ApplicationError> {
1150        Ok(match data {
1151            WidgetUnitNode::None => WidgetUnitNodePrefab::None,
1152            WidgetUnitNode::AreaBox(data) => {
1153                WidgetUnitNodePrefab::AreaBox(self.area_box_to_prefab(data)?)
1154            }
1155            WidgetUnitNode::PortalBox(data) => {
1156                WidgetUnitNodePrefab::PortalBox(self.portal_box_to_prefab(data)?)
1157            }
1158            WidgetUnitNode::ContentBox(data) => {
1159                WidgetUnitNodePrefab::ContentBox(self.content_box_to_prefab(data)?)
1160            }
1161            WidgetUnitNode::FlexBox(data) => {
1162                WidgetUnitNodePrefab::FlexBox(self.flex_box_to_prefab(data)?)
1163            }
1164            WidgetUnitNode::GridBox(data) => {
1165                WidgetUnitNodePrefab::GridBox(self.grid_box_to_prefab(data)?)
1166            }
1167            WidgetUnitNode::SizeBox(data) => {
1168                WidgetUnitNodePrefab::SizeBox(self.size_box_to_prefab(data)?)
1169            }
1170            WidgetUnitNode::ImageBox(data) => {
1171                WidgetUnitNodePrefab::ImageBox(self.image_box_to_prefab(data)?)
1172            }
1173            WidgetUnitNode::TextBox(data) => {
1174                WidgetUnitNodePrefab::TextBox(self.text_box_to_prefab(data)?)
1175            }
1176        })
1177    }
1178
1179    fn tuple_to_prefab(
1180        &self,
1181        data: &[WidgetNode],
1182    ) -> Result<Vec<WidgetNodePrefab>, ApplicationError> {
1183        data.iter()
1184            .map(|node| self.node_to_prefab(node))
1185            .collect::<Result<_, _>>()
1186    }
1187
1188    fn area_box_to_prefab(
1189        &self,
1190        data: &AreaBoxNode,
1191    ) -> Result<AreaBoxNodePrefab, ApplicationError> {
1192        Ok(AreaBoxNodePrefab {
1193            id: data.id.to_owned(),
1194            slot: Box::new(self.node_to_prefab(&data.slot)?),
1195        })
1196    }
1197
1198    fn portal_box_to_prefab(
1199        &self,
1200        data: &PortalBoxNode,
1201    ) -> Result<PortalBoxNodePrefab, ApplicationError> {
1202        Ok(PortalBoxNodePrefab {
1203            id: data.id.to_owned(),
1204            slot: Box::new(match &*data.slot {
1205                PortalBoxSlotNode::Slot(slot) => {
1206                    PortalBoxSlotNodePrefab::Slot(self.node_to_prefab(slot)?)
1207                }
1208                PortalBoxSlotNode::ContentItem(item) => {
1209                    PortalBoxSlotNodePrefab::ContentItem(ContentBoxItemNodePrefab {
1210                        slot: self.node_to_prefab(&item.slot)?,
1211                        layout: item.layout.clone(),
1212                    })
1213                }
1214                PortalBoxSlotNode::FlexItem(item) => {
1215                    PortalBoxSlotNodePrefab::FlexItem(FlexBoxItemNodePrefab {
1216                        slot: self.node_to_prefab(&item.slot)?,
1217                        layout: item.layout.clone(),
1218                    })
1219                }
1220                PortalBoxSlotNode::GridItem(item) => {
1221                    PortalBoxSlotNodePrefab::GridItem(GridBoxItemNodePrefab {
1222                        slot: self.node_to_prefab(&item.slot)?,
1223                        layout: item.layout.clone(),
1224                    })
1225                }
1226            }),
1227            owner: data.owner.to_owned(),
1228        })
1229    }
1230
1231    fn content_box_to_prefab(
1232        &self,
1233        data: &ContentBoxNode,
1234    ) -> Result<ContentBoxNodePrefab, ApplicationError> {
1235        Ok(ContentBoxNodePrefab {
1236            id: data.id.to_owned(),
1237            props: self.props_registry.serialize(&data.props)?,
1238            items: data
1239                .items
1240                .iter()
1241                .map(|v| {
1242                    Ok(ContentBoxItemNodePrefab {
1243                        slot: self.node_to_prefab(&v.slot)?,
1244                        layout: v.layout.clone(),
1245                    })
1246                })
1247                .collect::<Result<_, ApplicationError>>()?,
1248            clipping: data.clipping,
1249            content_reposition: data.content_reposition,
1250            transform: data.transform,
1251        })
1252    }
1253
1254    fn flex_box_to_prefab(
1255        &self,
1256        data: &FlexBoxNode,
1257    ) -> Result<FlexBoxNodePrefab, ApplicationError> {
1258        Ok(FlexBoxNodePrefab {
1259            id: data.id.to_owned(),
1260            props: self.props_registry.serialize(&data.props)?,
1261            items: data
1262                .items
1263                .iter()
1264                .map(|v| {
1265                    Ok(FlexBoxItemNodePrefab {
1266                        slot: self.node_to_prefab(&v.slot)?,
1267                        layout: v.layout.clone(),
1268                    })
1269                })
1270                .collect::<Result<_, ApplicationError>>()?,
1271            direction: data.direction,
1272            separation: data.separation,
1273            wrap: data.wrap,
1274            transform: data.transform,
1275        })
1276    }
1277
1278    fn grid_box_to_prefab(
1279        &self,
1280        data: &GridBoxNode,
1281    ) -> Result<GridBoxNodePrefab, ApplicationError> {
1282        Ok(GridBoxNodePrefab {
1283            id: data.id.to_owned(),
1284            props: self.props_registry.serialize(&data.props)?,
1285            items: data
1286                .items
1287                .iter()
1288                .map(|v| {
1289                    Ok(GridBoxItemNodePrefab {
1290                        slot: self.node_to_prefab(&v.slot)?,
1291                        layout: v.layout.clone(),
1292                    })
1293                })
1294                .collect::<Result<_, ApplicationError>>()?,
1295            cols: data.cols,
1296            rows: data.rows,
1297            transform: data.transform,
1298        })
1299    }
1300
1301    fn size_box_to_prefab(
1302        &self,
1303        data: &SizeBoxNode,
1304    ) -> Result<SizeBoxNodePrefab, ApplicationError> {
1305        Ok(SizeBoxNodePrefab {
1306            id: data.id.to_owned(),
1307            props: self.props_registry.serialize(&data.props)?,
1308            slot: Box::new(self.node_to_prefab(&data.slot)?),
1309            width: data.width,
1310            height: data.height,
1311            margin: data.margin,
1312            keep_aspect_ratio: data.keep_aspect_ratio,
1313            transform: data.transform,
1314        })
1315    }
1316
1317    fn image_box_to_prefab(
1318        &self,
1319        data: &ImageBoxNode,
1320    ) -> Result<ImageBoxNodePrefab, ApplicationError> {
1321        Ok(ImageBoxNodePrefab {
1322            id: data.id.to_owned(),
1323            props: self.props_registry.serialize(&data.props)?,
1324            width: data.width,
1325            height: data.height,
1326            content_keep_aspect_ratio: data.content_keep_aspect_ratio,
1327            material: data.material.clone(),
1328            transform: data.transform,
1329        })
1330    }
1331
1332    fn text_box_to_prefab(
1333        &self,
1334        data: &TextBoxNode,
1335    ) -> Result<TextBoxNodePrefab, ApplicationError> {
1336        Ok(TextBoxNodePrefab {
1337            id: data.id.to_owned(),
1338            props: self.props_registry.serialize(&data.props)?,
1339            text: data.text.clone(),
1340            width: data.width,
1341            height: data.height,
1342            horizontal_align: data.horizontal_align,
1343            vertical_align: data.vertical_align,
1344            direction: data.direction,
1345            font: data.font.clone(),
1346            color: data.color,
1347            transform: data.transform,
1348        })
1349    }
1350
1351    fn node_from_prefab(&self, data: WidgetNodePrefab) -> Result<WidgetNode, ApplicationError> {
1352        Ok(match data {
1353            WidgetNodePrefab::None => WidgetNode::None,
1354            WidgetNodePrefab::Component(data) => {
1355                WidgetNode::Component(self.component_from_prefab(data)?)
1356            }
1357            WidgetNodePrefab::Unit(data) => WidgetNode::Unit(self.unit_from_prefab(data)?),
1358            WidgetNodePrefab::Tuple(data) => WidgetNode::Tuple(self.tuple_from_prefab(data)?),
1359        })
1360    }
1361
1362    fn component_from_prefab(
1363        &self,
1364        data: WidgetComponentPrefab,
1365    ) -> Result<WidgetComponent, ApplicationError> {
1366        if let Some(processor) = self.component_mappings.get(&data.type_name) {
1367            Ok(WidgetComponent {
1368                processor: processor.clone(),
1369                type_name: data.type_name,
1370                key: data.key,
1371                idref: Default::default(),
1372                props: self.deserialize_props(data.props)?,
1373                shared_props: match data.shared_props {
1374                    Some(p) => Some(self.deserialize_props(p)?),
1375                    None => None,
1376                },
1377                listed_slots: data
1378                    .listed_slots
1379                    .into_iter()
1380                    .map(|v| self.node_from_prefab(v))
1381                    .collect::<Result<_, ApplicationError>>()?,
1382                named_slots: data
1383                    .named_slots
1384                    .into_iter()
1385                    .map(|(k, v)| Ok((k, self.node_from_prefab(v)?)))
1386                    .collect::<Result<_, ApplicationError>>()?,
1387            })
1388        } else {
1389            Err(ApplicationError::ComponentMappingNotFound(
1390                data.type_name.clone(),
1391            ))
1392        }
1393    }
1394
1395    fn unit_from_prefab(
1396        &self,
1397        data: WidgetUnitNodePrefab,
1398    ) -> Result<WidgetUnitNode, ApplicationError> {
1399        Ok(match data {
1400            WidgetUnitNodePrefab::None => WidgetUnitNode::None,
1401            WidgetUnitNodePrefab::AreaBox(data) => {
1402                WidgetUnitNode::AreaBox(self.area_box_from_prefab(data)?)
1403            }
1404            WidgetUnitNodePrefab::PortalBox(data) => {
1405                WidgetUnitNode::PortalBox(self.portal_box_from_prefab(data)?)
1406            }
1407            WidgetUnitNodePrefab::ContentBox(data) => {
1408                WidgetUnitNode::ContentBox(self.content_box_from_prefab(data)?)
1409            }
1410            WidgetUnitNodePrefab::FlexBox(data) => {
1411                WidgetUnitNode::FlexBox(self.flex_box_from_prefab(data)?)
1412            }
1413            WidgetUnitNodePrefab::GridBox(data) => {
1414                WidgetUnitNode::GridBox(self.grid_box_from_prefab(data)?)
1415            }
1416            WidgetUnitNodePrefab::SizeBox(data) => {
1417                WidgetUnitNode::SizeBox(self.size_box_from_prefab(data)?)
1418            }
1419            WidgetUnitNodePrefab::ImageBox(data) => {
1420                WidgetUnitNode::ImageBox(self.image_box_from_prefab(data)?)
1421            }
1422            WidgetUnitNodePrefab::TextBox(data) => {
1423                WidgetUnitNode::TextBox(self.text_box_from_prefab(data)?)
1424            }
1425        })
1426    }
1427
1428    fn tuple_from_prefab(
1429        &self,
1430        data: Vec<WidgetNodePrefab>,
1431    ) -> Result<Vec<WidgetNode>, ApplicationError> {
1432        data.into_iter()
1433            .map(|data| self.node_from_prefab(data))
1434            .collect::<Result<_, _>>()
1435    }
1436
1437    fn area_box_from_prefab(
1438        &self,
1439        data: AreaBoxNodePrefab,
1440    ) -> Result<AreaBoxNode, ApplicationError> {
1441        Ok(AreaBoxNode {
1442            id: data.id,
1443            slot: Box::new(self.node_from_prefab(*data.slot)?),
1444        })
1445    }
1446
1447    fn portal_box_from_prefab(
1448        &self,
1449        data: PortalBoxNodePrefab,
1450    ) -> Result<PortalBoxNode, ApplicationError> {
1451        Ok(PortalBoxNode {
1452            id: data.id,
1453            slot: Box::new(match *data.slot {
1454                PortalBoxSlotNodePrefab::Slot(slot) => {
1455                    PortalBoxSlotNode::Slot(self.node_from_prefab(slot)?)
1456                }
1457                PortalBoxSlotNodePrefab::ContentItem(item) => {
1458                    PortalBoxSlotNode::ContentItem(ContentBoxItemNode {
1459                        slot: self.node_from_prefab(item.slot)?,
1460                        layout: item.layout,
1461                    })
1462                }
1463                PortalBoxSlotNodePrefab::FlexItem(item) => {
1464                    PortalBoxSlotNode::FlexItem(FlexBoxItemNode {
1465                        slot: self.node_from_prefab(item.slot)?,
1466                        layout: item.layout,
1467                    })
1468                }
1469                PortalBoxSlotNodePrefab::GridItem(item) => {
1470                    PortalBoxSlotNode::GridItem(GridBoxItemNode {
1471                        slot: self.node_from_prefab(item.slot)?,
1472                        layout: item.layout,
1473                    })
1474                }
1475            }),
1476            owner: data.owner,
1477        })
1478    }
1479
1480    fn content_box_from_prefab(
1481        &self,
1482        data: ContentBoxNodePrefab,
1483    ) -> Result<ContentBoxNode, ApplicationError> {
1484        Ok(ContentBoxNode {
1485            id: data.id,
1486            props: self.props_registry.deserialize(data.props)?,
1487            items: data
1488                .items
1489                .into_iter()
1490                .map(|v| {
1491                    Ok(ContentBoxItemNode {
1492                        slot: self.node_from_prefab(v.slot)?,
1493                        layout: v.layout,
1494                    })
1495                })
1496                .collect::<Result<_, ApplicationError>>()?,
1497            clipping: data.clipping,
1498            content_reposition: data.content_reposition,
1499            transform: data.transform,
1500        })
1501    }
1502
1503    fn flex_box_from_prefab(
1504        &self,
1505        data: FlexBoxNodePrefab,
1506    ) -> Result<FlexBoxNode, ApplicationError> {
1507        Ok(FlexBoxNode {
1508            id: data.id,
1509            props: self.props_registry.deserialize(data.props)?,
1510            items: data
1511                .items
1512                .into_iter()
1513                .map(|v| {
1514                    Ok(FlexBoxItemNode {
1515                        slot: self.node_from_prefab(v.slot)?,
1516                        layout: v.layout,
1517                    })
1518                })
1519                .collect::<Result<_, ApplicationError>>()?,
1520            direction: data.direction,
1521            separation: data.separation,
1522            wrap: data.wrap,
1523            transform: data.transform,
1524        })
1525    }
1526
1527    fn grid_box_from_prefab(
1528        &self,
1529        data: GridBoxNodePrefab,
1530    ) -> Result<GridBoxNode, ApplicationError> {
1531        Ok(GridBoxNode {
1532            id: data.id,
1533            props: self.props_registry.deserialize(data.props)?,
1534            items: data
1535                .items
1536                .into_iter()
1537                .map(|v| {
1538                    Ok(GridBoxItemNode {
1539                        slot: self.node_from_prefab(v.slot)?,
1540                        layout: v.layout,
1541                    })
1542                })
1543                .collect::<Result<_, ApplicationError>>()?,
1544            cols: data.cols,
1545            rows: data.rows,
1546            transform: data.transform,
1547        })
1548    }
1549
1550    fn size_box_from_prefab(
1551        &self,
1552        data: SizeBoxNodePrefab,
1553    ) -> Result<SizeBoxNode, ApplicationError> {
1554        Ok(SizeBoxNode {
1555            id: data.id,
1556            props: self.props_registry.deserialize(data.props)?,
1557            slot: Box::new(self.node_from_prefab(*data.slot)?),
1558            width: data.width,
1559            height: data.height,
1560            margin: data.margin,
1561            keep_aspect_ratio: data.keep_aspect_ratio,
1562            transform: data.transform,
1563        })
1564    }
1565
1566    fn image_box_from_prefab(
1567        &self,
1568        data: ImageBoxNodePrefab,
1569    ) -> Result<ImageBoxNode, ApplicationError> {
1570        Ok(ImageBoxNode {
1571            id: data.id,
1572            props: self.props_registry.deserialize(data.props)?,
1573            width: data.width,
1574            height: data.height,
1575            content_keep_aspect_ratio: data.content_keep_aspect_ratio,
1576            material: data.material,
1577            transform: data.transform,
1578        })
1579    }
1580
1581    fn text_box_from_prefab(
1582        &self,
1583        data: TextBoxNodePrefab,
1584    ) -> Result<TextBoxNode, ApplicationError> {
1585        Ok(TextBoxNode {
1586            id: data.id,
1587            props: self.props_registry.deserialize(data.props)?,
1588            text: data.text,
1589            width: data.width,
1590            height: data.height,
1591            horizontal_align: data.horizontal_align,
1592            vertical_align: data.vertical_align,
1593            direction: data.direction,
1594            font: data.font,
1595            color: data.color,
1596            transform: data.transform,
1597        })
1598    }
1599}
1600
1601#[allow(clippy::large_enum_variant)]
1602enum WidgetStackItem {
1603    Node {
1604        node: WidgetNode,
1605        path: Vec<Cow<'static, str>>,
1606        possible_key: String,
1607        master_shared_props: Option<Props>,
1608    },
1609    AreaBox {
1610        node: AreaBoxNode,
1611    },
1612    PortalBox {
1613        node: PortalBoxNode,
1614    },
1615    ContentBox {
1616        node: ContentBoxNode,
1617    },
1618    FlexBox {
1619        node: FlexBoxNode,
1620    },
1621    GridBox {
1622        node: GridBoxNode,
1623    },
1624    SizeBox {
1625        node: SizeBoxNode,
1626    },
1627}