Skip to main content

ferroui_controls/page/
drawer_page.rs

1use super::safe_area_padding_extensions::SafeAreaPaddingExtensions;
2use super::{
3    DefaultPageDataTemplate, DrawerBehavior, DrawerClosingEventArgs, DrawerLayoutBehavior, DrawerPlacement,
4    NavigatedFromEventArgs, NavigatedToEventArgs, NavigationPage, NavigationType, Page, PageImpl,
5};
6use crate::automation::{AccessibilityView, AutomationProperties};
7use crate::documents::TextElement;
8use crate::presenters::ContentPresenter;
9use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt, ToggleButton};
10use crate::templates::IDataTemplate;
11use crate::{
12    Border, ContentControl, Control, ControlImpl, ControlImplExt, SplitView, SplitViewDisplayMode,
13    SplitViewPanePlacement,
14};
15use ferroui_base::input::gesture_recognizers::SwipeGestureRecognizer;
16use ferroui_base::input::{
17    InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyboardNavigationHandler,
18    NavigationDirection, PointerPressedEventArgs, SwipeDirection, SwipeGestureEventArgs,
19};
20use ferroui_base::interactivity::{
21    Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
22};
23use ferroui_base::layout::{HorizontalAlignment, LayoutableImpl, VerticalAlignment};
24use ferroui_base::media::{FlowDirection, IBrush};
25use ferroui_base::reactive::{IDisposable, ObservableExt};
26use ferroui_base::{
27    ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, instantiate, BoxedValue,
28    FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty,
29    FerroPropertyChangedEventArgs, Point, Ref, StaticType, StyledElement, StyledElementImpl, StyledProperty,
30    StyledPropertyOptions, Thickness, TypeInfo, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
31};
32use std::cell::{Cell, RefCell};
33use std::rc::Rc;
34
35const EDGE_GESTURE_WIDTH: f64 = 20.0;
36
37const PC_PLACEMENT_RIGHT: &str = ":placement-right";
38const PC_PLACEMENT_TOP: &str = ":placement-top";
39const PC_PLACEMENT_BOTTOM: &str = ":placement-bottom";
40const PC_DETAIL_IS_NAV_PAGE: &str = ":detail-is-navpage";
41
42/// A page that provides a drawer pattern.
43#[repr(C)]
44pub struct DrawerPage {
45    base: Page,
46    content_presenter: RefCell<Option<Ref<ContentPresenter>>>,
47    drawer_presenter: RefCell<Option<Ref<ContentPresenter>>>,
48    drawer_header_presenter: RefCell<Option<Ref<ContentPresenter>>>,
49    drawer_footer_presenter: RefCell<Option<Ref<ContentPresenter>>>,
50    split_view: RefCell<Option<Ref<SplitView>>>,
51    top_bar: RefCell<Option<Ref<Border>>>,
52    pane_button: RefCell<Option<Ref<ToggleButton>>>,
53    backdrop: RefCell<Option<Ref<Border>>>,
54    /// The pointer pressed handler of the backdrop, while it is attached.
55    backdrop_pointer_pressed: Cell<Option<RoutedEventHandlerToken>>,
56    swipe_start_point: Cell<Point>,
57    nav_bar_visible_sub: RefCell<Option<Rc<dyn IDisposable>>>,
58    suppress_drawer_events: Cell<bool>,
59    has_had_first_page: Cell<bool>,
60    hide_top_bar: Cell<bool>,
61    swipe_recognizer: Ref<SwipeGestureRecognizer>,
62    /// The swipe gesture handler, while attached to the visual tree.
63    swipe_gesture_handler: Cell<Option<RoutedEventHandlerToken>>,
64    /// The pointer pressed handler that records where a swipe starts, while
65    /// attached to the visual tree.
66    swipe_pointer_pressed_handler: Cell<Option<RoutedEventHandlerToken>>,
67}
68
69ferro_class!(DrawerPage: Page);
70
71ferro_class_info!(DrawerPage {
72    new: DrawerPage::new,
73    markup: {
74        // XAML-SEAM: the content properties depend on their templates (markup assignment order).
75        content: Content,
76        property_attributes: [
77            Drawer: [DependsOn("DrawerTemplate")],
78            Content: [DependsOn("ContentTemplate")],
79            DrawerHeader: [DependsOn("DrawerHeaderTemplate")],
80            DrawerFooter: [DependsOn("DrawerFooterTemplate")],
81        ],
82        attributes: [
83            TemplatePart("PART_DrawerPresenter", type(Ref<ContentPresenter>)),
84            TemplatePart("PART_DrawerHeader", type(Ref<ContentPresenter>)),
85            TemplatePart("PART_DrawerFooter", type(Ref<ContentPresenter>)),
86            TemplatePart("PART_ContentPresenter", type(Ref<ContentPresenter>)),
87            TemplatePart("PART_SplitView", type(Ref<SplitView>)),
88            TemplatePart("PART_TopBar", type(Ref<Border>)),
89            TemplatePart("PART_PaneButton", type(Ref<ToggleButton>)),
90            TemplatePart("PART_CompactPaneToggle", type(Ref<ToggleButton>)),
91            TemplatePart("PART_Backdrop", type(Ref<Border>)),
92            PseudoClasses(":placement-right", ":placement-top", ":placement-bottom", ":detail-is-navpage"),
93        ],
94    },
95});
96
97ferro_impl_classes!(DrawerPage: LayoutableImpl, InteractiveImpl);
98
99impl FerroObjectImpl for DrawerPage {
100    fn constructed(this: &Self) {
101        Self::parent_constructed(this);
102
103        this.gesture_recognizers().add(this.swipe_recognizer.clone());
104        this.update_swipe_recognizer_axes();
105    }
106
107    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
108        Self::parent_on_property_changed(this, change);
109
110        let property = change.property();
111        let is_content = property == Self::content_property().as_property();
112
113        if property == Self::drawer_property().as_property() || is_content {
114            let (old_value, new_value) = change.get_old_and_new_value::<Option<BoxedValue>>();
115
116            if let Some(old_logical) = old_value.as_ref().and_then(Control::logical_from_boxed) {
117                StyledElement::logical_children(this).remove(&old_logical);
118            }
119            if let Some(new_logical) = new_value.as_ref().and_then(Control::logical_from_boxed) {
120                StyledElement::logical_children(this).add(new_logical);
121            }
122            this.update_active_page();
123
124            if is_content {
125                this.has_had_first_page.set(false);
126                this.dispose_nav_bar_visible_sub();
127
128                if let Some(old_nav) = navigation_page_of(&old_value) {
129                    old_nav.set_drawer_page(None);
130                }
131                if let Some(new_nav) = navigation_page_of(&new_value) {
132                    new_nav.set_drawer_page(Some(this));
133                    let subscription = this.observe_nav_bar_visible(&new_nav);
134                    *this.nav_bar_visible_sub.borrow_mut() = Some(subscription);
135                }
136                this.update_detail_nav_bar_visible_pseudo_class();
137            }
138        } else if property == Self::is_open_property().as_property()
139            || property == Self::display_mode_property().as_property()
140        {
141            this.sync_current_page();
142            this.update_backdrop_state();
143
144            if property == Self::is_open_property().as_property() {
145                this.update_drawer_focus();
146
147                if !this.suppress_drawer_events.get() {
148                    if change.get_new_value::<bool>() {
149                        this.raise_event(&RoutedEventArgs::with_event(Self::opened_event()));
150                    } else {
151                        this.raise_event(&RoutedEventArgs::with_event(Self::closed_event()));
152                    }
153                }
154            }
155        } else if property == Self::drawer_background_property().as_property() {
156            this.apply_drawer_background();
157        } else if property == Self::drawer_behavior_property().as_property()
158            || property == Self::drawer_layout_behavior_property().as_property()
159            || property == Self::drawer_breakpoint_length_property().as_property()
160        {
161            this.update_split_view_display_mode();
162
163            if property == Self::drawer_behavior_property().as_property() {
164                if let Some(nav) = navigation_page_of(&this.content()) {
165                    nav.set_drawer_page(Some(this));
166                }
167            }
168        } else if property == Visual::bounds_property().as_property() && this.drawer_breakpoint_length() > 0.0 {
169            this.update_split_view_display_mode();
170        } else if property == Self::is_gesture_enabled_property().as_property() {
171            this.swipe_recognizer.set_is_enabled(change.get_new_value::<bool>());
172        } else if property == Self::drawer_placement_property().as_property() {
173            this.update_swipe_recognizer_axes();
174            this.update_pane_placement();
175            this.update_content_safe_area_padding();
176        } else if property == Self::backdrop_brush_property().as_property() {
177            this.update_backdrop_state();
178        } else if property == Visual::flow_direction_property().as_property() {
179            this.update_content_safe_area_padding();
180        } else if property == Self::drawer_header_foreground_property().as_property() {
181            let presenter = this.drawer_header_presenter.borrow().clone();
182            Self::apply_foreground(presenter.as_ref(), change.get_new_value::<Option<Rc<dyn IBrush>>>());
183        } else if property == Self::drawer_footer_foreground_property().as_property() {
184            let presenter = this.drawer_footer_presenter.borrow().clone();
185            Self::apply_foreground(presenter.as_ref(), change.get_new_value::<Option<Rc<dyn IBrush>>>());
186        }
187    }
188}
189
190impl StyledElementImpl for DrawerPage {
191    fn style_key_override(_this: &Self) -> &'static TypeInfo {
192        <DrawerPage as StaticType>::TYPE
193    }
194}
195
196impl VisualImpl for DrawerPage {
197    fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
198        Self::parent_on_attached_to_visual_tree(this, e);
199
200        let weak = this.to_ref().downgrade();
201        let token = this.add_handler(InputElement::swipe_gesture_event(), move |_, e: &SwipeGestureEventArgs| {
202            if let Some(this) = weak.upgrade() {
203                this.on_swipe_gesture(e);
204            }
205        });
206        if let Some(previous) = this.swipe_gesture_handler.replace(Some(token)) {
207            this.remove_handler(InputElement::swipe_gesture_event(), previous);
208        }
209
210        let weak = this.to_ref().downgrade();
211        let token = this.add_handler_with(
212            InputElement::pointer_pressed_event(),
213            move |_, e: &PointerPressedEventArgs| {
214                if let Some(this) = weak.upgrade() {
215                    this.on_swipe_pointer_pressed(e);
216                }
217            },
218            RoutingStrategies::DIRECT | RoutingStrategies::BUBBLE,
219            true,
220        );
221        if let Some(previous) = this.swipe_pointer_pressed_handler.replace(Some(token)) {
222            this.remove_handler(InputElement::pointer_pressed_event(), previous);
223        }
224
225        this.attach_backdrop_pointer_pressed();
226        this.restore_navigation_state();
227    }
228
229    fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
230        Self::parent_on_detached_from_visual_tree(this, e);
231
232        if let Some(token) = this.swipe_gesture_handler.take() {
233            this.remove_handler(InputElement::swipe_gesture_event(), token);
234        }
235        if let Some(token) = this.swipe_pointer_pressed_handler.take() {
236            this.remove_handler(InputElement::pointer_pressed_event(), token);
237        }
238
239        this.detach_backdrop_pointer_pressed();
240
241        this.clear_navigation_state();
242    }
243}
244
245impl InputElementImpl for DrawerPage {
246    fn on_key_down(this: &Self, e: &KeyEventArgs) {
247        Self::parent_on_key_down(this, e);
248
249        if e.key == Key::Escape
250            && this.is_open()
251            && this.drawer_behavior() != DrawerBehavior::Locked
252            && this.is_overlay_display_mode()
253        {
254            this.set_current_value(Self::is_open_property(), false);
255            e.set_handled(true);
256        }
257    }
258}
259
260impl ControlImpl for DrawerPage {
261    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
262        crate::automation::peers::DrawerPageAutomationPeer::new(this).upcast()
263    }
264
265    fn on_loaded(this: &Self, e: &RoutedEventArgs) {
266        Self::parent_on_loaded(this, e);
267
268        if !this.has_had_first_page.get() {
269            if let Some(current_page) = this.current_page() {
270                this.has_had_first_page.set(true);
271                current_page.send_navigated_to(&NavigatedToEventArgs::new(None, NavigationType::Push));
272            }
273        }
274    }
275}
276
277impl TemplatedControlImpl for DrawerPage {
278    fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
279        Self::parent_on_apply_template(this, e);
280
281        this.detach_backdrop_pointer_pressed();
282
283        let name_scope = e.name_scope();
284        *this.content_presenter.borrow_mut() = name_scope.find_as::<ContentPresenter>("PART_ContentPresenter");
285        *this.drawer_presenter.borrow_mut() = name_scope.find_as::<ContentPresenter>("PART_DrawerPresenter");
286        *this.drawer_header_presenter.borrow_mut() = name_scope.find_as::<ContentPresenter>("PART_DrawerHeader");
287        *this.drawer_footer_presenter.borrow_mut() = name_scope.find_as::<ContentPresenter>("PART_DrawerFooter");
288        *this.split_view.borrow_mut() = name_scope.find_as::<SplitView>("PART_SplitView");
289        *this.top_bar.borrow_mut() = name_scope.find_as::<Border>("PART_TopBar");
290        *this.pane_button.borrow_mut() = name_scope.find_as::<ToggleButton>("PART_PaneButton");
291        *this.backdrop.borrow_mut() = name_scope.find_as::<Border>("PART_Backdrop");
292
293        let backdrop = this.backdrop.borrow().clone();
294        if let Some(backdrop) = backdrop {
295            if this.is_attached_to_visual_tree() {
296                this.attach_backdrop_pointer_pressed();
297            }
298
299            AutomationProperties::set_accessibility_view(&backdrop, AccessibilityView::Raw);
300        }
301
302        let drawer_header_presenter = this.drawer_header_presenter.borrow().clone();
303        Self::apply_foreground(drawer_header_presenter.as_ref(), this.drawer_header_foreground());
304        let drawer_footer_presenter = this.drawer_footer_presenter.borrow().clone();
305        Self::apply_foreground(drawer_footer_presenter.as_ref(), this.drawer_footer_foreground());
306
307        this.apply_drawer_background();
308        this.update_split_view_display_mode();
309        this.update_pane_placement();
310        this.update_backdrop_state();
311        this.update_content_safe_area_padding();
312    }
313}
314
315impl PageImpl for DrawerPage {
316    fn update_content_safe_area_padding(this: &Self) {
317        let content_presenter = this.content_presenter.borrow().clone();
318        let drawer_presenter = this.drawer_presenter.borrow().clone();
319        let (Some(content_presenter), Some(drawer_presenter)) = (content_presenter, drawer_presenter) else {
320            return;
321        };
322
323        let sa = this.safe_area_padding();
324        let has_drawer_header = this.drawer_header().is_some();
325        let has_drawer_footer = this.drawer_footer().is_some();
326        let drawer_header_presenter = this.drawer_header_presenter.borrow().clone().filter(|_| has_drawer_header);
327        let drawer_footer_presenter = this.drawer_footer_presenter.borrow().clone().filter(|_| has_drawer_footer);
328        let placement = this.drawer_placement();
329
330        if this.is_vertical_placement() {
331            drawer_presenter.set_padding(if placement == DrawerPlacement::Top {
332                Thickness::new(sa.left, if has_drawer_header { 0.0 } else { sa.top }, sa.right, 0.0)
333            } else {
334                Thickness::new(sa.left, 0.0, sa.right, if has_drawer_footer { 0.0 } else { sa.bottom })
335            });
336
337            if let Some(drawer_header_presenter) = drawer_header_presenter {
338                drawer_header_presenter.set_padding(if placement == DrawerPlacement::Bottom {
339                    Thickness::new(sa.left, 0.0, sa.right, 0.0)
340                } else {
341                    Thickness::new(sa.left, sa.top, sa.right, 0.0)
342                });
343            }
344
345            if let Some(drawer_footer_presenter) = drawer_footer_presenter {
346                drawer_footer_presenter.set_padding(if placement == DrawerPlacement::Bottom {
347                    Thickness::new(sa.left, 0.0, sa.right, sa.bottom)
348                } else {
349                    Thickness::new(sa.left, 0.0, sa.right, 0.0)
350                });
351            }
352        } else {
353            let is_pane_on_right = this.is_pane_on_right();
354            let top = if has_drawer_header { 0.0 } else { sa.top };
355            let bottom = if has_drawer_footer { 0.0 } else { sa.bottom };
356
357            drawer_presenter.set_padding(if is_pane_on_right {
358                Thickness::new(0.0, top, sa.right, bottom)
359            } else {
360                Thickness::new(sa.left, top, 0.0, bottom)
361            });
362
363            if let Some(drawer_header_presenter) = drawer_header_presenter {
364                drawer_header_presenter.set_padding(if is_pane_on_right {
365                    Thickness::new(0.0, sa.top, sa.right, 0.0)
366                } else {
367                    Thickness::new(sa.left, sa.top, 0.0, 0.0)
368                });
369            }
370
371            if let Some(drawer_footer_presenter) = drawer_footer_presenter {
372                drawer_footer_presenter.set_padding(if is_pane_on_right {
373                    Thickness::new(0.0, 0.0, sa.right, sa.bottom)
374                } else {
375                    Thickness::new(sa.left, 0.0, 0.0, sa.bottom)
376                });
377            }
378        }
379
380        let top_bar = this.top_bar.borrow().clone();
381        if let Some(top_bar) = top_bar {
382            top_bar.set_margin(Thickness::new(sa.left, sa.top, sa.right, 0.0));
383        }
384
385        content_presenter.set_padding(this.padding());
386
387        if let Some(detail) = page_of(&this.content()) {
388            let remaining_safe_area = this.padding().get_remaining_safe_area_padding(this.safe_area_padding());
389            detail.set_safe_area_padding(Thickness::new(
390                remaining_safe_area.left,
391                if this.hide_top_bar.get() { remaining_safe_area.top } else { 0.0 },
392                remaining_safe_area.right,
393                remaining_safe_area.bottom,
394            ));
395        }
396    }
397
398    fn update_active_page(this: &Self) {
399        let previous_page = this.current_page();
400
401        this.set_current_value(Page::current_page_property(), this.resolve_current_page());
402
403        if !same_page(&previous_page, &this.current_page()) && this.visual_root().is_some() {
404            this.has_had_first_page.set(true);
405            if let Some(previous_page) = &previous_page {
406                previous_page
407                    .send_navigated_from(&NavigatedFromEventArgs::new(this.current_page(), NavigationType::Replace));
408            }
409            if let Some(current_page) = this.current_page() {
410                current_page.send_navigated_to(&NavigatedToEventArgs::new(previous_page, NavigationType::Replace));
411            }
412        }
413
414        this.update_content_safe_area_padding();
415    }
416}
417
418ferro_properties! {
419    impl DrawerPage {
420        /// Defines the `Drawer` property.
421        pub fn drawer_property() -> StyledProperty<Option<BoxedValue>> {
422            FerroProperty::register::<DrawerPage, _>("Drawer", None)
423        }
424
425        /// Defines the `Content` property.
426        pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
427            FerroProperty::register::<DrawerPage, _>("Content", None)
428        }
429
430        /// Defines the `IsOpen` property.
431        pub fn is_open_property() -> StyledProperty<bool> {
432            FerroProperty::register_with::<DrawerPage, _>(
433                "IsOpen",
434                StyledPropertyOptions::new(false).coerce(DrawerPage::coerce_is_open),
435            )
436        }
437
438        /// Defines the `DrawerLength` property.
439        pub fn drawer_length_property() -> StyledProperty<f64> {
440            FerroProperty::register_with::<DrawerPage, _>(
441                "DrawerLength",
442                StyledPropertyOptions::new(320.0).validate(DrawerPage::validate_length),
443            )
444        }
445
446        /// Defines the `CompactDrawerLength` property.
447        pub fn compact_drawer_length_property() -> StyledProperty<f64> {
448            FerroProperty::register_with::<DrawerPage, _>(
449                "CompactDrawerLength",
450                StyledPropertyOptions::new(48.0).validate(DrawerPage::validate_length),
451            )
452        }
453
454        /// Defines the `DrawerBreakpointLength` property.
455        pub fn drawer_breakpoint_length_property() -> StyledProperty<f64> {
456            FerroProperty::register::<DrawerPage, _>("DrawerBreakpointLength", 0.0)
457        }
458
459        /// Defines the `IsGestureEnabled` property.
460        pub fn is_gesture_enabled_property() -> StyledProperty<bool> {
461            FerroProperty::register::<DrawerPage, _>("IsGestureEnabled", true)
462        }
463
464        /// Defines the `DrawerBehavior` property.
465        pub fn drawer_behavior_property() -> StyledProperty<DrawerBehavior> {
466            FerroProperty::register::<DrawerPage, _>("DrawerBehavior", DrawerBehavior::Auto)
467        }
468
469        /// Defines the `DrawerLayoutBehavior` property.
470        pub fn drawer_layout_behavior_property() -> StyledProperty<DrawerLayoutBehavior> {
471            FerroProperty::register::<DrawerPage, _>("DrawerLayoutBehavior", DrawerLayoutBehavior::Overlay)
472        }
473
474        /// Defines the `DrawerPlacement` property.
475        pub fn drawer_placement_property() -> StyledProperty<DrawerPlacement> {
476            FerroProperty::register::<DrawerPage, _>("DrawerPlacement", DrawerPlacement::Left)
477        }
478
479        /// Defines the `DrawerHeader` property.
480        pub fn drawer_header_property() -> StyledProperty<Option<BoxedValue>> {
481            FerroProperty::register::<DrawerPage, _>("DrawerHeader", None)
482        }
483
484        /// Defines the `DrawerFooter` property.
485        pub fn drawer_footer_property() -> StyledProperty<Option<BoxedValue>> {
486            FerroProperty::register::<DrawerPage, _>("DrawerFooter", None)
487        }
488
489        /// Defines the `DrawerHeaderTemplate` property.
490        pub fn drawer_header_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
491            FerroProperty::register::<DrawerPage, _>("DrawerHeaderTemplate", None)
492        }
493
494        /// Defines the `DrawerFooterTemplate` property.
495        pub fn drawer_footer_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
496            FerroProperty::register::<DrawerPage, _>("DrawerFooterTemplate", None)
497        }
498
499        /// Defines the `DrawerIcon` property.
500        pub fn drawer_icon_property() -> StyledProperty<Option<BoxedValue>> {
501            FerroProperty::register::<DrawerPage, _>("DrawerIcon", None)
502        }
503
504        /// Defines the `DrawerIconTemplate` property.
505        pub fn drawer_icon_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
506            FerroProperty::register::<DrawerPage, _>("DrawerIconTemplate", None)
507        }
508
509        /// Defines the `DrawerTemplate` property.
510        pub fn drawer_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
511            FerroProperty::register::<DrawerPage, _>("DrawerTemplate", None)
512        }
513
514        /// Defines the `ContentTemplate` property.
515        pub fn content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
516            let default_template: Rc<dyn IDataTemplate> = DefaultPageDataTemplate::new();
517            FerroProperty::register::<DrawerPage, _>("ContentTemplate", Some(default_template))
518        }
519
520        /// Defines the `DrawerBackground` property.
521        pub fn drawer_background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
522            FerroProperty::register::<DrawerPage, _>("DrawerBackground", None)
523        }
524
525        /// Defines the `DrawerHeaderBackground` property.
526        pub fn drawer_header_background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
527            FerroProperty::register::<DrawerPage, _>("DrawerHeaderBackground", None)
528        }
529
530        /// Defines the `DrawerHeaderForeground` property.
531        pub fn drawer_header_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
532            FerroProperty::register::<DrawerPage, _>("DrawerHeaderForeground", None)
533        }
534
535        /// Defines the `DrawerFooterBackground` property.
536        pub fn drawer_footer_background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
537            FerroProperty::register::<DrawerPage, _>("DrawerFooterBackground", None)
538        }
539
540        /// Defines the `DrawerFooterForeground` property.
541        pub fn drawer_footer_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
542            FerroProperty::register::<DrawerPage, _>("DrawerFooterForeground", None)
543        }
544
545        /// Defines the `BackdropBrush` property.
546        pub fn backdrop_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
547            FerroProperty::register::<DrawerPage, _>("BackdropBrush", None)
548        }
549
550        /// Defines the `HorizontalContentAlignment` property.
551        pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
552            ContentControl::horizontal_content_alignment_property().add_owner::<DrawerPage>()
553        }
554
555        /// Defines the `VerticalContentAlignment` property.
556        pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
557            ContentControl::vertical_content_alignment_property().add_owner::<DrawerPage>()
558        }
559
560        /// Defines the `DisplayMode` property.
561        pub fn display_mode_property() -> StyledProperty<SplitViewDisplayMode> {
562            SplitView::display_mode_property().add_owner::<DrawerPage>()
563        }
564    }
565}
566
567impl DrawerPage {
568    ferro_routed_event!(
569        /// Defines the `Opened` routed event.
570        pub fn opened_event() -> RoutedEvent<RoutedEventArgs> {
571            RoutedEvent::register::<DrawerPage, _>("Opened", RoutingStrategies::BUBBLE)
572        }
573    );
574
575    ferro_routed_event!(
576        /// Defines the `Closing` routed event.
577        pub fn closing_event() -> RoutedEvent<DrawerClosingEventArgs> {
578            RoutedEvent::register::<DrawerPage, _>("Closing", RoutingStrategies::BUBBLE)
579        }
580    );
581
582    ferro_routed_event!(
583        /// Defines the `Closed` routed event.
584        pub fn closed_event() -> RoutedEvent<RoutedEventArgs> {
585            RoutedEvent::register::<DrawerPage, _>("Closed", RoutingStrategies::BUBBLE)
586        }
587    );
588
589    fn static_constructor() {
590        Page::page_navigation_system_back_button_pressed_event().add_class_handler::<DrawerPage>(
591            |sender, event_args| {
592                if event_args.handled() {
593                    return;
594                }
595
596                if sender.is_open()
597                    && sender.drawer_behavior() != DrawerBehavior::Locked
598                    && sender.drawer_behavior() != DrawerBehavior::Disabled
599                {
600                    sender.set_is_open(false);
601                    event_args.set_handled(true);
602                } else {
603                    let page_event =
604                        RoutedEventArgs::with_event(Page::page_navigation_system_back_button_pressed_event());
605                    if let Some(current_page) = sender.current_page() {
606                        current_page.raise_event(&page_event);
607                    }
608
609                    if page_event.handled() {
610                        event_args.set_handled(true);
611                    }
612                }
613            },
614        );
615    }
616
617    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
618    pub fn construct() -> Self {
619        Self {
620            base: Page::construct(),
621            content_presenter: RefCell::new(None),
622            drawer_presenter: RefCell::new(None),
623            drawer_header_presenter: RefCell::new(None),
624            drawer_footer_presenter: RefCell::new(None),
625            split_view: RefCell::new(None),
626            top_bar: RefCell::new(None),
627            pane_button: RefCell::new(None),
628            backdrop: RefCell::new(None),
629            backdrop_pointer_pressed: Cell::new(None),
630            swipe_start_point: Cell::new(Point::default()),
631            nav_bar_visible_sub: RefCell::new(None),
632            suppress_drawer_events: Cell::new(false),
633            has_had_first_page: Cell::new(false),
634            hide_top_bar: Cell::new(false),
635            swipe_recognizer: SwipeGestureRecognizer::new(),
636            swipe_gesture_handler: Cell::new(None),
637            swipe_pointer_pressed_handler: Cell::new(None),
638        }
639    }
640
641    /// Initializes a new instance of the [`DrawerPage`] class.
642    pub fn new() -> Ref<Self> {
643        instantiate(Self::construct())
644    }
645
646    fn is_rtl(&self) -> bool {
647        self.flow_direction() == FlowDirection::RightToLeft
648    }
649
650    fn is_vertical_placement(&self) -> bool {
651        let placement = self.drawer_placement();
652        placement == DrawerPlacement::Top || placement == DrawerPlacement::Bottom
653    }
654
655    fn is_pane_on_right(&self) -> bool {
656        (self.drawer_placement() == DrawerPlacement::Right) != self.is_rtl()
657    }
658
659    fn is_overlay_display_mode(&self) -> bool {
660        let display_mode = self.display_mode();
661        display_mode == SplitViewDisplayMode::Overlay || display_mode == SplitViewDisplayMode::CompactOverlay
662    }
663
664    /// Occurs when `IsOpen` changes to true.
665    pub fn opened(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
666        self.add_handler(Self::opened_event(), handler)
667    }
668
669    /// Occurs when the drawer is about to close.
670    pub fn closing(
671        &self,
672        handler: impl Fn(&Interactive, &DrawerClosingEventArgs) + 'static,
673    ) -> RoutedEventHandlerToken {
674        self.add_handler(Self::closing_event(), handler)
675    }
676
677    /// Occurs when `IsOpen` changes to false and closing is not cancelled.
678    pub fn closed(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
679        self.add_handler(Self::closed_event(), handler)
680    }
681
682    fn validate_length(value: &f64) -> bool {
683        !value.is_nan() && !value.is_infinite() && *value >= 0.0
684    }
685
686    fn coerce_is_open(instance: &FerroObject, value: bool) -> bool {
687        let Some(drawer) = instance.downcast_ref::<DrawerPage>() else {
688            return value;
689        };
690        if drawer.suppress_drawer_events.get() {
691            return value;
692        }
693
694        if value && drawer.drawer_behavior() == DrawerBehavior::Disabled {
695            return false;
696        }
697
698        if !value && drawer.is_open() && drawer.drawer_behavior() != DrawerBehavior::Disabled {
699            let args = DrawerClosingEventArgs::with_event(Self::closing_event());
700            drawer.raise_event(&args);
701            if args.cancel() {
702                return true;
703            }
704        }
705
706        value
707    }
708
709    /// Gets or sets the drawer pane content.
710    pub fn drawer(&self) -> Option<BoxedValue> {
711        self.get_value(Self::drawer_property())
712    }
713
714    pub fn set_drawer(&self, value: Option<BoxedValue>) {
715        self.set_value(Self::drawer_property(), value)
716    }
717
718    /// Gets or sets the main content.
719    pub fn content(&self) -> Option<BoxedValue> {
720        self.get_value(Self::content_property())
721    }
722
723    pub fn set_content(&self, value: Option<BoxedValue>) {
724        self.set_value(Self::content_property(), value)
725    }
726
727    /// Gets or sets whether the drawer pane is currently open.
728    ///
729    /// Setting this property to true while the drawer behavior is
730    /// [`DrawerBehavior::Disabled`] is a no-op; the value is coerced back
731    /// to false. When closing programmatically, the `Closing` event is
732    /// raised and can be cancelled; if cancelled, the property reverts to
733    /// true. The `Closing` event is not raised when the drawer is forced
734    /// closed because the drawer behavior is set to
735    /// [`DrawerBehavior::Disabled`].
736    pub fn is_open(&self) -> bool {
737        self.get_value(Self::is_open_property())
738    }
739
740    pub fn set_is_open(&self, value: bool) {
741        self.set_value(Self::is_open_property(), value)
742    }
743
744    /// Gets or sets the width of the drawer pane.
745    pub fn drawer_length(&self) -> f64 {
746        self.get_value(Self::drawer_length_property())
747    }
748
749    pub fn set_drawer_length(&self, value: f64) {
750        self.set_value(Self::drawer_length_property(), value)
751    }
752
753    /// Gets or sets the compact drawer width.
754    pub fn compact_drawer_length(&self) -> f64 {
755        self.get_value(Self::compact_drawer_length_property())
756    }
757
758    pub fn set_compact_drawer_length(&self, value: f64) {
759        self.set_value(Self::compact_drawer_length_property(), value)
760    }
761
762    /// Gets or sets the size threshold for switching to overlay mode. Set
763    /// to 0 to disable.
764    pub fn drawer_breakpoint_length(&self) -> f64 {
765        self.get_value(Self::drawer_breakpoint_length_property())
766    }
767
768    pub fn set_drawer_breakpoint_length(&self, value: f64) {
769        self.set_value(Self::drawer_breakpoint_length_property(), value)
770    }
771
772    /// Gets or sets whether swipe gestures can open or close the drawer.
773    pub fn is_gesture_enabled(&self) -> bool {
774        self.get_value(Self::is_gesture_enabled_property())
775    }
776
777    pub fn set_is_gesture_enabled(&self, value: bool) {
778        self.set_value(Self::is_gesture_enabled_property(), value)
779    }
780
781    /// Gets or sets the open/close behavior of the drawer pane.
782    pub fn drawer_behavior(&self) -> DrawerBehavior {
783        self.get_value(Self::drawer_behavior_property())
784    }
785
786    pub fn set_drawer_behavior(&self, value: DrawerBehavior) {
787        self.set_value(Self::drawer_behavior_property(), value)
788    }
789
790    /// Gets or sets the layout behavior of the drawer.
791    pub fn drawer_layout_behavior(&self) -> DrawerLayoutBehavior {
792        self.get_value(Self::drawer_layout_behavior_property())
793    }
794
795    pub fn set_drawer_layout_behavior(&self, value: DrawerLayoutBehavior) {
796        self.set_value(Self::drawer_layout_behavior_property(), value)
797    }
798
799    /// Gets or sets which edge of the control the drawer appears from.
800    pub fn drawer_placement(&self) -> DrawerPlacement {
801        self.get_value(Self::drawer_placement_property())
802    }
803
804    pub fn set_drawer_placement(&self, value: DrawerPlacement) {
805        self.set_value(Self::drawer_placement_property(), value)
806    }
807
808    /// Gets or sets the header content displayed at the top of the drawer
809    /// pane.
810    pub fn drawer_header(&self) -> Option<BoxedValue> {
811        self.get_value(Self::drawer_header_property())
812    }
813
814    pub fn set_drawer_header(&self, value: Option<BoxedValue>) {
815        self.set_value(Self::drawer_header_property(), value)
816    }
817
818    /// Gets or sets the footer content displayed at the bottom of the
819    /// drawer pane.
820    pub fn drawer_footer(&self) -> Option<BoxedValue> {
821        self.get_value(Self::drawer_footer_property())
822    }
823
824    pub fn set_drawer_footer(&self, value: Option<BoxedValue>) {
825        self.set_value(Self::drawer_footer_property(), value)
826    }
827
828    /// Gets or sets the data template used to display the drawer header.
829    pub fn drawer_header_template(&self) -> Option<Rc<dyn IDataTemplate>> {
830        self.get_value(Self::drawer_header_template_property())
831    }
832
833    pub fn set_drawer_header_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
834        self.set_value(Self::drawer_header_template_property(), value)
835    }
836
837    /// Gets or sets the data template used to display the drawer footer.
838    pub fn drawer_footer_template(&self) -> Option<Rc<dyn IDataTemplate>> {
839        self.get_value(Self::drawer_footer_template_property())
840    }
841
842    pub fn set_drawer_footer_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
843        self.set_value(Self::drawer_footer_template_property(), value)
844    }
845
846    /// Gets or sets the icon displayed in the drawer toggle button.
847    pub fn drawer_icon(&self) -> Option<BoxedValue> {
848        self.get_value(Self::drawer_icon_property())
849    }
850
851    pub fn set_drawer_icon(&self, value: Option<BoxedValue>) {
852        self.set_value(Self::drawer_icon_property(), value)
853    }
854
855    /// Gets or sets the data template used to display the drawer icon.
856    pub fn drawer_icon_template(&self) -> Option<Rc<dyn IDataTemplate>> {
857        self.get_value(Self::drawer_icon_template_property())
858    }
859
860    pub fn set_drawer_icon_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
861        self.set_value(Self::drawer_icon_template_property(), value)
862    }
863
864    /// Gets or sets the data template used to display the drawer content.
865    pub fn drawer_template(&self) -> Option<Rc<dyn IDataTemplate>> {
866        self.get_value(Self::drawer_template_property())
867    }
868
869    pub fn set_drawer_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
870        self.set_value(Self::drawer_template_property(), value)
871    }
872
873    /// Gets or sets the data template used to display the content.
874    pub fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
875        self.get_value(Self::content_template_property())
876    }
877
878    pub fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
879        self.set_value(Self::content_template_property(), value)
880    }
881
882    /// Gets or sets the background brush of the drawer pane.
883    pub fn drawer_background(&self) -> Option<Rc<dyn IBrush>> {
884        self.get_value(Self::drawer_background_property())
885    }
886
887    pub fn set_drawer_background(&self, value: Option<Rc<dyn IBrush>>) {
888        self.set_value(Self::drawer_background_property(), value)
889    }
890
891    /// Gets or sets the background brush of the drawer header area.
892    pub fn drawer_header_background(&self) -> Option<Rc<dyn IBrush>> {
893        self.get_value(Self::drawer_header_background_property())
894    }
895
896    pub fn set_drawer_header_background(&self, value: Option<Rc<dyn IBrush>>) {
897        self.set_value(Self::drawer_header_background_property(), value)
898    }
899
900    /// Gets or sets the foreground brush of the drawer header area.
901    pub fn drawer_header_foreground(&self) -> Option<Rc<dyn IBrush>> {
902        self.get_value(Self::drawer_header_foreground_property())
903    }
904
905    pub fn set_drawer_header_foreground(&self, value: Option<Rc<dyn IBrush>>) {
906        self.set_value(Self::drawer_header_foreground_property(), value)
907    }
908
909    /// Gets or sets the background brush of the drawer footer area.
910    pub fn drawer_footer_background(&self) -> Option<Rc<dyn IBrush>> {
911        self.get_value(Self::drawer_footer_background_property())
912    }
913
914    pub fn set_drawer_footer_background(&self, value: Option<Rc<dyn IBrush>>) {
915        self.set_value(Self::drawer_footer_background_property(), value)
916    }
917
918    /// Gets or sets the foreground brush of the drawer footer area.
919    pub fn drawer_footer_foreground(&self) -> Option<Rc<dyn IBrush>> {
920        self.get_value(Self::drawer_footer_foreground_property())
921    }
922
923    pub fn set_drawer_footer_foreground(&self, value: Option<Rc<dyn IBrush>>) {
924        self.set_value(Self::drawer_footer_foreground_property(), value)
925    }
926
927    /// Gets or sets the backdrop brush for overlay mode.
928    pub fn backdrop_brush(&self) -> Option<Rc<dyn IBrush>> {
929        self.get_value(Self::backdrop_brush_property())
930    }
931
932    pub fn set_backdrop_brush(&self, value: Option<Rc<dyn IBrush>>) {
933        self.set_value(Self::backdrop_brush_property(), value)
934    }
935
936    /// Gets or sets the horizontal alignment of the detail content.
937    pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
938        self.get_value(Self::horizontal_content_alignment_property())
939    }
940
941    pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
942        self.set_value(Self::horizontal_content_alignment_property(), value)
943    }
944
945    /// Gets or sets the vertical alignment of the detail content.
946    pub fn vertical_content_alignment(&self) -> VerticalAlignment {
947        self.get_value(Self::vertical_content_alignment_property())
948    }
949
950    pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
951        self.set_value(Self::vertical_content_alignment_property(), value)
952    }
953
954    /// Gets or sets the display mode of the split view.
955    pub fn display_mode(&self) -> SplitViewDisplayMode {
956        self.get_value(Self::display_mode_property())
957    }
958
959    pub fn set_display_mode(&self, value: SplitViewDisplayMode) {
960        self.set_value(Self::display_mode_property(), value)
961    }
962
963    fn dispose_nav_bar_visible_sub(&self) {
964        let subscription = self.nav_bar_visible_sub.borrow_mut().take();
965        if let Some(subscription) = subscription {
966            subscription.dispose();
967        }
968    }
969
970    /// Follows the effective visibility of the navigation bar of the
971    /// navigation page.
972    fn observe_nav_bar_visible(&self, nav: &NavigationPage) -> Rc<dyn IDisposable> {
973        let weak = self.to_ref().downgrade();
974        let object: &FerroObject = nav;
975        FerroObjectExtensions::get_observable(object, NavigationPage::is_nav_bar_effectively_visible_property())
976            .subscribe_fn(move |_| {
977                if let Some(this) = weak.upgrade() {
978                    this.update_detail_nav_bar_visible_pseudo_class();
979                }
980            })
981    }
982
983    fn restore_navigation_state(&self) {
984        let Some(nav) = navigation_page_of(&self.content()) else {
985            return;
986        };
987
988        self.dispose_nav_bar_visible_sub();
989        nav.set_drawer_page(Some(self));
990        let subscription = self.observe_nav_bar_visible(&nav);
991        *self.nav_bar_visible_sub.borrow_mut() = Some(subscription);
992        self.update_detail_nav_bar_visible_pseudo_class();
993    }
994
995    fn clear_navigation_state(&self) {
996        self.dispose_nav_bar_visible_sub();
997
998        if let Some(nav) = navigation_page_of(&self.content()) {
999            nav.set_drawer_page(None);
1000        }
1001    }
1002
1003    fn attach_backdrop_pointer_pressed(&self) {
1004        let backdrop = self.backdrop.borrow().clone();
1005        if let Some(backdrop) = backdrop {
1006            let weak = self.to_ref().downgrade();
1007            let token =
1008                backdrop.add_handler(InputElement::pointer_pressed_event(), move |_, e: &PointerPressedEventArgs| {
1009                    if let Some(this) = weak.upgrade() {
1010                        this.on_backdrop_pressed(e);
1011                    }
1012                });
1013            if let Some(previous) = self.backdrop_pointer_pressed.replace(Some(token)) {
1014                backdrop.remove_handler(InputElement::pointer_pressed_event(), previous);
1015            }
1016        }
1017    }
1018
1019    fn detach_backdrop_pointer_pressed(&self) {
1020        let backdrop = self.backdrop.borrow().clone();
1021        if let Some(backdrop) = backdrop {
1022            if let Some(token) = self.backdrop_pointer_pressed.take() {
1023                backdrop.remove_handler(InputElement::pointer_pressed_event(), token);
1024            }
1025        }
1026    }
1027
1028    fn update_swipe_recognizer_axes(&self) {
1029        self.swipe_recognizer.set_can_vertically_swipe(self.is_vertical_placement());
1030        self.swipe_recognizer.set_can_horizontally_swipe(!self.is_vertical_placement());
1031    }
1032
1033    fn on_swipe_pointer_pressed(&self, e: &PointerPressedEventArgs) {
1034        self.swipe_start_point.set(e.get_position(Some(self)));
1035    }
1036
1037    fn on_swipe_gesture(&self, e: &SwipeGestureEventArgs) {
1038        let display_mode = self.display_mode();
1039        let bounds = self.bounds();
1040        if !self.is_gesture_enabled()
1041            || self.drawer_behavior() == DrawerBehavior::Disabled
1042            || self.drawer_behavior() == DrawerBehavior::Locked
1043            || display_mode == SplitViewDisplayMode::Inline
1044            || bounds.width <= 0.0
1045            || bounds.height <= 0.0
1046        {
1047            return;
1048        }
1049
1050        let swipe_direction = e.swipe_direction();
1051        let swipe_start_point = self.swipe_start_point.get();
1052        let open_gesture_edge = if display_mode == SplitViewDisplayMode::CompactOverlay
1053            || display_mode == SplitViewDisplayMode::CompactInline
1054        {
1055            self.compact_drawer_length()
1056        } else {
1057            EDGE_GESTURE_WIDTH
1058        };
1059
1060        let (toward_pane, in_edge) = if self.is_vertical_placement() {
1061            if swipe_direction != SwipeDirection::Up && swipe_direction != SwipeDirection::Down {
1062                return;
1063            }
1064
1065            if self.drawer_placement() == DrawerPlacement::Bottom {
1066                (swipe_direction == SwipeDirection::Up, swipe_start_point.y >= bounds.height - open_gesture_edge)
1067            } else {
1068                (swipe_direction == SwipeDirection::Down, swipe_start_point.y <= open_gesture_edge)
1069            }
1070        } else {
1071            if swipe_direction != SwipeDirection::Left && swipe_direction != SwipeDirection::Right {
1072                return;
1073            }
1074
1075            if self.is_pane_on_right() {
1076                (swipe_direction == SwipeDirection::Left, swipe_start_point.x >= bounds.width - open_gesture_edge)
1077            } else {
1078                (swipe_direction == SwipeDirection::Right, swipe_start_point.x <= open_gesture_edge)
1079            }
1080        };
1081
1082        if !self.is_open() {
1083            if toward_pane && in_edge {
1084                self.set_current_value(Self::is_open_property(), true);
1085                e.set_handled(true);
1086            }
1087        } else if !toward_pane {
1088            self.set_current_value(Self::is_open_property(), false);
1089            e.set_handled(true);
1090        }
1091    }
1092
1093    fn sync_current_page(&self) {
1094        self.set_current_value(Page::current_page_property(), self.resolve_current_page());
1095    }
1096
1097    fn resolve_current_page(&self) -> Option<Ref<Page>> {
1098        let drawer_is_overlay = self.is_open() && self.is_overlay_display_mode();
1099        let drawer_page = if drawer_is_overlay { page_of(&self.drawer()) } else { None };
1100        drawer_page.or_else(|| page_of(&self.content()))
1101    }
1102
1103    fn update_pane_placement(&self) {
1104        let split_view = self.split_view.borrow().clone();
1105        let Some(split_view) = split_view else {
1106            return;
1107        };
1108
1109        let placement = self.drawer_placement();
1110        split_view.set_pane_placement(match placement {
1111            DrawerPlacement::Right => SplitViewPanePlacement::Right,
1112            DrawerPlacement::Top => SplitViewPanePlacement::Top,
1113            DrawerPlacement::Bottom => SplitViewPanePlacement::Bottom,
1114            DrawerPlacement::Left => SplitViewPanePlacement::Left,
1115        });
1116
1117        self.pseudo_classes().set(PC_PLACEMENT_RIGHT, self.drawer_placement() == DrawerPlacement::Right);
1118        self.pseudo_classes().set(PC_PLACEMENT_TOP, self.drawer_placement() == DrawerPlacement::Top);
1119        self.pseudo_classes().set(PC_PLACEMENT_BOTTOM, self.drawer_placement() == DrawerPlacement::Bottom);
1120    }
1121
1122    /// Sets `IsOpen` without raising the drawer events.
1123    fn set_is_open_suppressing_drawer_events(&self, value: bool) {
1124        struct Reset<'a>(&'a Cell<bool>);
1125        impl Drop for Reset<'_> {
1126            fn drop(&mut self) {
1127                self.0.set(false);
1128            }
1129        }
1130
1131        self.suppress_drawer_events.set(true);
1132        let _reset = Reset(&self.suppress_drawer_events);
1133        self.set_current_value(Self::is_open_property(), value);
1134    }
1135
1136    fn update_split_view_display_mode(&self) {
1137        let previous_mode = self.display_mode();
1138
1139        let mode = self.resolve_split_view_display_mode();
1140        self.set_current_value(Self::display_mode_property(), mode);
1141
1142        if self.drawer_behavior() == DrawerBehavior::Disabled {
1143            self.set_current_value(Self::is_open_property(), false);
1144            return;
1145        }
1146
1147        if self.drawer_behavior() == DrawerBehavior::Locked {
1148            self.set_current_value(Self::is_open_property(), true);
1149            return;
1150        }
1151
1152        if self.split_view.borrow().is_none() {
1153            return;
1154        }
1155
1156        if self.drawer_breakpoint_length() > 0.0 && previous_mode != mode {
1157            if mode == SplitViewDisplayMode::Inline {
1158                self.set_is_open_suppressing_drawer_events(true);
1159            } else if mode == SplitViewDisplayMode::Overlay {
1160                self.set_is_open_suppressing_drawer_events(false);
1161            }
1162        }
1163    }
1164
1165    fn resolve_split_view_display_mode(&self) -> SplitViewDisplayMode {
1166        match self.drawer_behavior() {
1167            DrawerBehavior::Locked => return SplitViewDisplayMode::Inline,
1168            DrawerBehavior::Disabled | DrawerBehavior::Flyout => return SplitViewDisplayMode::Overlay,
1169            DrawerBehavior::Auto => {}
1170        }
1171
1172        let breakpoint = self.drawer_breakpoint_length();
1173        if breakpoint > 0.0 {
1174            let length = if self.is_vertical_placement() { self.bounds().height } else { self.bounds().width };
1175            if length > 0.0 && length < breakpoint {
1176                return SplitViewDisplayMode::Overlay;
1177            }
1178        }
1179
1180        match self.drawer_layout_behavior() {
1181            DrawerLayoutBehavior::Split => SplitViewDisplayMode::Inline,
1182            DrawerLayoutBehavior::CompactOverlay => SplitViewDisplayMode::CompactOverlay,
1183            DrawerLayoutBehavior::CompactInline => SplitViewDisplayMode::CompactInline,
1184            DrawerLayoutBehavior::Overlay => SplitViewDisplayMode::Overlay,
1185        }
1186    }
1187
1188    fn update_drawer_focus(&self) {
1189        if !self.is_open() {
1190            let pane_button = self.pane_button.borrow().clone();
1191            if let Some(pane_button) = pane_button {
1192                pane_button.focus();
1193            }
1194        } else if self.is_overlay_display_mode() {
1195            let drawer_presenter = self.drawer_presenter.borrow().clone();
1196            if let Some(drawer_presenter) = drawer_presenter {
1197                let first_focusable =
1198                    KeyboardNavigationHandler::get_next(&drawer_presenter.upcast(), NavigationDirection::Next);
1199                if let Some(first_focusable) = first_focusable {
1200                    first_focusable.focus();
1201                }
1202            }
1203        }
1204    }
1205
1206    fn update_backdrop_state(&self) {
1207        let backdrop = self.backdrop.borrow().clone();
1208        let Some(backdrop) = backdrop else {
1209            return;
1210        };
1211        let show = self.is_open() && self.backdrop_brush().is_some() && self.is_overlay_display_mode();
1212        backdrop.set_is_visible(show);
1213        backdrop.set_is_hit_test_visible(show);
1214    }
1215
1216    fn on_backdrop_pressed(&self, e: &PointerPressedEventArgs) {
1217        self.set_current_value(Self::is_open_property(), false);
1218        e.set_handled(true);
1219    }
1220
1221    fn apply_drawer_background(&self) {
1222        let split_view = self.split_view.borrow().clone();
1223        let Some(split_view) = split_view else {
1224            return;
1225        };
1226
1227        match self.drawer_background() {
1228            Some(drawer_background) => split_view.set_pane_background(Some(drawer_background)),
1229            None => split_view.clear_value(SplitView::pane_background_property()),
1230        }
1231    }
1232
1233    fn apply_foreground(presenter: Option<&Ref<ContentPresenter>>, brush: Option<Rc<dyn IBrush>>) {
1234        let Some(presenter) = presenter else {
1235            return;
1236        };
1237
1238        match brush {
1239            Some(brush) => TextElement::set_foreground_on(presenter, Some(brush)),
1240            None => presenter.clear_value(TextElement::foreground_property()),
1241        }
1242    }
1243
1244    fn update_detail_nav_bar_visible_pseudo_class(&self) {
1245        self.hide_top_bar
1246            .set(navigation_page_of(&self.content()).is_some_and(|nav_page| nav_page.is_nav_bar_effectively_visible()));
1247        self.pseudo_classes().set(PC_DETAIL_IS_NAV_PAGE, self.hide_top_bar.get());
1248    }
1249}
1250
1251/// `value as Page`.
1252fn page_of(value: &Option<BoxedValue>) -> Option<Ref<Page>> {
1253    value.as_ref().and_then(Control::from_boxed).and_then(|control| control.cast::<Page>())
1254}
1255
1256/// `value as NavigationPage`.
1257fn navigation_page_of(value: &Option<BoxedValue>) -> Option<Ref<NavigationPage>> {
1258    value.as_ref().and_then(Control::from_boxed).and_then(|control| control.cast::<NavigationPage>())
1259}
1260
1261/// `ReferenceEquals(a, b)`.
1262fn same_page(a: &Option<Ref<Page>>, b: &Option<Ref<Page>>) -> bool {
1263    match (a, b) {
1264        (None, None) => true,
1265        (Some(a), Some(b)) => a.ptr_eq(b),
1266        _ => false,
1267    }
1268}