Skip to main content

cranpose_ui/widgets/
layout.rs

1//! Generic Layout widget and SubcomposeLayout
2
3use std::{cell::RefCell, rc::Rc};
4
5use cranpose_core::{NodeId, SlotId};
6use cranpose_ui_graphics::Size;
7use cranpose_ui_layout::{MeasurePolicy, Placement};
8
9use super::{nodes::LayoutNode, scopes::BoxWithConstraintsScopeImpl};
10use crate::{
11    composable,
12    modifier::Modifier,
13    subcompose_layout::{
14        Constraints, MeasurePolicy as SubcomposeMeasurePolicy, MeasureResult, SubcomposeLayoutNode,
15        SubcomposeLayoutScope, SubcomposeMeasureScope, SubcomposeMeasureScopeImpl,
16    },
17};
18
19struct RetainedMeasurePolicy<P> {
20    value: P,
21    policy: Rc<dyn MeasurePolicy>,
22}
23
24#[composable]
25pub fn Layout<F, P>(modifier: Modifier, measure_policy: P, mut content: F) -> NodeId
26where
27    F: FnMut() + 'static,
28    P: MeasurePolicy + Clone + PartialEq + 'static,
29{
30    let policy_holder = cranpose_core::remember({
31        let measure_policy = measure_policy.clone();
32        move || {
33            Rc::new(RefCell::new(RetainedMeasurePolicy {
34                value: measure_policy.clone(),
35                policy: Rc::new(measure_policy),
36            }))
37        }
38    })
39    .with(|holder| holder.clone());
40    let policy = {
41        let mut holder = policy_holder.borrow_mut();
42        if holder.value != measure_policy {
43            holder.value = measure_policy.clone();
44            holder.policy = Rc::new(measure_policy);
45        }
46        Rc::clone(&holder.policy)
47    };
48    let modifier_for_reset = modifier.clone();
49    let policy_for_reset = Rc::clone(&policy);
50    let id = cranpose_core::with_current_composer(|composer| {
51        composer.emit_recyclable_node(
52            || LayoutNode::new(modifier.clone(), Rc::clone(&policy)),
53            move |node| {
54                *node = LayoutNode::new(modifier_for_reset.clone(), Rc::clone(&policy_for_reset));
55            },
56        )
57    });
58    let composed_density = crate::density::density();
59    if let Err(err) = cranpose_core::with_node_mut(id, |node: &mut LayoutNode| {
60        node.set_modifier(modifier.clone());
61        node.set_measure_policy(Rc::clone(&policy));
62        node.set_density(composed_density);
63    }) {
64        debug_assert!(false, "failed to update Layout node: {err}");
65    }
66    cranpose_core::push_parent(id);
67    compose_under_modifier_locals(&modifier, &mut content);
68    cranpose_core::pop_parent();
69    id
70}
71
72fn compose_under_modifier_locals(modifier: &Modifier, content: &mut dyn FnMut()) {
73    let provided = modifier.provided_composition_locals();
74    if provided.is_empty() {
75        content();
76        return;
77    }
78    cranpose_core::CompositionLocalProvider(provided, content);
79}
80
81#[composable]
82pub fn SubcomposeLayout(
83    modifier: Modifier,
84    measure_policy: impl for<'scope> Fn(
85        &mut SubcomposeMeasureScopeImpl<'scope>,
86        Constraints,
87    ) -> MeasureResult
88    + 'static,
89) -> NodeId {
90    cranpose_core::debug_label_current_scope("SubcomposeLayout");
91    let policy_cell =
92        cranpose_core::remember(|| Rc::new(RefCell::new(None::<Rc<SubcomposeMeasurePolicy>>)))
93            .with(|cell| cell.clone());
94    let current_policy: Rc<SubcomposeMeasurePolicy> = Rc::new(measure_policy);
95    let policy_captures_changed = {
96        let mut policy_cell_ref = policy_cell.borrow_mut();
97        let changed = policy_cell_ref
98            .as_ref()
99            .is_none_or(|previous| !Rc::ptr_eq(previous, &current_policy));
100        *policy_cell_ref = Some(current_policy);
101        changed
102    };
103    let policy: Rc<SubcomposeMeasurePolicy> = cranpose_core::remember(move || {
104        let policy_cell = policy_cell.clone();
105        let policy: Rc<SubcomposeMeasurePolicy> =
106            Rc::new(
107                move |scope, constraints| match policy_cell.borrow().as_ref().cloned() {
108                    Some(current) => current(scope, constraints),
109                    None => empty_subcompose_measure_result(constraints),
110                },
111            );
112        policy
113    })
114    .with(|policy| policy.clone());
115    let id = cranpose_core::with_current_composer(|composer| {
116        composer.emit_node(|| SubcomposeLayoutNode::new(modifier.clone(), Rc::clone(&policy)))
117    });
118    let captured_context =
119        cranpose_core::with_current_composer(|composer| composer.capture_composition_context());
120    let composed_density = crate::density::density();
121    if let Err(err) = cranpose_core::with_node_mut(id, |node: &mut SubcomposeLayoutNode| {
122        node.set_modifier(modifier.clone());
123        node.set_measure_policy(Rc::clone(&policy));
124        node.set_captured_context(captured_context.clone());
125        node.set_density(composed_density);
126        if policy_captures_changed {
127            node.invalidate_subcomposition();
128        }
129    }) {
130        debug_assert!(false, "failed to update SubcomposeLayout node: {err}");
131    }
132    id
133}
134
135fn empty_subcompose_measure_result(constraints: Constraints) -> MeasureResult {
136    let (width, height) = constraints.constrain(0.0, 0.0);
137    MeasureResult::new(Size { width, height }, Vec::new())
138}
139
140#[composable(no_skip)]
141pub fn BoxWithConstraints<F>(modifier: Modifier, content: F) -> NodeId
142where
143    F: FnMut(BoxWithConstraintsScopeImpl) + 'static,
144{
145    let content_ref: Rc<RefCell<F>> = Rc::new(RefCell::new(content));
146    SubcomposeLayout(modifier, move |scope, constraints| {
147        let scope_impl = BoxWithConstraintsScopeImpl::new(constraints);
148        let scope_for_content = scope_impl;
149        let measurables = {
150            let content_ref = Rc::clone(&content_ref);
151            scope.subcompose(SlotId::new(0), constraints, move || {
152                cranpose_core::debug_label_current_scope("BoxWithConstraints.slot(0)");
153                let mut content = content_ref.borrow_mut();
154                content(scope_for_content);
155            })
156        };
157        let child_constraints = Constraints {
158            min_width: 0.0,
159            max_width: constraints.max_width,
160            min_height: 0.0,
161            max_height: constraints.max_height,
162        };
163
164        let mut width = 0.0_f32;
165        let mut height = 0.0_f32;
166        let mut placements = Vec::with_capacity(measurables.len());
167
168        for measurable in measurables {
169            let placeable = scope.measure(measurable, child_constraints);
170            width = width.max(placeable.width());
171            height = height.max(placeable.height());
172            placeable.place(0.0, 0.0);
173            placements.push(Placement::new(placeable.node_id(), 0.0, 0.0, 0));
174        }
175
176        width = width.clamp(constraints.min_width, constraints.max_width);
177        height = height.clamp(constraints.min_height, constraints.max_height);
178        scope.layout(width, height, placements)
179    })
180}
181
182#[cfg(test)]
183#[path = "tests/layout_tests.rs"]
184mod tests;