1use super::{INavigation, NavigatedFromEventArgs, NavigatedToEventArgs, NavigatingFromEventArgs};
2use crate::automation::AutomationProperties;
3use crate::i_headered::{register_headered, IHeadered};
4use crate::primitives::{TemplatedControl, TemplatedControlImpl};
5use crate::templates::IDataTemplate;
6use crate::{ControlImpl, ControlImplExt};
7use ferroui_base::input::InputElementImpl;
8use ferroui_base::interactivity::{
9 Interactive, InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
10};
11use ferroui_base::layout::{Layoutable, LayoutableImpl};
12use ferroui_base::reactive::{Disposable, IDisposable};
13use ferroui_base::utilities::HandlerList;
14use ferroui_base::{
15 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, ferro_routed_event, AnyValue, BoxedValue,
16 DirectProperty, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Nullable, Ref,
17 StyledElementImpl, StyledProperty, StyledPropertyOptions, Thickness, VisualImpl,
18};
19use std::cell::RefCell;
20use std::future::Future;
21use std::pin::Pin;
22use std::rc::Rc;
23
24pub type NavigatingTask = Pin<Box<dyn Future<Output = ()>>>;
28
29type NavigatingHandler = dyn Fn(&NavigatingFromEventArgs) -> NavigatingTask;
30
31#[repr(C)]
33pub struct Page {
34 base: TemplatedControl,
35 navigation: RefCell<Option<Rc<dyn INavigation>>>,
36 navigated_to: HandlerList<dyn Fn(&NavigatedToEventArgs)>,
37 navigating: HandlerList<NavigatingHandler>,
38 navigated_from: HandlerList<dyn Fn(&NavigatedFromEventArgs)>,
39}
40
41ferro_class! {
42 Page: TemplatedControl, virtuals PageImpl: TemplatedControlImpl {
43 fn on_navigated_to(this, args: &NavigatedToEventArgs);
45 fn on_navigating_from(this, args: &NavigatingFromEventArgs);
51 fn on_navigated_from(this, args: &NavigatedFromEventArgs);
53 fn on_system_back_button_pressed(this) -> bool;
56 fn update_content_safe_area_padding(this);
58 fn update_active_page(this);
60 }
61}
62
63ferro_class_info!(Page {});
64
65ferro_impl_classes!(
66 Page: StyledElementImpl,
67 VisualImpl,
68 LayoutableImpl,
69 InteractiveImpl,
70 InputElementImpl,
71 TemplatedControlImpl
72);
73
74impl FerroObjectImpl for Page {
75 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
76 Self::parent_on_property_changed(this, change);
77
78 if change.property() == Self::safe_area_padding_property().as_property()
79 || change.property() == TemplatedControl::padding_property().as_property()
80 {
81 this.update_content_safe_area_padding();
82 }
83
84 if change.property() == Self::header_property().as_property() {
85 let header = change.get_new_value::<Option<BoxedValue>>();
86 let name = header.as_ref().and_then(|header| {
87 let header: &dyn AnyValue = &**header;
88 header.downcast_ref::<String>().cloned()
89 });
90 AutomationProperties::set_name(this, Some(name.as_deref().unwrap_or("")));
91 }
92 }
93}
94
95impl ControlImpl for Page {
96 fn on_loaded(this: &Self, e: &RoutedEventArgs) {
97 Self::parent_on_loaded(this, e);
98 this.update_content_safe_area_padding();
99 }
100}
101
102impl PageImpl for Page {
103 fn on_navigated_to(this: &Self, args: &NavigatedToEventArgs) {
104 for (_, handler) in this.navigated_to.snapshot().iter() {
105 handler(args);
106 }
107 }
108
109 fn on_navigating_from(_this: &Self, _args: &NavigatingFromEventArgs) {}
110
111 fn on_navigated_from(this: &Self, args: &NavigatedFromEventArgs) {
112 for (_, handler) in this.navigated_from.snapshot().iter() {
113 handler(args);
114 }
115 }
116
117 fn on_system_back_button_pressed(_this: &Self) -> bool {
118 false
119 }
120
121 fn update_content_safe_area_padding(_this: &Self) {}
122
123 fn update_active_page(_this: &Self) {}
124}
125
126impl IHeadered for Page {
127 fn header(&self) -> Option<BoxedValue> {
128 Page::header(self)
129 }
130
131 fn set_header(&self, value: Option<BoxedValue>) {
132 Page::set_header(self, value)
133 }
134}
135
136ferro_properties! {
137 impl Page {
138 pub fn safe_area_padding_property() -> StyledProperty<Thickness> {
140 FerroProperty::register_with::<Page, _>(
141 "SafeAreaPadding",
142 StyledPropertyOptions::new(Thickness::default()).validate(|value| {
143 TemplatedControl::padding_property().validate_value().is_none_or(|validate| validate(value))
144 }),
145 )
146 }
147
148 pub fn header_property() -> StyledProperty<Option<BoxedValue>> {
150 FerroProperty::register::<Page, _>("Header", None)
151 }
152
153 pub fn header_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
155 FerroProperty::register::<Page, _>("HeaderTemplate", None)
156 }
157
158 pub fn icon_property() -> StyledProperty<Option<BoxedValue>> {
160 FerroProperty::register::<Page, _>("Icon", None)
161 }
162
163 pub fn icon_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
165 FerroProperty::register::<Page, _>("IconTemplate", None)
166 }
167
168 pub fn current_page_property() -> StyledProperty<Option<Ref<Page>>> {
170 FerroProperty::register::<Page, _>("CurrentPage", None)
171 }
172
173 pub fn is_in_navigation_page_property() -> StyledProperty<bool> {
175 FerroProperty::register::<Page, _>("IsInNavigationPage", false)
176 }
177
178 pub fn navigation_property() -> DirectProperty<Page, Option<Rc<dyn INavigation>>> {
180 FerroProperty::register_direct::<Page, _>(
181 "Navigation",
182 |o| o.navigation(),
183 Some(|o, v| o.set_navigation(v)),
184 None,
185 )
186 }
187 }
188}
189
190impl Page {
191 ferro_routed_event!(
192 pub fn page_navigation_system_back_button_pressed_event() -> RoutedEvent<RoutedEventArgs> {
195 RoutedEvent::register::<Page, _>("PageNavigationSystemBackButtonPressed", RoutingStrategies::DIRECT)
196 }
197 );
198
199 fn static_constructor() {
200 register_headered::<Page>();
201
202 Layoutable::affects_measure::<Page>(&[Self::safe_area_padding_property().as_property()]);
203 Self::page_navigation_system_back_button_pressed_event().add_class_handler::<Page>(|sender, event_args| {
204 if event_args.handled() {
205 return;
206 }
207
208 if sender.on_system_back_button_pressed() {
209 event_args.set_handled(true);
210 }
211 });
212 }
213
214 pub fn construct() -> Self {
216 Self {
217 base: TemplatedControl::construct(),
218 navigation: RefCell::new(None),
219 navigated_to: HandlerList::new(),
220 navigating: HandlerList::new(),
221 navigated_from: HandlerList::new(),
222 }
223 }
224
225 pub fn header(&self) -> Option<BoxedValue> {
227 self.get_value(Self::header_property())
228 }
229
230 pub fn set_header(&self, value: Option<BoxedValue>) {
231 self.set_value(Self::header_property(), value)
232 }
233
234 pub fn header_template(&self) -> Option<Rc<dyn IDataTemplate>> {
236 self.get_value(Self::header_template_property())
237 }
238
239 pub fn set_header_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
240 self.set_value(Self::header_template_property(), value)
241 }
242
243 pub fn icon(&self) -> Option<BoxedValue> {
245 self.get_value(Self::icon_property())
246 }
247
248 pub fn set_icon(&self, value: Option<BoxedValue>) {
249 self.set_value(Self::icon_property(), value)
250 }
251
252 pub fn icon_template(&self) -> Option<Rc<dyn IDataTemplate>> {
254 self.get_value(Self::icon_template_property())
255 }
256
257 pub fn set_icon_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
258 self.set_value(Self::icon_template_property(), value)
259 }
260
261 pub fn safe_area_padding(&self) -> Thickness {
263 self.get_value(Self::safe_area_padding_property())
264 }
265
266 pub fn set_safe_area_padding(&self, value: Thickness) {
267 self.set_value(Self::safe_area_padding_property(), value)
268 }
269
270 pub fn current_page(&self) -> Option<Ref<Page>> {
272 self.get_value(Self::current_page_property())
273 }
274
275 pub fn set_current_page(&self, value: impl Into<Nullable<Page>>) {
276 self.set_value(Self::current_page_property(), value.into().0)
277 }
278
279 pub fn navigation(&self) -> Option<Rc<dyn INavigation>> {
281 self.navigation.borrow().clone()
282 }
283
284 pub fn set_navigation(&self, value: Option<Rc<dyn INavigation>>) {
285 self.set_and_raise(Self::navigation_property(), &self.navigation, value);
286 }
287
288 pub fn is_in_navigation_page(&self) -> bool {
290 self.get_value(Self::is_in_navigation_page_property())
291 }
292
293 pub fn set_is_in_navigation_page(&self, value: bool) {
294 self.set_value(Self::is_in_navigation_page_property(), value)
295 }
296
297 pub fn page_navigation_system_back_button_pressed(
300 &self,
301 handler: impl Fn(&Interactive, &RoutedEventArgs) + 'static,
302 ) -> RoutedEventHandlerToken {
303 self.add_handler(Self::page_navigation_system_back_button_pressed_event(), handler)
304 }
305
306 fn subscribe<F: ?Sized + 'static>(
307 &self,
308 select: fn(&Page) -> &HandlerList<F>,
309 handler: Rc<F>,
310 ) -> Rc<dyn IDisposable> {
311 let token = select(self).add(handler);
312 let weak = self.to_ref().downgrade();
313 Disposable::create(move || {
314 if let Some(this) = weak.upgrade() {
315 select(&this).remove(token);
316 }
317 })
318 }
319
320 pub fn navigated_to(&self, handler: impl Fn(&NavigatedToEventArgs) + 'static) -> Rc<dyn IDisposable> {
323 self.subscribe::<dyn Fn(&NavigatedToEventArgs)>(|page| &page.navigated_to, Rc::new(handler))
324 }
325
326 pub fn navigating(
335 &self,
336 handler: impl Fn(&NavigatingFromEventArgs) -> NavigatingTask + 'static,
337 ) -> Rc<dyn IDisposable> {
338 self.subscribe::<NavigatingHandler>(|page| &page.navigating, Rc::new(handler))
339 }
340
341 pub fn navigated_from(&self, handler: impl Fn(&NavigatedFromEventArgs) + 'static) -> Rc<dyn IDisposable> {
344 self.subscribe::<dyn Fn(&NavigatedFromEventArgs)>(|page| &page.navigated_from, Rc::new(handler))
345 }
346
347 pub(crate) fn send_navigated_to(&self, args: &NavigatedToEventArgs) {
348 self.on_navigated_to(args)
349 }
350
351 pub(crate) async fn send_navigating_async(this: Ref<Page>, args: NavigatingFromEventArgs) {
355 this.on_navigating_from(&args);
356
357 if args.cancel() {
358 return;
359 }
360
361 let navigating = this.navigating.snapshot();
362 for (_, handler) in navigating.iter() {
363 handler(&args).await;
364 if args.cancel() {
365 return;
366 }
367 }
368 }
369
370 pub(crate) fn send_navigated_from(&self, args: &NavigatedFromEventArgs) {
371 self.on_navigated_from(args)
372 }
373
374 pub(crate) fn set_in_navigation_page(&self, value: bool) {
375 self.set_current_value(Self::is_in_navigation_page_property(), value)
376 }
377}