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#[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 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 swipe_gesture_handler: Cell<Option<RoutedEventHandlerToken>>,
64 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 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 pub fn drawer_property() -> StyledProperty<Option<BoxedValue>> {
422 FerroProperty::register::<DrawerPage, _>("Drawer", None)
423 }
424
425 pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
427 FerroProperty::register::<DrawerPage, _>("Content", None)
428 }
429
430 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 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 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 pub fn drawer_breakpoint_length_property() -> StyledProperty<f64> {
456 FerroProperty::register::<DrawerPage, _>("DrawerBreakpointLength", 0.0)
457 }
458
459 pub fn is_gesture_enabled_property() -> StyledProperty<bool> {
461 FerroProperty::register::<DrawerPage, _>("IsGestureEnabled", true)
462 }
463
464 pub fn drawer_behavior_property() -> StyledProperty<DrawerBehavior> {
466 FerroProperty::register::<DrawerPage, _>("DrawerBehavior", DrawerBehavior::Auto)
467 }
468
469 pub fn drawer_layout_behavior_property() -> StyledProperty<DrawerLayoutBehavior> {
471 FerroProperty::register::<DrawerPage, _>("DrawerLayoutBehavior", DrawerLayoutBehavior::Overlay)
472 }
473
474 pub fn drawer_placement_property() -> StyledProperty<DrawerPlacement> {
476 FerroProperty::register::<DrawerPage, _>("DrawerPlacement", DrawerPlacement::Left)
477 }
478
479 pub fn drawer_header_property() -> StyledProperty<Option<BoxedValue>> {
481 FerroProperty::register::<DrawerPage, _>("DrawerHeader", None)
482 }
483
484 pub fn drawer_footer_property() -> StyledProperty<Option<BoxedValue>> {
486 FerroProperty::register::<DrawerPage, _>("DrawerFooter", None)
487 }
488
489 pub fn drawer_header_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
491 FerroProperty::register::<DrawerPage, _>("DrawerHeaderTemplate", None)
492 }
493
494 pub fn drawer_footer_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
496 FerroProperty::register::<DrawerPage, _>("DrawerFooterTemplate", None)
497 }
498
499 pub fn drawer_icon_property() -> StyledProperty<Option<BoxedValue>> {
501 FerroProperty::register::<DrawerPage, _>("DrawerIcon", None)
502 }
503
504 pub fn drawer_icon_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
506 FerroProperty::register::<DrawerPage, _>("DrawerIconTemplate", None)
507 }
508
509 pub fn drawer_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
511 FerroProperty::register::<DrawerPage, _>("DrawerTemplate", None)
512 }
513
514 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 pub fn drawer_background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
522 FerroProperty::register::<DrawerPage, _>("DrawerBackground", None)
523 }
524
525 pub fn drawer_header_background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
527 FerroProperty::register::<DrawerPage, _>("DrawerHeaderBackground", None)
528 }
529
530 pub fn drawer_header_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
532 FerroProperty::register::<DrawerPage, _>("DrawerHeaderForeground", None)
533 }
534
535 pub fn drawer_footer_background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
537 FerroProperty::register::<DrawerPage, _>("DrawerFooterBackground", None)
538 }
539
540 pub fn drawer_footer_foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
542 FerroProperty::register::<DrawerPage, _>("DrawerFooterForeground", None)
543 }
544
545 pub fn backdrop_brush_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
547 FerroProperty::register::<DrawerPage, _>("BackdropBrush", None)
548 }
549
550 pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
552 ContentControl::horizontal_content_alignment_property().add_owner::<DrawerPage>()
553 }
554
555 pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
557 ContentControl::vertical_content_alignment_property().add_owner::<DrawerPage>()
558 }
559
560 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 pub fn opened_event() -> RoutedEvent<RoutedEventArgs> {
571 RoutedEvent::register::<DrawerPage, _>("Opened", RoutingStrategies::BUBBLE)
572 }
573 );
574
575 ferro_routed_event!(
576 pub fn closing_event() -> RoutedEvent<DrawerClosingEventArgs> {
578 RoutedEvent::register::<DrawerPage, _>("Closing", RoutingStrategies::BUBBLE)
579 }
580 );
581
582 ferro_routed_event!(
583 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 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 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 pub fn opened(&self, handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static) -> RoutedEventHandlerToken {
666 self.add_handler(Self::opened_event(), handler)
667 }
668
669 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
1251fn 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
1256fn 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
1261fn 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}