Skip to main content

ferroui_controls/flyouts/
flyout_base.rs

1use crate::Control;
2use ferroui_base::data::core::ValueTypes;
3use ferroui_base::reactive::{Disposable, IDisposable};
4use ferroui_base::utilities::HandlerList;
5use ferroui_base::{
6    ferro_class, ferro_property, AttachedProperty, DirectProperty, ElementRef, FerroObject, FerroObjectImpl,
7    FerroProperty, Nullable, Ref, StyledProperty,
8};
9use std::cell::RefCell;
10use std::rc::Rc;
11
12/// The base class of flyouts.
13///
14/// This class is abstract: only its subclasses can be created.
15#[repr(C)]
16pub struct FlyoutBase {
17    base: FerroObject,
18    /// The flyout does not own its target: the reference is weak.
19    target: RefCell<Option<ElementRef<Control>>>,
20    opened: HandlerList<dyn Fn()>,
21    closed: HandlerList<dyn Fn()>,
22}
23
24ferro_class! {
25    FlyoutBase: FerroObject, virtuals FlyoutBaseImpl: FerroObjectImpl {
26        /// Shows the flyout at the given control.
27        fn show_at(this, placement_target: &Control);
28        /// Hides the flyout.
29        fn hide(this);
30        /// Raises the `Opened` event.
31        fn on_opened(this);
32        /// Raises the `Closed` event.
33        fn on_closed(this);
34    }
35}
36
37ferroui_base::ferro_impl_classes!(FlyoutBase: FerroObjectImpl);
38
39impl FlyoutBaseImpl for FlyoutBase {
40    fn show_at(_this: &Self, _placement_target: &Control) {
41        panic!("FlyoutBase is abstract.")
42    }
43
44    fn hide(_this: &Self) {
45        panic!("FlyoutBase is abstract.")
46    }
47
48    fn on_opened(this: &Self) {
49        for (_, handler) in this.opened.snapshot().iter() {
50            handler();
51        }
52    }
53
54    fn on_closed(this: &Self) {
55        for (_, handler) in this.closed.snapshot().iter() {
56            handler();
57        }
58    }
59}
60
61ferroui_base::ferro_properties! { impl FlyoutBase {
62    ferro_property!(
63        /// Defines the `IsOpen` property.
64        pub fn is_open_property() -> StyledProperty<bool> {
65            FerroProperty::register::<FlyoutBase, _>("IsOpen", false)
66        }
67    );
68
69    ferro_property!(
70        /// Defines the `Target` property.
71        pub fn target_property() -> DirectProperty<FlyoutBase, Option<ElementRef<Control>>> {
72            ValueTypes::register_element_ref::<Control>();
73            FerroProperty::register_direct::<FlyoutBase, _>("Target", |x| x.target.borrow().clone(), None, None)
74        }
75    );
76
77    ferro_property!(
78        /// Defines the `AttachedFlyout` attached property.
79        pub fn attached_flyout_property() -> AttachedProperty<Option<Ref<FlyoutBase>>> {
80            FerroProperty::register_attached::<FlyoutBase, Control, _>("AttachedFlyout", None)
81        }
82    );
83} }
84
85impl FlyoutBase {
86    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
87    pub fn construct() -> Self {
88        Self {
89            base: FerroObject::construct(),
90            target: RefCell::new(None),
91            opened: HandlerList::new(),
92            closed: HandlerList::new(),
93        }
94    }
95
96    fn subscribe(&self, select: fn(&FlyoutBase) -> &HandlerList<dyn Fn()>, handler: Rc<dyn Fn()>) -> Rc<dyn IDisposable> {
97        let token = select(self).add(handler);
98        let weak = self.to_ref().downgrade();
99        Disposable::create(move || {
100            if let Some(this) = weak.upgrade() {
101                select(&this).remove(token);
102            }
103        })
104    }
105
106    /// Raised when the flyout is opened. Disposing the returned handle
107    /// unsubscribes.
108    pub fn opened(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
109        self.subscribe(|flyout| &flyout.opened, Rc::new(handler))
110    }
111
112    /// Raised when the flyout is closed. Disposing the returned handle
113    /// unsubscribes.
114    pub fn closed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
115        self.subscribe(|flyout| &flyout.closed, Rc::new(handler))
116    }
117
118    /// Whether this flyout is currently open.
119    ///
120    /// Setting this property to `true` will show the flyout at the last
121    /// known placement target. If no target has been set via `show_at`,
122    /// setting this to `true` will have no effect.
123    pub fn is_open(&self) -> bool {
124        self.get_value(Self::is_open_property())
125    }
126
127    pub fn set_is_open(&self, value: bool) {
128        self.set_value(Self::is_open_property(), value)
129    }
130
131    /// The target used for showing the flyout.
132    pub fn target(&self) -> Option<Ref<Control>> {
133        ElementRef::resolve(&self.target.borrow())
134    }
135
136    /// Sets the target used for showing the flyout. For derived classes.
137    pub fn set_target(&self, value: impl Into<Nullable<Control>>) {
138        self.set_and_raise(Self::target_property(), &self.target, ElementRef::from_nullable(value.into().0));
139    }
140
141    /// Gets the flyout attached to `element`.
142    pub fn get_attached_flyout(element: &Control) -> Option<Ref<FlyoutBase>> {
143        element.get_value(Self::attached_flyout_property())
144    }
145
146    /// Attaches a flyout to `element`.
147    pub fn set_attached_flyout(element: &Control, value: impl Into<Nullable<FlyoutBase>>) {
148        element.set_value(Self::attached_flyout_property(), value.into().0)
149    }
150
151    /// Shows the flyout attached to `flyout_owner`, if any, at it.
152    pub fn show_attached_flyout(flyout_owner: &Control) {
153        let flyout = Self::get_attached_flyout(flyout_owner);
154        if let Some(flyout) = flyout {
155            flyout.show_at(flyout_owner);
156        }
157    }
158}