Skip to main content

ferroui_diagnostics/diagnostics/controls/
control_highlight_adorner.rs

1//! Port of `Diagnostics/Controls/ControlHighlightAdorner.cs`.
2
3use ferroui_base::input::{InputElementImpl, InputElement};
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::{Layoutable, LayoutableImpl};
6use ferroui_base::media::{Color, Colors, DrawingContext, IBrush, IPen, Pen, SolidColorBrush};
7use ferroui_base::reactive::{Disposable, IDisposable};
8use ferroui_base::{
9    ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectImpl, Ref, StyledElementImpl,
10    Thickness, Visual, VisualImpl, VisualImplExt,
11};
12use ferroui_controls::primitives::{AdornerLayer, TemplatedControl};
13use ferroui_controls::{Border, Control, ControlImpl, Panel};
14use std::rc::Rc;
15
16ferroui_base::ferro_thread_local! {
17    // `_layoutHighlightAdorner`: the frames of the padding, the content and the margin of the
18    // highlighted control (one per thread; the objects of the port are not shared between
19    // threads).
20    static LAYOUT_HIGHLIGHT_ADORNER: Ref<Panel> = ControlHighlightAdorner::create_layout_highlight_adorner();
21}
22
23/// The adorner that highlights a control: a frame drawn with a pen around the control, or the
24/// frames of its padding, content and margin.
25#[repr(C)]
26pub struct ControlHighlightAdorner {
27    base: Control,
28    pen: Rc<dyn IPen>,
29}
30
31ferro_class!(ControlHighlightAdorner: Control);
32ferro_impl_classes!(
33    ControlHighlightAdorner: FerroObjectImpl,
34    StyledElementImpl,
35    LayoutableImpl,
36    InteractiveImpl,
37    InputElementImpl,
38    ControlImpl
39);
40ferro_class_info!(ControlHighlightAdorner {});
41
42impl VisualImpl for ControlHighlightAdorner {
43    fn render(this: &Self, context: &mut DrawingContext) {
44        Self::parent_render(this, context);
45        context.draw_rectangle_outline(&this.pen, this.bounds().deflate(2.0), 0.0);
46    }
47}
48
49impl ControlHighlightAdorner {
50    /// The static constructor of the original: creates the shared adorner of the layout.
51    fn create_layout_highlight_adorner() -> Ref<Panel> {
52        let panel = Panel::new();
53        panel.set_clip_to_bounds(false);
54
55        //Padding frame
56        let padding_frame = Border::new();
57        padding_frame.set_border_brush(Some(SolidColorBrush::with_color_and_opacity(Colors::GREEN, 0.5).into()));
58        panel.children().add(padding_frame);
59
60        //Content frame
61        let content_frame = Border::new();
62        content_frame.set_background(Some(
63            SolidColorBrush::with_color_and_opacity(Color::from_rgb(160, 197, 232), 0.5).into(),
64        ));
65        panel.children().add(content_frame);
66
67        //Margin frame
68        let margin_frame = Border::new();
69        margin_frame.set_border_brush(Some(SolidColorBrush::with_color_and_opacity(Colors::YELLOW, 0.5).into()));
70        panel.children().add(margin_frame);
71
72        AdornerLayer::set_is_clip_enabled(&panel, false);
73        panel
74    }
75
76    fn construct(pen: Rc<dyn IPen>) -> Self {
77        Self { base: Control::construct(), pen }
78    }
79
80    /// The private constructor of the original.
81    fn new(pen: Rc<dyn IPen>) -> Ref<Self> {
82        let this = instantiate(Self::construct(pen));
83        this.set_value(Visual::clip_property(), None);
84        this
85    }
86
87    /// Highlights `owner` with a frame drawn with `highlight_brush`, in the adorner layer of
88    /// `owner`. Disposing the result removes the frame; `None` when `owner` has no adorner layer.
89    pub fn add(owner: &InputElement, highlight_brush: Rc<dyn IBrush>) -> Option<Rc<dyn IDisposable>> {
90        let layer = AdornerLayer::get_adorner_layer(owner)?;
91
92        let pen: Rc<dyn IPen> = Rc::new(Pen::with_brush(Some(highlight_brush), 2.0).to_immutable());
93        let adorner = ControlHighlightAdorner::new(pen);
94        AdornerLayer::set_adorned_element(&adorner, owner.to_ref());
95        layer.children().add(adorner.clone());
96
97        Some(Disposable::create_with_state((layer, adorner), |(layer, adorner)| {
98            layer.children().remove(adorner);
99        }))
100    }
101
102    /// C# `Add(Visual visual, bool visualizeMarginPadding)`: shows the layout of `visual` (the
103    /// frames of its padding, content and margin when `visualize_margin_padding` is set) in its
104    /// adorner layer. Disposing the result removes the frames; `None` when `visual` has no
105    /// adorner layer or its layout is already shown.
106    pub fn add_visual(visual: &Visual, visualize_margin_padding: bool) -> Option<Rc<dyn IDisposable>> {
107        let layer = AdornerLayer::get_adorner_layer(visual)?;
108        let layout_highlight_adorner = LAYOUT_HIGHLIGHT_ADORNER.with(Ref::clone);
109
110        if layer.children().contains(&layout_highlight_adorner.clone().upcast()) {
111            return None;
112        }
113        layer.children().add(layout_highlight_adorner.clone());
114        AdornerLayer::set_adorned_element(&layout_highlight_adorner, visual.to_ref());
115        let children = layout_highlight_adorner.children();
116        let border = |index: usize| children.get(index).cast::<Border>().expect("the frames of the layout are borders");
117        let padding_border = border(0);
118        let content_border = border(1);
119        let margin_border = border(2);
120        if visualize_margin_padding {
121            padding_border.set_border_thickness(visual.get_value(TemplatedControl::padding_property()));
122            content_border.set_margin(visual.get_value(TemplatedControl::padding_property()));
123            margin_border.set_border_thickness(visual.get_value(Layoutable::margin_property()));
124            margin_border.set_margin(Self::invert_thickness(visual.get_value(Layoutable::margin_property())));
125        } else {
126            padding_border.set_border_thickness(Thickness::default());
127            content_border.set_margin(Thickness::default());
128            margin_border.set_border_thickness(Thickness::default());
129            margin_border.set_margin(Thickness::default());
130        }
131
132        Some(Disposable::create_with_state((layer, layout_highlight_adorner), |(layer, adorner)| {
133            layer.children().remove(adorner);
134        }))
135    }
136
137    fn invert_thickness(input: Thickness) -> Thickness {
138        Thickness::new(-input.left, -input.top, -input.right, -input.bottom)
139    }
140}