Skip to main content

ferroui_controls/primitives/
light_dismiss_overlay_layer.rs

1use super::VisualLayerManager;
2use crate::{Border, ControlImpl, TopLevel};
3use ferroui_base::input::{InputElement, InputElementImpl};
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::LayoutableImpl;
6use ferroui_base::media::{Brushes, IBrush};
7use ferroui_base::reactive::IDisposable;
8use ferroui_base::rendering::ICustomHitTest;
9use ferroui_base::{
10    ferro_class, ferro_impl_classes, instantiate, FerroObjectImpl, Point, Ref, StyledElementImpl,
11    Visual, VisualImpl, WeakRef,
12};
13use std::cell::RefCell;
14use std::rc::Rc;
15
16/// A layer that is used to dismiss a popup when the user clicks outside
17/// (internal upstream).
18#[repr(C)]
19pub struct LightDismissOverlayLayer {
20    base: Border,
21    registrations: RefCell<Vec<Rc<Registration>>>,
22}
23
24ferro_class!(LightDismissOverlayLayer: Border);
25ferroui_base::ferro_class_info!(LightDismissOverlayLayer { new: LightDismissOverlayLayer::new });
26ferro_impl_classes!(
27    LightDismissOverlayLayer: StyledElementImpl,
28    LayoutableImpl,
29    InteractiveImpl,
30    InputElementImpl,
31    ControlImpl
32);
33
34ferroui_base::ferro_impl_classes!(LightDismissOverlayLayer: FerroObjectImpl);
35
36impl VisualImpl for LightDismissOverlayLayer {
37    fn custom_hit_test(this: &Self, point: Point) -> Option<bool> {
38        Some(ICustomHitTest::hit_test(this, point))
39    }
40
41    fn custom_hit_test_geometry(
42        this: &Self,
43        geometry: &Ref<ferroui_base::media::Geometry>,
44    ) -> Option<ferroui_base::media::IntersectionResult> {
45        Some(ICustomHitTest::hit_test_geometry(this, geometry))
46    }
47}
48
49impl ICustomHitTest for LightDismissOverlayLayer {
50    fn hit_test(&self, point: Point) -> bool {
51        LightDismissOverlayLayer::hit_test(self, point)
52    }
53}
54
55impl LightDismissOverlayLayer {
56    fn static_constructor() {
57        let transparent: Rc<dyn IBrush> = Brushes::transparent();
58        Border::background_property().override_default_value::<LightDismissOverlayLayer>(Some(transparent));
59        Visual::is_visible_property().override_default_value::<LightDismissOverlayLayer>(false);
60    }
61
62    pub fn new() -> Ref<Self> {
63        instantiate(Self { base: Border::construct(), registrations: RefCell::new(Vec::new()) })
64    }
65
66    /// The element of the most recent registration that still receives
67    /// input while the layer is shown.
68    pub fn input_pass_through_element(&self) -> Option<Ref<InputElement>> {
69        let last = self.registrations.borrow().last().cloned();
70        last.and_then(|registration| registration.input_pass_through_element())
71    }
72
73    /// Returns the light dismiss overlay for a specified visual, or `None`
74    /// if none found.
75    pub fn get_light_dismiss_overlay_layer(visual: &Visual) -> Option<Ref<LightDismissOverlayLayer>> {
76        let manager = match visual.to_ref().cast::<TopLevel>() {
77            Some(top_level) => {
78                top_level.get_template_descendants().into_iter().find_map(|v| v.cast::<VisualLayerManager>())
79            }
80            None => visual.find_ancestor_of_type::<VisualLayerManager>(false),
81        };
82
83        manager.map(|manager| manager.light_dismiss_overlay_layer())
84    }
85
86    /// Shows the layer until the returned registration is disposed.
87    /// `input_pass_through_element` keeps receiving input while this is
88    /// the most recent registration.
89    pub fn register(&self, input_pass_through_element: Option<&Ref<InputElement>>) -> Rc<dyn IDisposable> {
90        let registration = Rc::new(Registration {
91            owner: RefCell::new(Some(self.to_ref().downgrade())),
92            input_pass_through_element: input_pass_through_element.map(Ref::downgrade),
93        });
94        self.registrations.borrow_mut().push(registration.clone());
95        self.update_state();
96        registration
97    }
98
99    /// Whether the layer is hit at the specified point, in coordinates
100    /// relative to the layer.
101    pub fn hit_test(&self, point: Point) -> bool {
102        if let Some(v) = self.input_pass_through_element() {
103            if let Some(ie) = self.visual_root().and_then(|root| root.cast::<InputElement>()) {
104                let this: Ref<Visual> = self.to_ref().upcast();
105                if let Some(hit) = ie.input_hit_test_filtered(point, &|x: &Visual| !x.to_ref().ptr_eq(&this), true) {
106                    return !v.is_visual_ancestor_of(&hit);
107                }
108            }
109        }
110
111        true
112    }
113
114    fn unregister(&self, registration: &Registration) {
115        self.registrations.borrow_mut().retain(|r| !std::ptr::eq(Rc::as_ptr(r), registration));
116        self.update_state();
117    }
118
119    fn update_state(&self) {
120        let is_visible = !self.registrations.borrow().is_empty();
121        self.set_is_visible(is_visible);
122    }
123}
124
125struct Registration {
126    owner: RefCell<Option<WeakRef<LightDismissOverlayLayer>>>,
127    input_pass_through_element: Option<WeakRef<InputElement>>,
128}
129
130impl Registration {
131    fn input_pass_through_element(&self) -> Option<Ref<InputElement>> {
132        self.input_pass_through_element.as_ref().and_then(WeakRef::upgrade)
133    }
134}
135
136impl IDisposable for Registration {
137    fn dispose(&self) {
138        let owner = self.owner.borrow_mut().take();
139        if let Some(owner) = owner.and_then(|owner| owner.upgrade()) {
140            owner.unregister(self);
141        }
142    }
143}