ferroui_controls/page/
page_navigation_host.rs1use super::{DefaultPageDataTemplate, NavigatedFromEventArgs, NavigatedToEventArgs, NavigationType, Page};
2use crate::platform::{IInsetsManager, SafeAreaChangedArgs};
3use crate::presenters::ContentPresenter;
4use crate::primitives::{TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt};
5use crate::templates::IDataTemplate;
6use crate::{ContentControl, ContentControlImpl, Control, ControlImpl, TopLevel};
7use ferroui_base::input::InputElementImpl;
8use ferroui_base::interactivity::{InteractiveImpl, RoutedEventArgs, RoutedEventHandlerToken};
9use ferroui_base::layout::LayoutableImpl;
10use ferroui_base::reactive::IDisposable;
11use ferroui_base::{
12 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, FerroObjectImpl,
13 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, Ref, StaticType, StyledElementImpl,
14 StyledProperty, Thickness, TypeInfo, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
15};
16use std::cell::RefCell;
17use std::rc::Rc;
18
19struct TopLevelSubscriptions {
21 top_level: Ref<TopLevel>,
22 back_requested: RoutedEventHandlerToken,
23 scaling_changed: Rc<dyn IDisposable>,
24}
25
26struct InsetsSubscription {
28 insets_manager: Rc<dyn IInsetsManager>,
29 safe_area_changed: Rc<dyn IDisposable>,
30}
31
32#[repr(C)]
35pub struct PageNavigationHost {
36 base: ContentControl,
37 top_level: RefCell<Option<TopLevelSubscriptions>>,
38 inset_manager: RefCell<Option<InsetsSubscription>>,
39 content_presenter: RefCell<Option<Ref<ContentPresenter>>>,
40 content_presenter_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
44}
45
46ferro_class!(PageNavigationHost: ContentControl);
47ferro_class_info!(PageNavigationHost { new: PageNavigationHost::new });
48ferro_impl_classes!(
49 PageNavigationHost: LayoutableImpl,
50 InteractiveImpl,
51 InputElementImpl,
52 ControlImpl,
53 ContentControlImpl
54);
55
56impl FerroObjectImpl for PageNavigationHost {
57 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
58 Self::parent_on_property_changed(this, change);
59
60 if change.property() == Self::page_property().as_property() {
61 let (old_page, new_page) = change.get_old_and_new_value::<Option<Ref<Page>>>();
62
63 this.set_current_value(
64 ContentControl::content_property(),
65 new_page.as_ref().map(|page| Control::boxed(page.clone())),
66 );
67
68 if let (Some(inset_manager), Some(new_page)) = (this.inset_manager(), &new_page) {
69 new_page.set_safe_area_padding(inset_manager.safe_area_padding());
70 }
71
72 if let Some(old_page) = &old_page {
73 old_page.send_navigated_from(&NavigatedFromEventArgs::new(new_page.clone(), NavigationType::Replace));
74 }
75 if let Some(new_page) = &new_page {
76 new_page.send_navigated_to(&NavigatedToEventArgs::new(old_page.clone(), NavigationType::Replace));
77 }
78 }
79 }
80}
81
82impl StyledElementImpl for PageNavigationHost {
83 fn style_key_override(_this: &Self) -> &'static TypeInfo {
84 <ContentControl as StaticType>::TYPE
85 }
86}
87
88impl VisualImpl for PageNavigationHost {
89 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
90 Self::parent_on_attached_to_visual_tree(this, e);
91
92 this.clean_up_subscriptions();
93
94 let top_level = TopLevel::get_top_level(Some(this));
95 let inset_manager = top_level.as_ref().and_then(|top_level| top_level.insets_manager());
96
97 if let Some(inset_manager) = inset_manager {
98 let weak = this.to_ref().downgrade();
99 let safe_area_changed = inset_manager.safe_area_changed(Rc::new(move |e| {
100 if let Some(this) = weak.upgrade() {
101 this.inset_manager_safe_area_changed(e);
102 }
103 }));
104 inset_manager.set_display_edge_to_edge_preference(true);
105 *this.inset_manager.borrow_mut() = Some(InsetsSubscription { insets_manager: inset_manager, safe_area_changed });
106 }
107
108 if let Some(top_level) = top_level {
109 let weak = this.to_ref().downgrade();
110 let back_requested = top_level.back_requested(move |_, e| {
111 if let Some(this) = weak.upgrade() {
112 this.top_level_back_requested(e);
113 }
114 });
115 let weak = this.to_ref().downgrade();
116 let scaling_changed = top_level.scaling_changed(move || {
117 if let Some(this) = weak.upgrade() {
118 this.top_level_scaling_changed();
119 }
120 });
121 *this.top_level.borrow_mut() = Some(TopLevelSubscriptions { top_level, back_requested, scaling_changed });
122 }
123
124 this.attach_content_presenter();
125 }
126
127 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
128 Self::parent_on_detached_from_visual_tree(this, e);
129
130 this.clean_up_subscriptions();
131
132 if this.content_presenter.borrow().is_some() {
133 this.remove_content_presenter_property_changed();
134 }
135 }
136}
137
138impl TemplatedControlImpl for PageNavigationHost {
139 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
140 Self::parent_on_apply_template(this, e);
141
142 if this.content_presenter.borrow().is_some() {
143 this.remove_content_presenter_property_changed();
144 }
145
146 *this.content_presenter.borrow_mut() = Some(e.name_scope().get_as::<ContentPresenter>("PART_ContentPresenter"));
147
148 this.attach_content_presenter();
149 }
150}
151
152ferro_properties! {
153 impl PageNavigationHost {
154 pub fn page_property() -> StyledProperty<Option<Ref<Page>>> {
156 FerroProperty::register::<PageNavigationHost, _>("Page", None)
157 }
158 }
159}
160
161impl PageNavigationHost {
162 fn static_constructor() {
163 let template: Rc<dyn IDataTemplate> = DefaultPageDataTemplate::new();
164 ContentControl::content_template_property().override_default_value::<PageNavigationHost>(Some(template));
165 TopLevel::auto_safe_area_padding_property().override_default_value::<PageNavigationHost>(false);
166 }
167
168 pub fn construct() -> Self {
170 Self {
171 base: ContentControl::construct(),
172 top_level: RefCell::new(None),
173 inset_manager: RefCell::new(None),
174 content_presenter: RefCell::new(None),
175 content_presenter_subscription: RefCell::new(None),
176 }
177 }
178
179 pub fn new() -> Ref<Self> {
180 instantiate(Self::construct())
181 }
182
183 pub fn page(&self) -> Option<Ref<Page>> {
185 self.get_value(Self::page_property())
186 }
187
188 pub fn set_page(&self, value: impl Into<Nullable<Page>>) {
189 self.set_value(Self::page_property(), value.into().0)
190 }
191
192 fn inset_manager(&self) -> Option<Rc<dyn IInsetsManager>> {
193 self.inset_manager.borrow().as_ref().map(|subscription| subscription.insets_manager.clone())
194 }
195
196 fn presented_page(presenter: Option<Ref<ContentPresenter>>) -> Option<Ref<Page>> {
199 presenter.and_then(|presenter| presenter.child()).and_then(|child| child.cast::<Page>())
200 }
201
202 fn top_level_scaling_changed(&self) {
203 let content_presenter = self.content_presenter.borrow().clone();
204 if let (Some(inset_manager), Some(page)) = (self.inset_manager(), Self::presented_page(content_presenter)) {
205 page.set_safe_area_padding(inset_manager.safe_area_padding());
206 }
207 }
208
209 fn clean_up_subscriptions(&self) {
210 let inset_manager = self.inset_manager.borrow_mut().take();
211 if let Some(inset_manager) = inset_manager {
212 inset_manager.safe_area_changed.dispose();
213 }
214
215 let top_level = self.top_level.borrow_mut().take();
216 if let Some(top_level) = top_level {
217 top_level.top_level.remove_handler(TopLevel::back_requested_event(), top_level.back_requested);
218 top_level.scaling_changed.dispose();
219 }
220 }
221
222 fn remove_content_presenter_property_changed(&self) {
225 let subscription = self.content_presenter_subscription.borrow_mut().take();
226 if let Some(subscription) = subscription {
227 subscription.dispose();
228 }
229 }
230
231 fn attach_content_presenter(&self) {
232 let Some(content_presenter) = self.content_presenter.borrow().clone() else {
233 return;
234 };
235
236 if let (Some(inset_manager), Some(page)) =
237 (self.inset_manager(), Self::presented_page(Some(content_presenter.clone())))
238 {
239 page.set_safe_area_padding(inset_manager.safe_area_padding());
240 }
241
242 if self.is_attached_to_visual_tree() {
243 let weak = self.to_ref().downgrade();
244 let subscription = content_presenter.property_changed(move |e| {
245 if let Some(this) = weak.upgrade() {
246 this.content_presenter_property_changed(e);
247 }
248 });
249 let previous = self.content_presenter_subscription.borrow_mut().replace(subscription);
250 if let Some(previous) = previous {
251 previous.dispose();
252 }
253 }
254 }
255
256 fn content_presenter_property_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
257 if e.property() != ContentPresenter::child_property().as_property() {
258 return;
259 }
260
261 let (old_value, new_value) = e.get_old_and_new_value::<Option<Ref<Control>>>();
262
263 if let Some(old_page) = old_value.and_then(|old| old.cast::<Page>()) {
264 old_page.set_safe_area_padding(Thickness::default());
265 }
266
267 if let (Some(new_page), Some(inset_manager)) =
268 (new_value.and_then(|new| new.cast::<Page>()), self.inset_manager())
269 {
270 new_page.set_safe_area_padding(inset_manager.safe_area_padding());
271 }
272 }
273
274 fn top_level_back_requested(&self, e: &RoutedEventArgs) {
275 if e.handled() {
276 return;
277 }
278
279 if let Some(page) = Self::presented_page(self.presenter()) {
280 let forwarded = RoutedEventArgs::with_event(Page::page_navigation_system_back_button_pressed_event());
281 page.raise_event(&forwarded);
282 e.set_handled(forwarded.handled());
283 }
284 }
285
286 fn inset_manager_safe_area_changed(&self, e: &SafeAreaChangedArgs) {
287 if self.content().is_some() {
288 if let Some(page) = Self::presented_page(self.presenter()) {
289 page.set_safe_area_padding(e.safe_area_padding());
290 }
291 }
292 }
293}