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#[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 pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[":pressed", ":selected"]);
92}
93
94ferroui_base::ferro_properties! { impl TabItem {
95 ferro_property!(
96 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 pub fn is_selected_property() -> StyledProperty<bool> {
105 SelectingItemsControl::is_selected_property().add_owner::<TabItem>()
106 }
107 );
108
109 ferro_property!(
110 pub fn icon_property() -> StyledProperty<Option<BoxedValue>> {
112 FerroProperty::register::<TabItem, _>("Icon", None)
113 }
114 );
115
116 ferro_property!(
117 pub fn icon_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
119 FerroProperty::register::<TabItem, _>("IconTemplate", None)
120 }
121 );
122
123 ferro_property!(
124 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 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 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 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 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 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 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 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 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