Skip to main content

ferroui_controls/flyouts/
menu_flyout.rs

1use super::{
2    Flyout, FlyoutBaseImpl, MenuFlyoutPresenter, PopupFlyoutBase, PopupFlyoutBaseImpl, PopupFlyoutBaseImplExt,
3};
4use crate::item_collection::ItemCollection;
5use crate::items_source::{IItemsList, ItemsChangedHandler, ItemsSource};
6use crate::templates::IDataTemplate;
7use crate::{Control, ItemsControl, ItemsSourceView};
8use ferroui_base::controls::Classes;
9use ferroui_base::styling::ControlTheme;
10use ferroui_base::utilities::CancelEventArgs;
11use ferroui_base::{
12    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, BoxedValue, FerroObjectImpl,
13    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, Ref, StyledElement, StyledProperty,
14};
15use std::any::Any;
16use std::cell::OnceCell;
17use std::rc::Rc;
18
19/// A flyout that displays a menu.
20#[repr(C)]
21pub struct MenuFlyout {
22    base: PopupFlyoutBase,
23    items: ItemCollection,
24    /// The items as the items source of the presenter.
25    presenter_items_source: ItemsSource,
26    classes: OnceCell<Classes>,
27}
28
29ferro_class!(MenuFlyout: PopupFlyoutBase);
30ferro_class_info!(MenuFlyout {
31    new: MenuFlyout::new,
32    markup: {
33        content: Items,
34        properties: [
35            Items: ItemCollection { get: MenuFlyout::items },
36        ],
37    },
38});
39ferro_impl_classes!(MenuFlyout: FlyoutBaseImpl);
40
41impl FerroObjectImpl for MenuFlyout {
42    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
43        Self::parent_on_property_changed(this, change);
44
45        if change.property() == Self::items_source_property().as_property() {
46            this.items.set_items_source(change.get_new_value::<Option<ItemsSource>>());
47        }
48    }
49}
50
51impl PopupFlyoutBaseImpl for MenuFlyout {
52    fn create_presenter(this: &Self) -> Ref<Control> {
53        let presenter = MenuFlyoutPresenter::new();
54        presenter.set_items_source(Some(this.presenter_items_source.clone()));
55        presenter.bind_indexer(
56            &ItemsControl::item_template_property().bind(),
57            &this.indexer(&Self::item_template_property().bind()),
58        );
59        presenter.bind_indexer(
60            &ItemsControl::item_container_theme_property().bind(),
61            &this.indexer(&Self::item_container_theme_property().bind()),
62        );
63        presenter.upcast()
64    }
65
66    fn on_opening(this: &Self, args: &CancelEventArgs) {
67        if let Some(presenter) = this.popup().child() {
68            if let Some(classes) = this.classes.get() {
69                PopupFlyoutBase::set_presenter_classes(Some(&presenter), classes);
70            }
71
72            if let Some(theme) = this.flyout_presenter_theme() {
73                presenter.set_value(StyledElement::theme_property(), Some(theme));
74            }
75        }
76
77        Self::parent_on_opening(this, args);
78    }
79}
80
81/// The items of a menu flyout as an untyped, notifying list: what the items
82/// collection is to an items control that uses it as its items source.
83struct ItemCollectionList(Rc<ItemsSourceView>);
84
85impl IItemsList for ItemCollectionList {
86    fn count(&self) -> usize {
87        self.0.count()
88    }
89
90    fn get_at(&self, index: usize) -> Option<BoxedValue> {
91        self.0.get_at(index)
92    }
93
94    fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
95        self.0.index_of(item)
96    }
97
98    fn is_notifying(&self) -> bool {
99        true
100    }
101
102    fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
103        Some(self.0.add_collection_changed(handler))
104    }
105
106    fn remove_collection_changed(&self, token: u64) {
107        self.0.remove_collection_changed(token);
108    }
109
110    fn as_any(&self) -> &dyn Any {
111        self
112    }
113}
114
115ferro_properties! {
116    impl MenuFlyout {
117        /// Defines the `ItemsSource` property.
118        pub fn items_source_property() -> StyledProperty<Option<ItemsSource>> {
119            FerroProperty::register::<MenuFlyout, _>("ItemsSource", None)
120        }
121
122        /// Defines the `ItemTemplate` property.
123        pub fn item_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
124            FerroProperty::register::<MenuFlyout, _>("ItemTemplate", None)
125        }
126
127        /// Defines the `ItemContainerTheme` property.
128        pub fn item_container_theme_property() -> StyledProperty<Option<Ref<ControlTheme>>> {
129            ItemsControl::item_container_theme_property().add_owner::<MenuFlyout>()
130        }
131
132        /// Defines the `FlyoutPresenterTheme` property.
133        pub fn flyout_presenter_theme_property() -> StyledProperty<Option<Ref<ControlTheme>>> {
134            Flyout::flyout_presenter_theme_property().add_owner::<MenuFlyout>()
135        }
136    }
137}
138
139impl MenuFlyout {
140    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
141    pub fn construct() -> Self {
142        let items = ItemCollection::new();
143        let presenter_items_source = ItemsSource::new(Rc::new(ItemCollectionList(items.view().clone())));
144        Self { base: PopupFlyoutBase::construct(), items, presenter_items_source, classes: OnceCell::new() }
145    }
146
147    /// Creates a menu flyout.
148    pub fn new() -> Ref<Self> {
149        instantiate(Self::construct())
150    }
151
152    /// The classes collection to apply to the flyout presenter this flyout
153    /// is hosting.
154    pub fn flyout_presenter_classes(&self) -> Classes {
155        self.classes.get_or_init(Classes::new).clone()
156    }
157
158    /// The items of the menu flyout.
159    pub fn items(&self) -> ItemCollection {
160        self.items.clone()
161    }
162
163    /// Gets or sets the items of the menu flyout.
164    pub fn items_source(&self) -> Option<ItemsSource> {
165        self.get_value(Self::items_source_property())
166    }
167
168    pub fn set_items_source(&self, value: Option<ItemsSource>) {
169        self.set_value(Self::items_source_property(), value)
170    }
171
172    /// Gets or sets the template used for the items.
173    pub fn item_template(&self) -> Option<Rc<dyn IDataTemplate>> {
174        self.get_value(Self::item_template_property())
175    }
176
177    pub fn set_item_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
178        self.set_value(Self::item_template_property(), value)
179    }
180
181    /// Gets or sets the control theme that is applied to the container
182    /// element generated for each item.
183    pub fn item_container_theme(&self) -> Option<Ref<ControlTheme>> {
184        self.get_value(Self::item_container_theme_property())
185    }
186
187    pub fn set_item_container_theme(&self, value: impl Into<Nullable<ControlTheme>>) {
188        self.set_value(Self::item_container_theme_property(), value.into().0)
189    }
190
191    /// Gets or sets the control theme that is applied to the container
192    /// element generated for the flyout presenter.
193    pub fn flyout_presenter_theme(&self) -> Option<Ref<ControlTheme>> {
194        self.get_value(Self::flyout_presenter_theme_property())
195    }
196
197    pub fn set_flyout_presenter_theme(&self, value: impl Into<Nullable<ControlTheme>>) {
198        self.set_value(Self::flyout_presenter_theme_property(), value.into().0)
199    }
200}