lgui-core 0.2.0

Platform-neutral runtime and UI primitives for LGUI
Documentation
use super::{
    AnimationRegistry, ComponentStateStore, ComponentTree, ContextRegistry, EffectRegistry,
    HookStateStore, HostTree, InteractionRole, LayoutSpec, RenderCx, RenderPhase, TextStyle,
    UiElement, UiId, UiInteractionState, UiNode, UiNodeKind, UiRect, UiRenderContext, UiScale,
    UiScope, UiTaskSpawner, UiUpdateQueue, VisualStyle,
};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;

pub trait RootComponent {
    fn render_root(self, cx: &mut RenderCx<'_, '_>) -> super::Element;
}

#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct HostProjectionMetrics {
    pub visited_nodes: usize,
    pub reused_component_roots: usize,
}

pub struct HostTreeBuilder {
    tree: HostTree,
    parent_stack: Vec<UiId>,
    retained: bool,
    fresh_owners: HashSet<super::ComponentId>,
    seen_by_owner: HashMap<super::ComponentId, HashSet<UiId>>,
    structure_changed: bool,
    projection_metrics: HostProjectionMetrics,
}

struct RenderTransaction<'a> {
    component_states: &'a ComponentStateStore,
    component_tree: &'a ComponentTree,
    contexts: &'a ContextRegistry,
    hook_states: &'a HookStateStore,
    effects: &'a EffectRegistry,
    committed: bool,
}

impl RenderTransaction<'_> {
    fn commit(&mut self) {
        self.committed = true;
    }
}

impl Drop for RenderTransaction<'_> {
    fn drop(&mut self) {
        if self.committed {
            return;
        }
        self.component_tree.abort_render();
        self.contexts.abort_render(self.component_tree);
        self.hook_states.abort_render(self.component_tree);
        self.component_states.abort_frame();
        self.effects.abort_frame();
    }
}

impl HostTreeBuilder {
    pub fn new() -> Self {
        Self {
            tree: HostTree::new(),
            parent_stack: Vec::new(),
            retained: false,
            fresh_owners: HashSet::new(),
            seen_by_owner: HashMap::new(),
            structure_changed: false,
            projection_metrics: HostProjectionMetrics::default(),
        }
    }

    pub fn from_retained(tree: HostTree) -> Self {
        Self {
            tree,
            parent_stack: Vec::new(),
            retained: true,
            fresh_owners: HashSet::new(),
            seen_by_owner: HashMap::new(),
            structure_changed: false,
            projection_metrics: HostProjectionMetrics::default(),
        }
    }

    pub fn push(&mut self, mut node: UiNode) -> UiId {
        if node.parent.is_none() {
            node.parent = self.parent_stack.last().cloned();
        }
        let id = node.id.clone();
        self.tree.push(node);
        id
    }

    pub(crate) fn mount_element(&mut self, element: UiElement) -> UiId {
        self.projection_metrics.visited_nodes += 1;
        let (mut node, children, boundary) = element.into_parts();
        if node.parent.is_none() {
            node.parent = self.parent_stack.last().cloned();
        }
        let id = node.id.clone();
        let previous_children = self
            .tree
            .node(&id)
            .map(|current| current.children.clone())
            .unwrap_or_default();
        let retain_children = boundary.is_some_and(|boundary| boundary.retain_children);
        if let Some(boundary) = boundary {
            if !boundary.retain_children {
                self.fresh_owners.insert(boundary.id);
            }
        }
        node.children = previous_children.clone();
        if retain_children {
            self.projection_metrics.reused_component_roots += 1;
        }
        if let Some(owner) = node.component_owner {
            self.seen_by_owner
                .entry(owner)
                .or_default()
                .insert(id.clone());
        }
        if self.retained {
            self.tree.upsert(node);
        } else {
            self.tree.push(node);
        }
        if !retain_children {
            self.parent_stack.push(id.clone());
            let next_children = children
                .iter()
                .cloned()
                .map(|child| self.mount_element(child))
                .collect::<Vec<_>>();
            self.parent_stack.pop();
            self.structure_changed |= previous_children != next_children;
            self.tree.set_children(&id, next_children);
        }
        id
    }

    pub fn node(&mut self, id: UiId, kind: UiNodeKind, rect: UiRect) -> UiId {
        self.push(UiNode::new(id, kind, rect))
    }

    pub fn interactive(
        &mut self,
        id: UiId,
        kind: UiNodeKind,
        rect: UiRect,
        interaction: InteractionRole,
    ) -> UiId {
        self.push(UiNode::new(id, kind, rect).interaction(interaction))
    }

    pub fn styled(&mut self, id: UiId, kind: UiNodeKind, rect: UiRect, style: VisualStyle) -> UiId {
        self.push(UiNode::new(id, kind, rect).style(style))
    }

    pub fn text(
        &mut self,
        id: UiId,
        rect: UiRect,
        text: impl Into<std::borrow::Cow<'static, str>>,
        style: TextStyle,
    ) -> UiId {
        self.push(UiNode::new(id, UiNodeKind::Text, rect).text(text, style))
    }

    pub fn laid_out(
        &mut self,
        id: UiId,
        kind: UiNodeKind,
        rect: UiRect,
        layout: LayoutSpec,
    ) -> UiId {
        self.push(UiNode::new(id, kind, rect).layout(layout))
    }

    pub fn in_phase(
        &mut self,
        id: UiId,
        kind: UiNodeKind,
        rect: UiRect,
        phase: RenderPhase,
    ) -> UiId {
        self.push(UiNode::new(id, kind, rect).render_phase(phase))
    }

    pub fn with_parent<T>(&mut self, id: UiId, build: impl FnOnce(&mut Self) -> T) -> T {
        self.parent_stack.push(id);
        let result = build(self);
        self.parent_stack.pop();
        result
    }

    pub fn element(&mut self, element: UiElement) -> UiId {
        element.mount(self)
    }

    pub fn mount(
        &mut self,
        component: impl RootComponent,
        viewport: UiRect,
        interaction: &UiInteractionState,
        animations: &AnimationRegistry,
        component_states: &ComponentStateStore,
        component_tree: &ComponentTree,
        contexts: &ContextRegistry,
        hook_states: &HookStateStore,
        hook_updates: &Arc<UiUpdateQueue>,
        task_spawner: Option<&UiTaskSpawner>,
        effects: &EffectRegistry,
        scale: UiScale,
    ) -> UiId {
        component_states.begin_frame();
        component_tree.begin_render();
        contexts.begin_render();
        hook_states.begin_render();
        effects.begin_frame();
        let mut transaction = RenderTransaction {
            component_states,
            component_tree,
            contexts,
            hook_states,
            effects,
            committed: false,
        };
        let scope = UiScope::new("ui");
        let context = UiRenderContext::new(
            interaction,
            animations,
            component_states,
            component_tree,
            contexts,
            hook_states,
            hook_updates,
            task_spawner,
            effects,
            viewport,
            scale,
        );
        let root = {
            let mut cx = RenderCx::new(&scope, &context);
            let _current_context = context.contexts().enter_current(cx.component_id());
            let view = component.render_root(&mut cx);
            let root = cx.component_id();
            self.element(
                cx.compile(view)
                    .claim_component_owner(root)
                    .component_boundary(root),
            )
        };
        contexts.validate_listener_hooks();
        component_states.end_frame();
        component_tree.end_render();
        self.fresh_owners
            .extend(component_tree.take_executed_in_render());
        for owner in self.fresh_owners.clone() {
            let keep = self.seen_by_owner.get(&owner).cloned().unwrap_or_default();
            self.structure_changed |= self.tree.retain_owner_nodes(owner, &keep);
        }
        self.structure_changed |= self.tree.prune_dead_component_owners(component_tree);
        if self.structure_changed {
            self.tree.reorder_by_hierarchy();
        }
        contexts.commit_listener_effects(component_tree, effects);
        contexts.end_render(component_tree);
        hook_states.end_render(component_tree);
        effects.end_frame(component_tree);
        transaction.commit();
        root
    }

    pub fn finish(self) -> HostTree {
        self.tree
    }

    pub fn projection_metrics(&self) -> HostProjectionMetrics {
        self.projection_metrics
    }
}

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

#[cfg(test)]
#[path = "builder_test.rs"]
mod tests;