Skip to main content

ferroui_diagnostics/diagnostics/views/
layout_explorer_view.rs

1//! Port of `Diagnostics/Views/LayoutExplorerView.xaml.cs`: the class of the document
2//! `LayoutExplorerView.xaml`.
3
4use crate::diagnostics::controls::ThicknessEditor;
5use ferroui_base::input::InputElementImpl;
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::LayoutableImpl;
8use ferroui_base::{
9    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectImpl, FerroPropertyChangedEventArgs,
10    Point, Rect, Ref, StyledElementImpl, Visual, VisualImpl,
11};
12use ferroui_controls::primitives::TemplatedControlImpl;
13use ferroui_controls::shapes::Rectangle;
14use ferroui_controls::{Border, Canvas, ContentControlImpl, ControlImpl, Grid, UserControl};
15
16/// The layout explorer: the margin, the border and the padding of a control as nested editors
17/// around its content, its size with guidelines from the content to the size labels, and its
18/// alignments.
19#[repr(C)]
20pub struct LayoutExplorerView {
21    base: UserControl,
22}
23
24ferro_class!(LayoutExplorerView: UserControl);
25ferro_impl_classes!(
26    LayoutExplorerView: FerroObjectImpl,
27    StyledElementImpl,
28    VisualImpl,
29    LayoutableImpl,
30    InteractiveImpl,
31    InputElementImpl,
32    ControlImpl,
33    TemplatedControlImpl,
34    ContentControlImpl
35);
36ferro_class_info!(LayoutExplorerView { new: LayoutExplorerView::new });
37
38/// The named elements of the document the guidelines are computed from.
39struct LayoutParts {
40    border_area: Ref<ThicknessEditor>,
41    padding_area: Ref<ThicknessEditor>,
42    horizontal_size_begin: Ref<Rectangle>,
43    horizontal_size_end: Ref<Rectangle>,
44    vertical_size_begin: Ref<Rectangle>,
45    vertical_size_end: Ref<Rectangle>,
46    layout_root: Ref<Grid>,
47    horizontal_size: Ref<Border>,
48    vertical_size: Ref<Border>,
49    content_area: Ref<Border>,
50}
51
52impl LayoutExplorerView {
53    /// The URI of the document of the class.
54    pub const DOCUMENT_URI: &'static str = "ferres://FerroUI.Diagnostics/diagnostics/views/LayoutExplorerView.xaml";
55
56    pub fn construct() -> Self {
57        Self { base: UserControl::construct() }
58    }
59
60    /// # Panics
61    /// When the document fails to load or lacks one of its named elements (an exception of the
62    /// constructor in the managed original).
63    pub fn new() -> Ref<Self> {
64        let this = instantiate(Self::construct());
65        Self::initialize_component(&this);
66
67        let content_area = this.get_control::<Border>("ContentArea");
68
69        let mut visual: Option<Ref<Visual>> = Some(content_area.upcast());
70        while let Some(current) = visual {
71            if current.ptr_eq(&this) {
72                break;
73            }
74            let weak = this.downgrade();
75            // The subscription lives as long as the element (never disposed, as in the managed
76            // original).
77            let _ = current.property_changed(move |e: &FerroPropertyChangedEventArgs<'_>| {
78                if e.property() == Visual::bounds_property().as_property() {
79                    if let Some(this) = weak.upgrade() {
80                        this.update_size_guidelines();
81                    }
82                }
83            });
84            visual = current.visual_parent();
85        }
86
87        this
88    }
89
90    fn initialize_component(this: &Ref<Self>) {
91        crate::register_types();
92        if let Err(error) = crate::compiled_diagnostics_views_layout_explorer_view::populate(None, this) {
93            panic!("{}: {error}", Self::DOCUMENT_URI);
94        }
95    }
96
97    fn parts(&self) -> LayoutParts {
98        LayoutParts {
99            border_area: self.get_control::<ThicknessEditor>("BorderArea"),
100            padding_area: self.get_control::<ThicknessEditor>("PaddingArea"),
101            horizontal_size_begin: self.get_control::<Rectangle>("HorizontalSizeBegin"),
102            horizontal_size_end: self.get_control::<Rectangle>("HorizontalSizeEnd"),
103            vertical_size_begin: self.get_control::<Rectangle>("VerticalSizeBegin"),
104            vertical_size_end: self.get_control::<Rectangle>("VerticalSizeEnd"),
105            horizontal_size: self.get_control::<Border>("HorizontalSize"),
106            vertical_size: self.get_control::<Border>("VerticalSize"),
107            content_area: self.get_control::<Border>("ContentArea"),
108            layout_root: self.get_control::<Grid>("LayoutRoot"),
109        }
110    }
111
112    fn update_size_guidelines(&self) {
113        let parts = self.parts();
114
115        if parts.border_area.is_present() {
116            Self::update_guidelines(&parts, &parts.border_area);
117        } else if parts.padding_area.is_present() {
118            Self::update_guidelines(&parts, &parts.padding_area);
119        } else {
120            Self::update_guidelines(&parts, &parts.content_area);
121        }
122    }
123
124    // That's what TransformedBounds.Bounds actually was. The original changed
125    // `v.TransformedBounds.Bounds` to `GetPseudoTransformedBounds`.
126    fn get_pseudo_transformed_bounds(v: &Visual) -> Rect {
127        Rect::from_size(v.bounds().size())
128    }
129
130    fn translate_to_root(parts: &LayoutParts, point: Point, from: &Visual) -> Point {
131        from.translate_point(point, &parts.layout_root).unwrap_or_default()
132    }
133
134    fn set_position(rect: &Rectangle, start: Point) {
135        Canvas::set_left(rect, start.x);
136        Canvas::set_top(rect, start.y);
137    }
138
139    fn update_guidelines(parts: &LayoutParts, area: &Visual) {
140        let bounds = Self::get_pseudo_transformed_bounds(area);
141
142        // Horizontal guideline
143        {
144            let size_area = Self::translate_to_root(
145                parts,
146                Self::get_pseudo_transformed_bounds(&parts.horizontal_size).bottom_left(),
147                &parts.horizontal_size,
148            );
149
150            let start = Self::translate_to_root(parts, bounds.bottom_left(), area);
151
152            Self::set_position(&parts.horizontal_size_begin, start);
153
154            let end = Self::translate_to_root(parts, bounds.bottom_right(), area);
155
156            Self::set_position(&parts.horizontal_size_end, end.with_x(end.x - 1.0));
157
158            let height = size_area.y - start.y + 2.0;
159
160            parts.horizontal_size_begin.set_height(height);
161            parts.horizontal_size_end.set_height(height);
162        }
163
164        // Vertical guideline
165        {
166            let size_area = Self::translate_to_root(
167                parts,
168                Self::get_pseudo_transformed_bounds(&parts.vertical_size).top_right(),
169                &parts.vertical_size,
170            );
171
172            let start = Self::translate_to_root(parts, bounds.top_right(), area);
173
174            Self::set_position(&parts.vertical_size_begin, start);
175
176            let end = Self::translate_to_root(parts, bounds.bottom_right(), area);
177
178            Self::set_position(&parts.vertical_size_end, end.with_y(end.y - 1.0));
179
180            let width = size_area.x - start.x + 2.0;
181
182            parts.vertical_size_begin.set_width(width);
183            parts.vertical_size_end.set_width(width);
184        }
185    }
186}