1use super::{DefaultPageDataTemplate, NavigationType, Page, PageImpl};
2use crate::primitives::TemplatedControlImpl;
3use crate::templates::IDataTemplate;
4use crate::{
5 Control, ControlImpl, IItemsList, ItemsChangedEventArgs, ItemsChangedHandler, ItemsSource, ItemsView,
6};
7use ferroui_base::collections::{FerroList, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
8use ferroui_base::data::core::ValueTypes;
9use ferroui_base::input::InputElementImpl;
10use ferroui_base::interactivity::InteractiveImpl;
11use ferroui_base::layout::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, BoxedValue, FerroObjectImpl,
16 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElement, StyledElementImpl,
17 StyledProperty, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
18};
19use std::any::Any;
20use std::cell::{Cell, RefCell};
21use std::rc::{Rc, Weak};
22
23pub type PageList = FerroList<Ref<Page>>;
26
27pub type PagesChangedHandler = dyn for<'a> Fn(&NotifyCollectionChangedEventArgs<'a, Ref<Page>>);
29
30#[repr(C)]
32pub struct MultiPage {
33 base: Page,
34 current_page_changed: HandlerList<dyn Fn()>,
35 pages_changed: HandlerList<PagesChangedHandler>,
36 pages_subscription: RefCell<Option<(PageList, u64)>>,
39 observes_pages: Cell<bool>,
42}
43
44ferro_class! {
45 MultiPage: Page, virtuals MultiPageImpl: PageImpl {
46 fn update_active_page_with(this, navigation_type: NavigationType);
62 }
63}
64
65ferro_class_info!(MultiPage {
66 markup: {
67 content: Pages,
68 },
69});
70
71ferro_impl_classes!(
72 MultiPage: StyledElementImpl,
73 LayoutableImpl,
74 InteractiveImpl,
75 InputElementImpl,
76 ControlImpl,
77 TemplatedControlImpl
78);
79
80impl FerroObjectImpl for MultiPage {
81 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
82 Self::parent_on_property_changed(this, change);
83
84 if change.property() == Self::pages_property().as_property() {
85 let (old_value, new_value) = change.get_old_and_new_value::<Option<PageList>>();
86 if old_value == new_value {
87 return;
88 }
89
90 if let Some(old_notify_collection) = &old_value {
91 this.unsubscribe_from_pages(old_notify_collection);
92 }
93
94 let logical_children = StyledElement::logical_children(this);
95 logical_children.clear();
96
97 if let Some(new_items) = &new_value {
98 for page in new_items.snapshot().iter() {
99 logical_children.add(page.clone().upcast());
100 }
101 }
102
103 if let Some(new_notify_collection) = &new_value {
104 this.subscribe_to_pages(new_notify_collection);
105 }
106
107 if new_value.is_some() {
108 this.update_active_page();
109 } else {
110 this.set_current_value(Page::current_page_property(), None);
111 }
112 } else if change.property() == Page::current_page_property().as_property() {
113 for (_, handler) in this.current_page_changed.snapshot().iter() {
114 handler();
115 }
116 }
117 }
118}
119
120impl Drop for MultiPage {
121 fn drop(&mut self) {
122 if let Some((collection, token)) = self.pages_subscription.get_mut().take() {
125 collection.remove_collection_changed(token);
126 }
127 }
128}
129
130impl VisualImpl for MultiPage {
131 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
132 Self::parent_on_attached_to_visual_tree(this, e);
133
134 if let Some(collection) = this.pages() {
135 this.unsubscribe_from_pages(&collection);
136 this.subscribe_to_pages(&collection);
137 }
138 }
139
140 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
141 Self::parent_on_detached_from_visual_tree(this, e);
142
143 if let Some(collection) = this.pages() {
144 this.unsubscribe_from_pages(&collection);
145 }
146 }
147}
148
149impl PageImpl for MultiPage {
150 fn update_active_page(this: &Self) {
155 this.update_active_page_with(NavigationType::Replace)
156 }
157}
158
159impl MultiPageImpl for MultiPage {
160 fn update_active_page_with(_this: &Self, _navigation_type: NavigationType) {}
161}
162
163ferro_properties! {
164 impl MultiPage {
165 pub fn pages_property() -> StyledProperty<Option<PageList>> {
167 FerroProperty::register::<MultiPage, _>("Pages", None)
168 }
169
170 pub fn items_source_property() -> StyledProperty<Option<ItemsSource>> {
172 FerroProperty::register::<MultiPage, _>("ItemsSource", None)
173 }
174
175 pub fn page_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
177 let default_template: Rc<dyn IDataTemplate> = DefaultPageDataTemplate::new();
178 FerroProperty::register::<MultiPage, _>("PageTemplate", Some(default_template))
179 }
180 }
181}
182
183impl MultiPage {
184 fn static_constructor() {
185 ValueTypes::register_conversion::<PageList, ItemsSource>(|pages| Some(Self::items_source_of(pages)));
188 ValueTypes::register_conversion::<PageList, Option<ItemsSource>>(|pages| Some(Some(Self::items_source_of(pages))));
189 ValueTypes::register_conversion::<Option<PageList>, Option<ItemsSource>>(|pages| {
190 Some(pages.as_ref().map(Self::items_source_of))
191 });
192 }
193
194 pub fn construct() -> Self {
196 Self {
197 base: Page::construct(),
198 current_page_changed: HandlerList::new(),
199 pages_changed: HandlerList::new(),
200 pages_subscription: RefCell::new(None),
201 observes_pages: Cell::new(true),
202 }
203 }
204
205 pub fn pages(&self) -> Option<PageList> {
211 self.get_value(Self::pages_property())
212 }
213
214 pub fn set_pages(&self, value: Option<PageList>) {
215 self.set_value(Self::pages_property(), value)
216 }
217
218 pub fn items_source(&self) -> Option<ItemsSource> {
223 self.get_value(Self::items_source_property())
224 }
225
226 pub fn set_items_source(&self, value: Option<ItemsSource>) {
227 self.set_value(Self::items_source_property(), value)
228 }
229
230 pub fn page_template(&self) -> Option<Rc<dyn IDataTemplate>> {
233 self.get_value(Self::page_template_property())
234 }
235
236 pub fn set_page_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
237 self.set_value(Self::page_template_property(), value)
238 }
239
240 pub fn current_page_changed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
243 let token = self.current_page_changed.add(Rc::new(handler));
244 let weak = self.to_ref().downgrade();
245 Disposable::create(move || {
246 if let Some(this) = weak.upgrade() {
247 this.current_page_changed.remove(token);
248 }
249 })
250 }
251
252 pub fn pages_changed(
255 &self,
256 handler: impl for<'a> Fn(&NotifyCollectionChangedEventArgs<'a, Ref<Page>>) + 'static,
257 ) -> Rc<dyn IDisposable> {
258 let token = self.pages_changed.add(Rc::new(handler));
259 let weak = self.to_ref().downgrade();
260 Disposable::create(move || {
261 if let Some(this) = weak.upgrade() {
262 this.pages_changed.remove(token);
263 }
264 })
265 }
266
267 pub fn items_source_of(pages: &PageList) -> ItemsSource {
272 ferroui_base::ferro_thread_local! {
273 static SOURCES: RefCell<Vec<Weak<PageItemsList>>> = const { RefCell::new(Vec::new()) };
274 }
275
276 SOURCES.with(|sources| {
277 let mut sources = sources.borrow_mut();
278 sources.retain(|source| source.strong_count() > 0);
279 if let Some(existing) = sources.iter().filter_map(Weak::upgrade).find(|source| source.0.ptr_eq(pages)) {
280 return ItemsSource::new(existing);
281 }
282 let list = Rc::new(PageItemsList(pages.clone()));
283 sources.push(Rc::downgrade(&list));
284 ItemsSource::new(list)
285 })
286 }
287
288 pub fn pages_items_source(&self) -> Option<ItemsSource> {
291 self.pages().as_ref().map(Self::items_source_of)
292 }
293
294 pub(crate) fn set_observes_pages(&self, value: bool) {
300 self.observes_pages.set(value);
301 }
302
303 fn subscribe_to_pages(&self, collection: &PageList) {
304 if !self.observes_pages.get() {
305 return;
306 }
307
308 let weak = self.to_ref().downgrade();
309 let token = collection.add_collection_changed(Rc::new(
310 move |e: &NotifyCollectionChangedEventArgs<'_, Ref<Page>>| {
311 if let Some(this) = weak.upgrade() {
312 this.notify_collection_collection_changed(e);
313 }
314 },
315 ));
316 let previous = self.pages_subscription.borrow_mut().replace((collection.clone(), token));
317 if let Some((previous, token)) = previous {
318 previous.remove_collection_changed(token);
319 }
320 }
321
322 fn unsubscribe_from_pages(&self, collection: &PageList) {
323 let subscription = {
324 let mut subscription = self.pages_subscription.borrow_mut();
325 if subscription.as_ref().is_some_and(|(subscribed, _)| subscribed.ptr_eq(collection)) {
326 subscription.take()
327 } else {
328 None
329 }
330 };
331 if let Some((subscribed, token)) = subscription {
332 subscribed.remove_collection_changed(token);
333 }
334 }
335
336 fn notify_collection_collection_changed(&self, e: &NotifyCollectionChangedEventArgs<'_, Ref<Page>>) {
337 let logical_children = StyledElement::logical_children(self);
338 match e.action {
339 NotifyCollectionChangedAction::Reset => {
340 logical_children.clear();
341 if let Some(pages) = self.pages() {
342 for page in pages.snapshot().iter() {
343 logical_children.add(page.clone().upcast());
344 }
345 }
346 }
347 _ => {
348 for old in e.old_items {
349 logical_children.remove(&old.clone().upcast());
350 }
351
352 if !e.new_items.is_empty() {
353 let mut insert_idx =
354 if e.new_starting_index >= 0 { e.new_starting_index as usize } else { logical_children.count() };
355 for new_item in e.new_items {
356 logical_children.insert(insert_idx, new_item.clone().upcast());
357 insert_idx += 1;
358 }
359 }
360 }
361 }
362
363 for (_, handler) in self.pages_changed.snapshot().iter() {
364 handler(e);
365 }
366
367 let mut nav_type = NavigationType::Replace;
368 if let Some(current) = self.current_page() {
369 let mut current_removed = false;
370 if e.action == NotifyCollectionChangedAction::Remove || e.action == NotifyCollectionChangedAction::Replace
371 {
372 current_removed = e.old_items.iter().any(|old| old.ptr_eq(¤t));
373 } else if e.action == NotifyCollectionChangedAction::Reset {
374 current_removed = true;
375 if let Some(pages) = self.pages() {
376 if pages.snapshot().iter().any(|page| page.ptr_eq(¤t)) {
377 current_removed = false;
378 }
379 }
380 }
381
382 if current_removed {
383 nav_type = NavigationType::Remove;
384 }
385 }
386
387 self.update_active_page_with(nav_type);
388 }
389}
390
391struct PageItemsList(PageList);
394
395fn box_pages(pages: &[Ref<Page>]) -> Vec<Option<BoxedValue>> {
396 pages.iter().map(|page| Some(Control::boxed(page.clone()))).collect()
397}
398
399impl IItemsList for PageItemsList {
400 fn count(&self) -> usize {
401 self.0.count()
402 }
403
404 fn get_at(&self, index: usize) -> Option<BoxedValue> {
405 Some(Control::boxed(self.0.get(index)))
406 }
407
408 fn index_of(&self, item: &Option<BoxedValue>) -> i32 {
409 let Some(control) = item.as_ref().and_then(Control::from_boxed) else {
410 return -1;
411 };
412 let control: &Control = &control;
413 self.0
414 .snapshot()
415 .iter()
416 .position(|page| {
417 let page: &Control = page;
418 std::ptr::eq(page, control)
419 })
420 .map_or(-1, |index| index as i32)
421 }
422
423 fn is_notifying(&self) -> bool {
424 true
425 }
426
427 fn add_collection_changed(&self, handler: Rc<ItemsChangedHandler>) -> Option<u64> {
428 Some(self.0.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, Ref<Page>>| {
429 let new_items = box_pages(e.new_items);
430 let old_items = box_pages(e.old_items);
431 handler(&ItemsChangedEventArgs {
432 action: e.action,
433 new_items: ItemsView::from_slice(&new_items),
434 old_items: ItemsView::from_slice(&old_items),
435 new_starting_index: e.new_starting_index,
436 old_starting_index: e.old_starting_index,
437 });
438 })))
439 }
440
441 fn remove_collection_changed(&self, token: u64) {
442 self.0.remove_collection_changed(token);
443 }
444
445 fn as_any(&self) -> &dyn Any {
446 self
447 }
448}