Skip to main content

ferroui_controls/page/
navigation_page.rs

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/// A navigation page that supports simple stack-based navigation.
49#[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    /// `None` stands for a completed task.
60    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    /// The navigation stack, as the reference stack enumerates: the visible
68    /// (top) page first, the root page last. It is the value of the `Pages`
69    /// property.
70    navigation_stack: PageList,
71    /// The modal stack: the oldest modal first, the topmost modal last.
72    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    /// The drawer page hosting this navigation page; set by `set_drawer_page`.
92    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        // XAML-SEAM: the `Content` property depends on `PageTemplate` (markup assignment order).
114        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        // The navigation stack is mutated by the navigation methods only, which maintain the logical
141        // children and the active page themselves (the stack of the reference does not notify of changes).
142        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            // A key handler cannot be asynchronous; fire-and-forget is intentional.
264            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    // The navigation type is intentionally unused; lifecycle events are fired in each navigation method
368    // directly and the transition direction is controlled by `is_pop`.
369    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            // A previous transition may have been canceled above before its teardown ran, leaving its
398            // outgoing page in the back presenter; reconcile the logical tree.
399            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
534/// The interface handle of a navigation page: what the pages it hosts hold
535/// as their `Navigation`. It does not keep the navigation page alive (the
536/// navigation page owns the pages).
537struct 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
759/// How a page transition ended.
760enum TransitionOutcome {
761    Completed,
762    Canceled,
763    Faulted,
764}
765
766/// Waits for a page transition without re-raising its failure.
767struct 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
787/// Clears the navigating flag when a navigation ends, however it ends (the
788/// `finally` block of the navigation methods).
789struct NavigatingScope(Ref<NavigationPage>);
790
791impl Drop for NavigatingScope {
792    fn drop(&mut self) {
793        self.0.set_is_navigating(false);
794    }
795}
796
797/// Clears the transition override when a navigation with an explicit
798/// transition ends, however it ends.
799struct 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        /// Defines the `Content` property.
811        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        /// Defines the `PageTransition` property.
824        pub fn page_transition_property() -> StyledProperty<Option<Rc<dyn IPageTransition>>> {
825            FerroProperty::register::<NavigationPage, _>("PageTransition", None)
826        }
827
828        /// Defines the `ModalTransition` property.
829        pub fn modal_transition_property() -> StyledProperty<Option<Rc<dyn IPageTransition>>> {
830            FerroProperty::register::<NavigationPage, _>("ModalTransition", None)
831        }
832
833        /// Defines the `IsBackButtonEffectivelyVisible` property.
834        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        /// Defines the `IsNavBarEffectivelyVisible` property.
844        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        /// Defines the `BarLayoutBehavior` attached property.
854        pub fn bar_layout_behavior_property() -> AttachedProperty<Option<BarLayoutBehavior>> {
855            FerroProperty::register_attached::<NavigationPage, Page, _>("BarLayoutBehavior", None)
856        }
857
858        /// Defines the `HasShadow` property.
859        pub fn has_shadow_property() -> StyledProperty<bool> {
860            FerroProperty::register::<NavigationPage, _>("HasShadow", false)
861        }
862
863        /// Defines the `BarHeight` property.
864        pub fn bar_height_property() -> StyledProperty<f64> {
865            FerroProperty::register::<NavigationPage, _>("BarHeight", 48.0)
866        }
867
868        /// Defines the `BarHeightOverride` attached property.
869        pub fn bar_height_override_property() -> AttachedProperty<Option<f64>> {
870            FerroProperty::register_attached::<NavigationPage, Page, _>("BarHeightOverride", None)
871        }
872
873        /// Defines the `EffectiveBarHeight` property.
874        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        /// Defines the `BackButtonContent` attached property.
884        pub fn back_button_content_property() -> AttachedProperty<Option<BoxedValue>> {
885            FerroProperty::register_attached::<NavigationPage, Page, _>("BackButtonContent", None)
886        }
887
888        /// Defines the `HasBackButton` attached property.
889        pub fn has_back_button_property() -> AttachedProperty<bool> {
890            FerroProperty::register_attached::<NavigationPage, Page, _>("HasBackButton", true)
891        }
892
893        /// Defines the `IsBackButtonVisible` property.
894        pub fn is_back_button_visible_property() -> StyledProperty<bool> {
895            FerroProperty::register::<NavigationPage, _>("IsBackButtonVisible", true)
896        }
897
898        /// Defines the `TopCommandBar` attached property.
899        pub fn top_command_bar_property() -> AttachedProperty<Option<Ref<Control>>> {
900            FerroProperty::register_attached::<NavigationPage, Page, _>("TopCommandBar", None)
901        }
902
903        /// Defines the `BottomCommandBar` attached property.
904        pub fn bottom_command_bar_property() -> AttachedProperty<Option<Ref<Control>>> {
905            FerroProperty::register_attached::<NavigationPage, Page, _>("BottomCommandBar", None)
906        }
907
908        /// Defines the `HasNavigationBar` attached property.
909        pub fn has_navigation_bar_property() -> AttachedProperty<bool> {
910            FerroProperty::register_attached::<NavigationPage, Page, _>("HasNavigationBar", true)
911        }
912
913        /// Defines the `IsGestureEnabled` property.
914        pub fn is_gesture_enabled_property() -> StyledProperty<bool> {
915            FerroProperty::register::<NavigationPage, _>("IsGestureEnabled", true)
916        }
917
918        /// Defines the `IsNavigating` property.
919        pub fn is_navigating_property() -> DirectProperty<NavigationPage, bool> {
920            FerroProperty::register_direct::<NavigationPage, _>("IsNavigating", |o| o.is_navigating.get(), None, false)
921        }
922
923        /// Defines the `CanGoBack` property.
924        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        /// Defines the `IsBackButtonEnabled` attached property.
929        pub fn is_back_button_enabled_property() -> AttachedProperty<bool> {
930            FerroProperty::register_attached::<NavigationPage, Page, _>("IsBackButtonEnabled", true)
931        }
932
933        /// Defines the `IsBackButtonEffectivelyEnabled` property.
934        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            // A property-changed handler cannot be asynchronous; fire-and-forget is intentional.
1039            drop(x.push_async(page));
1040        });
1041    }
1042
1043    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
1044    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    /// Initializes a new instance of the navigation page.
1098    pub fn new() -> Ref<Self> {
1099        instantiate(Self::construct())
1100    }
1101
1102    /// The navigation page as the navigation service of the pages it hosts.
1103    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    /// The transition used when pushing or popping pages.
1116    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    /// The transition used when presenting or dismissing modal pages.
1125    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    /// The effective back-button visibility.
1152    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    /// The root page of the navigation stack.
1165    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    /// The effective navigation bar visibility.
1174    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    /// Whether the navigation bar has a shadow.
1187    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    /// The height of the navigation bar.
1196    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    /// The effective navigation bar height.
1205    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    /// Whether the back button is globally visible for this navigation
1214    /// page.
1215    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    /// Whether edge-swipe gestures can be used to navigate back.
1224    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    /// Whether a navigation operation is currently in progress.
1233    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    /// Whether the navigation stack has more than one entry.
1242    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    /// The current navigation stack as a read-only list: the root page
1259    /// first, the visible page last. The same list is returned until the
1260    /// stack changes.
1261    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    /// The current modal stack. Index 0 is the oldest (bottom-most) modal;
1273    /// the last index is the most recently pushed (topmost) modal.
1274    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    /// The number of pages in the navigation stack.
1285    pub fn stack_depth(&self) -> i32 {
1286        self.navigation_stack.count() as i32
1287    }
1288
1289    /// Gets the custom back-button content for the specified page.
1290    pub fn get_back_button_content(page: &Page) -> Option<BoxedValue> {
1291        page.get_value(&**Self::back_button_content_property())
1292    }
1293
1294    /// Sets custom content for the back button on the specified page.
1295    pub fn set_back_button_content(page: &Page, content: Option<BoxedValue>) {
1296        page.set_value(&**Self::back_button_content_property(), content)
1297    }
1298
1299    /// Gets whether the back button is visible for the specified page.
1300    pub fn get_has_back_button(page: &Page) -> bool {
1301        page.get_value(&**Self::has_back_button_property())
1302    }
1303
1304    /// Sets whether the back button is visible for the specified page.
1305    pub fn set_has_back_button(page: &Page, value: bool) {
1306        page.set_value(&**Self::has_back_button_property(), value)
1307    }
1308
1309    /// Gets the header of the specified page.
1310    pub fn get_header(page: &Page) -> Option<BoxedValue> {
1311        page.get_value(Page::header_property())
1312    }
1313
1314    /// Sets the header of the specified page.
1315    pub fn set_header(page: &Page, header: Option<BoxedValue>) {
1316        page.set_value(Page::header_property(), header)
1317    }
1318
1319    /// Gets the top command bar assigned to the specified page.
1320    pub fn get_top_command_bar(page: &Page) -> Option<Ref<Control>> {
1321        page.get_value(&**Self::top_command_bar_property())
1322    }
1323
1324    /// Sets a top command bar for the specified page.
1325    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    /// Gets the bottom command bar assigned to the specified page.
1330    pub fn get_bottom_command_bar(page: &Page) -> Option<Ref<Control>> {
1331        page.get_value(&**Self::bottom_command_bar_property())
1332    }
1333
1334    /// Sets a bottom command bar for the specified page.
1335    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    /// Gets whether the navigation bar is visible for the specified page.
1340    pub fn get_has_navigation_bar(page: &Page) -> bool {
1341        page.get_value(&**Self::has_navigation_bar_property())
1342    }
1343
1344    /// Sets whether the navigation bar is visible for the specified page.
1345    pub fn set_has_navigation_bar(page: &Page, value: bool) {
1346        page.set_value(&**Self::has_navigation_bar_property(), value)
1347    }
1348
1349    /// Gets the bar layout behavior for the specified page.
1350    pub fn get_bar_layout_behavior(page: &Page) -> Option<BarLayoutBehavior> {
1351        page.get_value(&**Self::bar_layout_behavior_property())
1352    }
1353
1354    /// Sets the bar layout behavior for the specified page.
1355    pub fn set_bar_layout_behavior(page: &Page, value: Option<BarLayoutBehavior>) {
1356        page.set_value(&**Self::bar_layout_behavior_property(), value)
1357    }
1358
1359    /// Gets the per-page navigation bar height override for the specified
1360    /// page.
1361    pub fn get_bar_height_override(page: &Page) -> Option<f64> {
1362        page.get_value(&**Self::bar_height_override_property())
1363    }
1364
1365    /// Sets the per-page navigation bar height override.
1366    pub fn set_bar_height_override(page: &Page, value: Option<f64>) {
1367        page.set_value(&**Self::bar_height_override_property(), value)
1368    }
1369
1370    /// Gets whether the back button is enabled for the specified page.
1371    pub fn get_is_back_button_enabled(page: &Page) -> bool {
1372        page.get_value(&**Self::is_back_button_enabled_property())
1373    }
1374
1375    /// Sets whether the back button is enabled for the specified page.
1376    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    /// Occurs when a page is pushed onto the navigation stack. Disposing
1395    /// the returned handle unsubscribes.
1396    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    /// Occurs when a page is popped from the navigation stack. Disposing
1401    /// the returned handle unsubscribes.
1402    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    /// Occurs when the stack is popped to root. Disposing the returned
1407    /// handle unsubscribes.
1408    ///
1409    /// The page of the args is the root page that is now current, not any
1410    /// of the pages that were popped. To observe each popped page
1411    /// individually, subscribe to [`popped`](Self::popped).
1412    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    /// Occurs when a page has been inserted into the navigation stack.
1417    /// Disposing the returned handle unsubscribes.
1418    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    /// Occurs when a page has been removed from the navigation stack.
1423    /// Disposing the returned handle unsubscribes.
1424    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    /// Occurs when a modal page is pushed. Disposing the returned handle
1429    /// unsubscribes.
1430    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    /// Occurs when a modal page is popped. Disposing the returned handle
1435    /// unsubscribes.
1436    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    /// The modal stack, the topmost modal first.
1471    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    /// Whether the page is in the navigation stack (the page set of the
1480    /// reference, which always mirrors the stack).
1481    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    /// Removes and returns the top of the navigation stack.
1486    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    /// The drawer page hosting this navigation page.
1535    fn drawer_page(&self) -> Option<Ref<DrawerPage>> {
1536        self.drawer_page.borrow().as_ref().and_then(WeakRef::upgrade)
1537    }
1538
1539    /// Whether a drawer page hosts this navigation page and lets the back
1540    /// button toggle its drawer.
1541    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    /// Returns the page that would become active after a pop.
1567    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    /// Performs the stack mutation and lifecycle events for a push. The
1586    /// visual transition runs subsequently via the active page update.
1587    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    /// Performs the stack mutation for a pop. The visual transition runs
1602    /// subsequently via the active page update. Callers are responsible for
1603    /// firing lifecycle events via `send_pop_lifecycle_events` after
1604    /// awaiting the page transition where possible.
1605    fn execute_pop_core(&self) -> Option<Ref<Page>> {
1606        let old = self.pop_navigation_stack();
1607
1608        // Remove the page from the logical tree only if it isn't in a presenter.
1609        // If it is, it's animating out and will be removed once the animation completes.
1610        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    /// Pushes `page` onto the navigation stack asynchronously using the
1688    /// page transition of the navigation page.
1689    ///
1690    /// If a navigation transition is already in progress, the call returns
1691    /// immediately without pushing the page and without raising any events.
1692    /// If a `Navigating` handler of the outgoing page cancels, the push is
1693    /// silently aborted: the stack is not modified, no events are raised,
1694    /// and nothing panics.
1695    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    /// Pushes `page` onto the navigation stack asynchronously using
1700    /// `transition`.
1701    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    /// Pushes `page` onto the navigation stack asynchronously using
1710    /// `transition`, with an optional `parameter`.
1711    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    /// Pops the top page from the navigation stack asynchronously using the
1754    /// page transition of the navigation page.
1755    ///
1756    /// The result is `None` if the stack has only the root page or if a
1757    /// navigation transition is already in progress.
1758    pub fn pop_async(&self) -> DispatcherTask<Option<Ref<Page>>> {
1759        start_async(Self::pop_async_core(self.to_ref()))
1760    }
1761
1762    /// Pops the top page from the navigation stack asynchronously using
1763    /// `transition`.
1764    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) = &current_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    /// Pops all pages to the root page using the page transition of the
1835    /// navigation page.
1836    ///
1837    /// If a navigation transition is already in progress, the call returns
1838    /// immediately without modifying the stack and without raising any
1839    /// events.
1840    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        // Remove the page from the logical tree only if it isn't in a presenter.
1846        // If it is, it will be removed once the animation is complete.
1847        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    /// Pops all pages to the root page using `transition`.
1857    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) = &current_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    /// Pops to a specific page in the stack using the page transition of
1926    /// the navigation page.
1927    ///
1928    /// All pages above `page` are removed from the stack. Each removed page
1929    /// is navigated from with [`NavigationType::Pop`], and the popped event
1930    /// is raised for each one. The target page is navigated to with
1931    /// [`NavigationType::Pop`]. If `page` is already the top of the stack
1932    /// the method returns immediately without raising any events.
1933    ///
1934    /// If a navigation transition is already in progress, the call returns
1935    /// immediately without modifying the stack and without raising any
1936    /// events.
1937    ///
1938    /// The task fails when `page` is not in the navigation stack.
1939    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    /// Pops all pages above `page` using `transition`.
1944    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    /// The modal transition of the navigation that is starting: the
1961    /// override if there is one, else the modal transition of the
1962    /// navigation page. Consumes the override.
1963    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    /// Cancels the running modal transition and makes a new cancellation
1973    /// source the current one.
1974    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    /// Runs a modal transition to its end. A cancelled transition and a
1992    /// failed one both end normally: lifecycle events still fire afterwards.
1993    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    /// Pushes a modal page using the modal transition of the navigation
2113    /// page.
2114    ///
2115    /// If a navigation transition is already in progress, the call returns
2116    /// immediately without pushing the page and without raising any events.
2117    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    /// Pushes `page` as a modal page using `transition`.
2122    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    /// Pushes `page` as a modal page using `transition`, with an optional
2131    /// `parameter`.
2132    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    /// Pops the top modal page using the modal transition of the
2256    /// navigation page.
2257    ///
2258    /// The result is `None` if there are no modal pages or if a navigation
2259    /// transition is already in progress.
2260    pub fn pop_modal_async(&self) -> DispatcherTask<Option<Ref<Page>>> {
2261        start_async(Self::pop_modal_async_core(self.to_ref()))
2262    }
2263
2264    /// Pops the top modal page using `transition`.
2265    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    /// Pops all modal pages using the modal transition of the navigation
2343    /// page.
2344    ///
2345    /// All modals are dismissed in a single transition rather than
2346    /// one-by-one, so lifecycle events differ from popping the modals in a
2347    /// loop:
2348    ///
2349    /// - The `Navigating` event is consulted only on the topmost modal.
2350    ///   Intermediate modals do not receive a cancellation opportunity. If
2351    ///   the top modal cancels, the entire dismiss is aborted and no modals
2352    ///   are popped.
2353    /// - Every dismissed modal is navigated from, in LIFO order.
2354    /// - Only the current page is navigated to.
2355    pub fn pop_all_modals_async(&self) -> DispatcherTask<()> {
2356        start_async(Self::pop_all_modals_async_core(self.to_ref()))
2357    }
2358
2359    /// Pops all modal pages using `transition`.
2360    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    /// Removes a page from the navigation stack without animation.
2375    ///
2376    /// If a navigation transition is already in progress, this call is a
2377    /// no-op: the page is not removed and no events are raised.
2378    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    /// Inserts a page into the stack before the specified page.
2421    ///
2422    /// If a navigation transition is already in progress, this call is a
2423    /// no-op: the page is not inserted and no events are raised.
2424    ///
2425    /// # Panics
2426    /// Panics when `before` is not in the navigation stack, or `page` is
2427    /// already hosted by this navigation page.
2428    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        // The page goes below `before`: right after it in the top-first order of the stack.
2443        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    /// Replaces the top page with `page` using the page transition of the
2523    /// navigation page.
2524    ///
2525    /// If a navigation transition is already in progress, the call returns
2526    /// immediately without modifying the stack and without raising any
2527    /// events.
2528    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    /// Replaces the top page with `page` using `transition`.
2533    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    /// Replaces the top page with `page` using `transition`, with an
2542    /// optional `parameter`.
2543    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    /// Awaits the page transition started by the last active page update.
2591    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            // A transition whose task was dropped by a dispatcher shutdown ends the wait as well.
2595            let _ = task.await;
2596        }
2597    }
2598
2599    /// Fires lifecycle events after a pop: the old page is navigated from,
2600    /// the new current page is navigated to, and the popped event is
2601    /// raised.
2602    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    /// Swaps the top of the navigation stack with `page`.
2628    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        // Remove the replaced page from the logical tree only if it isn't in a presenter.
2635        // If it is, it's animating out and will be removed once the animation completes.
2636        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(&current_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(&current_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            // A gesture handler cannot be asynchronous; fire-and-forget is intentional.
2811            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(&current_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(&current_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(&current_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
2878/// The stream geometry held by a resource value, if it holds one.
2879fn 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}