Skip to main content

ferroui_controls/page/
selecting_multi_page.rs

1use super::{
2    MultiPage, MultiPageImpl, NavigatedFromEventArgs, NavigatedToEventArgs, NavigationType, Page, PageImpl,
3    PageSelectionChangedEventArgs,
4};
5use crate::primitives::TemplatedControlImpl;
6use crate::ControlImpl;
7use ferroui_base::input::InputElementImpl;
8use ferroui_base::interactivity::{
9    Interactive, InteractiveImpl, RoutedEvent, RoutedEventHandlerToken, RoutingStrategies,
10};
11use ferroui_base::layout::LayoutableImpl;
12use ferroui_base::{
13    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, DirectProperty,
14    FerroObjectImpl, FerroProperty, Ref, StyledElementImpl, VisualImpl,
15};
16use std::cell::{Cell, RefCell};
17
18/// Base class for multi-page controls with index-based selection.
19///
20/// This class is abstract: its abstract member panics unless overridden.
21#[repr(C)]
22pub struct SelectingMultiPage {
23    base: MultiPage,
24    selected_index: Cell<i32>,
25    selected_page: RefCell<Option<Ref<Page>>>,
26}
27
28ferro_class! {
29    SelectingMultiPage: MultiPage, virtuals SelectingMultiPageImpl: MultiPageImpl {
30        /// Applies the requested index change.
31        fn apply_selected_index(this, index: i32);
32    }
33}
34
35ferro_class_info!(SelectingMultiPage {});
36
37ferro_impl_classes!(
38    SelectingMultiPage: FerroObjectImpl,
39    StyledElementImpl,
40    VisualImpl,
41    LayoutableImpl,
42    InteractiveImpl,
43    InputElementImpl,
44    ControlImpl,
45    TemplatedControlImpl,
46    PageImpl,
47    MultiPageImpl
48);
49
50impl SelectingMultiPageImpl for SelectingMultiPage {
51    fn apply_selected_index(_this: &Self, _index: i32) {
52        panic!("SelectingMultiPage is abstract.")
53    }
54}
55
56ferro_properties! {
57    impl SelectingMultiPage {
58        /// Defines the `SelectedIndex` property.
59        pub fn selected_index_property() -> DirectProperty<SelectingMultiPage, i32> {
60            FerroProperty::register_direct::<SelectingMultiPage, _>(
61                "SelectedIndex",
62                |o| o.selected_index(),
63                Some(|o, v| o.set_selected_index(v)),
64                0,
65            )
66        }
67
68        /// Defines the `SelectedPage` property.
69        pub fn selected_page_property() -> DirectProperty<SelectingMultiPage, Option<Ref<Page>>> {
70            FerroProperty::register_direct::<SelectingMultiPage, _>("SelectedPage", |o| o.selected_page(), None, None)
71        }
72    }
73}
74
75impl SelectingMultiPage {
76    ferro_routed_event!(
77        /// Defines the `SelectionChanged` routed event.
78        pub fn selection_changed_event() -> RoutedEvent<PageSelectionChangedEventArgs> {
79            RoutedEvent::register::<SelectingMultiPage, _>("SelectionChanged", RoutingStrategies::BUBBLE)
80        }
81    );
82
83    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
84    pub fn construct() -> Self {
85        Self { base: MultiPage::construct(), selected_index: Cell::new(-1), selected_page: RefCell::new(None) }
86    }
87
88    /// Gets or sets the zero-based index of the selected page.
89    pub fn selected_index(&self) -> i32 {
90        self.selected_index.get()
91    }
92
93    pub fn set_selected_index(&self, value: i32) {
94        self.apply_selected_index(value)
95    }
96
97    /// Gets the currently selected page, or `None` if no page is selected.
98    pub fn selected_page(&self) -> Option<Ref<Page>> {
99        self.selected_page.borrow().clone()
100    }
101
102    /// Raised when the selected page changes.
103    pub fn selection_changed(
104        &self,
105        handler: impl Fn(&Interactive, &PageSelectionChangedEventArgs) + 'static,
106    ) -> RoutedEventHandlerToken {
107        self.add_handler(Self::selection_changed_event(), handler)
108    }
109
110    /// Commits a selection change and fires lifecycle events on the
111    /// outgoing and incoming pages.
112    ///
113    /// Raises `SelectionChanged`, then `NavigatedFrom` on the outgoing page
114    /// and `NavigatedTo` on the incoming page. If the incoming and outgoing
115    /// pages are the same object no events are fired. Subclasses should
116    /// call this method rather than modifying selection state directly.
117    pub fn commit_selection(&self, new_index: i32, new_page: Option<Ref<Page>>, navigation_type: NavigationType) {
118        let previous_page = self.selected_page();
119        self.set_and_raise_cell(Self::selected_index_property(), &self.selected_index, new_index);
120        self.set_and_raise(Self::selected_page_property(), &self.selected_page, new_page.clone());
121        self.set_current_value(Page::current_page_property(), new_page.clone());
122        if !same_page(&previous_page, &new_page) {
123            self.raise_event(&PageSelectionChangedEventArgs::new(
124                Some(Self::selection_changed_event()),
125                previous_page.clone(),
126                new_page.clone(),
127            ));
128
129            if let Some(previous_page) = &previous_page {
130                previous_page.send_navigated_from(&NavigatedFromEventArgs::new(new_page.clone(), navigation_type));
131            }
132
133            if let Some(new_page) = &new_page {
134                new_page.send_navigated_to(&NavigatedToEventArgs::new(previous_page, navigation_type));
135            }
136        }
137    }
138
139    /// Stores the selected index without routing through a child control.
140    pub fn store_selected_index(&self, index: i32) {
141        self.set_and_raise_cell(Self::selected_index_property(), &self.selected_index, index);
142    }
143
144    /// Returns the page at `index` from the `Pages` collection, or `None`
145    /// if the index is out of range.
146    pub fn resolve_page_at_index(&self, index: i32) -> Option<Ref<Page>> {
147        let pages = self.pages()?;
148        if index < 0 {
149            return None;
150        }
151        pages.try_get(index as usize)
152    }
153}
154
155/// Whether two page references are the same object (or both null).
156pub(crate) fn same_page(a: &Option<Ref<Page>>, b: &Option<Ref<Page>>) -> bool {
157    match (a, b) {
158        (None, None) => true,
159        (Some(a), Some(b)) => a.ptr_eq(b),
160        _ => false,
161    }
162}