Skip to main content

ferroui_controls/
tab_item.rs

1use crate::i_selectable::{register_selectable, ISelectable};
2use crate::metadata::PseudoClassesAttribute;
3use crate::mixins::{PressedMixin, SelectableMixin};
4use crate::primitives::{HeaderedContentControl, SelectingItemsControl, TemplatedControl, TemplatedControlImpl};
5use crate::templates::IDataTemplate;
6use crate::{ContentControlImpl, Control, ControlImpl, Dock, IconElement};
7use ferroui_base::input::{
8    AccessKeyHandler, AccessKeyPressedEventArgs, FocusChangedEventArgs, InputElement, InputElementImpl,
9    InputElementImplExt, PointerPressedEventArgs, PointerReleasedEventArgs,
10};
11use ferroui_base::interactivity::{IRoutedEventArgs, InteractiveImpl};
12use ferroui_base::layout::LayoutableImpl;
13use ferroui_base::{
14    ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, DirectProperty, FerroObjectImpl,
15    FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StyledElement, StyledElementImpl,
16    StyledProperty, VisualImpl,
17};
18use std::cell::Cell;
19use std::rc::Rc;
20
21/// An item in a `TabControl`.
22#[repr(C)]
23pub struct TabItem {
24    base: HeaderedContentControl,
25    tab_strip_placement: Cell<Option<Dock>>,
26}
27
28ferro_class!(TabItem: HeaderedContentControl);
29ferroui_base::ferro_class_info!(TabItem { new: TabItem::new });
30ferro_impl_classes!(
31    TabItem: StyledElementImpl,
32    VisualImpl,
33    LayoutableImpl,
34    InteractiveImpl,
35    TemplatedControlImpl,
36    ContentControlImpl
37);
38
39impl ControlImpl for TabItem {
40    fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
41        crate::automation::peers::ListItemAutomationPeer::new(this).upcast()
42    }
43}
44
45impl FerroObjectImpl for TabItem {
46    fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
47        Self::parent_on_property_changed(this, change);
48        if change.property() == TemplatedControl::foreground_property().as_property()
49            || change.property() == Self::icon_property().as_property()
50        {
51            this.update_icon_foreground();
52        }
53    }
54}
55
56impl InputElementImpl for TabItem {
57    fn on_access_key(this: &Self, e: &dyn IRoutedEventArgs) {
58        this.focus();
59        this.set_current_value(Self::is_selected_property(), true);
60        e.as_routed_event_args().set_handled(true);
61    }
62
63    fn on_got_focus(this: &Self, e: &FocusChangedEventArgs) {
64        Self::parent_on_got_focus(this, e);
65        this.update_selection_from_event(e);
66    }
67
68    fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
69        Self::parent_on_pointer_pressed(this, e);
70        this.update_selection_from_event(e);
71    }
72
73    fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
74        Self::parent_on_pointer_released(this, e);
75        this.update_selection_from_event(e);
76    }
77}
78
79impl ISelectable for TabItem {
80    fn is_selected(&self) -> bool {
81        TabItem::is_selected(self)
82    }
83
84    fn set_is_selected(&self, value: bool) {
85        TabItem::set_is_selected(self, value)
86    }
87}
88
89impl TabItem {
90    /// The pseudoclasses set by the class.
91    pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[":pressed", ":selected"]);
92}
93
94ferroui_base::ferro_properties! { impl TabItem {
95    ferro_property!(
96        /// Defines the `TabStripPlacement` property.
97        pub fn tab_strip_placement_property() -> DirectProperty<TabItem, Option<Dock>> {
98            FerroProperty::register_direct::<TabItem, _>("TabStripPlacement", |o| o.tab_strip_placement(), None, None)
99        }
100    );
101
102    ferro_property!(
103        /// Defines the `IsSelected` property.
104        pub fn is_selected_property() -> StyledProperty<bool> {
105            SelectingItemsControl::is_selected_property().add_owner::<TabItem>()
106        }
107    );
108
109    ferro_property!(
110        /// Defines the `Icon` property.
111        pub fn icon_property() -> StyledProperty<Option<BoxedValue>> {
112            FerroProperty::register::<TabItem, _>("Icon", None)
113        }
114    );
115
116    ferro_property!(
117        /// Defines the `IconTemplate` property.
118        pub fn icon_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
119            FerroProperty::register::<TabItem, _>("IconTemplate", None)
120        }
121    );
122
123    ferro_property!(
124        /// Defines the `IndicatorTemplate` property.
125        pub fn indicator_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
126            FerroProperty::register::<TabItem, _>("IndicatorTemplate", None)
127        }
128    );
129} }
130
131impl TabItem {
132    pub(crate) fn static_constructor() {
133        register_selectable::<TabItem>();
134        SelectableMixin::attach::<TabItem>(Self::is_selected_property());
135        PressedMixin::attach::<TabItem>();
136        InputElement::focusable_property().override_default_value::<TabItem>(true);
137        StyledElement::data_context_property().changed().add_class_handler::<TabItem>(|x, e| x.update_header(e));
138        crate::automation::AutomationProperties::control_type_override_property()
139            .override_default_value::<TabItem>(Some(crate::automation::peers::AutomationControlType::TabItem));
140        crate::automation::AutomationProperties::is_offscreen_behavior_property()
141            .override_default_value::<TabItem>(crate::automation::IsOffscreenBehavior::FromClip);
142        AccessKeyHandler::access_key_pressed_event().add_class_handler::<TabItem>(Self::on_access_key_pressed);
143    }
144
145    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
146    pub fn construct() -> Self {
147        Self { base: HeaderedContentControl::construct(), tab_strip_placement: Cell::new(None) }
148    }
149
150    pub fn new() -> Ref<Self> {
151        instantiate(Self::construct())
152    }
153
154    /// Gets the placement of this tab relative to the outer `TabControl`,
155    /// if there is one.
156    pub fn tab_strip_placement(&self) -> Option<Dock> {
157        self.tab_strip_placement.get()
158    }
159
160    pub(crate) fn set_tab_strip_placement(&self, value: Option<Dock>) {
161        self.set_and_raise_cell(Self::tab_strip_placement_property(), &self.tab_strip_placement, value);
162    }
163
164    /// Gets or sets the selection state of the item.
165    pub fn is_selected(&self) -> bool {
166        self.get_value(Self::is_selected_property())
167    }
168
169    pub fn set_is_selected(&self, value: bool) {
170        self.set_value(Self::is_selected_property(), value)
171    }
172
173    /// Gets or sets the icon displayed alongside the tab header.
174    pub fn icon(&self) -> Option<BoxedValue> {
175        self.get_value(Self::icon_property())
176    }
177
178    pub fn set_icon(&self, value: Option<BoxedValue>) {
179        self.set_value(Self::icon_property(), value)
180    }
181
182    /// Gets or sets the data template used to display the icon of the
183    /// control.
184    pub fn icon_template(&self) -> Option<Rc<dyn IDataTemplate>> {
185        self.get_value(Self::icon_template_property())
186    }
187
188    pub fn set_icon_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
189        self.set_value(Self::icon_template_property(), value)
190    }
191
192    /// Gets or sets the data template used to render the selection
193    /// indicator.
194    pub fn indicator_template(&self) -> Option<Rc<dyn IDataTemplate>> {
195        self.get_value(Self::indicator_template_property())
196    }
197
198    pub fn set_indicator_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
199        self.set_value(Self::indicator_template_property(), value)
200    }
201
202    fn on_access_key_pressed(tab_item: &TabItem, e: &AccessKeyPressedEventArgs) {
203        if e.handled() || (e.target().is_some() && tab_item.is_selected()) {
204            return;
205        }
206
207        e.set_target(Some(tab_item.to_ref().upcast()));
208        e.set_handled(true);
209    }
210
211    /// Updates the selection of the owning selecting items control from an
212    /// event raised on this container.
213    pub fn update_selection_from_event(&self, e: &dyn IRoutedEventArgs) -> bool {
214        let this = self.to_ref().upcast();
215        match SelectingItemsControl::selecting_items_control_from_item_container(&this) {
216            Some(owner) => owner.update_selection_from_event(&this, e),
217            None => false,
218        }
219    }
220
221    fn update_icon_foreground(&self) {
222        let icon = self.icon().as_ref().and_then(Control::from_boxed).and_then(|icon| icon.cast::<IconElement>());
223        if let Some(icon) = icon {
224            match self.foreground() {
225                Some(fg) => icon.set_value(TemplatedControl::foreground_property(), Some(fg)),
226                None => icon.clear_value(TemplatedControl::foreground_property()),
227            }
228        }
229    }
230
231    fn update_header(&self, obj: &FerroPropertyChangedEventArgs<'_>) {
232        let (old_value, new_value) = obj.get_old_and_new_value::<Option<BoxedValue>>();
233        let header = self.header();
234
235        if header.is_none() {
236            let new_control = new_value.as_ref().and_then(Control::from_boxed);
237
238            // A data context with a header: a headered content control.
239            if let Some(headered) = new_control.as_ref().and_then(|c| crate::i_headered::as_headered(c)) {
240                let headered_header = headered.header();
241                if !crate::reference_equals(&header, &headered_header) {
242                    self.set_current_value(HeaderedContentControl::header_property(), headered_header);
243                }
244            } else if new_control.is_none() {
245                self.set_current_value(HeaderedContentControl::header_property(), new_value);
246            }
247        } else if crate::reference_equals(&header, &old_value) {
248            self.set_current_value(HeaderedContentControl::header_property(), new_value);
249        }
250    }
251}
252