Skip to main content

cranpose_ui/widgets/
layout.rs

1//! Generic Layout widget and SubcomposeLayout
2
3#![allow(non_snake_case)]
4
5use super::nodes::LayoutNode;
6use super::scopes::{BoxWithConstraintsScope, BoxWithConstraintsScopeImpl};
7use crate::composable;
8use crate::modifier::Modifier;
9use crate::subcompose_layout::{
10    Constraints, MeasurePolicy as SubcomposeMeasurePolicy, MeasureResult, SubcomposeLayoutNode,
11    SubcomposeLayoutScope, SubcomposeMeasureScope, SubcomposeMeasureScopeImpl,
12};
13use cranpose_core::{NodeId, SlotId};
14use cranpose_ui_layout::{MeasurePolicy, Placement};
15use std::cell::RefCell;
16use std::rc::Rc;
17
18#[composable]
19pub fn Layout<F, P>(modifier: Modifier, measure_policy: P, content: F) -> NodeId
20where
21    F: FnMut() + 'static,
22    P: MeasurePolicy + Clone + PartialEq + 'static,
23{
24    let policy: Rc<dyn MeasurePolicy> = Rc::new(measure_policy);
25    let id = cranpose_core::with_current_composer(|composer| {
26        composer.emit_node(|| LayoutNode::new(modifier.clone(), Rc::clone(&policy)))
27    });
28    if let Err(err) = cranpose_core::with_node_mut(id, |node: &mut LayoutNode| {
29        node.set_modifier(modifier.clone());
30        node.set_measure_policy(Rc::clone(&policy));
31    }) {
32        debug_assert!(false, "failed to update Layout node: {err}");
33    }
34    cranpose_core::push_parent(id);
35    content();
36    cranpose_core::pop_parent();
37    id
38}
39
40#[composable]
41pub fn SubcomposeLayout(
42    modifier: Modifier,
43    measure_policy: impl for<'scope> Fn(&mut SubcomposeMeasureScopeImpl<'scope>, Constraints) -> MeasureResult
44        + 'static,
45) -> NodeId {
46    let policy: Rc<SubcomposeMeasurePolicy> = Rc::new(measure_policy);
47    let id = cranpose_core::with_current_composer(|composer| {
48        composer.emit_node(|| SubcomposeLayoutNode::new(modifier.clone(), Rc::clone(&policy)))
49    });
50    if let Err(err) = cranpose_core::with_node_mut(id, |node: &mut SubcomposeLayoutNode| {
51        node.set_modifier(modifier.clone());
52        node.set_measure_policy(Rc::clone(&policy));
53    }) {
54        debug_assert!(false, "failed to update SubcomposeLayout node: {err}");
55    }
56    id
57}
58
59#[composable(no_skip)]
60pub fn BoxWithConstraints<F>(modifier: Modifier, content: F) -> NodeId
61where
62    F: FnMut(BoxWithConstraintsScopeImpl) + 'static,
63{
64    let content_ref: Rc<RefCell<F>> = Rc::new(RefCell::new(content));
65    SubcomposeLayout(modifier, move |scope, constraints| {
66        let scope_impl = BoxWithConstraintsScopeImpl::new(constraints);
67        let scope_for_content = scope_impl;
68        let measurables = {
69            let content_ref = Rc::clone(&content_ref);
70            scope.subcompose(SlotId::new(0), move || {
71                let mut content = content_ref.borrow_mut();
72                content(scope_for_content);
73            })
74        };
75        let width_dp = if scope_impl.max_width().0.is_finite() {
76            scope_impl.max_width()
77        } else {
78            scope_impl.min_width()
79        };
80        let height_dp = if scope_impl.max_height().0.is_finite() {
81            scope_impl.max_height()
82        } else {
83            scope_impl.min_height()
84        };
85        let width = scope_impl.to_px(width_dp);
86        let height = scope_impl.to_px(height_dp);
87        let placements: Vec<Placement> = measurables
88            .into_iter()
89            .map(|measurable| Placement::new(measurable.node_id(), 0.0, 0.0, 0))
90            .collect();
91        scope.layout(width, height, placements)
92    })
93}