Skip to main content

open_gpui/elements/
measured.rs

1use crate::{
2    AnyElement, App, Bounds, Element, ElementId, GlobalElementId, InspectorElementId, IntoElement,
3    LayoutId, Pixels, Window,
4};
5use std::rc::Rc;
6
7/// Builds a wrapper that reports an element's layout-pass bounds during prepaint.
8///
9/// This is intentionally toolkit-neutral: callers provide the semantic identifier
10/// and decide how to store or interpret the reported bounds.
11pub fn measured_element(
12    id: impl Into<ElementId>,
13    child: impl IntoElement,
14    listener: impl Fn(&ElementId, Bounds<Pixels>, Option<&GlobalElementId>, &mut Window, &mut App)
15    + 'static,
16) -> MeasuredElement {
17    MeasuredElement {
18        id: id.into(),
19        child: Some(child.into_any_element()),
20        listener: Rc::new(listener),
21    }
22}
23
24/// An element wrapper that reports its own computed bounds without affecting child layout.
25pub struct MeasuredElement {
26    id: ElementId,
27    child: Option<AnyElement>,
28    listener: Rc<
29        dyn Fn(&ElementId, Bounds<Pixels>, Option<&GlobalElementId>, &mut Window, &mut App)
30            + 'static,
31    >,
32}
33
34impl Element for MeasuredElement {
35    type RequestLayoutState = AnyElement;
36    type PrepaintState = ();
37
38    fn id(&self) -> Option<ElementId> {
39        Some(self.id.clone())
40    }
41
42    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
43        None
44    }
45
46    fn request_layout(
47        &mut self,
48        _id: Option<&GlobalElementId>,
49        _inspector_id: Option<&InspectorElementId>,
50        window: &mut Window,
51        cx: &mut App,
52    ) -> (LayoutId, Self::RequestLayoutState) {
53        let mut child = self.child.take().expect("measured element child missing");
54        let layout_id = child.request_layout(window, cx);
55        (layout_id, child)
56    }
57
58    fn prepaint(
59        &mut self,
60        global_id: Option<&GlobalElementId>,
61        _inspector_id: Option<&InspectorElementId>,
62        bounds: Bounds<Pixels>,
63        child: &mut Self::RequestLayoutState,
64        window: &mut Window,
65        cx: &mut App,
66    ) {
67        (self.listener)(&self.id, bounds, global_id, window, cx);
68        child.prepaint(window, cx);
69    }
70
71    fn paint(
72        &mut self,
73        _global_id: Option<&GlobalElementId>,
74        _inspector_id: Option<&InspectorElementId>,
75        _bounds: Bounds<Pixels>,
76        child: &mut Self::RequestLayoutState,
77        _prepaint: &mut Self::PrepaintState,
78        window: &mut Window,
79        cx: &mut App,
80    ) {
81        child.paint(window, cx);
82    }
83}
84
85impl IntoElement for MeasuredElement {
86    type Element = Self;
87
88    fn into_element(self) -> Self::Element {
89        self
90    }
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use crate::{
97        Empty, ParentElement as _, Styled as _, TestAppContext, VisualTestContext, div, point, px,
98        size,
99    };
100    use std::{cell::RefCell, rc::Rc};
101
102    #[derive(Clone, Debug)]
103    struct MeasuredBounds {
104        id: ElementId,
105        bounds: Bounds<Pixels>,
106        global_id: Option<String>,
107    }
108
109    #[crate::test]
110    fn measured_element_reports_nested_layout_pass_bounds(cx: &mut TestAppContext) {
111        let reported = Rc::new(RefCell::new(Vec::new()));
112        let window = cx.add_window(|_, _| Empty);
113        let mut cx = VisualTestContext::from_window(window.into(), cx);
114
115        cx.draw(
116            point(px(10.0), px(20.0)),
117            size(px(100.0), px(80.0)),
118            |_, _| {
119                let report_root = reported.clone();
120                let report_child = reported.clone();
121
122                measured_element(
123                    "semantic-root".to_string(),
124                    div().size_full().child(measured_element(
125                        "semantic-child".to_string(),
126                        div().w(px(30.0)).h(px(20.0)),
127                        move |id, bounds, global_id, _, _| {
128                            report_child.borrow_mut().push(MeasuredBounds {
129                                id: id.clone(),
130                                bounds,
131                                global_id: global_id.map(ToString::to_string),
132                            });
133                        },
134                    )),
135                    move |id, bounds, global_id, _, _| {
136                        report_root.borrow_mut().push(MeasuredBounds {
137                            id: id.clone(),
138                            bounds,
139                            global_id: global_id.map(ToString::to_string),
140                        });
141                    },
142                )
143            },
144        );
145
146        let reported = reported.borrow();
147        let root = reported
148            .iter()
149            .find(|entry| entry.id == ElementId::from("semantic-root".to_string()))
150            .expect("root measured bounds");
151        let child = reported
152            .iter()
153            .find(|entry| entry.id == ElementId::from("semantic-child".to_string()))
154            .expect("child measured bounds");
155
156        assert_eq!(root.bounds.origin, point(px(10.0), px(20.0)));
157        assert_eq!(root.bounds.size, size(px(100.0), px(80.0)));
158        assert_eq!(child.bounds.origin, point(px(10.0), px(20.0)));
159        assert_eq!(child.bounds.size, size(px(30.0), px(20.0)));
160        assert!(
161            root.global_id
162                .as_deref()
163                .is_some_and(|id| id.contains("semantic-root"))
164        );
165        assert!(
166            child
167                .global_id
168                .as_deref()
169                .is_some_and(|id| id.contains("semantic-child"))
170        );
171    }
172}