1use super::{
2 start_async, BarLayoutBehavior, DrawerBehavior, DrawerPage, INavigation, ModalPoppedEventArgs,
3 ModalPushedEventArgs, MultiPage, MultiPageImpl, NavigatedFromEventArgs, NavigatedToEventArgs,
4 NavigatingFromEventArgs, NavigationEventArgs, NavigationType, Page, PageImpl, PageInsertedEventArgs, PageList,
5 PageRemovedEventArgs, SafeAreaPaddingExtensions,
6};
7use crate::automation::AutomationProperties;
8use crate::presenters::ContentPresenter;
9use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt};
10use crate::{Border, Button, Control, ControlImpl, Panel, PathIcon, SizeChangedEventArgs, ToolTip};
11use ferroui_base::animation::IPageTransition;
12use ferroui_base::controls::ResourceKey;
13use ferroui_base::input::gesture_recognizers::SwipeGestureRecognizer;
14use ferroui_base::input::{
15 InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyModifiers, PointerPressedEventArgs,
16 SwipeDirection, SwipeGestureEventArgs,
17};
18use ferroui_base::interactivity::{InteractiveImpl, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies};
19use ferroui_base::layout::{LayoutableImpl, LayoutableImplExt};
20use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
21use ferroui_base::media::{FlowDirection, Geometry, StreamGeometry};
22use ferroui_base::reactive::{Disposable, IDisposable, ObservableExt};
23use ferroui_base::threading::{CancellationTokenSource, DispatcherTask};
24use ferroui_base::utilities::HandlerList;
25use ferroui_base::{
26 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, AnyValue, AttachedProperty,
27 BoxedValue, DirectProperty, FerroObject, FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt,
28 FerroProperty, FerroPropertyChangedEventArgs, IntoRef, Nullable, Point, Ref, Size, StaticType, StyledElement,
29 StyledElementImpl, StyledProperty, Thickness, TypeInfo, Visual, VisualImpl, VisualImplExt,
30 VisualTreeAttachmentEventArgs, WeakRef,
31};
32use std::any::Any;
33use std::cell::{Cell, RefCell};
34use std::future::Future;
35use std::pin::Pin;
36use std::rc::Rc;
37use std::task::{Context, Poll};
38
39const EDGE_GESTURE_WIDTH: f64 = 20.0;
40
41const PC_NAV_BAR_INSET: &str = ":nav-bar-inset";
42const PC_NAV_BAR_COMPACT: &str = ":nav-bar-compact";
43
44const ALREADY_HOSTED: &str = "The page is already hosted by this NavigationPage.";
45
46type Subscription = RefCell<Option<Rc<dyn IDisposable>>>;
47
48#[repr(C)]
50pub struct NavigationPage {
51 base: MultiPage,
52 back_button: RefCell<Option<(Ref<Button>, RoutedEventHandlerToken)>>,
53 back_button_default_icon: RefCell<Option<Ref<Control>>>,
54 back_button_content_presenter: RefCell<Option<Ref<ContentPresenter>>>,
55 content_host: RefCell<Option<Ref<Panel>>>,
56 page_presenter: RefCell<Option<Ref<ContentPresenter>>>,
57 page_back_presenter: RefCell<Option<Ref<ContentPresenter>>>,
58 current_transition: RefCell<Option<Rc<CancellationTokenSource>>>,
59 last_page_transition_task: RefCell<Option<DispatcherTask<()>>>,
61 current_modal_transition: RefCell<Option<Rc<CancellationTokenSource>>>,
62 nav_bar: RefCell<Option<(Ref<Border>, RoutedEventHandlerToken)>>,
63 nav_bar_shadow: RefCell<Option<Ref<Border>>>,
64 is_pop: Cell<bool>,
65 has_had_first_page: Cell<bool>,
66 effective_bar_layout_behavior: Cell<BarLayoutBehavior>,
67 navigation_stack: PageList,
71 modal_stack: RefCell<Vec<Ref<Page>>>,
73 cached_navigation_stack: RefCell<Option<Rc<Vec<Ref<Page>>>>>,
74 cached_modal_stack: RefCell<Option<Rc<Vec<Ref<Page>>>>>,
75 modal_back_presenter: RefCell<Option<Ref<ContentPresenter>>>,
76 modal_presenter: RefCell<Option<Ref<ContentPresenter>>>,
77 top_command_bar_presenter: RefCell<Option<Ref<ContentPresenter>>>,
78 has_navigation_bar_sub: Subscription,
79 has_back_button_sub: Subscription,
80 is_back_button_enabled_sub: Subscription,
81 bar_layout_behavior_sub: Subscription,
82 bar_height_sub: Subscription,
83 back_button_content_sub: Subscription,
84 restoring_pages_property: Cell<bool>,
85 is_navigating: Cell<bool>,
86 can_go_back: Cell<bool>,
87 is_back_button_effectively_visible: Cell<bool>,
88 is_nav_bar_effectively_visible: Cell<bool>,
89 effective_bar_height: Cell<f64>,
90 is_back_button_effectively_enabled: Cell<bool>,
91 drawer_page: RefCell<Option<WeakRef<DrawerPage>>>,
93 override_transition: RefCell<Option<Rc<dyn IPageTransition>>>,
94 swipe_start_point: Cell<Point>,
95 last_swipe_gesture_id: Cell<i32>,
96 has_override_transition: Cell<bool>,
97 swipe_gesture_handler: Cell<Option<RoutedEventHandlerToken>>,
98 pushed: HandlerList<dyn Fn(&NavigationEventArgs)>,
99 popped: HandlerList<dyn Fn(&NavigationEventArgs)>,
100 popped_to_root: HandlerList<dyn Fn(&NavigationEventArgs)>,
101 page_inserted: HandlerList<dyn Fn(&PageInsertedEventArgs)>,
102 page_removed: HandlerList<dyn Fn(&PageRemovedEventArgs)>,
103 modal_pushed: HandlerList<dyn Fn(&ModalPushedEventArgs)>,
104 modal_popped: HandlerList<dyn Fn(&ModalPoppedEventArgs)>,
105}
106
107ferro_class!(NavigationPage: MultiPage);
108
109ferro_class_info!(NavigationPage {
110 new: NavigationPage::new,
111 interfaces: [Rc<dyn INavigation> => navigation_of],
112 markup: {
113 content: Content,
115 property_attributes: [Content: [DependsOn("PageTemplate")]],
116 attributes: [
117 TemplatePart("PART_NavigationBar", type(Ref<Border>)),
118 TemplatePart("PART_BackButton", type(Ref<Button>)),
119 TemplatePart("PART_ContentHost", type(Ref<Panel>)),
120 TemplatePart("PART_PagePresenter", type(Ref<ContentPresenter>)),
121 TemplatePart("PART_PageBackPresenter", type(Ref<ContentPresenter>)),
122 TemplatePart("PART_BackButtonDefaultIcon", type(Ref<Control>)),
123 TemplatePart("PART_BackButtonContentPresenter", type(Ref<ContentPresenter>)),
124 TemplatePart("PART_TopCommandBar", type(Ref<ContentPresenter>)),
125 TemplatePart("PART_BottomCommandBar", type(Ref<ContentPresenter>)),
126 TemplatePart("PART_ModalBackPresenter", type(Ref<ContentPresenter>)),
127 TemplatePart("PART_ModalPresenter", type(Ref<ContentPresenter>)),
128 TemplatePart("PART_NavBarShadow", type(Ref<Border>)),
129 PseudoClasses(":nav-bar-inset", ":nav-bar-compact"),
130 ],
131 },
132});
133
134ferro_impl_classes!(NavigationPage: InteractiveImpl);
135
136impl FerroObjectImpl for NavigationPage {
137 fn constructed(this: &Self) {
138 Self::parent_constructed(this);
139
140 this.set_observes_pages(false);
143 this.set_current_value(MultiPage::pages_property(), Some(this.navigation_stack.clone()));
144 let recognizer = SwipeGestureRecognizer::new();
145 recognizer.set_can_horizontally_swipe(true);
146 recognizer.set_can_vertically_swipe(false);
147 this.gesture_recognizers().add(recognizer);
148
149 let weak = this.to_ref().downgrade();
150 this.add_handler_with(
151 InputElement::pointer_pressed_event(),
152 move |_, e: &PointerPressedEventArgs| {
153 if let Some(this) = weak.upgrade() {
154 this.on_swipe_pointer_pressed(e);
155 }
156 },
157 RoutingStrategies::DIRECT | RoutingStrategies::BUBBLE,
158 true,
159 );
160 }
161
162 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
163 if change.property() == MultiPage::pages_property().as_property()
164 && !this.restoring_pages_property.get()
165 && change
166 .get_new_value::<Option<PageList>>()
167 .is_none_or(|new_value| !new_value.ptr_eq(&this.navigation_stack))
168 {
169 {
170 struct Restoring<'a>(&'a Cell<bool>);
171 impl Drop for Restoring<'_> {
172 fn drop(&mut self) {
173 self.0.set(false);
174 }
175 }
176
177 this.restoring_pages_property.set(true);
178 let _restoring = Restoring(&this.restoring_pages_property);
179 this.set_current_value(MultiPage::pages_property(), Some(this.navigation_stack.clone()));
180 }
181
182 panic!(
183 "Direct assignment to NavigationPage.Pages is not supported. Use PushAsync, PopAsync, InsertPage, RemovePage, or ReplaceAsync to modify the navigation stack."
184 );
185 }
186
187 if change.property() == Page::safe_area_padding_property().as_property() {
188 this.update_effective_bar_height();
189 }
190
191 Self::parent_on_property_changed(this, change);
192 }
193}
194
195impl StyledElementImpl for NavigationPage {
196 fn style_key_override(_this: &Self) -> &'static TypeInfo {
197 <NavigationPage as StaticType>::TYPE
198 }
199}
200
201impl VisualImpl for NavigationPage {
202 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
203 Self::parent_on_attached_to_visual_tree(this, e);
204
205 this.restore_navigation_state();
206
207 let weak = this.to_ref().downgrade();
208 let token = this.add_handler(InputElement::swipe_gesture_event(), move |_, e: &SwipeGestureEventArgs| {
209 if let Some(this) = weak.upgrade() {
210 this.on_swipe_gesture(e);
211 }
212 });
213 if let Some(previous) = this.swipe_gesture_handler.replace(Some(token)) {
214 this.remove_handler(InputElement::swipe_gesture_event(), previous);
215 }
216 }
217
218 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
219 Self::parent_on_detached_from_visual_tree(this, e);
220
221 if let Some(token) = this.swipe_gesture_handler.take() {
222 this.remove_handler(InputElement::swipe_gesture_event(), token);
223 }
224
225 let t = this.current_transition.borrow_mut().take();
226 if let Some(t) = t {
227 t.cancel();
228 }
229
230 let mt = this.current_modal_transition.borrow_mut().take();
231 if let Some(mt) = mt {
232 mt.cancel();
233 }
234
235 this.dispose_page_subscriptions();
236
237 this.clear_navigation_state();
238 this.invalidate_navigation_stack_cache();
239 }
240}
241
242impl LayoutableImpl for NavigationPage {
243 fn measure_override(this: &Self, available_size: Size) -> Size {
244 let top_command_bar_presenter = this.top_command_bar_presenter.borrow().clone();
245 if let Some(presenter) = top_command_bar_presenter {
246 if presenter.content().is_some() && available_size.width > 0.0 && available_size.width.is_finite() {
247 let max_width = (available_size.width * 0.5).floor();
248 if presenter.max_width() != max_width {
249 presenter.set_max_width(max_width);
250 }
251 }
252 }
253
254 Self::parent_measure_override(this, available_size)
255 }
256}
257
258impl InputElementImpl for NavigationPage {
259 fn on_key_down(this: &Self, e: &KeyEventArgs) {
260 Self::parent_on_key_down(this, e);
261
262 if e.key == Key::Left && e.key_modifiers == KeyModifiers::ALT && this.stack_depth() > 1 {
263 drop(this.pop_async());
265 e.set_handled(true);
266 }
267 }
268}
269
270impl TemplatedControlImpl for NavigationPage {
271 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
272 Self::parent_on_apply_template(this, e);
273
274 this.set_back_button(Some(e.name_scope().get_as::<Button>("PART_BackButton")));
275 *this.back_button_default_icon.borrow_mut() = e.name_scope().find_as::<Control>("PART_BackButtonDefaultIcon");
276 *this.back_button_content_presenter.borrow_mut() =
277 e.name_scope().find_as::<ContentPresenter>("PART_BackButtonContentPresenter");
278
279 *this.content_host.borrow_mut() = e.name_scope().find_as::<Panel>("PART_ContentHost");
280 *this.page_presenter.borrow_mut() = e.name_scope().find_as::<ContentPresenter>("PART_PagePresenter");
281 *this.page_back_presenter.borrow_mut() = e.name_scope().find_as::<ContentPresenter>("PART_PageBackPresenter");
282 let previous_nav_bar = this.nav_bar.borrow_mut().take();
283 if let Some((nav_bar, token)) = previous_nav_bar {
284 nav_bar.remove_handler(Control::size_changed_event(), token);
285 }
286 let nav_bar = e.name_scope().get_as::<Border>("PART_NavigationBar");
287 let weak = this.to_ref().downgrade();
288 let token = nav_bar.size_changed(move |_, e| {
289 if let Some(this) = weak.upgrade() {
290 this.on_nav_bar_size_changed(e);
291 }
292 });
293 *this.nav_bar.borrow_mut() = Some((nav_bar, token));
294 *this.nav_bar_shadow.borrow_mut() = e.name_scope().find_as::<Border>("PART_NavBarShadow");
295 *this.top_command_bar_presenter.borrow_mut() = e.name_scope().find_as::<ContentPresenter>("PART_TopCommandBar");
296 this.update_top_command_bar_max_width();
297
298 *this.modal_back_presenter.borrow_mut() = e.name_scope().find_as::<ContentPresenter>("PART_ModalBackPresenter");
299
300 let modal_presenter = e.name_scope().find_as::<ContentPresenter>("PART_ModalPresenter");
301 *this.modal_presenter.borrow_mut() = modal_presenter.clone();
302 if let Some(modal_presenter) = modal_presenter {
303 modal_presenter.set_content(this.modal_content());
304 modal_presenter.set_is_visible(this.is_modal_visible());
305 }
306
307 this.restore_navigation_state();
308
309 this.apply_nav_bar_visibility();
310 this.apply_back_button_enabled(this.is_back_button_effectively_enabled());
311 this.apply_has_shadow();
312 this.update_active_page();
313 }
314}
315
316impl PageImpl for NavigationPage {
317 fn update_content_safe_area_padding(this: &Self) {
318 let content_host = this.content_host.borrow().clone();
319 let nav_bar = this.nav_bar();
320 if let (Some(_content_host), Some(nav_bar)) = (content_host, nav_bar) {
321 let page_safe_area_padding = this.safe_area_padding();
322 let padding = this.padding();
323 let is_nav_bar_effectively_visible = this.is_nav_bar_effectively_visible();
324 let safe_area_padding = if is_nav_bar_effectively_visible {
325 Thickness::new(
326 page_safe_area_padding.left,
327 0.0,
328 page_safe_area_padding.right,
329 page_safe_area_padding.bottom,
330 )
331 } else {
332 page_safe_area_padding
333 };
334 nav_bar.set_padding(if is_nav_bar_effectively_visible {
335 Thickness::new(page_safe_area_padding.left, page_safe_area_padding.top, page_safe_area_padding.right, 0.0)
336 } else {
337 Thickness::default()
338 });
339
340 let page_presenter = this.page_presenter.borrow().clone();
341 if let Some(page_presenter) = page_presenter {
342 page_presenter.set_padding(padding);
343 }
344 let page_back_presenter = this.page_back_presenter.borrow().clone();
345 if let Some(page_back_presenter) = page_back_presenter {
346 page_back_presenter.set_padding(padding);
347 }
348
349 if let Some(current_page) = this.current_page() {
350 let remaining_safe_area = padding.get_remaining_safe_area_padding(safe_area_padding);
351 current_page.set_safe_area_padding(Thickness::new(
352 remaining_safe_area.left,
353 remaining_safe_area.top,
354 remaining_safe_area.right,
355 remaining_safe_area.bottom,
356 ));
357 }
358
359 for modal in this.modal_stack_top_first() {
360 modal.set_safe_area_padding(this.safe_area_padding());
361 }
362 }
363 }
364}
365
366impl MultiPageImpl for NavigationPage {
367 fn update_active_page_with(this: &Self, _navigation_type: NavigationType) {
370 let is_pop = this.is_pop.replace(false);
371
372 let has_override = this.has_override_transition.replace(false);
373 let override_transition = this.override_transition.borrow_mut().take();
374
375 let page = this.navigation_stack.snapshot().first().cloned();
376 let page_content = page.as_ref().map(|page| Control::boxed(page.clone()));
377
378 let content_host = this.content_host.borrow().clone();
379 let page_presenter = this.page_presenter.borrow().clone();
380 let page_back_presenter = this.page_back_presenter.borrow().clone();
381 if let (Some(_content_host), Some(page_presenter), Some(page_back_presenter)) =
382 (content_host, page_presenter, page_back_presenter)
383 {
384 let resolved_transition = if !this.has_had_first_page.get() {
385 None
386 } else if has_override {
387 override_transition
388 } else {
389 this.page_transition()
390 };
391
392 let current_transition = this.current_transition.borrow_mut().take();
393 if let Some(current_transition) = current_transition {
394 current_transition.cancel();
395 }
396
397 this.detach_orphaned_page_from_logical_tree(page_back_presenter.content().as_ref());
400 page_back_presenter.set_is_visible(false);
401 page_back_presenter.set_content(None);
402 page_back_presenter.set_render_transform(None);
403 page_back_presenter.set_opacity(1.0);
404
405 let stray_cp =
406 page.as_ref().and_then(|page| page.parent()).and_then(|parent| parent.cast::<ContentPresenter>());
407 if let Some(stray_cp) = stray_cp {
408 if !stray_cp.ptr_eq(&page_presenter) && !stray_cp.ptr_eq(&page_back_presenter) {
409 stray_cp.set_content(None);
410 }
411 }
412
413 if let Some(resolved_transition) = resolved_transition {
414 let cancel = Rc::new(CancellationTokenSource::new());
415 *this.current_transition.borrow_mut() = Some(cancel.clone());
416
417 let old_presenter = page_presenter;
418 let new_presenter = page_back_presenter;
419
420 new_presenter.set_content(page_content.clone());
421 new_presenter.set_is_visible(true);
422
423 if is_pop {
424 old_presenter.set_z_index(1);
425 new_presenter.set_z_index(0);
426 } else {
427 new_presenter.set_z_index(1);
428 old_presenter.set_z_index(0);
429 }
430
431 let task = start_async(Self::run_page_transition_async(
432 this.to_ref(),
433 resolved_transition,
434 old_presenter.clone(),
435 new_presenter.clone(),
436 !is_pop,
437 cancel,
438 ));
439 *this.last_page_transition_task.borrow_mut() = Some(task);
440
441 *this.page_presenter.borrow_mut() = Some(new_presenter);
442 *this.page_back_presenter.borrow_mut() = Some(old_presenter);
443 } else {
444 let old_page_content = page_presenter.content();
445 *this.last_page_transition_task.borrow_mut() = None;
446
447 page_presenter.set_content(page_content.clone());
448 page_presenter.set_is_visible(page.is_some());
449 page_presenter.set_z_index(0);
450
451 page_back_presenter.set_content(None);
452 page_back_presenter.set_is_visible(false);
453 page_back_presenter.set_z_index(0);
454
455 this.detach_orphaned_page_from_logical_tree(old_page_content.as_ref());
456 }
457
458 if page.is_some() {
459 this.has_had_first_page.set(true);
460 }
461 }
462
463 this.set_current_value(Self::content_property(), page_content);
464
465 this.set_current_value(Page::current_page_property(), page.clone());
466
467 this.dispose_page_subscriptions();
468
469 if let Some(page) = &page {
470 let observe = |update: fn(&NavigationPage)| {
471 let weak = this.to_ref().downgrade();
472 move || {
473 if let Some(this) = weak.upgrade() {
474 update(&this);
475 }
476 }
477 };
478 let object: &FerroObject = page;
479
480 let update = observe(Self::update_is_nav_bar_effectively_visible);
481 let subscription =
482 FerroObjectExtensions::get_observable(object, &**Self::has_navigation_bar_property())
483 .subscribe_fn(move |_| update());
484 *this.has_navigation_bar_sub.borrow_mut() = Some(subscription);
485
486 let update = observe(Self::update_is_back_button_effectively_visible);
487 let subscription = FerroObjectExtensions::get_observable(object, &**Self::has_back_button_property())
488 .subscribe_fn(move |_| update());
489 *this.has_back_button_sub.borrow_mut() = Some(subscription);
490
491 let update = observe(Self::update_is_back_button_effectively_enabled);
492 let subscription =
493 FerroObjectExtensions::get_observable(object, &**Self::is_back_button_enabled_property())
494 .subscribe_fn(move |_| update());
495 *this.is_back_button_enabled_sub.borrow_mut() = Some(subscription);
496
497 let update = observe(Self::update_bar_layout_behavior_effective);
498 let subscription =
499 FerroObjectExtensions::get_observable(object, &**Self::bar_layout_behavior_property())
500 .subscribe_fn(move |_| update());
501 *this.bar_layout_behavior_sub.borrow_mut() = Some(subscription);
502
503 let update = observe(Self::update_effective_bar_height);
504 let subscription =
505 FerroObjectExtensions::get_observable(object, &**Self::bar_height_override_property())
506 .subscribe_fn(move |_| update());
507 *this.bar_height_sub.borrow_mut() = Some(subscription);
508
509 let update = observe(Self::update_back_button_content);
510 let subscription =
511 FerroObjectExtensions::get_observable(object, &**Self::back_button_content_property())
512 .subscribe_fn(move |_| update());
513 *this.back_button_content_sub.borrow_mut() = Some(subscription);
514 }
515
516 this.update_is_nav_bar_effectively_visible();
517 this.update_bar_layout_behavior_effective();
518 this.update_effective_bar_height();
519
520 this.invalidate_navigation_stack_cache();
521 this.update_content_safe_area_padding();
522 this.update_is_back_button_effectively_visible();
523 this.update_is_back_button_effectively_enabled();
524 this.update_back_button_content();
525 }
526}
527
528impl ControlImpl for NavigationPage {
529 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
530 crate::automation::peers::NavigationPageAutomationPeer::new(this).upcast()
531 }
532}
533
534struct NavigationPageNavigation {
538 page: WeakRef<NavigationPage>,
539 id: *const (),
540}
541
542impl NavigationPageNavigation {
543 fn page(&self) -> Option<Ref<NavigationPage>> {
544 self.page.upgrade()
545 }
546}
547
548impl INavigation for NavigationPageNavigation {
549 fn navigation_stack(&self) -> Rc<Vec<Ref<Page>>> {
550 self.page().map(|page| page.navigation_stack()).unwrap_or_default()
551 }
552
553 fn modal_stack(&self) -> Rc<Vec<Ref<Page>>> {
554 self.page().map(|page| page.modal_stack()).unwrap_or_default()
555 }
556
557 fn stack_depth(&self) -> i32 {
558 self.page().map_or(0, |page| page.stack_depth())
559 }
560
561 fn can_go_back(&self) -> bool {
562 self.page().is_some_and(|page| page.can_go_back())
563 }
564
565 fn push_async(&self, page: Ref<Page>) -> DispatcherTask<()> {
566 match self.page() {
567 Some(this) => this.push_async(page),
568 None => super::completed_task(),
569 }
570 }
571
572 fn push_async_with_transition(
573 &self,
574 page: Ref<Page>,
575 transition: Option<Rc<dyn IPageTransition>>,
576 ) -> DispatcherTask<()> {
577 match self.page() {
578 Some(this) => this.push_async_with_transition(page, transition),
579 None => super::completed_task(),
580 }
581 }
582
583 fn push_async_with_parameter(
584 &self,
585 page: Ref<Page>,
586 transition: Option<Rc<dyn IPageTransition>>,
587 parameter: Option<BoxedValue>,
588 ) -> DispatcherTask<()> {
589 match self.page() {
590 Some(this) => this.push_async_with_parameter(page, transition, parameter),
591 None => super::completed_task(),
592 }
593 }
594
595 fn pop_async(&self) -> DispatcherTask<Option<Ref<Page>>> {
596 match self.page() {
597 Some(this) => this.pop_async(),
598 None => start_async(async { None }),
599 }
600 }
601
602 fn pop_async_with_transition(
603 &self,
604 transition: Option<Rc<dyn IPageTransition>>,
605 ) -> DispatcherTask<Option<Ref<Page>>> {
606 match self.page() {
607 Some(this) => this.pop_async_with_transition(transition),
608 None => start_async(async { None }),
609 }
610 }
611
612 fn pop_to_root_async(&self) -> DispatcherTask<()> {
613 match self.page() {
614 Some(this) => this.pop_to_root_async(),
615 None => super::completed_task(),
616 }
617 }
618
619 fn pop_to_root_async_with_transition(&self, transition: Option<Rc<dyn IPageTransition>>) -> DispatcherTask<()> {
620 match self.page() {
621 Some(this) => this.pop_to_root_async_with_transition(transition),
622 None => super::completed_task(),
623 }
624 }
625
626 fn pop_to_page_async(&self, page: Ref<Page>) -> DispatcherTask<()> {
627 match self.page() {
628 Some(this) => this.pop_to_page_async(page),
629 None => super::completed_task(),
630 }
631 }
632
633 fn pop_to_page_async_with_transition(
634 &self,
635 page: Ref<Page>,
636 transition: Option<Rc<dyn IPageTransition>>,
637 ) -> DispatcherTask<()> {
638 match self.page() {
639 Some(this) => this.pop_to_page_async_with_transition(page, transition),
640 None => super::completed_task(),
641 }
642 }
643
644 fn replace_async(&self, page: Ref<Page>) -> DispatcherTask<()> {
645 match self.page() {
646 Some(this) => this.replace_async(page),
647 None => super::completed_task(),
648 }
649 }
650
651 fn replace_async_with_transition(
652 &self,
653 page: Ref<Page>,
654 transition: Option<Rc<dyn IPageTransition>>,
655 ) -> DispatcherTask<()> {
656 match self.page() {
657 Some(this) => this.replace_async_with_transition(page, transition),
658 None => super::completed_task(),
659 }
660 }
661
662 fn replace_async_with_parameter(
663 &self,
664 page: Ref<Page>,
665 transition: Option<Rc<dyn IPageTransition>>,
666 parameter: Option<BoxedValue>,
667 ) -> DispatcherTask<()> {
668 match self.page() {
669 Some(this) => this.replace_async_with_parameter(page, transition, parameter),
670 None => super::completed_task(),
671 }
672 }
673
674 fn push_modal_async(&self, page: Ref<Page>) -> DispatcherTask<()> {
675 match self.page() {
676 Some(this) => this.push_modal_async(page),
677 None => super::completed_task(),
678 }
679 }
680
681 fn push_modal_async_with_transition(
682 &self,
683 page: Ref<Page>,
684 transition: Option<Rc<dyn IPageTransition>>,
685 ) -> DispatcherTask<()> {
686 match self.page() {
687 Some(this) => this.push_modal_async_with_transition(page, transition),
688 None => super::completed_task(),
689 }
690 }
691
692 fn push_modal_async_with_parameter(
693 &self,
694 page: Ref<Page>,
695 transition: Option<Rc<dyn IPageTransition>>,
696 parameter: Option<BoxedValue>,
697 ) -> DispatcherTask<()> {
698 match self.page() {
699 Some(this) => this.push_modal_async_with_parameter(page, transition, parameter),
700 None => super::completed_task(),
701 }
702 }
703
704 fn pop_modal_async(&self) -> DispatcherTask<Option<Ref<Page>>> {
705 match self.page() {
706 Some(this) => this.pop_modal_async(),
707 None => start_async(async { None }),
708 }
709 }
710
711 fn pop_modal_async_with_transition(
712 &self,
713 transition: Option<Rc<dyn IPageTransition>>,
714 ) -> DispatcherTask<Option<Ref<Page>>> {
715 match self.page() {
716 Some(this) => this.pop_modal_async_with_transition(transition),
717 None => start_async(async { None }),
718 }
719 }
720
721 fn pop_all_modals_async(&self) -> DispatcherTask<()> {
722 match self.page() {
723 Some(this) => this.pop_all_modals_async(),
724 None => super::completed_task(),
725 }
726 }
727
728 fn pop_all_modals_async_with_transition(
729 &self,
730 transition: Option<Rc<dyn IPageTransition>>,
731 ) -> DispatcherTask<()> {
732 match self.page() {
733 Some(this) => this.pop_all_modals_async_with_transition(transition),
734 None => super::completed_task(),
735 }
736 }
737
738 fn insert_page(&self, page: Ref<Page>, before: Ref<Page>) {
739 if let Some(this) = self.page() {
740 this.insert_page(page, before);
741 }
742 }
743
744 fn remove_page(&self, page: Ref<Page>) {
745 if let Some(this) = self.page() {
746 this.remove_page(page);
747 }
748 }
749
750 fn reference_id(&self) -> *const () {
751 self.id
752 }
753}
754
755fn navigation_of(page: Ref<NavigationPage>) -> Rc<dyn INavigation> {
756 page.as_navigation()
757}
758
759enum TransitionOutcome {
761 Completed,
762 Canceled,
763 Faulted,
764}
765
766struct TransitionOutcomeFuture {
768 task: DispatcherTask<()>,
769}
770
771impl Future for TransitionOutcomeFuture {
772 type Output = TransitionOutcome;
773
774 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<TransitionOutcome> {
775 if self.task.is_faulted() {
776 return Poll::Ready(TransitionOutcome::Faulted);
777 }
778
779 match Pin::new(&mut self.task).poll(cx) {
780 Poll::Ready(Ok(())) => Poll::Ready(TransitionOutcome::Completed),
781 Poll::Ready(Err(_)) => Poll::Ready(TransitionOutcome::Canceled),
782 Poll::Pending => Poll::Pending,
783 }
784 }
785}
786
787struct NavigatingScope(Ref<NavigationPage>);
790
791impl Drop for NavigatingScope {
792 fn drop(&mut self) {
793 self.0.set_is_navigating(false);
794 }
795}
796
797struct OverrideTransitionScope(Ref<NavigationPage>);
800
801impl Drop for OverrideTransitionScope {
802 fn drop(&mut self) {
803 self.0.has_override_transition.set(false);
804 *self.0.override_transition.borrow_mut() = None;
805 }
806}
807
808ferro_properties! {
809 impl NavigationPage {
810 pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
812 FerroProperty::register::<NavigationPage, _>("Content", None)
813 }
814
815 pub(crate) fn modal_content_property() -> StyledProperty<Option<BoxedValue>> {
816 FerroProperty::register::<NavigationPage, _>("ModalContent", None)
817 }
818
819 pub(crate) fn is_modal_visible_property() -> StyledProperty<bool> {
820 FerroProperty::register::<NavigationPage, _>("IsModalVisible", false)
821 }
822
823 pub fn page_transition_property() -> StyledProperty<Option<Rc<dyn IPageTransition>>> {
825 FerroProperty::register::<NavigationPage, _>("PageTransition", None)
826 }
827
828 pub fn modal_transition_property() -> StyledProperty<Option<Rc<dyn IPageTransition>>> {
830 FerroProperty::register::<NavigationPage, _>("ModalTransition", None)
831 }
832
833 pub fn is_back_button_effectively_visible_property() -> DirectProperty<NavigationPage, bool> {
835 FerroProperty::register_direct::<NavigationPage, _>(
836 "IsBackButtonEffectivelyVisible",
837 |o| o.is_back_button_effectively_visible(),
838 None,
839 false,
840 )
841 }
842
843 pub(crate) fn is_nav_bar_effectively_visible_property() -> DirectProperty<NavigationPage, bool> {
845 FerroProperty::register_direct::<NavigationPage, _>(
846 "IsNavBarEffectivelyVisible",
847 |o| o.is_nav_bar_effectively_visible(),
848 None,
849 false,
850 )
851 }
852
853 pub fn bar_layout_behavior_property() -> AttachedProperty<Option<BarLayoutBehavior>> {
855 FerroProperty::register_attached::<NavigationPage, Page, _>("BarLayoutBehavior", None)
856 }
857
858 pub fn has_shadow_property() -> StyledProperty<bool> {
860 FerroProperty::register::<NavigationPage, _>("HasShadow", false)
861 }
862
863 pub fn bar_height_property() -> StyledProperty<f64> {
865 FerroProperty::register::<NavigationPage, _>("BarHeight", 48.0)
866 }
867
868 pub fn bar_height_override_property() -> AttachedProperty<Option<f64>> {
870 FerroProperty::register_attached::<NavigationPage, Page, _>("BarHeightOverride", None)
871 }
872
873 pub fn effective_bar_height_property() -> DirectProperty<NavigationPage, f64> {
875 FerroProperty::register_direct::<NavigationPage, _>(
876 "EffectiveBarHeight",
877 |o| o.effective_bar_height(),
878 None,
879 0.0,
880 )
881 }
882
883 pub fn back_button_content_property() -> AttachedProperty<Option<BoxedValue>> {
885 FerroProperty::register_attached::<NavigationPage, Page, _>("BackButtonContent", None)
886 }
887
888 pub fn has_back_button_property() -> AttachedProperty<bool> {
890 FerroProperty::register_attached::<NavigationPage, Page, _>("HasBackButton", true)
891 }
892
893 pub fn is_back_button_visible_property() -> StyledProperty<bool> {
895 FerroProperty::register::<NavigationPage, _>("IsBackButtonVisible", true)
896 }
897
898 pub fn top_command_bar_property() -> AttachedProperty<Option<Ref<Control>>> {
900 FerroProperty::register_attached::<NavigationPage, Page, _>("TopCommandBar", None)
901 }
902
903 pub fn bottom_command_bar_property() -> AttachedProperty<Option<Ref<Control>>> {
905 FerroProperty::register_attached::<NavigationPage, Page, _>("BottomCommandBar", None)
906 }
907
908 pub fn has_navigation_bar_property() -> AttachedProperty<bool> {
910 FerroProperty::register_attached::<NavigationPage, Page, _>("HasNavigationBar", true)
911 }
912
913 pub fn is_gesture_enabled_property() -> StyledProperty<bool> {
915 FerroProperty::register::<NavigationPage, _>("IsGestureEnabled", true)
916 }
917
918 pub fn is_navigating_property() -> DirectProperty<NavigationPage, bool> {
920 FerroProperty::register_direct::<NavigationPage, _>("IsNavigating", |o| o.is_navigating.get(), None, false)
921 }
922
923 pub fn can_go_back_property() -> DirectProperty<NavigationPage, bool> {
925 FerroProperty::register_direct::<NavigationPage, _>("CanGoBack", |o| o.can_go_back(), None, false)
926 }
927
928 pub fn is_back_button_enabled_property() -> AttachedProperty<bool> {
930 FerroProperty::register_attached::<NavigationPage, Page, _>("IsBackButtonEnabled", true)
931 }
932
933 fn is_back_button_effectively_enabled_property() -> DirectProperty<NavigationPage, bool> {
935 FerroProperty::register_direct::<NavigationPage, _>(
936 "IsBackButtonEffectivelyEnabled",
937 |o| o.is_back_button_effectively_enabled(),
938 None,
939 false,
940 )
941 }
942 }
943}
944
945impl NavigationPage {
946 fn static_constructor() {
947 Page::page_navigation_system_back_button_pressed_event().add_class_handler::<NavigationPage>(
948 |sender, event_args| {
949 if event_args.handled() {
950 return;
951 }
952
953 let top_modal = sender.modal_stack.borrow().last().cloned();
954 if let Some(top_modal) = top_modal {
955 let forwarded =
956 RoutedEventArgs::with_event(Page::page_navigation_system_back_button_pressed_event());
957 top_modal.raise_event(&forwarded);
958
959 event_args.set_handled(true);
960
961 if !forwarded.handled() {
962 drop(sender.pop_modal_async());
963 }
964
965 return;
966 }
967
968 if let Some(current_page) = sender.current_page() {
969 let forwarded =
970 RoutedEventArgs::with_event(Page::page_navigation_system_back_button_pressed_event());
971 current_page.raise_event(&forwarded);
972
973 if forwarded.handled() {
974 event_args.set_handled(true);
975 }
976 }
977
978 if event_args.handled() {
979 return;
980 }
981
982 if sender.stack_depth() > 1 {
983 event_args.set_handled(true);
984 drop(sender.pop_async());
985 }
986 },
987 );
988
989 Self::modal_content_property().changed().add_class_handler::<NavigationPage>(|x, e| {
990 let modal_presenter = x.modal_presenter.borrow().clone();
991 if let Some(modal_presenter) = modal_presenter {
992 modal_presenter.set_content(e.get_new_value::<Option<BoxedValue>>());
993 }
994 });
995
996 Self::is_modal_visible_property().changed().add_class_handler::<NavigationPage>(|x, e| {
997 let modal_presenter = x.modal_presenter.borrow().clone();
998 if let Some(modal_presenter) = modal_presenter {
999 modal_presenter.set_is_visible(e.get_new_value::<bool>());
1000 }
1001 });
1002
1003 Self::is_back_button_visible_property()
1004 .changed()
1005 .add_class_handler::<NavigationPage>(|x, _| x.update_is_back_button_effectively_visible());
1006
1007 Self::bar_height_property()
1008 .changed()
1009 .add_class_handler::<NavigationPage>(|x, _| x.update_effective_bar_height());
1010
1011 Self::has_shadow_property().changed().add_class_handler::<NavigationPage>(|x, _| x.apply_has_shadow());
1012
1013 Self::effective_bar_height_property()
1014 .changed()
1015 .add_class_handler::<NavigationPage>(|x, _| x.apply_has_shadow());
1016
1017 Self::is_nav_bar_effectively_visible_property().changed().add_class_handler::<NavigationPage>(|x, _| {
1018 x.apply_nav_bar_visibility();
1019 x.apply_has_shadow();
1020 });
1021
1022 Self::is_back_button_effectively_enabled_property()
1023 .changed()
1024 .add_class_handler::<NavigationPage>(|x, e| x.apply_back_button_enabled(e.get_new_value::<bool>()));
1025
1026 Self::content_property().changed().add_class_handler::<NavigationPage>(|x, e| {
1027 let page = e
1028 .get_new_value::<Option<BoxedValue>>()
1029 .as_ref()
1030 .and_then(Control::from_boxed)
1031 .and_then(|control| control.cast::<Page>());
1032 let Some(page) = page else {
1033 return;
1034 };
1035 if x.stack_depth() > 0 {
1036 return;
1037 }
1038 drop(x.push_async(page));
1040 });
1041 }
1042
1043 pub fn construct() -> Self {
1045 Self {
1046 base: MultiPage::construct(),
1047 back_button: RefCell::new(None),
1048 back_button_default_icon: RefCell::new(None),
1049 back_button_content_presenter: RefCell::new(None),
1050 content_host: RefCell::new(None),
1051 page_presenter: RefCell::new(None),
1052 page_back_presenter: RefCell::new(None),
1053 current_transition: RefCell::new(None),
1054 last_page_transition_task: RefCell::new(None),
1055 current_modal_transition: RefCell::new(None),
1056 nav_bar: RefCell::new(None),
1057 nav_bar_shadow: RefCell::new(None),
1058 is_pop: Cell::new(false),
1059 has_had_first_page: Cell::new(false),
1060 effective_bar_layout_behavior: Cell::new(BarLayoutBehavior::Inset),
1061 navigation_stack: PageList::new(),
1062 modal_stack: RefCell::new(Vec::new()),
1063 cached_navigation_stack: RefCell::new(None),
1064 cached_modal_stack: RefCell::new(None),
1065 modal_back_presenter: RefCell::new(None),
1066 modal_presenter: RefCell::new(None),
1067 top_command_bar_presenter: RefCell::new(None),
1068 has_navigation_bar_sub: RefCell::new(None),
1069 has_back_button_sub: RefCell::new(None),
1070 is_back_button_enabled_sub: RefCell::new(None),
1071 bar_layout_behavior_sub: RefCell::new(None),
1072 bar_height_sub: RefCell::new(None),
1073 back_button_content_sub: RefCell::new(None),
1074 restoring_pages_property: Cell::new(false),
1075 is_navigating: Cell::new(false),
1076 can_go_back: Cell::new(false),
1077 is_back_button_effectively_visible: Cell::new(false),
1078 is_nav_bar_effectively_visible: Cell::new(false),
1079 effective_bar_height: Cell::new(0.0),
1080 is_back_button_effectively_enabled: Cell::new(false),
1081 drawer_page: RefCell::new(None),
1082 override_transition: RefCell::new(None),
1083 swipe_start_point: Cell::new(Point::default()),
1084 last_swipe_gesture_id: Cell::new(0),
1085 has_override_transition: Cell::new(false),
1086 swipe_gesture_handler: Cell::new(None),
1087 pushed: HandlerList::new(),
1088 popped: HandlerList::new(),
1089 popped_to_root: HandlerList::new(),
1090 page_inserted: HandlerList::new(),
1091 page_removed: HandlerList::new(),
1092 modal_pushed: HandlerList::new(),
1093 modal_popped: HandlerList::new(),
1094 }
1095 }
1096
1097 pub fn new() -> Ref<Self> {
1099 instantiate(Self::construct())
1100 }
1101
1102 pub fn as_navigation(&self) -> Rc<dyn INavigation> {
1104 let object: &FerroObject = self;
1105 Rc::new(NavigationPageNavigation {
1106 page: self.to_ref().downgrade(),
1107 id: object as *const FerroObject as *const (),
1108 })
1109 }
1110
1111 fn is_rtl(&self) -> bool {
1112 self.flow_direction() == FlowDirection::RightToLeft
1113 }
1114
1115 pub fn page_transition(&self) -> Option<Rc<dyn IPageTransition>> {
1117 self.get_value(Self::page_transition_property())
1118 }
1119
1120 pub fn set_page_transition(&self, value: Option<Rc<dyn IPageTransition>>) {
1121 self.set_value(Self::page_transition_property(), value)
1122 }
1123
1124 pub fn modal_transition(&self) -> Option<Rc<dyn IPageTransition>> {
1126 self.get_value(Self::modal_transition_property())
1127 }
1128
1129 pub fn set_modal_transition(&self, value: Option<Rc<dyn IPageTransition>>) {
1130 self.set_value(Self::modal_transition_property(), value)
1131 }
1132
1133 pub(crate) fn modal_content(&self) -> Option<BoxedValue> {
1134 self.get_value(Self::modal_content_property())
1135 }
1136
1137 #[allow(dead_code)]
1138 pub(crate) fn set_modal_content(&self, value: Option<BoxedValue>) {
1139 self.set_value(Self::modal_content_property(), value)
1140 }
1141
1142 pub(crate) fn is_modal_visible(&self) -> bool {
1143 self.get_value(Self::is_modal_visible_property())
1144 }
1145
1146 #[allow(dead_code)]
1147 pub(crate) fn set_is_modal_visible(&self, value: bool) {
1148 self.set_value(Self::is_modal_visible_property(), value)
1149 }
1150
1151 pub fn is_back_button_effectively_visible(&self) -> bool {
1153 self.is_back_button_effectively_visible.get()
1154 }
1155
1156 fn set_is_back_button_effectively_visible(&self, value: bool) {
1157 self.set_and_raise_cell(
1158 Self::is_back_button_effectively_visible_property(),
1159 &self.is_back_button_effectively_visible,
1160 value,
1161 );
1162 }
1163
1164 pub fn content(&self) -> Option<BoxedValue> {
1166 self.get_value(Self::content_property())
1167 }
1168
1169 pub fn set_content(&self, value: Option<BoxedValue>) {
1170 self.set_value(Self::content_property(), value)
1171 }
1172
1173 pub(crate) fn is_nav_bar_effectively_visible(&self) -> bool {
1175 self.is_nav_bar_effectively_visible.get()
1176 }
1177
1178 fn set_is_nav_bar_effectively_visible(&self, value: bool) {
1179 self.set_and_raise_cell(
1180 Self::is_nav_bar_effectively_visible_property(),
1181 &self.is_nav_bar_effectively_visible,
1182 value,
1183 );
1184 }
1185
1186 pub fn has_shadow(&self) -> bool {
1188 self.get_value(Self::has_shadow_property())
1189 }
1190
1191 pub fn set_has_shadow(&self, value: bool) {
1192 self.set_value(Self::has_shadow_property(), value)
1193 }
1194
1195 pub fn bar_height(&self) -> f64 {
1197 self.get_value(Self::bar_height_property())
1198 }
1199
1200 pub fn set_bar_height(&self, value: f64) {
1201 self.set_value(Self::bar_height_property(), value)
1202 }
1203
1204 pub fn effective_bar_height(&self) -> f64 {
1206 self.effective_bar_height.get()
1207 }
1208
1209 fn set_effective_bar_height(&self, value: f64) {
1210 self.set_and_raise_cell(Self::effective_bar_height_property(), &self.effective_bar_height, value);
1211 }
1212
1213 pub fn is_back_button_visible(&self) -> bool {
1216 self.get_value(Self::is_back_button_visible_property())
1217 }
1218
1219 pub fn set_is_back_button_visible(&self, value: bool) {
1220 self.set_value(Self::is_back_button_visible_property(), value)
1221 }
1222
1223 pub fn is_gesture_enabled(&self) -> bool {
1225 self.get_value(Self::is_gesture_enabled_property())
1226 }
1227
1228 pub fn set_is_gesture_enabled(&self, value: bool) {
1229 self.set_value(Self::is_gesture_enabled_property(), value)
1230 }
1231
1232 pub fn is_navigating(&self) -> bool {
1234 self.is_navigating.get()
1235 }
1236
1237 fn set_is_navigating(&self, value: bool) {
1238 self.set_and_raise_cell(Self::is_navigating_property(), &self.is_navigating, value);
1239 }
1240
1241 pub fn can_go_back(&self) -> bool {
1243 self.can_go_back.get()
1244 }
1245
1246 fn is_back_button_effectively_enabled(&self) -> bool {
1247 self.is_back_button_effectively_enabled.get()
1248 }
1249
1250 fn set_is_back_button_effectively_enabled(&self, value: bool) {
1251 self.set_and_raise_cell(
1252 Self::is_back_button_effectively_enabled_property(),
1253 &self.is_back_button_effectively_enabled,
1254 value,
1255 );
1256 }
1257
1258 pub fn navigation_stack(&self) -> Rc<Vec<Ref<Page>>> {
1262 if let Some(cached) = self.cached_navigation_stack.borrow().as_ref() {
1263 return cached.clone();
1264 }
1265
1266 let result: Rc<Vec<Ref<Page>>> = Rc::new(self.navigation_stack.snapshot().iter().rev().cloned().collect());
1267 *self.cached_navigation_stack.borrow_mut() = Some(result.clone());
1268
1269 result
1270 }
1271
1272 pub fn modal_stack(&self) -> Rc<Vec<Ref<Page>>> {
1275 if let Some(cached) = self.cached_modal_stack.borrow().as_ref() {
1276 return cached.clone();
1277 }
1278
1279 let result = Rc::new(self.modal_stack.borrow().clone());
1280 *self.cached_modal_stack.borrow_mut() = Some(result.clone());
1281 result
1282 }
1283
1284 pub fn stack_depth(&self) -> i32 {
1286 self.navigation_stack.count() as i32
1287 }
1288
1289 pub fn get_back_button_content(page: &Page) -> Option<BoxedValue> {
1291 page.get_value(&**Self::back_button_content_property())
1292 }
1293
1294 pub fn set_back_button_content(page: &Page, content: Option<BoxedValue>) {
1296 page.set_value(&**Self::back_button_content_property(), content)
1297 }
1298
1299 pub fn get_has_back_button(page: &Page) -> bool {
1301 page.get_value(&**Self::has_back_button_property())
1302 }
1303
1304 pub fn set_has_back_button(page: &Page, value: bool) {
1306 page.set_value(&**Self::has_back_button_property(), value)
1307 }
1308
1309 pub fn get_header(page: &Page) -> Option<BoxedValue> {
1311 page.get_value(Page::header_property())
1312 }
1313
1314 pub fn set_header(page: &Page, header: Option<BoxedValue>) {
1316 page.set_value(Page::header_property(), header)
1317 }
1318
1319 pub fn get_top_command_bar(page: &Page) -> Option<Ref<Control>> {
1321 page.get_value(&**Self::top_command_bar_property())
1322 }
1323
1324 pub fn set_top_command_bar(page: &Page, command_bar: impl Into<Nullable<Control>>) {
1326 page.set_value(&**Self::top_command_bar_property(), command_bar.into().0)
1327 }
1328
1329 pub fn get_bottom_command_bar(page: &Page) -> Option<Ref<Control>> {
1331 page.get_value(&**Self::bottom_command_bar_property())
1332 }
1333
1334 pub fn set_bottom_command_bar(page: &Page, command_bar: impl Into<Nullable<Control>>) {
1336 page.set_value(&**Self::bottom_command_bar_property(), command_bar.into().0)
1337 }
1338
1339 pub fn get_has_navigation_bar(page: &Page) -> bool {
1341 page.get_value(&**Self::has_navigation_bar_property())
1342 }
1343
1344 pub fn set_has_navigation_bar(page: &Page, value: bool) {
1346 page.set_value(&**Self::has_navigation_bar_property(), value)
1347 }
1348
1349 pub fn get_bar_layout_behavior(page: &Page) -> Option<BarLayoutBehavior> {
1351 page.get_value(&**Self::bar_layout_behavior_property())
1352 }
1353
1354 pub fn set_bar_layout_behavior(page: &Page, value: Option<BarLayoutBehavior>) {
1356 page.set_value(&**Self::bar_layout_behavior_property(), value)
1357 }
1358
1359 pub fn get_bar_height_override(page: &Page) -> Option<f64> {
1362 page.get_value(&**Self::bar_height_override_property())
1363 }
1364
1365 pub fn set_bar_height_override(page: &Page, value: Option<f64>) {
1367 page.set_value(&**Self::bar_height_override_property(), value)
1368 }
1369
1370 pub fn get_is_back_button_enabled(page: &Page) -> bool {
1372 page.get_value(&**Self::is_back_button_enabled_property())
1373 }
1374
1375 pub fn set_is_back_button_enabled(page: &Page, value: bool) {
1377 page.set_value(&**Self::is_back_button_enabled_property(), value)
1378 }
1379
1380 fn subscribe<F: ?Sized + 'static>(
1381 &self,
1382 select: fn(&NavigationPage) -> &HandlerList<F>,
1383 handler: Rc<F>,
1384 ) -> Rc<dyn IDisposable> {
1385 let token = select(self).add(handler);
1386 let weak = self.to_ref().downgrade();
1387 Disposable::create(move || {
1388 if let Some(this) = weak.upgrade() {
1389 select(&this).remove(token);
1390 }
1391 })
1392 }
1393
1394 pub fn pushed(&self, handler: impl Fn(&NavigationEventArgs) + 'static) -> Rc<dyn IDisposable> {
1397 self.subscribe::<dyn Fn(&NavigationEventArgs)>(|x| &x.pushed, Rc::new(handler))
1398 }
1399
1400 pub fn popped(&self, handler: impl Fn(&NavigationEventArgs) + 'static) -> Rc<dyn IDisposable> {
1403 self.subscribe::<dyn Fn(&NavigationEventArgs)>(|x| &x.popped, Rc::new(handler))
1404 }
1405
1406 pub fn popped_to_root(&self, handler: impl Fn(&NavigationEventArgs) + 'static) -> Rc<dyn IDisposable> {
1413 self.subscribe::<dyn Fn(&NavigationEventArgs)>(|x| &x.popped_to_root, Rc::new(handler))
1414 }
1415
1416 pub fn page_inserted(&self, handler: impl Fn(&PageInsertedEventArgs) + 'static) -> Rc<dyn IDisposable> {
1419 self.subscribe::<dyn Fn(&PageInsertedEventArgs)>(|x| &x.page_inserted, Rc::new(handler))
1420 }
1421
1422 pub fn page_removed(&self, handler: impl Fn(&PageRemovedEventArgs) + 'static) -> Rc<dyn IDisposable> {
1425 self.subscribe::<dyn Fn(&PageRemovedEventArgs)>(|x| &x.page_removed, Rc::new(handler))
1426 }
1427
1428 pub fn modal_pushed(&self, handler: impl Fn(&ModalPushedEventArgs) + 'static) -> Rc<dyn IDisposable> {
1431 self.subscribe::<dyn Fn(&ModalPushedEventArgs)>(|x| &x.modal_pushed, Rc::new(handler))
1432 }
1433
1434 pub fn modal_popped(&self, handler: impl Fn(&ModalPoppedEventArgs) + 'static) -> Rc<dyn IDisposable> {
1437 self.subscribe::<dyn Fn(&ModalPoppedEventArgs)>(|x| &x.modal_popped, Rc::new(handler))
1438 }
1439
1440 fn raise<A>(handlers: &HandlerList<dyn Fn(&A)>, args: &A) {
1441 for (_, handler) in handlers.snapshot().iter() {
1442 handler(args);
1443 }
1444 }
1445
1446 fn back_button(&self) -> Option<Ref<Button>> {
1447 self.back_button.borrow().as_ref().map(|(button, _)| button.clone())
1448 }
1449
1450 fn set_back_button(&self, value: Option<Ref<Button>>) {
1451 let previous = self.back_button.borrow_mut().take();
1452 if let Some((button, token)) = previous {
1453 button.remove_handler(Button::click_event(), token);
1454 }
1455 if let Some(button) = value {
1456 let weak = self.to_ref().downgrade();
1457 let token = button.click(move |_, e| {
1458 if let Some(this) = weak.upgrade() {
1459 this.back_button_clicked(e);
1460 }
1461 });
1462 *self.back_button.borrow_mut() = Some((button, token));
1463 }
1464 }
1465
1466 fn nav_bar(&self) -> Option<Ref<Border>> {
1467 self.nav_bar.borrow().as_ref().map(|(nav_bar, _)| nav_bar.clone())
1468 }
1469
1470 fn modal_stack_top_first(&self) -> Vec<Ref<Page>> {
1472 self.modal_stack.borrow().iter().rev().cloned().collect()
1473 }
1474
1475 fn top_modal(&self) -> Option<Ref<Page>> {
1476 self.modal_stack.borrow().last().cloned()
1477 }
1478
1479 fn page_set_contains(&self, page: &Page) -> bool {
1482 self.navigation_stack.snapshot().iter().any(|p| std::ptr::eq::<Page>(&**p, page))
1483 }
1484
1485 fn pop_navigation_stack(&self) -> Option<Ref<Page>> {
1487 let top = self.navigation_stack.try_get(0)?;
1488 self.navigation_stack.remove_at(0);
1489 Some(top)
1490 }
1491
1492 fn is_top_of_navigation_stack(&self, page: &Page) -> bool {
1493 self.navigation_stack.snapshot().first().is_some_and(|top| std::ptr::eq::<Page>(&**top, page))
1494 }
1495
1496 fn dispose_page_subscriptions(&self) {
1497 for subscription in [
1498 &self.has_navigation_bar_sub,
1499 &self.has_back_button_sub,
1500 &self.is_back_button_enabled_sub,
1501 &self.bar_layout_behavior_sub,
1502 &self.bar_height_sub,
1503 &self.back_button_content_sub,
1504 ] {
1505 let subscription = subscription.borrow_mut().take();
1506 if let Some(subscription) = subscription {
1507 subscription.dispose();
1508 }
1509 }
1510 }
1511
1512 fn on_nav_bar_size_changed(&self, _e: &SizeChangedEventArgs) {
1513 self.update_top_command_bar_max_width();
1514 }
1515
1516 fn update_top_command_bar_max_width(&self) {
1517 let top_command_bar_presenter = self.top_command_bar_presenter.borrow().clone();
1518 let (Some(presenter), Some(nav_bar)) = (top_command_bar_presenter, self.nav_bar()) else {
1519 return;
1520 };
1521 if presenter.content().is_none() {
1522 return;
1523 }
1524 let nav_bar_width = nav_bar.bounds().width;
1525 if nav_bar_width <= 0.0 {
1526 return;
1527 }
1528 let max_width = (nav_bar_width * 0.5).floor();
1529 if presenter.max_width() != max_width {
1530 presenter.set_max_width(max_width);
1531 }
1532 }
1533
1534 fn drawer_page(&self) -> Option<Ref<DrawerPage>> {
1536 self.drawer_page.borrow().as_ref().and_then(WeakRef::upgrade)
1537 }
1538
1539 fn drawer_page_allows_toggle(&self) -> bool {
1542 self.drawer_page().is_some_and(|drawer_page| {
1543 drawer_page.drawer_behavior() != DrawerBehavior::Locked
1544 && drawer_page.drawer_behavior() != DrawerBehavior::Disabled
1545 })
1546 }
1547
1548 pub(crate) fn set_drawer_page(&self, drawer_page: Option<&DrawerPage>) {
1549 *self.drawer_page.borrow_mut() = drawer_page.map(|drawer_page| drawer_page.to_ref().downgrade());
1550 self.update_is_back_button_effectively_visible();
1551 self.update_back_button_content();
1552 }
1553
1554 fn back_button_clicked(&self, _event_args: &RoutedEventArgs) {
1555 if self.stack_depth() <= 1 && self.drawer_page_allows_toggle() {
1556 if let Some(drawer_page) = self.drawer_page() {
1557 drawer_page.set_is_open(!drawer_page.is_open());
1558 }
1559 return;
1560 }
1561 if self.can_go_back() {
1562 drop(self.pop_async());
1563 }
1564 }
1565
1566 fn peek_destination_page(&self) -> Option<Ref<Page>> {
1568 let stack = self.navigation_stack.snapshot();
1569 if stack.len() < 2 {
1570 return None;
1571 }
1572 Some(stack[1].clone())
1573 }
1574
1575 fn throw_if_page_is_already_present(&self, page: &Page) {
1576 if self.page_set_contains(page) {
1577 panic!("{ALREADY_HOSTED}");
1578 }
1579
1580 if self.modal_stack.borrow().iter().any(|modal| std::ptr::eq::<Page>(&**modal, page)) {
1581 panic!("{ALREADY_HOSTED}");
1582 }
1583 }
1584
1585 fn execute_push_core(&self, page: &Ref<Page>, _previous_page: Option<&Ref<Page>>) {
1588 self.throw_if_page_is_already_present(page);
1589
1590 self.navigation_stack.insert(0, page.clone());
1591 self.invalidate_navigation_stack_cache();
1592
1593 StyledElement::logical_children(self).add(page.clone().upcast());
1594
1595 page.set_navigation(Some(self.as_navigation()));
1596 page.set_in_navigation_page(true);
1597
1598 self.update_active_page();
1599 }
1600
1601 fn execute_pop_core(&self) -> Option<Ref<Page>> {
1606 let old = self.pop_navigation_stack();
1607
1608 if let Some(old) = &old {
1611 if !self.is_page_in_presenter(old) {
1612 StyledElement::logical_children(self).remove(&old.clone().upcast());
1613 }
1614 }
1615
1616 self.invalidate_navigation_stack_cache();
1617 self.is_pop.set(true);
1618 self.update_active_page();
1619
1620 if let Some(old) = &old {
1621 old.set_navigation(None);
1622 old.set_in_navigation_page(false);
1623 old.set_safe_area_padding(Thickness::default());
1624 }
1625
1626 old
1627 }
1628
1629 async fn push_async_private(this: Ref<Self>, page: Ref<Page>, parameter: Option<BoxedValue>) {
1630 if this.is_navigating.get() {
1631 return;
1632 }
1633
1634 this.set_is_navigating(true);
1635 let _navigating = NavigatingScope(this.clone());
1636
1637 let previous_page = this.current_page();
1638
1639 if let Some(previous_page) = &previous_page {
1640 let navigating_args =
1641 NavigatingFromEventArgs::with_parameter(Some(page.clone()), NavigationType::Push, parameter.clone());
1642 Page::send_navigating_async(previous_page.clone(), navigating_args.clone()).await;
1643
1644 if navigating_args.cancel() {
1645 return;
1646 }
1647 }
1648
1649 this.execute_push_core(&page, previous_page.as_ref());
1650
1651 this.await_page_transition_async().await;
1652
1653 if let Some(previous_page) = &previous_page {
1654 previous_page.send_navigated_from(&NavigatedFromEventArgs::with_parameter(
1655 Some(page.clone()),
1656 NavigationType::Push,
1657 parameter.clone(),
1658 ));
1659 }
1660 page.send_navigated_to(&NavigatedToEventArgs::with_parameter(
1661 previous_page,
1662 NavigationType::Push,
1663 parameter.clone(),
1664 ));
1665 Self::raise(&this.pushed, &NavigationEventArgs::with_parameter(page, NavigationType::Push, parameter));
1666 }
1667
1668 fn begin_override_transition(&self, transition: Option<Rc<dyn IPageTransition>>) -> OverrideTransitionScope {
1669 *self.override_transition.borrow_mut() = transition;
1670 self.has_override_transition.set(true);
1671 OverrideTransitionScope(self.to_ref())
1672 }
1673
1674 async fn push_async_private_with_transition(
1675 this: Ref<Self>,
1676 page: Ref<Page>,
1677 transition: Option<Rc<dyn IPageTransition>>,
1678 parameter: Option<BoxedValue>,
1679 ) {
1680 if this.is_navigating.get() {
1681 return;
1682 }
1683 let _override = this.begin_override_transition(transition);
1684 Self::push_async_private(this.clone(), page, parameter).await;
1685 }
1686
1687 pub fn push_async(&self, page: impl IntoRef<Page>) -> DispatcherTask<()> {
1696 start_async(Self::push_async_private(self.to_ref(), page.into_ref(), None))
1697 }
1698
1699 pub fn push_async_with_transition(
1702 &self,
1703 page: impl IntoRef<Page>,
1704 transition: Option<Rc<dyn IPageTransition>>,
1705 ) -> DispatcherTask<()> {
1706 start_async(Self::push_async_private_with_transition(self.to_ref(), page.into_ref(), transition, None))
1707 }
1708
1709 pub fn push_async_with_parameter(
1712 &self,
1713 page: impl IntoRef<Page>,
1714 transition: Option<Rc<dyn IPageTransition>>,
1715 parameter: Option<BoxedValue>,
1716 ) -> DispatcherTask<()> {
1717 start_async(Self::push_async_private_with_transition(self.to_ref(), page.into_ref(), transition, parameter))
1718 }
1719
1720 async fn pop_async_core(this: Ref<Self>) -> Option<Ref<Page>> {
1721 if this.stack_depth() <= 1 {
1722 return None;
1723 }
1724 if this.is_navigating.get() {
1725 return None;
1726 }
1727
1728 this.set_is_navigating(true);
1729 let _navigating = NavigatingScope(this.clone());
1730
1731 let current_page = this.current_page();
1732 if let Some(current_page) = current_page {
1733 let destination = this.peek_destination_page();
1734 let navigating_args = NavigatingFromEventArgs::new(destination, NavigationType::Pop);
1735 Page::send_navigating_async(current_page, navigating_args.clone()).await;
1736
1737 if navigating_args.cancel() {
1738 return None;
1739 }
1740 }
1741
1742 let old = this.execute_pop_core();
1743
1744 this.await_page_transition_async().await;
1745
1746 if let Some(old) = &old {
1747 this.send_pop_lifecycle_events(old, NavigationType::Pop, None);
1748 }
1749
1750 old
1751 }
1752
1753 pub fn pop_async(&self) -> DispatcherTask<Option<Ref<Page>>> {
1759 start_async(Self::pop_async_core(self.to_ref()))
1760 }
1761
1762 pub fn pop_async_with_transition(
1765 &self,
1766 transition: Option<Rc<dyn IPageTransition>>,
1767 ) -> DispatcherTask<Option<Ref<Page>>> {
1768 let this = self.to_ref();
1769 start_async(async move {
1770 if this.is_navigating.get() {
1771 return None;
1772 }
1773 let _override = this.begin_override_transition(transition);
1774 Self::pop_async_core(this.clone()).await
1775 })
1776 }
1777
1778 async fn pop_to_root_async_core(this: Ref<Self>) {
1779 if this.stack_depth() <= 1 {
1780 return;
1781 }
1782 if this.is_navigating.get() {
1783 return;
1784 }
1785
1786 this.set_is_navigating(true);
1787 let _navigating = NavigatingScope(this.clone());
1788
1789 let navigation_stack = this.navigation_stack();
1790 let root_page = navigation_stack.first().cloned();
1791
1792 let current_page = this.current_page();
1793 if let Some(current_page) = ¤t_page {
1794 let navigating_args = NavigatingFromEventArgs::new(root_page.clone(), NavigationType::PopToRoot);
1795 Page::send_navigating_async(current_page.clone(), navigating_args.clone()).await;
1796 if navigating_args.cancel() {
1797 return;
1798 }
1799 }
1800
1801 let mut popped_pages = Vec::new();
1802
1803 loop {
1804 let popped = if this.navigation_stack.count() > 1 { this.pop_navigation_stack() } else { None };
1805 let Some(popped) = popped else {
1806 break;
1807 };
1808 this.tear_down_popped_page(&popped);
1809 popped_pages.push(popped);
1810 }
1811
1812 this.invalidate_navigation_stack_cache();
1813 this.is_pop.set(true);
1814 this.update_active_page();
1815
1816 this.await_page_transition_async().await;
1817
1818 for popped in &popped_pages {
1819 popped.send_navigated_from(&NavigatedFromEventArgs::new(root_page.clone(), NavigationType::PopToRoot));
1820 Self::raise(&this.popped, &NavigationEventArgs::new(popped.clone(), NavigationType::PopToRoot));
1821 }
1822
1823 let new_current_page = this.current_page();
1824
1825 if let Some(new_current_page) = new_current_page {
1826 new_current_page.send_navigated_to(&NavigatedToEventArgs::new(current_page, NavigationType::PopToRoot));
1827 Self::raise(
1828 &this.popped_to_root,
1829 &NavigationEventArgs::new(new_current_page, NavigationType::PopToRoot),
1830 );
1831 }
1832 }
1833
1834 pub fn pop_to_root_async(&self) -> DispatcherTask<()> {
1841 start_async(Self::pop_to_root_async_core(self.to_ref()))
1842 }
1843
1844 fn tear_down_popped_page(&self, popped: &Ref<Page>) {
1845 if !self.is_page_in_presenter(popped) {
1848 StyledElement::logical_children(self).remove(&popped.clone().upcast());
1849 }
1850
1851 popped.set_navigation(None);
1852 popped.set_in_navigation_page(false);
1853 popped.set_safe_area_padding(Thickness::default());
1854 }
1855
1856 pub fn pop_to_root_async_with_transition(&self, transition: Option<Rc<dyn IPageTransition>>) -> DispatcherTask<()> {
1858 let this = self.to_ref();
1859 start_async(async move {
1860 if this.is_navigating.get() {
1861 return;
1862 }
1863 let _override = this.begin_override_transition(transition);
1864 Self::pop_to_root_async_core(this.clone()).await;
1865 })
1866 }
1867
1868 async fn pop_to_page_async_core(this: Ref<Self>, page: Ref<Page>) {
1869 if !this.page_set_contains(&page) {
1870 panic!("Page is not in the navigation stack. (Parameter 'page')");
1871 }
1872
1873 if this.is_top_of_navigation_stack(&page) {
1874 return;
1875 }
1876
1877 if this.is_navigating.get() {
1878 return;
1879 }
1880
1881 this.set_is_navigating(true);
1882 let _navigating = NavigatingScope(this.clone());
1883
1884 let current_page = this.current_page();
1885 if let Some(current_page) = ¤t_page {
1886 let navigating_args = NavigatingFromEventArgs::new(Some(page.clone()), NavigationType::Pop);
1887 Page::send_navigating_async(current_page.clone(), navigating_args.clone()).await;
1888 if navigating_args.cancel() {
1889 return;
1890 }
1891 }
1892
1893 let mut popped_pages = Vec::new();
1894
1895 loop {
1896 let popped = if this.navigation_stack.count() > 1 && !this.is_top_of_navigation_stack(&page) {
1897 this.pop_navigation_stack()
1898 } else {
1899 None
1900 };
1901 let Some(popped) = popped else {
1902 break;
1903 };
1904 this.tear_down_popped_page(&popped);
1905 popped_pages.push(popped);
1906 }
1907
1908 this.invalidate_navigation_stack_cache();
1909 this.is_pop.set(true);
1910 this.update_active_page();
1911
1912 this.await_page_transition_async().await;
1913
1914 for popped in &popped_pages {
1915 popped.send_navigated_from(&NavigatedFromEventArgs::new(Some(page.clone()), NavigationType::Pop));
1916 Self::raise(&this.popped, &NavigationEventArgs::new(popped.clone(), NavigationType::Pop));
1917 }
1918
1919 let new_current_page = this.current_page();
1920 if let Some(new_current_page) = new_current_page {
1921 new_current_page.send_navigated_to(&NavigatedToEventArgs::new(current_page, NavigationType::Pop));
1922 }
1923 }
1924
1925 pub fn pop_to_page_async(&self, page: impl IntoRef<Page>) -> DispatcherTask<()> {
1940 start_async(Self::pop_to_page_async_core(self.to_ref(), page.into_ref()))
1941 }
1942
1943 pub fn pop_to_page_async_with_transition(
1945 &self,
1946 page: impl IntoRef<Page>,
1947 transition: Option<Rc<dyn IPageTransition>>,
1948 ) -> DispatcherTask<()> {
1949 let this = self.to_ref();
1950 let page = page.into_ref();
1951 start_async(async move {
1952 if this.is_navigating.get() {
1953 return;
1954 }
1955 let _override = this.begin_override_transition(transition);
1956 Self::pop_to_page_async_core(this.clone(), page).await;
1957 })
1958 }
1959
1960 fn take_effective_modal_transition(&self) -> Option<Rc<dyn IPageTransition>> {
1964 let override_transition = self.override_transition.borrow_mut().take();
1965 if self.has_override_transition.replace(false) {
1966 override_transition
1967 } else {
1968 self.modal_transition()
1969 }
1970 }
1971
1972 fn begin_modal_transition(&self) -> Rc<CancellationTokenSource> {
1975 let previous = self.current_modal_transition.borrow_mut().take();
1976 if let Some(previous) = previous {
1977 previous.cancel();
1978 }
1979 let cts = Rc::new(CancellationTokenSource::new());
1980 *self.current_modal_transition.borrow_mut() = Some(cts.clone());
1981 cts
1982 }
1983
1984 fn end_modal_transition(&self, cts: &Rc<CancellationTokenSource>) {
1985 let mut current = self.current_modal_transition.borrow_mut();
1986 if current.as_ref().is_some_and(|current| Rc::ptr_eq(current, cts)) {
1987 *current = None;
1988 }
1989 }
1990
1991 async fn run_modal_transition(
1994 this: &Ref<Self>,
1995 transition: &Rc<dyn IPageTransition>,
1996 from: Option<&Ref<ContentPresenter>>,
1997 to: Option<&Ref<ContentPresenter>>,
1998 forward: bool,
1999 cts: &Rc<CancellationTokenSource>,
2000 ) -> TransitionOutcome {
2001 let from: Option<Ref<Visual>> = from.map(|from| from.clone().upcast());
2002 let to: Option<Ref<Visual>> = to.map(|to| to.clone().upcast());
2003 let task = transition.start(from.as_ref(), to.as_ref(), forward, cts.token());
2004 let outcome = TransitionOutcomeFuture { task }.await;
2005 if let TransitionOutcome::Faulted = outcome {
2006 if let Some(logger) = Logger::try_get(LogEventLevel::Error, LogArea::CONTROL) {
2007 logger.log(Some(this as &dyn Any), "Modal transition threw an unhandled exception.");
2008 }
2009 }
2010 outcome
2011 }
2012
2013 async fn push_modal_async_private(this: Ref<Self>, page: Ref<Page>, parameter: Option<BoxedValue>) {
2014 if this.is_navigating.get() {
2015 return;
2016 }
2017 this.throw_if_page_is_already_present(&page);
2018
2019 this.set_is_navigating(true);
2020 let _navigating = NavigatingScope(this.clone());
2021
2022 let previous_modal = this.top_modal();
2023 let covered_page = previous_modal.clone().or_else(|| this.current_page());
2024
2025 if let Some(covered_page) = &covered_page {
2026 let navigating_args = NavigatingFromEventArgs::with_parameter(
2027 Some(page.clone()),
2028 NavigationType::PushModal,
2029 parameter.clone(),
2030 );
2031 Page::send_navigating_async(covered_page.clone(), navigating_args.clone()).await;
2032
2033 if navigating_args.cancel() {
2034 return;
2035 }
2036 }
2037
2038 this.modal_stack.borrow_mut().push(page.clone());
2039 *this.cached_modal_stack.borrow_mut() = None;
2040 page.set_navigation(Some(this.as_navigation()));
2041 page.set_in_navigation_page(true);
2042 page.set_safe_area_padding(this.safe_area_padding());
2043
2044 let effective_modal_transition = this.take_effective_modal_transition();
2045
2046 let modal_presenter = this.modal_presenter.borrow().clone();
2047 if let (Some(modal_presenter), Some(effective_modal_transition)) =
2048 (modal_presenter, effective_modal_transition)
2049 {
2050 let modal_back_presenter = this.modal_back_presenter.borrow().clone();
2051 if let (Some(previous_modal), Some(modal_back_presenter)) = (&previous_modal, modal_back_presenter) {
2052 modal_presenter.set_is_visible(false);
2053
2054 this.set_current_value(Self::modal_content_property(), Some(Control::boxed(page.clone())));
2055
2056 modal_back_presenter.set_content(Some(Control::boxed(previous_modal.clone())));
2057 modal_back_presenter.set_is_visible(true);
2058 } else {
2059 this.set_current_value(Self::modal_content_property(), Some(Control::boxed(page.clone())));
2060 }
2061 let modal_cts = this.begin_modal_transition();
2062 Self::run_modal_transition(
2063 &this,
2064 &effective_modal_transition,
2065 None,
2066 Some(&modal_presenter),
2067 true,
2068 &modal_cts,
2069 )
2070 .await;
2071
2072 let modal_back_presenter = this.modal_back_presenter.borrow().clone();
2073 if let Some(modal_back_presenter) = modal_back_presenter {
2074 modal_back_presenter.set_is_visible(false);
2075 modal_back_presenter.set_content(None);
2076 }
2077 this.end_modal_transition(&modal_cts);
2078 } else {
2079 this.set_current_value(Self::modal_content_property(), Some(Control::boxed(page.clone())));
2080 }
2081
2082 this.set_current_value(Self::is_modal_visible_property(), true);
2083
2084 if let Some(covered_page) = &covered_page {
2085 covered_page.send_navigated_from(&NavigatedFromEventArgs::with_parameter(
2086 Some(page.clone()),
2087 NavigationType::PushModal,
2088 parameter.clone(),
2089 ));
2090 }
2091 page.send_navigated_to(&NavigatedToEventArgs::with_parameter(
2092 covered_page,
2093 NavigationType::PushModal,
2094 parameter,
2095 ));
2096 Self::raise(&this.modal_pushed, &ModalPushedEventArgs::new(page));
2097 }
2098
2099 async fn push_modal_async_private_with_transition(
2100 this: Ref<Self>,
2101 page: Ref<Page>,
2102 transition: Option<Rc<dyn IPageTransition>>,
2103 parameter: Option<BoxedValue>,
2104 ) {
2105 if this.is_navigating.get() {
2106 return;
2107 }
2108 let _override = this.begin_override_transition(transition);
2109 Self::push_modal_async_private(this.clone(), page, parameter).await;
2110 }
2111
2112 pub fn push_modal_async(&self, page: impl IntoRef<Page>) -> DispatcherTask<()> {
2118 start_async(Self::push_modal_async_private(self.to_ref(), page.into_ref(), None))
2119 }
2120
2121 pub fn push_modal_async_with_transition(
2123 &self,
2124 page: impl IntoRef<Page>,
2125 transition: Option<Rc<dyn IPageTransition>>,
2126 ) -> DispatcherTask<()> {
2127 start_async(Self::push_modal_async_private_with_transition(self.to_ref(), page.into_ref(), transition, None))
2128 }
2129
2130 pub fn push_modal_async_with_parameter(
2133 &self,
2134 page: impl IntoRef<Page>,
2135 transition: Option<Rc<dyn IPageTransition>>,
2136 parameter: Option<BoxedValue>,
2137 ) -> DispatcherTask<()> {
2138 start_async(Self::push_modal_async_private_with_transition(
2139 self.to_ref(),
2140 page.into_ref(),
2141 transition,
2142 parameter,
2143 ))
2144 }
2145
2146 async fn pop_modal_async_core(this: Ref<Self>) -> Option<Ref<Page>> {
2147 if this.modal_stack.borrow().is_empty() {
2148 return None;
2149 }
2150 if this.is_navigating.get() {
2151 return None;
2152 }
2153
2154 this.set_is_navigating(true);
2155 let _navigating = NavigatingScope(this.clone());
2156
2157 let modal = this.top_modal()?;
2158 let revealed_page = {
2159 let below_top = {
2160 let stack = this.modal_stack.borrow();
2161 if stack.len() < 2 {
2162 None
2163 } else {
2164 Some(stack[stack.len() - 2].clone())
2165 }
2166 };
2167 match below_top {
2168 Some(page) => Some(page),
2169 None => this.current_page(),
2170 }
2171 };
2172
2173 let navigating_args = NavigatingFromEventArgs::new(revealed_page.clone(), NavigationType::PopModal);
2174 Page::send_navigating_async(modal, navigating_args.clone()).await;
2175
2176 if navigating_args.cancel() {
2177 return None;
2178 }
2179
2180 let modal = this.modal_stack.borrow_mut().pop()?;
2181 *this.cached_modal_stack.borrow_mut() = None;
2182
2183 modal.set_navigation(None);
2184 modal.set_in_navigation_page(false);
2185
2186 let effective_modal_transition = this.take_effective_modal_transition();
2187
2188 let next = this.top_modal();
2189 let modal_presenter = this.modal_presenter.borrow().clone();
2190 if let Some(next) = next {
2191 let next_content = Some(Control::boxed(next));
2192 if let (Some(modal_presenter), Some(effective_modal_transition)) =
2193 (modal_presenter, effective_modal_transition)
2194 {
2195 let modal_back_presenter = this.modal_back_presenter.borrow().clone();
2196 if let Some(modal_back_presenter) = modal_back_presenter {
2197 modal_back_presenter.set_content(next_content.clone());
2198 modal_back_presenter.set_is_visible(true);
2199 }
2200
2201 let pop_cts1 = this.begin_modal_transition();
2202 let outcome = Self::run_modal_transition(
2203 &this,
2204 &effective_modal_transition,
2205 Some(&modal_presenter),
2206 None,
2207 false,
2208 &pop_cts1,
2209 )
2210 .await;
2211 if let TransitionOutcome::Completed = outcome {
2212 this.swap_modal_presenters();
2213 let modal_back_presenter = this.modal_back_presenter.borrow().clone();
2214 if let Some(modal_back_presenter) = modal_back_presenter {
2215 modal_back_presenter.set_content(None);
2216 }
2217 }
2218
2219 this.set_current_value(Self::modal_content_property(), next_content);
2220 this.end_modal_transition(&pop_cts1);
2221 } else {
2222 this.set_current_value(Self::modal_content_property(), next_content);
2223 }
2224 } else if let (Some(modal_presenter), Some(effective_modal_transition)) =
2225 (modal_presenter, effective_modal_transition)
2226 {
2227 let pop_cts2 = this.begin_modal_transition();
2228 Self::run_modal_transition(
2229 &this,
2230 &effective_modal_transition,
2231 Some(&modal_presenter),
2232 None,
2233 false,
2234 &pop_cts2,
2235 )
2236 .await;
2237
2238 this.set_current_value(Self::is_modal_visible_property(), false);
2239 this.set_current_value(Self::modal_content_property(), None);
2240 this.end_modal_transition(&pop_cts2);
2241 } else {
2242 this.set_current_value(Self::is_modal_visible_property(), false);
2243 this.set_current_value(Self::modal_content_property(), None);
2244 }
2245
2246 modal.send_navigated_from(&NavigatedFromEventArgs::new(revealed_page.clone(), NavigationType::PopModal));
2247 if let Some(revealed_page) = revealed_page {
2248 revealed_page.send_navigated_to(&NavigatedToEventArgs::new(Some(modal.clone()), NavigationType::PopModal));
2249 }
2250
2251 Self::raise(&this.modal_popped, &ModalPoppedEventArgs::new(modal.clone()));
2252 Some(modal)
2253 }
2254
2255 pub fn pop_modal_async(&self) -> DispatcherTask<Option<Ref<Page>>> {
2261 start_async(Self::pop_modal_async_core(self.to_ref()))
2262 }
2263
2264 pub fn pop_modal_async_with_transition(
2266 &self,
2267 transition: Option<Rc<dyn IPageTransition>>,
2268 ) -> DispatcherTask<Option<Ref<Page>>> {
2269 let this = self.to_ref();
2270 start_async(async move {
2271 if this.is_navigating.get() {
2272 return None;
2273 }
2274 let _override = this.begin_override_transition(transition);
2275 Self::pop_modal_async_core(this.clone()).await
2276 })
2277 }
2278
2279 async fn pop_all_modals_async_core(this: Ref<Self>) {
2280 if this.modal_stack.borrow().is_empty() {
2281 return;
2282 }
2283 if this.is_navigating.get() {
2284 return;
2285 }
2286
2287 this.set_is_navigating(true);
2288 let _navigating = NavigatingScope(this.clone());
2289
2290 let Some(top_modal) = this.top_modal() else {
2291 return;
2292 };
2293 let revealed_page = this.current_page();
2294
2295 let navigating_args = NavigatingFromEventArgs::new(revealed_page.clone(), NavigationType::PopModal);
2296 Page::send_navigating_async(top_modal.clone(), navigating_args.clone()).await;
2297
2298 if navigating_args.cancel() {
2299 return;
2300 }
2301
2302 let effective_modal_transition = this.take_effective_modal_transition();
2303
2304 let modal_presenter = this.modal_presenter.borrow().clone();
2305 if let (Some(modal_presenter), Some(effective_modal_transition)) =
2306 (modal_presenter, effective_modal_transition)
2307 {
2308 let all_modals_cts = this.begin_modal_transition();
2309 Self::run_modal_transition(
2310 &this,
2311 &effective_modal_transition,
2312 Some(&modal_presenter),
2313 None,
2314 false,
2315 &all_modals_cts,
2316 )
2317 .await;
2318 this.end_modal_transition(&all_modals_cts);
2319 }
2320
2321 this.set_current_value(Self::modal_content_property(), None);
2322 this.set_current_value(Self::is_modal_visible_property(), false);
2323
2324 loop {
2325 let modal = this.modal_stack.borrow_mut().pop();
2326 let Some(modal) = modal else {
2327 break;
2328 };
2329 let next_page = this.top_modal().or_else(|| this.current_page());
2330 modal.send_navigated_from(&NavigatedFromEventArgs::new(next_page, NavigationType::PopModal));
2331 modal.set_navigation(None);
2332 modal.set_in_navigation_page(false);
2333 Self::raise(&this.modal_popped, &ModalPoppedEventArgs::new(modal));
2334 }
2335 *this.cached_modal_stack.borrow_mut() = None;
2336
2337 if let Some(revealed_page) = revealed_page {
2338 revealed_page.send_navigated_to(&NavigatedToEventArgs::new(Some(top_modal), NavigationType::PopModal));
2339 }
2340 }
2341
2342 pub fn pop_all_modals_async(&self) -> DispatcherTask<()> {
2356 start_async(Self::pop_all_modals_async_core(self.to_ref()))
2357 }
2358
2359 pub fn pop_all_modals_async_with_transition(
2361 &self,
2362 transition: Option<Rc<dyn IPageTransition>>,
2363 ) -> DispatcherTask<()> {
2364 let this = self.to_ref();
2365 start_async(async move {
2366 if this.is_navigating.get() {
2367 return;
2368 }
2369 let _override = this.begin_override_transition(transition);
2370 Self::pop_all_modals_async_core(this.clone()).await;
2371 })
2372 }
2373
2374 pub fn remove_page(&self, page: impl IntoRef<Page>) {
2379 let page = page.into_ref();
2380 if self.is_navigating.get() {
2381 return;
2382 }
2383
2384 if self.is_top_of_navigation_stack(&page) {
2385 let old = self.execute_pop_core();
2386 if let Some(old) = old {
2387 let new_current_page = self.current_page();
2388 old.send_navigated_from(&NavigatedFromEventArgs::new(new_current_page.clone(), NavigationType::Remove));
2389 if let Some(new_current_page) = new_current_page {
2390 new_current_page.send_navigated_to(&NavigatedToEventArgs::new(Some(old), NavigationType::Remove));
2391 }
2392 }
2393 Self::raise(&self.page_removed, &PageRemovedEventArgs::new(page));
2394 return;
2395 }
2396
2397 if !self.page_set_contains(&page) {
2398 return;
2399 }
2400
2401 let index = self.navigation_stack.snapshot().iter().position(|p| p.ptr_eq(&page));
2402 if let Some(index) = index {
2403 self.navigation_stack.remove_at(index);
2404 }
2405
2406 page.send_navigated_from(&NavigatedFromEventArgs::new(None, NavigationType::Remove));
2407
2408 page.set_navigation(None);
2409 page.set_in_navigation_page(false);
2410 page.set_safe_area_padding(Thickness::default());
2411
2412 StyledElement::logical_children(self).remove(&page.clone().upcast());
2413
2414 self.invalidate_navigation_stack_cache();
2415 self.update_is_back_button_effectively_visible();
2416
2417 Self::raise(&self.page_removed, &PageRemovedEventArgs::new(page));
2418 }
2419
2420 pub fn insert_page(&self, page: impl IntoRef<Page>, before: impl IntoRef<Page>) {
2429 let page = page.into_ref();
2430 let before = before.into_ref();
2431 if self.is_navigating.get() {
2432 return;
2433 }
2434
2435 self.throw_if_page_is_already_present(&page);
2436
2437 let before_idx = self.navigation_stack.snapshot().iter().position(|p| p.ptr_eq(&before));
2438 let Some(before_idx) = before_idx else {
2439 panic!("The 'before' page is not in the navigation stack.");
2440 };
2441
2442 self.navigation_stack.insert(before_idx + 1, page.clone());
2444
2445 page.set_navigation(Some(self.as_navigation()));
2446 page.set_in_navigation_page(true);
2447 page.set_safe_area_padding(self.safe_area_padding());
2448
2449 StyledElement::logical_children(self).add(page.clone().upcast());
2450
2451 self.invalidate_navigation_stack_cache();
2452 self.update_is_back_button_effectively_visible();
2453
2454 Self::raise(&self.page_inserted, &PageInsertedEventArgs::new(page, before));
2455 }
2456
2457 async fn replace_async_private(this: Ref<Self>, page: Ref<Page>, parameter: Option<BoxedValue>) {
2458 if this.stack_depth() == 0 {
2459 Self::push_async_private_with_transition(this.clone(), page, None, parameter).await;
2460 return;
2461 }
2462 if this.current_page().is_some_and(|current| current.ptr_eq(&page)) {
2463 return;
2464 }
2465 if this.is_navigating.get() {
2466 return;
2467 }
2468 this.throw_if_page_is_already_present(&page);
2469
2470 this.set_is_navigating(true);
2471 let _navigating = NavigatingScope(this.clone());
2472
2473 let previous_page = this.current_page();
2474
2475 if let Some(previous_page) = &previous_page {
2476 let navigating_args = NavigatingFromEventArgs::with_parameter(
2477 Some(page.clone()),
2478 NavigationType::Replace,
2479 parameter.clone(),
2480 );
2481 Page::send_navigating_async(previous_page.clone(), navigating_args.clone()).await;
2482 if navigating_args.cancel() {
2483 return;
2484 }
2485 }
2486
2487 this.execute_replace_core(&page, previous_page.as_ref());
2488
2489 this.await_page_transition_async().await;
2490
2491 if let Some(previous_page) = &previous_page {
2492 previous_page.set_navigation(None);
2493 previous_page.set_in_navigation_page(false);
2494 previous_page.set_safe_area_padding(Thickness::default());
2495 previous_page.send_navigated_from(&NavigatedFromEventArgs::with_parameter(
2496 Some(page.clone()),
2497 NavigationType::Replace,
2498 parameter.clone(),
2499 ));
2500 }
2501
2502 page.send_navigated_to(&NavigatedToEventArgs::with_parameter(
2503 previous_page,
2504 NavigationType::Replace,
2505 parameter,
2506 ));
2507 }
2508
2509 async fn replace_async_private_with_transition(
2510 this: Ref<Self>,
2511 page: Ref<Page>,
2512 transition: Option<Rc<dyn IPageTransition>>,
2513 parameter: Option<BoxedValue>,
2514 ) {
2515 if this.is_navigating.get() {
2516 return;
2517 }
2518 let _override = this.begin_override_transition(transition);
2519 Self::replace_async_private(this.clone(), page, parameter).await;
2520 }
2521
2522 pub fn replace_async(&self, page: impl IntoRef<Page>) -> DispatcherTask<()> {
2529 start_async(Self::replace_async_private(self.to_ref(), page.into_ref(), None))
2530 }
2531
2532 pub fn replace_async_with_transition(
2534 &self,
2535 page: impl IntoRef<Page>,
2536 transition: Option<Rc<dyn IPageTransition>>,
2537 ) -> DispatcherTask<()> {
2538 start_async(Self::replace_async_private_with_transition(self.to_ref(), page.into_ref(), transition, None))
2539 }
2540
2541 pub fn replace_async_with_parameter(
2544 &self,
2545 page: impl IntoRef<Page>,
2546 transition: Option<Rc<dyn IPageTransition>>,
2547 parameter: Option<BoxedValue>,
2548 ) -> DispatcherTask<()> {
2549 start_async(Self::replace_async_private_with_transition(
2550 self.to_ref(),
2551 page.into_ref(),
2552 transition,
2553 parameter,
2554 ))
2555 }
2556
2557 async fn run_page_transition_async(
2558 this: Ref<Self>,
2559 transition: Rc<dyn IPageTransition>,
2560 from: Ref<ContentPresenter>,
2561 to: Ref<ContentPresenter>,
2562 forward: bool,
2563 ct: Rc<CancellationTokenSource>,
2564 ) {
2565 let from_visual: Ref<Visual> = from.clone().upcast();
2566 let to_visual: Ref<Visual> = to.upcast();
2567 let task = transition.start(Some(&from_visual), Some(&to_visual), forward, ct.token());
2568 match (TransitionOutcomeFuture { task }).await {
2569 TransitionOutcome::Canceled => return,
2570 TransitionOutcome::Faulted => {
2571 if let Some(logger) = Logger::try_get(LogEventLevel::Error, LogArea::CONTROL) {
2572 logger.log(Some(&this as &dyn Any), "Page transition threw an unhandled exception.");
2573 }
2574 }
2575 TransitionOutcome::Completed => {}
2576 }
2577
2578 if ct.is_cancellation_requested() {
2579 return;
2580 }
2581
2582 let from_content = from.content();
2583 from.set_is_visible(false);
2584 from.set_content(None);
2585 from.set_render_transform(None);
2586 from.set_opacity(1.0);
2587 this.detach_orphaned_page_from_logical_tree(from_content.as_ref());
2588 }
2589
2590 async fn await_page_transition_async(&self) {
2592 let task = self.last_page_transition_task.borrow_mut().take();
2593 if let Some(task) = task {
2594 let _ = task.await;
2596 }
2597 }
2598
2599 fn send_pop_lifecycle_events(
2603 &self,
2604 old_page: &Ref<Page>,
2605 navigation_type: NavigationType,
2606 parameter: Option<BoxedValue>,
2607 ) {
2608 let new_current_page = self.current_page();
2609 old_page.send_navigated_from(&NavigatedFromEventArgs::with_parameter(
2610 new_current_page.clone(),
2611 navigation_type,
2612 parameter.clone(),
2613 ));
2614 if let Some(new_current_page) = new_current_page {
2615 new_current_page.send_navigated_to(&NavigatedToEventArgs::with_parameter(
2616 Some(old_page.clone()),
2617 navigation_type,
2618 parameter.clone(),
2619 ));
2620 }
2621 Self::raise(
2622 &self.popped,
2623 &NavigationEventArgs::with_parameter(old_page.clone(), navigation_type, parameter),
2624 );
2625 }
2626
2627 fn execute_replace_core(&self, page: &Ref<Page>, _replaced_page: Option<&Ref<Page>>) {
2629 let removed = self.pop_navigation_stack();
2630
2631 self.navigation_stack.insert(0, page.clone());
2632 self.invalidate_navigation_stack_cache();
2633
2634 if let Some(removed) = &removed {
2637 if !self.is_page_in_presenter(removed) {
2638 StyledElement::logical_children(self).remove(&removed.clone().upcast());
2639 }
2640 }
2641 StyledElement::logical_children(self).add(page.clone().upcast());
2642
2643 page.set_navigation(Some(self.as_navigation()));
2644 page.set_in_navigation_page(true);
2645
2646 self.update_active_page();
2647 }
2648
2649 fn detach_orphaned_page_from_logical_tree(&self, content: Option<&BoxedValue>) {
2650 let page = content.and_then(Control::from_boxed).and_then(|control| control.cast::<Page>());
2651 let Some(page) = page else {
2652 return;
2653 };
2654 let this: &StyledElement = self;
2655 if !self.page_set_contains(&page) && page.parent().is_some_and(|parent| std::ptr::eq(&*parent, this)) {
2656 StyledElement::logical_children(self).remove(&page.upcast());
2657 }
2658 }
2659
2660 fn is_page_in_presenter(&self, page: &Ref<Page>) -> bool {
2661 let holds_page = |presenter: &RefCell<Option<Ref<ContentPresenter>>>| {
2662 let presenter = presenter.borrow().clone();
2663 presenter
2664 .and_then(|presenter| presenter.content())
2665 .as_ref()
2666 .and_then(Control::from_boxed)
2667 .is_some_and(|content| content.ptr_eq(page))
2668 };
2669 holds_page(&self.page_presenter) || holds_page(&self.page_back_presenter)
2670 }
2671
2672 fn swap_modal_presenters(&self) {
2673 let modal_presenter = self.modal_presenter.borrow().clone();
2674 let modal_back_presenter = self.modal_back_presenter.borrow().clone();
2675 let (Some(modal_presenter), Some(modal_back_presenter)) = (modal_presenter, modal_back_presenter) else {
2676 return;
2677 };
2678
2679 let (z_index, back_z_index) = (modal_back_presenter.z_index(), modal_presenter.z_index());
2680 modal_presenter.set_z_index(z_index);
2681 modal_back_presenter.set_z_index(back_z_index);
2682
2683 *self.modal_presenter.borrow_mut() = Some(modal_back_presenter);
2684 *self.modal_back_presenter.borrow_mut() = Some(modal_presenter);
2685 }
2686
2687 fn invalidate_navigation_stack_cache(&self) {
2688 *self.cached_navigation_stack.borrow_mut() = None;
2689 }
2690
2691 fn restore_navigation_state(&self) {
2692 for page in self.navigation_stack().iter() {
2693 page.set_navigation(Some(self.as_navigation()));
2694 page.set_in_navigation_page(true);
2695 }
2696
2697 for modal in self.modal_stack_top_first() {
2698 modal.set_navigation(Some(self.as_navigation()));
2699 modal.set_in_navigation_page(true);
2700 }
2701 }
2702
2703 fn clear_navigation_state(&self) {
2704 for modal in self.modal_stack_top_first() {
2705 modal.set_navigation(None);
2706 modal.set_in_navigation_page(false);
2707 }
2708
2709 for page in self.navigation_stack().iter() {
2710 page.set_navigation(None);
2711 page.set_in_navigation_page(false);
2712 }
2713 }
2714
2715 pub(crate) fn update_is_back_button_effectively_visible(&self) {
2716 let depth = self.stack_depth();
2717
2718 let show_drawer_toggle = self.drawer_page_allows_toggle();
2719
2720 let current_page = self.current_page();
2721 self.set_is_back_button_effectively_visible(
2722 self.is_back_button_visible()
2723 && (depth > 1 || show_drawer_toggle)
2724 && current_page.is_some_and(|current_page| Self::get_has_back_button(¤t_page)),
2725 );
2726
2727 self.set_and_raise_cell(Self::can_go_back_property(), &self.can_go_back, depth > 1);
2728
2729 self.update_back_button_accessibility();
2730 }
2731
2732 fn update_is_back_button_effectively_enabled(&self) {
2733 let current_page = self.current_page();
2734 self.set_is_back_button_effectively_enabled(
2735 current_page.is_none_or(|current_page| Self::get_is_back_button_enabled(¤t_page)),
2736 );
2737 }
2738
2739 fn update_back_button_accessibility(&self) {
2740 let Some(back_button) = self.back_button() else {
2741 return;
2742 };
2743
2744 let is_drawer_toggle = self.stack_depth() <= 1 && self.drawer_page_allows_toggle();
2745
2746 let label = if is_drawer_toggle { "Toggle navigation drawer" } else { "Go back" };
2747
2748 AutomationProperties::set_name(&back_button, Some(label));
2749 let tip: BoxedValue = Rc::new(label.to_string());
2750 ToolTip::set_tip(&back_button, Some(tip));
2751 }
2752
2753 fn update_back_button_content(&self) {
2754 let current_page = self.current_page();
2755 let mut content = current_page.as_ref().and_then(|current_page| Self::get_back_button_content(current_page));
2756
2757 let show_toggle = current_page.is_some() && self.stack_depth() <= 1 && self.drawer_page_allows_toggle();
2758
2759 if content.is_none() && show_toggle {
2760 let icon_data = self.try_find_resource(&ResourceKey::from("NavigationPageMenuIcon"), None).flatten();
2761 let geometry = icon_data.as_ref().and_then(stream_geometry_of);
2762 if let Some(geometry) = geometry {
2763 let icon = PathIcon::new();
2764 icon.set_data(geometry);
2765 content = Some(Control::boxed(icon));
2766 }
2767 }
2768
2769 let back_button_default_icon = self.back_button_default_icon.borrow().clone();
2770 if let Some(back_button_default_icon) = back_button_default_icon {
2771 back_button_default_icon.set_is_visible(content.is_none());
2772 }
2773
2774 let back_button_content_presenter = self.back_button_content_presenter.borrow().clone();
2775 if let Some(back_button_content_presenter) = back_button_content_presenter {
2776 back_button_content_presenter.set_content(content.clone());
2777 back_button_content_presenter.set_is_visible(content.is_some());
2778 }
2779
2780 self.update_back_button_accessibility();
2781 }
2782
2783 fn on_swipe_gesture(&self, e: &SwipeGestureEventArgs) {
2784 if !self.is_gesture_enabled()
2785 || self.stack_depth() <= 1
2786 || self.is_navigating.get()
2787 || !self.modal_stack.borrow().is_empty()
2788 || e.id() == self.last_swipe_gesture_id.get()
2789 {
2790 return;
2791 }
2792
2793 let in_edge = if self.is_rtl() {
2794 self.swipe_start_point.get().x >= self.bounds().width - EDGE_GESTURE_WIDTH
2795 } else {
2796 self.swipe_start_point.get().x <= EDGE_GESTURE_WIDTH
2797 };
2798 if !in_edge {
2799 return;
2800 }
2801
2802 let should_pop = if self.is_rtl() {
2803 e.swipe_direction() == SwipeDirection::Left
2804 } else {
2805 e.swipe_direction() == SwipeDirection::Right
2806 };
2807 if should_pop {
2808 e.set_handled(true);
2809 self.last_swipe_gesture_id.set(e.id());
2810 drop(self.pop_async());
2812 }
2813 }
2814
2815 fn on_swipe_pointer_pressed(&self, e: &PointerPressedEventArgs) {
2816 self.swipe_start_point.set(e.get_position(Some(self)));
2817 }
2818
2819 fn update_is_nav_bar_effectively_visible(&self) {
2820 let nav_bar_visible = match self.current_page() {
2821 Some(current_page) => Self::get_has_navigation_bar(¤t_page),
2822 None => self.has_had_first_page.get(),
2823 };
2824 self.set_is_nav_bar_effectively_visible(nav_bar_visible);
2825 self.update_nav_bar_spacer();
2826 }
2827
2828 fn update_bar_layout_behavior_effective(&self) {
2829 self.effective_bar_layout_behavior.set(
2830 self.current_page()
2831 .and_then(|current_page| Self::get_bar_layout_behavior(¤t_page))
2832 .unwrap_or(BarLayoutBehavior::Inset),
2833 );
2834 self.update_nav_bar_spacer();
2835 }
2836
2837 fn update_nav_bar_spacer(&self) {
2838 self.pseudo_classes().set(
2839 PC_NAV_BAR_INSET,
2840 self.is_nav_bar_effectively_visible()
2841 && self.effective_bar_layout_behavior.get() == BarLayoutBehavior::Inset,
2842 );
2843 }
2844
2845 fn update_effective_bar_height(&self) {
2846 let content_bar_height = self
2847 .current_page()
2848 .and_then(|current_page| Self::get_bar_height_override(¤t_page))
2849 .unwrap_or_else(|| self.bar_height());
2850 self.set_effective_bar_height(content_bar_height + self.safe_area_padding().top);
2851 self.pseudo_classes().set(PC_NAV_BAR_COMPACT, content_bar_height < 40.0);
2852
2853 self.update_content_safe_area_padding();
2854 }
2855
2856 fn apply_nav_bar_visibility(&self) {
2857 if let Some(nav_bar) = self.nav_bar() {
2858 nav_bar.set_is_visible(self.is_nav_bar_effectively_visible());
2859 }
2860 }
2861
2862 fn apply_back_button_enabled(&self, enabled: bool) {
2863 if let Some(back_button) = self.back_button() {
2864 back_button.set_is_enabled(enabled);
2865 }
2866 }
2867
2868 fn apply_has_shadow(&self) {
2869 let nav_bar_shadow = self.nav_bar_shadow.borrow().clone();
2870 let Some(nav_bar_shadow) = nav_bar_shadow else {
2871 return;
2872 };
2873 nav_bar_shadow.set_margin(Thickness::new(0.0, self.effective_bar_height(), 0.0, 0.0));
2874 nav_bar_shadow.set_is_visible(self.has_shadow() && self.is_nav_bar_effectively_visible());
2875 }
2876}
2877
2878fn stream_geometry_of(value: &BoxedValue) -> Option<Ref<StreamGeometry>> {
2880 let value: &dyn AnyValue = &**value;
2881 if let Some(geometry) = value.downcast_ref::<Ref<StreamGeometry>>() {
2882 return Some(geometry.clone());
2883 }
2884 value.downcast_ref::<Ref<Geometry>>().and_then(|geometry| geometry.clone().cast::<StreamGeometry>())
2885}