cranpose-ui 0.9.21

Composable widgets, text, layouts and input for Cranpose apps
Documentation
use std::{cell::Cell, rc::Rc};

use cranpose_core::{Applier, NodeId};
use cranpose_ui::{
    Box, BoxSpec, Modifier, TestComposition, WindowRootDescriptor, WindowRootRoutingScratch,
    nearest_window_roots_into, run_test_composition,
};
use cranpose_ui_graphics::Size;

struct TestWindow;

impl WindowRootDescriptor for TestWindow {
    fn layout_size(&self) -> Size {
        Size::new(200.0, 120.0)
    }

    fn as_any(&self) -> &dyn std::any::Any {
        self
    }
}

fn capture_node(slot: &Cell<Option<NodeId>>, node: NodeId) {
    slot.set(Some(node));
}

fn node(slot: &Cell<Option<NodeId>>, label: &str) -> NodeId {
    slot.get()
        .unwrap_or_else(|| panic!("{label} was not composed"))
}

fn move_child(composition: &mut TestComposition, child: NodeId, parent: NodeId) {
    let mut applier = composition.applier_mut();
    let old_parent = applier
        .get_mut(child)
        .expect("child node exists")
        .parent()
        .expect("child has a parent");
    assert!(
        applier
            .get_mut(old_parent)
            .expect("old parent exists")
            .remove_child(child),
        "old parent contains the child"
    );
    applier
        .get_mut(child)
        .expect("child node exists")
        .on_removed_from_parent();
    assert!(
        applier
            .get_mut(parent)
            .expect("new parent exists")
            .insert_child(child),
        "new parent did not already contain the child"
    );
    applier
        .get_mut(child)
        .expect("child node exists")
        .on_attached_to_parent(parent);
}

#[test]
fn one_routing_scratch_tracks_reparenting_across_empty_batches() {
    let root_slot = Rc::new(Cell::new(None));
    let first_window_slot = Rc::new(Cell::new(None));
    let second_window_slot = Rc::new(Cell::new(None));
    let child_slot = Rc::new(Cell::new(None));
    let root_capture = Rc::clone(&root_slot);
    let first_capture = Rc::clone(&first_window_slot);
    let second_capture = Rc::clone(&second_window_slot);
    let child_capture = Rc::clone(&child_slot);

    let mut composition = run_test_composition(move || {
        let first_capture = Rc::clone(&first_capture);
        let second_capture = Rc::clone(&second_capture);
        let child_capture = Rc::clone(&child_capture);
        let root = Box(Modifier::empty(), BoxSpec::default(), move || {
            let first_capture = Rc::clone(&first_capture);
            let child_capture = Rc::clone(&child_capture);
            let first = Box(
                Modifier::empty().window_root(Rc::new(TestWindow)),
                BoxSpec::default(),
                move || {
                    let child_capture = Rc::clone(&child_capture);
                    let child = Box(Modifier::empty(), BoxSpec::default(), || {});
                    capture_node(&child_capture, child);
                },
            );
            capture_node(&first_capture, first);

            let second_capture = Rc::clone(&second_capture);
            let second = Box(
                Modifier::empty().window_root(Rc::new(TestWindow)),
                BoxSpec::default(),
                || {},
            );
            capture_node(&second_capture, second);
        });
        capture_node(&root_capture, root);
    });

    let root = node(&root_slot, "primary root");
    let first_window = node(&first_window_slot, "first window root");
    let second_window = node(&second_window_slot, "second window root");
    let child = node(&child_slot, "window child");
    let mut scratch = WindowRootRoutingScratch::default();
    let mut owners = Vec::new();

    {
        let mut applier = composition.applier_mut();
        nearest_window_roots_into(
            &mut applier,
            [child, child, first_window, root],
            &mut owners,
            &mut scratch,
        );
        assert_eq!(
            owners,
            [
                Some(first_window),
                Some(first_window),
                Some(first_window),
                None
            ],
            "the first window owns its descendants and itself"
        );

        nearest_window_roots_into(&mut applier, [], &mut owners, &mut scratch);
        assert!(owners.is_empty(), "an empty batch clears prior results");
    }

    move_child(&mut composition, child, second_window);
    {
        let mut applier = composition.applier_mut();
        nearest_window_roots_into(
            &mut applier,
            [child, first_window, second_window, root],
            &mut owners,
            &mut scratch,
        );
        assert_eq!(
            owners,
            [
                Some(second_window),
                Some(first_window),
                Some(second_window),
                None
            ],
            "the same node id follows its new window owner"
        );
    }

    move_child(&mut composition, child, root);
    let mut applier = composition.applier_mut();
    nearest_window_roots_into(&mut applier, [child], &mut owners, &mut scratch);
    assert_eq!(
        owners,
        [None],
        "reparenting into the primary tree clears the previous window owner"
    );
}