1use super::{
2 MultiPage, MultiPageImpl, NavigationType, Page, PageImpl, PageImplExt, SelectingMultiPage, SelectingMultiPageImpl,
3 TabPlacement,
4};
5use crate::primitives::{SelectingItemsControl, TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt};
6use crate::templates::IDataTemplate;
7use crate::{
8 ContainerClearingEventArgs, ContainerIndexChangedEventArgs, ContainerPreparedEventArgs, Control, ControlImpl, Dock,
9 ItemsSource, TabControl, TabItem,
10};
11use ferroui_base::animation::IPageTransition;
12use ferroui_base::input::gesture_recognizers::SwipeGestureRecognizer;
13use ferroui_base::input::{
14 InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyModifiers, NavigationMethod,
15 SwipeDirection, SwipeGestureEventArgs,
16};
17use ferroui_base::interactivity::{InteractiveImpl, RoutedEventArgs, RoutedEventHandlerToken};
18use ferroui_base::layout::LayoutableImpl;
19use ferroui_base::media::FlowDirection;
20use ferroui_base::reactive::IDisposable;
21use ferroui_base::threading::{Dispatcher, DispatcherPriority};
22use ferroui_base::{
23 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, AttachedProperty, BoxedValue,
24 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StaticType, StyledElement,
25 StyledElementImpl, StyledProperty, Thickness, TypeInfo, VisualImpl, VisualImplExt,
26 VisualTreeAttachmentEventArgs,
27};
28use std::cell::{Cell, RefCell};
29use std::collections::HashMap;
30use std::rc::Rc;
31
32const IS_MOBILE_PLATFORM: bool = cfg!(target_os = "android") || cfg!(target_os = "ios");
33
34struct ContainerPage {
37 tab_item: Ref<TabItem>,
38 page: Ref<Page>,
39 page_property_changed: Rc<dyn IDisposable>,
40}
41
42struct TabControlPart {
44 tab_control: Ref<TabControl>,
45 selection_changed: Option<RoutedEventHandlerToken>,
46 container_prepared: Rc<dyn IDisposable>,
47 container_index_changed: Rc<dyn IDisposable>,
48 container_clearing: Rc<dyn IDisposable>,
49}
50
51fn tab_item_key(tab_item: &TabItem) -> usize {
52 tab_item as *const TabItem as usize
53}
54
55fn page_key(page: &Page) -> usize {
56 page as *const Page as usize
57}
58
59#[repr(C)]
61pub struct TabbedPage {
62 base: SelectingMultiPage,
63 tab_control: RefCell<Option<TabControlPart>>,
64 ignoring_disabled_selection: Cell<bool>,
65 container_page_map: RefCell<HashMap<usize, ContainerPage>>,
67 page_container_map: RefCell<HashMap<usize, Ref<TabItem>>>,
69 template_created_pages: RefCell<HashMap<usize, Ref<Page>>>,
71 last_swipe_gesture_id: Cell<i32>,
72 swipe_recognizer: Ref<SwipeGestureRecognizer>,
73 swipe_gesture_handler: Cell<Option<RoutedEventHandlerToken>>,
74}
75
76ferro_class!(TabbedPage: SelectingMultiPage);
77
78ferro_class_info!(TabbedPage {
79 new: TabbedPage::new,
80 markup: {
81 attributes: [
82 TemplatePart("PART_TabControl", type(Ref<TabControl>)),
83 ],
84 },
85});
86
87ferro_impl_classes!(TabbedPage: LayoutableImpl, InteractiveImpl);
88
89impl FerroObjectImpl for TabbedPage {
90 fn constructed(this: &Self) {
91 Self::parent_constructed(this);
92
93 this.set_current_value(MultiPage::pages_property(), Some(super::PageList::new()));
94 this.gesture_recognizers().add(this.swipe_recognizer.clone());
95 this.update_swipe_recognizer_axes();
96 }
97
98 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
99 Self::parent_on_property_changed(this, change);
100
101 let tab_control = this.tab_control();
102 if change.property() == Self::tab_placement_property().as_property() {
103 this.apply_tab_placement();
104 this.update_swipe_recognizer_axes();
105 } else if change.property() == Self::page_transition_property().as_property() && tab_control.is_some() {
106 if let Some(tab_control) = tab_control {
107 tab_control.set_page_transition(change.get_new_value::<Option<Rc<dyn IPageTransition>>>());
108 }
109 } else if change.property() == Self::indicator_template_property().as_property() {
110 this.apply_indicator_template();
111 } else if change.property() == Self::is_gesture_enabled_property().as_property() {
112 this.swipe_recognizer.set_is_enabled(change.get_new_value::<bool>());
113 } else if change.property() == MultiPage::items_source_property().as_property() && tab_control.is_some() {
114 if let Some(tab_control) = tab_control {
115 tab_control.set_items_source(
116 change.get_new_value::<Option<ItemsSource>>().or_else(|| this.pages_items_source()),
117 );
118 }
119 } else if change.property() == MultiPage::pages_property().as_property()
120 && this.items_source().is_none()
121 && tab_control.is_some()
122 {
123 if let Some(tab_control) = tab_control {
124 tab_control.set_items_source(
125 change.get_new_value::<Option<super::PageList>>().as_ref().map(MultiPage::items_source_of),
126 );
127 }
128 } else if change.property() == MultiPage::page_template_property().as_property()
129 && this.items_source().is_some()
130 && tab_control.is_some()
131 {
132 this.rebuild_template_created_pages();
133 }
134 }
135}
136
137impl StyledElementImpl for TabbedPage {
138 fn style_key_override(_this: &Self) -> &'static TypeInfo {
139 <TabbedPage as StaticType>::TYPE
140 }
141}
142
143impl VisualImpl for TabbedPage {
144 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
145 Self::parent_on_attached_to_visual_tree(this, e);
146
147 let weak = this.to_ref().downgrade();
148 let token = this.add_handler(InputElement::swipe_gesture_event(), move |_, e: &SwipeGestureEventArgs| {
149 if let Some(this) = weak.upgrade() {
150 this.on_swipe_gesture(e);
151 }
152 });
153 if let Some(previous) = this.swipe_gesture_handler.replace(Some(token)) {
154 this.remove_handler(InputElement::swipe_gesture_event(), previous);
155 }
156 }
157
158 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
159 Self::parent_on_detached_from_visual_tree(this, e);
160
161 if let Some(token) = this.swipe_gesture_handler.take() {
162 this.remove_handler(InputElement::swipe_gesture_event(), token);
163 }
164 }
165}
166
167impl InputElementImpl for TabbedPage {
168 fn on_key_down(this: &Self, e: &KeyEventArgs) {
169 Self::parent_on_key_down(this, e);
170
171 if !this.is_keyboard_navigation_enabled() {
172 return;
173 }
174 let Some(tab_control) = this.tab_control() else {
175 return;
176 };
177
178 let resolved = this.resolve_tab_placement();
179 let is_horizontal = resolved == TabPlacement::Top || resolved == TabPlacement::Bottom;
180 let is_rtl = this.flow_direction() == FlowDirection::RightToLeft;
181
182 let mut next = if is_horizontal {
183 if is_rtl {
184 e.key == Key::Left
185 } else {
186 e.key == Key::Right
187 }
188 } else {
189 e.key == Key::Down
190 };
191 let mut prev = if is_horizontal {
192 if is_rtl {
193 e.key == Key::Right
194 } else {
195 e.key == Key::Left
196 }
197 } else {
198 e.key == Key::Up
199 };
200
201 if e.key_modifiers.contains(KeyModifiers::CONTROL) && e.key == Key::Tab {
202 next = !e.key_modifiers.contains(KeyModifiers::SHIFT);
203 prev = e.key_modifiers.contains(KeyModifiers::SHIFT);
204 }
205
206 if next {
207 let target = this.find_next_enabled_tab(tab_control.selected_index() + 1, 1);
208 if target >= 0 {
209 tab_control.set_selected_index(target);
210 this.focus_selected_tab_item();
211 e.set_handled(true);
212 }
213 } else if prev {
214 let target = this.find_next_enabled_tab(tab_control.selected_index() - 1, -1);
215 if target >= 0 {
216 tab_control.set_selected_index(target);
217 this.focus_selected_tab_item();
218 e.set_handled(true);
219 }
220 }
221 }
222}
223
224impl TemplatedControlImpl for TabbedPage {
225 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
226 Self::parent_on_apply_template(this, e);
227
228 let requested_index = this.selected_index();
229
230 let previous = this.tab_control.borrow_mut().take();
231 if let Some(previous) = previous {
232 if let Some(token) = previous.selection_changed {
233 previous.tab_control.remove_handler(SelectingItemsControl::selection_changed_event(), token);
234 }
235 previous.container_prepared.dispose();
236 previous.container_index_changed.dispose();
237 previous.container_clearing.dispose();
238 }
239
240 this.clear_container_pages();
241
242 let tab_control = e.name_scope().find_as::<TabControl>("PART_TabControl");
243
244 if let Some(tab_control) = tab_control {
245 let weak = this.to_ref().downgrade();
246 let container_prepared = tab_control.container_prepared(move |e| {
247 if let Some(this) = weak.upgrade() {
248 this.on_container_prepared(e);
249 }
250 });
251 let weak = this.to_ref().downgrade();
252 let container_index_changed = tab_control.container_index_changed(move |e| {
253 if let Some(this) = weak.upgrade() {
254 this.on_container_index_changed(e);
255 }
256 });
257 let weak = this.to_ref().downgrade();
258 let container_clearing = tab_control.container_clearing(move |e| {
259 if let Some(this) = weak.upgrade() {
260 this.on_container_clearing(e);
261 }
262 });
263 *this.tab_control.borrow_mut() = Some(TabControlPart {
264 tab_control: tab_control.clone(),
265 selection_changed: None,
266 container_prepared,
267 container_index_changed,
268 container_clearing,
269 });
270 tab_control.set_items_source(this.items_source().or_else(|| this.pages_items_source()));
271
272 if requested_index >= 0 {
273 tab_control.set_selected_index(requested_index);
274 }
275
276 let weak = this.to_ref().downgrade();
277 let token = tab_control.selection_changed(move |_, _| {
278 if let Some(this) = weak.upgrade() {
279 this.tab_control_selection_changed();
280 }
281 });
282 if let Some(part) = this.tab_control.borrow_mut().as_mut() {
283 part.selection_changed = Some(token);
284 }
285
286 if let Some(page_transition) = this.page_transition() {
287 tab_control.set_page_transition(Some(page_transition));
288 }
289
290 this.apply_tab_placement();
291 this.apply_indicator_template();
292 this.update_active_page();
293
294 let weak = this.to_ref().downgrade();
295 let captured_tab_control = tab_control;
296 Dispatcher::ui_thread().post_local(
297 move || {
298 if let Some(this) = weak.upgrade() {
299 this.sync_all_tab_enabled_states(&captured_tab_control);
300 }
301 },
302 DispatcherPriority::LOADED,
303 );
304 }
305 }
306}
307
308impl PageImpl for TabbedPage {
309 fn update_content_safe_area_padding(this: &Self) {
310 Self::parent_update_content_safe_area_padding(this);
311
312 if let Some(tab_control) = this.tab_control() {
313 let sa = this.safe_area_padding();
314
315 let (bar_margin, content_safe_area) = match this.resolve_tab_placement() {
316 TabPlacement::Bottom => {
317 (Thickness::new(sa.left, 0.0, sa.right, sa.bottom), Thickness::new(sa.left, sa.top, sa.right, 0.0))
318 }
319 TabPlacement::Left => {
320 (Thickness::new(sa.left, sa.top, 0.0, sa.bottom), Thickness::new(0.0, sa.top, sa.right, sa.bottom))
321 }
322 TabPlacement::Right => {
323 (Thickness::new(0.0, sa.top, sa.right, sa.bottom), Thickness::new(sa.left, sa.top, 0.0, sa.bottom))
324 }
325 _ => (Thickness::new(sa.left, sa.top, sa.right, 0.0), Thickness::new(sa.left, 0.0, sa.right, sa.bottom)),
326 };
327
328 tab_control.set_margin(bar_margin);
329
330 if let Some(current_page) = this.current_page() {
331 current_page.set_safe_area_padding(content_safe_area);
332 }
333 }
334 }
335}
336
337impl MultiPageImpl for TabbedPage {
338 fn update_active_page_with(this: &Self, navigation_type: NavigationType) {
339 if let Some(tab_control) = this.tab_control() {
340 let index = tab_control.selected_index();
341 this.commit_selection_if_resolved(index, navigation_type);
342 this.update_content_safe_area_padding();
343 }
344 }
345}
346
347impl SelectingMultiPageImpl for TabbedPage {
348 fn apply_selected_index(this: &Self, index: i32) {
349 if let Some(tab_control) = this.tab_control() {
350 tab_control.set_selected_index(index);
351 } else {
352 this.store_selected_index(index);
353 }
354 }
355}
356
357impl ControlImpl for TabbedPage {
358 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
359 crate::automation::peers::TabbedPageAutomationPeer::new(this).upcast()
360 }
361}
362
363ferro_properties! {
364 impl TabbedPage {
365 pub fn tab_placement_property() -> StyledProperty<TabPlacement> {
367 FerroProperty::register::<TabbedPage, _>("TabPlacement", TabPlacement::Auto)
368 }
369
370 pub fn is_keyboard_navigation_enabled_property() -> StyledProperty<bool> {
372 FerroProperty::register::<TabbedPage, _>("IsKeyboardNavigationEnabled", true)
373 }
374
375 pub fn is_gesture_enabled_property() -> StyledProperty<bool> {
377 FerroProperty::register::<TabbedPage, _>("IsGestureEnabled", false)
378 }
379
380 pub fn page_transition_property() -> StyledProperty<Option<Rc<dyn IPageTransition>>> {
382 FerroProperty::register::<TabbedPage, _>("PageTransition", None)
383 }
384
385 pub fn indicator_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
387 FerroProperty::register::<TabbedPage, _>("IndicatorTemplate", None)
388 }
389
390 pub fn is_tab_enabled_property() -> AttachedProperty<bool> {
392 FerroProperty::register_attached::<TabbedPage, Page, _>("IsTabEnabled", true)
393 }
394 }
395}
396
397impl TabbedPage {
398 fn static_constructor() {
399 InputElement::focusable_property().override_default_value::<TabbedPage>(true);
400 Page::page_navigation_system_back_button_pressed_event().add_class_handler::<TabbedPage>(
401 |sender, event_args| {
402 if event_args.handled() {
403 return;
404 }
405
406 let forwarded = RoutedEventArgs::with_event(Page::page_navigation_system_back_button_pressed_event());
407 if let Some(current_page) = sender.current_page() {
408 current_page.raise_event(&forwarded);
409 }
410
411 if forwarded.handled() {
412 event_args.set_handled(true);
413 }
414 },
415 );
416 }
417
418 pub fn construct() -> Self {
420 let swipe_recognizer = SwipeGestureRecognizer::new();
421 swipe_recognizer.set_is_enabled(false);
422 Self {
423 base: SelectingMultiPage::construct(),
424 tab_control: RefCell::new(None),
425 ignoring_disabled_selection: Cell::new(false),
426 container_page_map: RefCell::new(HashMap::new()),
427 page_container_map: RefCell::new(HashMap::new()),
428 template_created_pages: RefCell::new(HashMap::new()),
429 last_swipe_gesture_id: Cell::new(0),
430 swipe_recognizer,
431 swipe_gesture_handler: Cell::new(None),
432 }
433 }
434
435 pub fn new() -> Ref<Self> {
437 instantiate(Self::construct())
438 }
439
440 pub fn get_is_tab_enabled(page: &Page) -> bool {
443 page.get_value(Self::is_tab_enabled_property())
444 }
445
446 pub fn set_is_tab_enabled(page: &Page, value: bool) {
449 page.set_value(Self::is_tab_enabled_property(), value)
450 }
451
452 pub fn tab_placement(&self) -> TabPlacement {
454 self.get_value(Self::tab_placement_property())
455 }
456
457 pub fn set_tab_placement(&self, value: TabPlacement) {
458 self.set_value(Self::tab_placement_property(), value)
459 }
460
461 pub fn is_keyboard_navigation_enabled(&self) -> bool {
463 self.get_value(Self::is_keyboard_navigation_enabled_property())
464 }
465
466 pub fn set_is_keyboard_navigation_enabled(&self, value: bool) {
467 self.set_value(Self::is_keyboard_navigation_enabled_property(), value)
468 }
469
470 pub fn is_gesture_enabled(&self) -> bool {
478 self.get_value(Self::is_gesture_enabled_property())
479 }
480
481 pub fn set_is_gesture_enabled(&self, value: bool) {
482 self.set_value(Self::is_gesture_enabled_property(), value)
483 }
484
485 pub fn page_transition(&self) -> Option<Rc<dyn IPageTransition>> {
487 self.get_value(Self::page_transition_property())
488 }
489
490 pub fn set_page_transition(&self, value: Option<Rc<dyn IPageTransition>>) {
491 self.set_value(Self::page_transition_property(), value)
492 }
493
494 pub fn indicator_template(&self) -> Option<Rc<dyn IDataTemplate>> {
497 self.get_value(Self::indicator_template_property())
498 }
499
500 pub fn set_indicator_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
501 self.set_value(Self::indicator_template_property(), value)
502 }
503
504 fn tab_control(&self) -> Option<Ref<TabControl>> {
505 self.tab_control.borrow().as_ref().map(|part| part.tab_control.clone())
506 }
507
508 fn tab_item_from_index(tab_control: &TabControl, index: i32) -> Option<Ref<TabItem>> {
509 tab_control.container_from_index(index).and_then(|container| container.cast::<TabItem>())
510 }
511
512 fn container_page(&self, tab_item: &TabItem) -> Option<Ref<Page>> {
513 self.container_page_map.borrow().get(&tab_item_key(tab_item)).map(|entry| entry.page.clone())
514 }
515
516 fn page_container(&self, page: &Page) -> Option<Ref<TabItem>> {
517 self.page_container_map.borrow().get(&page_key(page)).cloned()
518 }
519
520 fn resolve_tab_placement(&self) -> TabPlacement {
521 if self.tab_placement() != TabPlacement::Auto {
522 return self.tab_placement();
523 }
524
525 if IS_MOBILE_PLATFORM {
526 TabPlacement::Bottom
527 } else {
528 TabPlacement::Top
529 }
530 }
531
532 fn apply_tab_placement(&self) {
533 let Some(tab_control) = self.tab_control() else {
534 return;
535 };
536
537 tab_control.set_tab_strip_placement(match self.resolve_tab_placement() {
538 TabPlacement::Bottom => Dock::Bottom,
539 TabPlacement::Left => Dock::Left,
540 TabPlacement::Right => Dock::Right,
541 _ => Dock::Top,
542 });
543 }
544
545 fn update_swipe_recognizer_axes(&self) {
546 let placement = self.resolve_tab_placement();
547 let is_horizontal = placement == TabPlacement::Top || placement == TabPlacement::Bottom;
548 self.swipe_recognizer.set_can_horizontally_swipe(is_horizontal);
549 self.swipe_recognizer.set_can_vertically_swipe(!is_horizontal);
550 }
551
552 fn apply_indicator_template(&self) {
553 let Some(tab_control) = self.tab_control() else {
554 return;
555 };
556
557 tab_control.set_indicator_template(self.indicator_template());
558 }
559
560 fn tab_control_selection_changed(&self) {
561 if self.ignoring_disabled_selection.get() {
562 return;
563 }
564
565 let Some(tab_control) = self.tab_control() else {
566 return;
567 };
568 let new_index = tab_control.selected_index();
569 let new_page = self.resolve_tab_page_at_index(new_index);
570
571 if let Some(new_page) = new_page {
572 if !Self::get_is_tab_enabled(&new_page) {
573 let target = self.find_nearest_enabled_tab(new_index);
574 if target < 0 {
575 self.select_ignoring_disabled_selection(&tab_control, self.selected_index());
576 return;
577 }
578
579 self.select_ignoring_disabled_selection(&tab_control, target);
580
581 if target == self.selected_index() {
582 return;
583 }
584
585 self.commit_selection_if_resolved(target, NavigationType::Replace);
586 self.update_content_safe_area_padding();
587 return;
588 }
589 }
590
591 self.commit_selection_if_resolved(new_index, NavigationType::Replace);
592 self.update_content_safe_area_padding();
593 }
594
595 fn select_ignoring_disabled_selection(&self, tab_control: &TabControl, index: i32) {
598 struct Reset<'a>(&'a Cell<bool>);
599 impl Drop for Reset<'_> {
600 fn drop(&mut self) {
601 self.0.set(false);
602 }
603 }
604
605 self.ignoring_disabled_selection.set(true);
606 let _reset = Reset(&self.ignoring_disabled_selection);
607 tab_control.set_selected_index(index);
608 }
609
610 fn sync_tab_item_with_page(tab_item: &TabItem, page: &Page) {
611 tab_item.set_is_enabled(Self::get_is_tab_enabled(page));
612 tab_item.set_header(page.header());
613 tab_item.set_icon(page.icon());
614 tab_item.set_icon_template(page.icon_template());
615 }
616
617 fn on_container_prepared(&self, e: &ContainerPreparedEventArgs) {
618 let Some(tab_item) = e.container().clone().cast::<TabItem>() else {
619 return;
620 };
621
622 let Some(page) = self.prepare_page_for_container(&tab_item, e.index()) else {
623 return;
624 };
625
626 Self::sync_tab_item_with_page(&tab_item, &page);
627
628 if e.index() == self.tab_control().map_or(-1, |tab_control| tab_control.selected_index()) {
629 self.update_active_page();
630 }
631 }
632
633 fn on_container_index_changed(&self, e: &ContainerIndexChangedEventArgs) {
643 let Some(tab_control) = self.tab_control() else {
644 return;
645 };
646 if e.new_index() != tab_control.selected_index() {
647 return;
648 }
649 let Some(selected_page) = self.selected_page() else {
650 return;
651 };
652 if self.page_container(&selected_page).is_some() {
653 return;
654 }
655 let Some(page) = e.container().clone().cast::<TabItem>().and_then(|tab_item| self.container_page(&tab_item))
656 else {
657 return;
658 };
659
660 self.commit_selection(e.new_index(), Some(page), NavigationType::Remove);
661 self.update_content_safe_area_padding();
662 }
663
664 fn on_container_clearing(&self, e: &ContainerClearingEventArgs) {
665 if let Some(tab_item) = e.container().clone().cast::<TabItem>() {
666 self.clear_container_page(&tab_item);
667 }
668 }
669
670 fn rebuild_template_created_pages(&self) {
671 let Some(tab_control) = self.tab_control() else {
672 return;
673 };
674 if self.items_source().is_none() {
675 return;
676 }
677
678 for i in 0..tab_control.item_count() {
679 let Some(tab_item) = Self::tab_item_from_index(&tab_control, i) else {
680 continue;
681 };
682
683 let Some(page) = self.prepare_page_for_container(&tab_item, i) else {
684 continue;
685 };
686
687 Self::sync_tab_item_with_page(&tab_item, &page);
688 }
689
690 self.update_active_page();
691 }
692
693 fn on_page_property_changed(&self, page: &Ref<Page>, e: &FerroPropertyChangedEventArgs<'_>) {
694 if self.tab_control().is_none() {
695 return;
696 }
697
698 if e.property() == Page::icon_property().as_property() {
699 if let Some(tab_item) = self.page_container(page) {
700 tab_item.set_icon(page.icon());
701 }
702 } else if e.property() == Page::icon_template_property().as_property() {
703 if let Some(tab_item) = self.page_container(page) {
704 tab_item.set_icon_template(page.icon_template());
705 }
706 } else if e.property() == Page::header_property().as_property() {
707 if let Some(tab_item) = self.page_container(page) {
708 tab_item.set_header(page.header());
709 }
710 } else if e.property() == Self::is_tab_enabled_property().as_property() {
711 self.sync_tab_enabled_state(page);
712 }
713 }
714
715 fn find_nearest_enabled_tab(&self, disabled_index: i32) -> i32 {
716 let count = self.get_tab_count();
717 for dist in 1..count {
718 let p = disabled_index - dist;
719 if p >= 0 && self.is_tab_enabled_at_index(p) {
720 return p;
721 }
722 let n = disabled_index + dist;
723 if n < count && self.is_tab_enabled_at_index(n) {
724 return n;
725 }
726 }
727
728 -1
729 }
730
731 pub(crate) fn find_next_enabled_tab(&self, start: i32, direction: i32) -> i32 {
732 let count = self.get_tab_count();
733 let mut i = start;
734 while i >= 0 && i < count {
735 if self.is_tab_enabled_at_index(i) {
736 return i;
737 }
738 i += direction;
739 }
740
741 -1
742 }
743
744 pub(crate) fn get_tab_count(&self) -> i32 {
745 if let Some(tab_control) = self.tab_control() {
746 return tab_control.item_count();
747 }
748
749 if let Some(source) = self.items_source() {
750 return source.count() as i32;
751 }
752
753 self.pages().map_or(0, |pages| pages.count() as i32)
754 }
755
756 fn is_tab_enabled_at_index(&self, index: i32) -> bool {
757 if let Some(page) = self
758 .tab_control()
759 .and_then(|tab_control| Self::tab_item_from_index(&tab_control, index))
760 .and_then(|tab_item| self.container_page(&tab_item))
761 {
762 return Self::get_is_tab_enabled(&page);
763 }
764 if let Some(page) = self.resolve_tab_page_at_index(index) {
765 return Self::get_is_tab_enabled(&page);
766 }
767 true
768 }
769
770 fn resolve_tab_page_at_index(&self, index: i32) -> Option<Ref<Page>> {
775 let tab_control = self.tab_control();
776 let tab_item = tab_control.as_ref().and_then(|tab_control| Self::tab_item_from_index(tab_control, index));
777
778 if let Some(page) = tab_item.as_ref().and_then(|tab_item| self.container_page(tab_item)) {
779 return Some(page);
780 }
781
782 if let Some(tab_control) = &tab_control {
783 let items_view = tab_control.items_view();
784 if index >= 0 && (index as usize) < items_view.count() {
785 return items_view
786 .get_at(index as usize)
787 .as_ref()
788 .and_then(Control::from_boxed)
789 .and_then(|control| control.cast::<Page>());
790 }
791 }
792
793 if let Some(page) = tab_item
794 .and_then(|tab_item| tab_item.content())
795 .as_ref()
796 .and_then(Control::from_boxed)
797 .and_then(|control| control.cast::<Page>())
798 {
799 return Some(page);
800 }
801
802 self.resolve_page_at_index(index)
803 }
804
805 fn sync_tab_enabled_state(&self, page: &Page) {
806 let Some(tab_control) = self.tab_control() else {
807 return;
808 };
809 let Some(tab_item) = self.page_container(page) else {
810 return;
811 };
812
813 tab_item.set_is_enabled(Self::get_is_tab_enabled(page));
814
815 let selected_container = Self::tab_item_from_index(&tab_control, tab_control.selected_index());
816 if !Self::get_is_tab_enabled(page) && selected_container.is_some_and(|selected| selected.ptr_eq(&tab_item)) {
817 let i = tab_control.selected_index();
818 let target = self.find_nearest_enabled_tab(i);
819 if target >= 0 && target != i {
820 tab_control.set_selected_index(target);
821 }
822 }
823 }
824
825 fn sync_all_tab_enabled_states(&self, tab_control: &TabControl) {
826 for i in 0..tab_control.item_count() {
827 if let Some(tab_item) = Self::tab_item_from_index(tab_control, i) {
828 if let Some(page) = self.container_page(&tab_item) {
829 tab_item.set_is_enabled(Self::get_is_tab_enabled(&page));
830 }
831 }
832 }
833 }
834
835 fn prepare_page_for_container(&self, tab_item: &Ref<TabItem>, index: i32) -> Option<Ref<Page>> {
836 self.clear_container_page(tab_item);
837
838 let page;
839
840 if self.items_source().is_some() {
841 let item = self.try_get_item_at_index(index)?;
842
843 page = self
844 .page_template()
845 .and_then(|page_template| page_template.build(&item))
846 .and_then(|control| control.cast::<Page>());
847 tab_item.set_content(page.as_ref().map(|page| Control::boxed(page.clone())));
848
849 let page = page?;
850
851 self.template_created_pages.borrow_mut().insert(page_key(&page), page.clone());
852 StyledElement::logical_children(self).add(page.clone().upcast());
853
854 self.map_container_to_page(tab_item, &page);
855 return Some(page);
856 }
857
858 let item = self.try_get_item_at_index(index)?;
859
860 page = item.as_ref().and_then(Control::from_boxed).and_then(|control| control.cast::<Page>());
861 let page = page?;
862
863 tab_item.set_content(Some(Control::boxed(page.clone())));
864
865 self.map_container_to_page(tab_item, &page);
866 Some(page)
867 }
868
869 fn map_container_to_page(&self, tab_item: &Ref<TabItem>, page: &Ref<Page>) {
870 let weak = self.to_ref().downgrade();
871 let weak_page = page.downgrade();
872 let page_property_changed = page.property_changed(move |e| {
873 if let (Some(this), Some(page)) = (weak.upgrade(), weak_page.upgrade()) {
874 this.on_page_property_changed(&page, e);
875 }
876 });
877
878 let replaced = self.container_page_map.borrow_mut().insert(
879 tab_item_key(tab_item),
880 ContainerPage { tab_item: tab_item.clone(), page: page.clone(), page_property_changed },
881 );
882 if let Some(replaced) = replaced {
883 replaced.page_property_changed.dispose();
884 }
885 self.page_container_map.borrow_mut().insert(page_key(page), tab_item.clone());
886 }
887
888 fn clear_container_pages(&self) {
889 let entries: Vec<ContainerPage> = self.container_page_map.borrow_mut().drain().map(|(_, entry)| entry).collect();
890 for entry in &entries {
891 entry.page_property_changed.dispose();
892 }
893
894 let template_created_pages: Vec<Ref<Page>> =
895 self.template_created_pages.borrow_mut().drain().map(|(_, page)| page).collect();
896 let logical_children = StyledElement::logical_children(self);
897 for page in template_created_pages {
898 logical_children.remove(&page.upcast());
899 }
900
901 self.page_container_map.borrow_mut().clear();
902 }
903
904 fn clear_container_page(&self, tab_item: &TabItem) {
905 let entry = self.container_page_map.borrow_mut().remove(&tab_item_key(tab_item));
906 let Some(entry) = entry else {
907 return;
908 };
909 debug_assert!(std::ptr::eq::<TabItem>(&*entry.tab_item, tab_item));
910
911 self.page_container_map.borrow_mut().remove(&page_key(&entry.page));
912 entry.page_property_changed.dispose();
913
914 let template_created = self.template_created_pages.borrow_mut().remove(&page_key(&entry.page));
915 if let Some(page) = template_created {
916 StyledElement::logical_children(self).remove(&page.upcast());
917 }
918 }
919
920 fn try_get_item_at_index(&self, index: i32) -> Option<Option<BoxedValue>> {
923 if index < 0 {
924 return None;
925 }
926 let index = index as usize;
927
928 if let Some(tab_control) = self.tab_control() {
929 let items_view = tab_control.items_view();
930 if index < items_view.count() {
931 return Some(items_view.get_at(index));
932 }
933 }
934
935 if let Some(source) = self.items_source() {
936 return (index < source.count()).then(|| source.get_at(index));
937 }
938
939 self.pages().and_then(|pages| pages.try_get(index)).map(|page| Some(Control::boxed(page)))
940 }
941
942 fn commit_selection_if_resolved(&self, index: i32, navigation_type: NavigationType) {
943 let page = self.resolve_tab_page_at_index(index);
944
945 if page.is_none()
946 && self.items_source().is_some()
947 && index >= 0
948 && self.tab_control().and_then(|tab_control| tab_control.container_from_index(index)).is_none()
949 {
950 self.store_selected_index(index);
951 return;
952 }
953
954 self.commit_selection(index, page, navigation_type);
955 }
956
957 fn on_swipe_gesture(&self, e: &SwipeGestureEventArgs) {
958 if !self.is_gesture_enabled() {
959 return;
960 }
961 let Some(tab_control) = self.tab_control() else {
962 return;
963 };
964 if e.id() == self.last_swipe_gesture_id.get() {
965 return;
966 }
967
968 let placement = self.resolve_tab_placement();
969 let is_horizontal = placement == TabPlacement::Top || placement == TabPlacement::Bottom;
970 let is_rtl = self.flow_direction() == FlowDirection::RightToLeft;
971
972 let delta = match (e.swipe_direction(), is_horizontal, is_rtl) {
973 (SwipeDirection::Left, true, false) => 1,
974 (SwipeDirection::Right, true, false) => -1,
975 (SwipeDirection::Left, true, true) => -1,
976 (SwipeDirection::Right, true, true) => 1,
977 (SwipeDirection::Up, false, _) => -1,
978 (SwipeDirection::Down, false, _) => 1,
979 _ => 0,
980 };
981
982 if delta == 0 {
983 return;
984 }
985
986 let next = self.find_next_enabled_tab(tab_control.selected_index() + delta, delta);
987 if next >= 0 {
988 tab_control.set_selected_index(next);
989 e.set_handled(true);
990 self.last_swipe_gesture_id.set(e.id());
991 }
992 }
993
994 fn focus_selected_tab_item(&self) {
995 let Some(tab_control) = self.tab_control() else {
996 return;
997 };
998
999 if let Some(container) = tab_control.container_from_index(tab_control.selected_index()) {
1000 container.focus_with(NavigationMethod::Directional, KeyModifiers::NONE);
1001 }
1002 }
1003}