1use crate::generators::RecycleKey;
2use crate::metadata::TemplatePartAttribute;
3use crate::presenters::{register_content_presenter_host, ContentPresenter, IContentPresenterHost, ItemsPresenter};
4use crate::primitives::{
5 SelectingItemsControl, SelectingItemsControlImpl, SelectingItemsControlImplExt, TemplateAppliedEventArgs,
6 TemplatedControlImpl,
7};
8use crate::templates::{FuncTemplate, IDataTemplate, ITemplateOf};
9use crate::{
10 ContentControl, Control, ControlImpl, Dock, ItemsControl, ItemsControlImpl, ItemsControlImplExt, Panel,
11 SelectionMode, TabItem, WrapPanel,
12};
13use ferroui_base::animation::IPageTransition;
14use ferroui_base::collections::FerroList;
15use ferroui_base::input::{
16 FocusChangedEventArgs, InputElement, InputElementImpl, KeyboardNavigation, KeyboardNavigationMode,
17 NavigationMethod, PointerEventArgs, PointerUpdateKind,
18};
19use ferroui_base::interactivity::{IRoutedEventArgs, InteractiveImpl};
20use ferroui_base::layout::{HorizontalAlignment, Layoutable, LayoutableImpl, LayoutableImplExt, VerticalAlignment};
21use ferroui_base::logging::{LogArea, LogEventLevel, Logger};
22use ferroui_base::reactive::{CompositeDisposable, IDisposable, ObservableExt};
23use ferroui_base::threading::{
24 CancellationTokenSource, Dispatcher, DispatcherPriority, DispatcherTask, FerroSynchronizationContext,
25};
26use ferroui_base::{
27 ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, DirectProperty, FerroObject,
28 FerroObjectExtensions, FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref,
29 Size, StaticType, StyledElement, StyledElementImpl, StyledProperty, Visual, VisualImpl,
30 VisualImplExt, VisualTreeAttachmentEventArgs,
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
39#[repr(C)]
42pub struct TabControl {
43 base: SelectingItemsControl,
44 selected_content: RefCell<Option<BoxedValue>>,
45 selected_content_template: RefCell<Option<Rc<dyn IDataTemplate>>>,
46 selected_item_subscriptions: RefCell<Option<CompositeDisposable>>,
47 items_presenter_part: RefCell<Option<Ref<ItemsPresenter>>>,
48 content_part: RefCell<Option<Ref<ContentPresenter>>>,
49 content_presenter2: RefCell<Option<Ref<ContentPresenter>>>,
50 previous_selected_index: Cell<i32>,
51 current_transition: RefCell<Option<Rc<CancellationTokenSource>>>,
52 should_animate: Cell<bool>,
53 pending_forward: Cell<bool>,
54}
55
56ferro_class! {
57 TabControl: SelectingItemsControl, virtuals TabControlImpl: SelectingItemsControlImpl {
58 fn register_content_presenter(this, presenter: &ContentPresenter) -> bool;
61 }
62}
63ferroui_base::ferro_class_info!(TabControl { new: TabControl::new });
64
65ferro_impl_classes!(TabControl: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
66
67impl FerroObjectImpl for TabControl {
68 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
69 Self::parent_on_property_changed(this, change);
70
71 if change.property() == Self::tab_strip_placement_property().as_property() {
72 this.refresh_containers();
73 } else if change.property() == Self::indicator_template_property().as_property() {
74 this.update_indicator_template();
75 } else if change.property() == Self::content_template_property().as_property() {
76 let new_template = change.get_new_value::<Option<Rc<dyn IDataTemplate>>>();
77 if this.selected_content_template() != new_template {
78 if let Some(container) = this.container_from_index(this.selected_index()) {
79 if container.get_value(ContentControl::content_template_property()).is_none() {
80 this.set_selected_content_template(new_template.clone());
81 if !this.should_animate.get() {
82 if let Some(content_part) = this.content_part() {
83 content_part.set_content_template(new_template);
84 }
85 }
86 }
87 }
88 }
89 } else if change.property() == KeyboardNavigation::tab_once_active_element_property().as_property() {
90 if let Some(panel) = this.items_presenter_part().and_then(|presenter| presenter.panel()) {
91 KeyboardNavigation::set_tab_once_active_element(
92 &panel,
93 change.get_new_value::<Option<Ref<InputElement>>>(),
94 );
95 }
96 }
97 }
98}
99
100impl VisualImpl for TabControl {
101 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
102 Self::parent_on_detached_from_visual_tree(this, e);
103
104 if let Some(current_transition) = this.current_transition.take() {
105 current_transition.cancel();
106 }
107 this.should_animate.set(false);
108 Self::reset_transition_presenter(this.content_presenter2().as_ref());
109 }
110}
111
112impl LayoutableImpl for TabControl {
113 fn arrange_override(this: &Self, final_size: Size) -> Size {
114 let result = Self::parent_arrange_override(this, final_size);
115
116 if this.should_animate.get() {
117 this.should_animate.set(false);
118 if let Some(current_transition) = this.current_transition.take() {
119 current_transition.cancel();
120 }
121 let cancel = Rc::new(CancellationTokenSource::new());
122 *this.current_transition.borrow_mut() = Some(cancel.clone());
123 let from = this.content_part();
124 let to = this.content_presenter2();
125 if let (Some(from), Some(to), Some(page_transition)) = (from, to, this.page_transition()) {
126 this.run_transition_async(page_transition, from, to, this.pending_forward.get(), cancel);
127 }
128 }
129
130 result
131 }
132}
133
134impl TemplatedControlImpl for TabControl {
135 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
136 let items_presenter_part = e.name_scope().find_as::<ItemsPresenter>("PART_ItemsPresenter");
137 *this.items_presenter_part.borrow_mut() = items_presenter_part.clone();
138 if let Some(items_presenter_part) = &items_presenter_part {
139 items_presenter_part.apply_template();
140 }
141
142 this.update_tab_strip_placement();
143
144 if let Some(panel) = items_presenter_part.and_then(|presenter| presenter.panel()) {
145 if !panel.is_set(KeyboardNavigation::tab_navigation_property().as_property()) {
146 panel.set_current_value(KeyboardNavigation::tab_navigation_property(), KeyboardNavigationMode::Once);
147 }
148 KeyboardNavigation::set_tab_once_active_element(
149 &panel,
150 KeyboardNavigation::get_tab_once_active_element(this),
151 );
152 }
153 }
154}
155
156impl ItemsControlImpl for TabControl {
157 fn create_container_for_item_override(
158 _this: &Self,
159 _item: &Option<BoxedValue>,
160 _index: i32,
161 _recycle_key: Option<RecycleKey>,
162 ) -> Ref<Control> {
163 TabItem::new().upcast()
164 }
165
166 fn needs_container_override(this: &Self, item: &Option<BoxedValue>, _index: i32) -> (bool, Option<RecycleKey>) {
167 this.needs_container::<TabItem>(item)
168 }
169
170 fn prepare_container_for_item_override(
171 this: &Self,
172 element: &Ref<Control>,
173 item: &Option<BoxedValue>,
174 index: i32,
175 ) {
176 Self::parent_prepare_container_for_item_override(this, element, item, index);
177
178 if let Some(tab_item) = element.downcast_ref::<TabItem>() {
179 tab_item.set_tab_strip_placement(Some(this.tab_strip_placement()));
180
181 if let Some(tmpl) = this.indicator_template() {
182 if !tab_item.is_set(TabItem::indicator_template_property().as_property()) {
183 tab_item.set_current_value(TabItem::indicator_template_property(), Some(tmpl));
184 }
185 }
186 }
187
188 if index == this.selected_index() {
189 this.update_selected_content(Some(element.clone()));
190 }
191 }
192
193 fn container_index_changed_override(this: &Self, container: &Ref<Control>, old_index: i32, new_index: i32) {
194 Self::parent_container_index_changed_override(this, container, old_index, new_index);
195
196 let selected_index = this.selected_index();
197
198 if selected_index == old_index || selected_index == new_index {
199 this.update_selected_content(None);
200 }
201 }
202
203 fn clear_container_for_item_override(this: &Self, element: &Ref<Control>) {
204 Self::parent_clear_container_for_item_override(this, element);
205 this.update_selected_content(None);
206 }
207}
208
209impl SelectingItemsControlImpl for TabControl {
210 fn should_trigger_selection(this: &Self, selectable: &Visual, event_args: &PointerEventArgs) -> bool {
211 matches!(
212 event_args.properties().pointer_update_kind,
213 PointerUpdateKind::LeftButtonPressed | PointerUpdateKind::LeftButtonReleased
214 ) && Self::parent_should_trigger_selection(this, selectable, event_args)
215 }
216
217 fn update_selection_from_event(this: &Self, container: &Ref<Control>, event_args: &dyn IRoutedEventArgs) -> bool {
218 if let Some(focus) = event_args.downcast_ref::<FocusChangedEventArgs>() {
219 if focus.navigation_method != NavigationMethod::Directional {
220 return false;
221 }
222 }
223
224 Self::parent_update_selection_from_event(this, container, event_args)
225 }
226}
227
228impl TabControlImpl for TabControl {
229 fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
230 let name = presenter.name();
231
232 if name.as_deref() == Some("PART_SelectedContentHost") {
233 *this.content_part.borrow_mut() = Some(presenter.to_ref());
234 presenter.set_content(this.selected_content());
235 presenter.set_content_template(this.selected_content_template());
236 return true;
237 }
238
239 if name.as_deref() == Some("PART_SelectedContentHost2") {
240 *this.content_presenter2.borrow_mut() = Some(presenter.to_ref());
241 presenter.set_is_visible(false);
242 return true;
243 }
244
245 false
246 }
247}
248
249impl IContentPresenterHost for TabControl {
250 fn logical_children(&self) -> &FerroList<Ref<StyledElement>> {
251 StyledElement::logical_children(self)
252 }
253
254 fn register_content_presenter(&self, presenter: &ContentPresenter) -> bool {
255 TabControl::register_content_presenter(self, presenter)
256 }
257}
258
259impl TabControl {
260 pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] = &[
262 TemplatePartAttribute::new("PART_ItemsPresenter", <ItemsPresenter as StaticType>::TYPE),
263 TemplatePartAttribute::new("PART_SelectedContentHost", <ContentPresenter as StaticType>::TYPE),
264 TemplatePartAttribute::new("PART_SelectedContentHost2", <ContentPresenter as StaticType>::TYPE),
265 ];
266}
267
268ferroui_base::ferro_properties! { impl TabControl {
269 ferro_property!(
270 pub fn tab_strip_placement_property() -> StyledProperty<Dock> {
272 FerroProperty::register::<TabControl, _>("TabStripPlacement", Dock::Top)
273 }
274 );
275
276 ferro_property!(
277 pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
279 ContentControl::horizontal_content_alignment_property().add_owner::<TabControl>()
280 }
281 );
282
283 ferro_property!(
284 pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
286 ContentControl::vertical_content_alignment_property().add_owner::<TabControl>()
287 }
288 );
289
290 ferro_property!(
291 pub fn content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
293 ContentControl::content_template_property().add_owner::<TabControl>()
294 }
295 );
296
297 ferro_property!(
298 pub fn selected_content_property() -> DirectProperty<TabControl, Option<BoxedValue>> {
300 FerroProperty::register_direct::<TabControl, _>("SelectedContent", |o| o.selected_content(), None, None)
301 }
302 );
303
304 ferro_property!(
305 pub fn selected_content_template_property() -> DirectProperty<TabControl, Option<Rc<dyn IDataTemplate>>> {
307 FerroProperty::register_direct::<TabControl, _>(
308 "SelectedContentTemplate",
309 |o| o.selected_content_template(),
310 None,
311 None,
312 )
313 }
314 );
315
316 ferro_property!(
317 pub fn page_transition_property() -> StyledProperty<Option<Rc<dyn IPageTransition>>> {
319 FerroProperty::register::<TabControl, _>("PageTransition", None)
320 }
321 );
322
323 ferro_property!(
324 pub fn indicator_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
326 FerroProperty::register::<TabControl, _>("IndicatorTemplate", None)
327 }
328 );
329} }
330
331impl TabControl {
332 fn static_constructor() {
334 register_content_presenter_host::<TabControl>();
335
336 let default_panel: Rc<dyn ITemplateOf<Option<Ref<Panel>>>> =
338 FuncTemplate::new(|| Some(WrapPanel::new().upcast::<Panel>()));
339
340 SelectingItemsControl::selection_mode_property()
341 .override_default_value::<TabControl>(SelectionMode::ALWAYS_SELECTED);
342 ItemsControl::items_panel_property().override_default_value::<TabControl>(default_panel);
343 Layoutable::affects_measure::<TabControl>(&[Self::tab_strip_placement_property().as_property()]);
344 SelectingItemsControl::selected_item_property()
345 .changed()
346 .add_class_handler::<TabControl>(|x, _| x.update_selected_content(None));
347 crate::automation::AutomationProperties::control_type_override_property()
348 .override_default_value::<TabControl>(Some(crate::automation::peers::AutomationControlType::Tab));
349 }
350
351 pub fn construct() -> Self {
353 Self {
354 base: SelectingItemsControl::construct(),
355 selected_content: RefCell::new(None),
356 selected_content_template: RefCell::new(None),
357 selected_item_subscriptions: RefCell::new(None),
358 items_presenter_part: RefCell::new(None),
359 content_part: RefCell::new(None),
360 content_presenter2: RefCell::new(None),
361 previous_selected_index: Cell::new(-1),
362 current_transition: RefCell::new(None),
363 should_animate: Cell::new(false),
364 pending_forward: Cell::new(false),
365 }
366 }
367
368 pub fn new() -> Ref<Self> {
369 instantiate(Self::construct())
370 }
371
372 pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
375 self.get_value(Self::horizontal_content_alignment_property())
376 }
377
378 pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
379 self.set_value(Self::horizontal_content_alignment_property(), value)
380 }
381
382 pub fn vertical_content_alignment(&self) -> VerticalAlignment {
385 self.get_value(Self::vertical_content_alignment_property())
386 }
387
388 pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
389 self.set_value(Self::vertical_content_alignment_property(), value)
390 }
391
392 pub fn tab_strip_placement(&self) -> Dock {
394 self.get_value(Self::tab_strip_placement_property())
395 }
396
397 pub fn set_tab_strip_placement(&self, value: Dock) {
398 self.set_value(Self::tab_strip_placement_property(), value)
399 }
400
401 pub fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
404 self.get_value(Self::content_template_property())
405 }
406
407 pub fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
408 self.set_value(Self::content_template_property(), value)
409 }
410
411 pub fn selected_content(&self) -> Option<BoxedValue> {
413 self.selected_content.borrow().clone()
414 }
415
416 pub(crate) fn set_selected_content(&self, value: Option<BoxedValue>) {
417 self.set_and_raise(Self::selected_content_property(), &self.selected_content, value);
418 }
419
420 pub fn selected_content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
422 self.selected_content_template.borrow().clone()
423 }
424
425 pub(crate) fn set_selected_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
426 self.set_and_raise(Self::selected_content_template_property(), &self.selected_content_template, value);
427 }
428
429 pub fn page_transition(&self) -> Option<Rc<dyn IPageTransition>> {
431 self.get_value(Self::page_transition_property())
432 }
433
434 pub fn set_page_transition(&self, value: Option<Rc<dyn IPageTransition>>) {
435 self.set_value(Self::page_transition_property(), value)
436 }
437
438 pub fn indicator_template(&self) -> Option<Rc<dyn IDataTemplate>> {
441 self.get_value(Self::indicator_template_property())
442 }
443
444 pub fn set_indicator_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
445 self.set_value(Self::indicator_template_property(), value)
446 }
447
448 pub(crate) fn items_presenter_part(&self) -> Option<Ref<ItemsPresenter>> {
449 self.items_presenter_part.borrow().clone()
450 }
451
452 pub(crate) fn content_part(&self) -> Option<Ref<ContentPresenter>> {
453 self.content_part.borrow().clone()
454 }
455
456 fn content_presenter2(&self) -> Option<Ref<ContentPresenter>> {
457 self.content_presenter2.borrow().clone()
458 }
459
460 #[cfg(test)]
463 pub(crate) fn set_content_presenters_for_test(
464 &self,
465 content_part: Option<Ref<ContentPresenter>>,
466 content_presenter2: Option<Ref<ContentPresenter>>,
467 ) {
468 *self.content_part.borrow_mut() = content_part;
469 *self.content_presenter2.borrow_mut() = content_presenter2;
470 }
471
472 fn select_content_template(
473 &self,
474 container_template: Option<Rc<dyn IDataTemplate>>,
475 ) -> Option<Rc<dyn IDataTemplate>> {
476 container_template.or_else(|| self.content_template())
477 }
478
479 fn update_selected_content(&self, container: Option<Ref<Control>>) {
480 if let Some(subscriptions) = self.selected_item_subscriptions.take() {
481 subscriptions.dispose();
482 }
483
484 if let Some(current_transition) = self.current_transition.take() {
485 current_transition.cancel();
486 }
487 self.should_animate.set(false);
488
489 Self::reset_transition_presenter(self.content_presenter2().as_ref());
490
491 let old_index = self.previous_selected_index.get();
492 self.previous_selected_index.set(self.selected_index());
493 let forward = self.selected_index() >= old_index || old_index < 0;
494
495 if self.selected_index() == -1 {
496 self.set_selected_content_template(None);
497 self.set_selected_content(None);
498 if let Some(content_part) = self.content_part() {
499 content_part.set_content(None);
500 content_part.set_content_template(None);
501 content_part.set_data_context(None);
502 }
503 return;
504 }
505
506 let container = container.or_else(|| self.container_from_index(self.selected_index()));
507 let Some(container) = container else { return };
508
509 if self.selected_content_template()
510 != self.select_content_template(container.get_value(ContentControl::content_template_property()))
511 {
512 self.set_selected_content_template(None);
513 if let Some(content_part) = self.content_part() {
514 content_part.set_content_template(None);
515 }
516 }
517
518 let should_transition = self.page_transition().is_some()
519 && self.content_presenter2().is_some()
520 && self.visual_root().is_some()
521 && old_index >= 0
522 && old_index != self.selected_index();
523 let is_initial_fire = Rc::new(Cell::new(true));
524
525 let weak = self.to_ref().downgrade();
528 let weak_container = container.downgrade();
529 let container_object: &FerroObject = &container;
530
531 let content_subscription = {
532 let (weak, weak_container) = (weak.clone(), weak_container.clone());
533 let observable =
534 FerroObjectExtensions::get_observable(container_object, ContentControl::content_property());
535 observable.subscribe_fn(move |content| {
536 let (Some(this), Some(container)) = (weak.upgrade(), weak_container.upgrade()) else { return };
537
538 this.set_selected_content(content.clone());
539
540 let template =
541 this.select_content_template(container.get_value(ContentControl::content_template_property()));
542 let control = match &template {
543 None => content.as_ref().and_then(Control::from_boxed),
544 Some(_) => None,
545 };
546
547 if is_initial_fire.get() && should_transition {
548 this.set_selected_content_template(template.clone());
549
550 let content_presenter2 =
551 this.content_presenter2().expect("the transition presenter is registered");
552 content_presenter2.set_content_template(template);
553 content_presenter2.set_is_visible(true);
554
555 match (&content, &control) {
556 (Some(content), Some(control)) => {
557 this.set_control_content(&content_presenter2, content, control, container.data_context())
558 }
559 _ => content_presenter2.set_content(content),
560 }
561
562 this.pending_forward.set(forward);
563 this.should_animate.set(true);
564 this.invalidate_arrange();
565 } else if let Some(content_part) = this.content_part() {
566 match (&content, &control) {
567 (Some(content), Some(control)) => {
568 this.set_control_content(&content_part, content, control, container.data_context())
569 }
570 _ => content_part.set_content(content),
571 }
572 }
573
574 is_initial_fire.set(false);
575 })
576 };
577
578 let content_template_subscription = {
579 let weak = weak.clone();
580 let observable =
581 FerroObjectExtensions::get_observable(container_object, ContentControl::content_template_property());
582 observable.subscribe_fn(move |v| {
583 let Some(this) = weak.upgrade() else { return };
584
585 this.set_selected_content_template(this.select_content_template(v));
586 if !this.should_animate.get() {
587 if let Some(content_part) = this.content_part() {
588 content_part.set_content_template(this.selected_content_template());
589 }
590 }
591 })
592 };
593
594 let data_context_subscription = {
595 let observable =
596 FerroObjectExtensions::get_observable(container_object, StyledElement::data_context_property());
597 observable.subscribe_fn(move |dc| {
598 let Some(this) = weak.upgrade() else { return };
599
600 let has_template_less_control_content = |presenter: &ContentPresenter| {
608 presenter.content().as_ref().and_then(Control::from_boxed).is_some()
609 && presenter.content_template().is_none()
610 };
611
612 match this.content_presenter2().filter(|presenter| presenter.is_visible()) {
613 Some(content_presenter2) => {
614 if has_template_less_control_content(&content_presenter2) {
615 content_presenter2.set_data_context(dc);
616 }
617 }
618 None => {
619 if let Some(content_part) = this.content_part() {
620 if has_template_less_control_content(&content_part) {
621 content_part.set_data_context(dc);
622 }
623 }
624 }
625 }
626 })
627 };
628
629 *self.selected_item_subscriptions.borrow_mut() = Some(CompositeDisposable::from_disposables([
630 content_subscription,
631 content_template_subscription,
632 data_context_subscription,
633 ]));
634 }
635
636 fn run_transition_async(
637 &self,
638 transition: Rc<dyn IPageTransition>,
639 from: Ref<ContentPresenter>,
640 to: Ref<ContentPresenter>,
641 forward: bool,
642 cts: Rc<CancellationTokenSource>,
643 ) {
644 let this = self.to_ref().downgrade();
645 let context = FerroSynchronizationContext::current().unwrap_or_else(|| {
646 FerroSynchronizationContext::with_dispatcher(&Dispatcher::current_dispatcher(), DispatcherPriority::NORMAL)
647 });
648
649 let from_visual: Ref<Visual> = from.clone().upcast();
652 let to_visual: Ref<Visual> = to.upcast();
653 let task = transition.start(Some(&from_visual), Some(&to_visual), forward, cts.token());
654
655 let _ = context.to_task_scheduler().start_local(async move {
656 match (TransitionOutcomeFuture { task }).await {
657 TransitionOutcome::Canceled => return,
658 TransitionOutcome::Faulted => {
659 if let Some(logger) = Logger::try_get(LogEventLevel::Error, LogArea::CONTROL) {
660 let source = this.upgrade();
661 logger.log(
662 source.as_ref().map(|source| source as &dyn Any),
663 "Tab transition threw an unhandled exception.",
664 );
665 }
666 }
667 TransitionOutcome::Completed => {}
668 }
669
670 if cts.is_cancellation_requested() {
671 return;
672 }
673
674 from.set_is_visible(false);
675 from.set_content(None);
676 from.set_content_template(None);
677 from.set_data_context(None);
678 from.set_render_transform(None);
679 from.set_opacity(1.0);
680
681 if let Some(this) = this.upgrade() {
682 let content_part = this.content_part.take();
683 let content_presenter2 = this.content_presenter2.replace(content_part);
684 *this.content_part.borrow_mut() = content_presenter2;
685 }
686 });
687 }
688
689 fn set_control_content(
690 &self,
691 presenter: &Ref<ContentPresenter>,
692 content: &BoxedValue,
693 control: &Ref<Control>,
694 data_context: Option<BoxedValue>,
695 ) {
696 self.clear_owning_content_presenter(presenter, control);
697 Self::clear_presenter_content_unless(self.content_part().as_ref(), presenter, control);
698 Self::clear_presenter_content_unless(self.content_presenter2().as_ref(), presenter, control);
699 presenter.set_content_with_data_context(Some(content.clone()), data_context);
700 }
701
702 fn clear_owning_content_presenter(&self, target_presenter: &Ref<ContentPresenter>, content: &Ref<Control>) {
703 let parent = content.visual_parent().and_then(|parent| parent.cast::<ContentPresenter>());
704 let Some(parent) = parent else { return };
705 let this: Ref<FerroObject> = self.to_ref().upcast();
706
707 if &parent != target_presenter && parent.host() == Some(this) && Self::has_content(&parent, content) {
708 Self::clear_presenter_content(Some(&parent), content);
709 }
710 }
711
712 fn clear_presenter_content_unless(
713 presenter: Option<&Ref<ContentPresenter>>,
714 target_presenter: &Ref<ContentPresenter>,
715 content: &Ref<Control>,
716 ) {
717 if presenter != Some(target_presenter) {
718 Self::clear_presenter_content(presenter, content);
719 }
720 }
721
722 fn clear_presenter_content(presenter: Option<&Ref<ContentPresenter>>, content: &Ref<Control>) {
723 if let Some(presenter) = presenter.filter(|presenter| Self::has_content(presenter, content)) {
724 presenter.set_content(None);
725 presenter.set_content_template(None);
726 presenter.set_data_context(None);
727 presenter.set_render_transform(None);
728 presenter.set_opacity(1.0);
729 }
730 }
731
732 fn has_content(presenter: &ContentPresenter, content: &Ref<Control>) -> bool {
734 presenter.content().as_ref().and_then(Control::from_boxed).as_ref() == Some(content)
735 }
736
737 fn reset_transition_presenter(presenter: Option<&Ref<ContentPresenter>>) {
738 let Some(presenter) = presenter else { return };
739
740 presenter.set_is_visible(false);
741 presenter.set_content(None);
742 presenter.set_content_template(None);
743 presenter.set_data_context(None);
744 presenter.set_render_transform(None);
745 presenter.set_opacity(1.0);
746 }
747
748 fn update_tab_strip_placement(&self) {
749 let Some(panel) = self.items_presenter_part().and_then(|presenter| presenter.panel()) else { return };
750
751 for control in panel.children().snapshot().iter() {
752 if let Some(tab_item) = control.downcast_ref::<TabItem>() {
753 tab_item.set_tab_strip_placement(Some(self.tab_strip_placement()));
754 }
755 }
756 }
757
758 fn update_indicator_template(&self) {
759 let Some(panel) = self.items_presenter_part().and_then(|presenter| presenter.panel()) else { return };
760
761 let template = self.indicator_template();
762 for control in panel.children().snapshot().iter() {
763 if let Some(tab_item) = control.downcast_ref::<TabItem>() {
764 if !tab_item.is_set(TabItem::indicator_template_property().as_property()) {
765 match &template {
766 Some(template) => tab_item
767 .set_current_value(TabItem::indicator_template_property(), Some(template.clone())),
768 None => tab_item.clear_value(TabItem::indicator_template_property()),
769 }
770 }
771 }
772 }
773 }
774}
775
776enum TransitionOutcome {
778 Completed,
779 Canceled,
780 Faulted,
781}
782
783struct TransitionOutcomeFuture {
785 task: DispatcherTask<()>,
786}
787
788impl Future for TransitionOutcomeFuture {
789 type Output = TransitionOutcome;
790
791 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<TransitionOutcome> {
792 if self.task.is_faulted() {
793 return Poll::Ready(TransitionOutcome::Faulted);
794 }
795
796 match Pin::new(&mut self.task).poll(cx) {
797 Poll::Ready(Ok(())) => Poll::Ready(TransitionOutcome::Completed),
798 Poll::Ready(Err(_)) => Poll::Ready(TransitionOutcome::Canceled),
799 Poll::Pending => Poll::Pending,
800 }
801 }
802}