1use crate::generators::RecycleKey;
2use crate::i_selectable::{register_selectable, ISelectable};
3use crate::items_source::ItemsChangedEventArgs;
4use crate::metadata::{PseudoClassesAttribute, TemplatePartAttribute};
5use crate::mixins::{PressedMixin, SelectableMixin};
6use crate::primitives::{
7 HeaderedItemsControl, HeaderedItemsControlImpl, SelectingItemsControl, TemplateAppliedEventArgs,
8 TemplatedControlImpl,
9};
10use crate::templates::{FuncTemplate, ITemplateOf};
11use crate::{
12 Control, ControlImpl, ItemsControl, ItemsControlImpl, ItemsControlImplExt, Panel, RequestBringIntoViewEventArgs,
13 StackPanel, TreeView,
14};
15use ferroui_base::collections::NotifyCollectionChangedAction;
16use ferroui_base::data::BindingMode;
17use ferroui_base::input::{
18 InputElement, InputElementImpl, InputElementImplExt, Key, KeyEventArgs, KeyModifiers, NavigationMethod,
19 PointerEventArgs, PointerPressedEventArgs, PointerReleasedEventArgs, TappedEventArgs,
20};
21use ferroui_base::interactivity::{
22 InteractiveImpl, RoutedEvent, RoutedEventArgs, RoutedEventHandlerToken, RoutingStrategies,
23};
24use ferroui_base::layout::LayoutableImpl;
25use ferroui_base::logical_tree::LogicalTreeAttachmentEventArgs;
26use ferroui_base::{
27 ferro_class, ferro_impl_classes, ferro_property, ferro_routed_event, instantiate, BoxedValue, DirectProperty,
28 FerroObjectImpl, FerroProperty, FerroPropertyChangedEventArgs, Rect, Ref, Size, StaticType,
29 StyledElement, StyledElementImpl, StyledElementImplExt, StyledProperty, StyledPropertyOptions, VisualImpl,
30 WeakRef,
31};
32use std::cell::{Cell, RefCell};
33use std::rc::Rc;
34
35#[repr(C)]
37pub struct TreeViewItem {
38 base: HeaderedItemsControl,
39 tree_view: RefCell<Option<WeakRef<TreeView>>>,
40 header: RefCell<Option<Ref<Control>>>,
41 header_presenter: RefCell<Option<Ref<Control>>>,
42 header_double_tapped_token: Cell<Option<RoutedEventHandlerToken>>,
43 level: Cell<i32>,
44 template_applied: Cell<bool>,
45 deferred_bring_into_view_flag: Cell<bool>,
46 items_panel_pointer_pressed: Cell<bool>,
47}
48
49ferro_class! {
50 TreeViewItem: HeaderedItemsControl, virtuals TreeViewItemImpl: HeaderedItemsControlImpl {
51 fn on_request_bring_into_view(this, e: &RequestBringIntoViewEventArgs);
54 fn on_header_double_tapped(this, e: &TappedEventArgs);
56 }
57}
58ferroui_base::ferro_class_info!(TreeViewItem { new: TreeViewItem::new });
59
60ferro_impl_classes!(
61 TreeViewItem: VisualImpl,
62 LayoutableImpl,
63 InteractiveImpl,
64 HeaderedItemsControlImpl
65);
66
67impl ControlImpl for TreeViewItem {
68 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
69 crate::automation::peers::TreeViewItemAutomationPeer::new(this).upcast()
70 }
71}
72
73ferroui_base::ferro_impl_classes!(TreeViewItem: FerroObjectImpl);
74
75impl StyledElementImpl for TreeViewItem {
76 fn on_attached_to_logical_tree(this: &Self, e: &LogicalTreeAttachmentEventArgs) {
77 Self::parent_on_attached_to_logical_tree(this, e);
78
79 let mut tree_view = None;
80 let mut ancestor = this.parent();
81 while let Some(current) = ancestor {
82 if let Some(found) = current.cast::<TreeView>() {
83 tree_view = Some(found);
84 break;
85 }
86 ancestor = current.parent();
87 }
88
89 *this.tree_view.borrow_mut() = tree_view.as_ref().map(Ref::downgrade);
90
91 this.set_level(Self::calculate_distance_from_logical_parent::<TreeView>(Some(this.to_ref().upcast()), -1) - 1);
92
93 let Some(tree_view) = tree_view else { return };
94
95 if this.item_template().is_none() {
96 if let Some(item_template) = tree_view.item_template() {
97 this.set_current_value(ItemsControl::item_template_property(), Some(item_template));
98 }
99 }
100
101 if this.item_container_theme().is_none() {
102 if let Some(item_container_theme) = tree_view.item_container_theme() {
103 this.set_current_value(ItemsControl::item_container_theme_property(), Some(item_container_theme));
104 }
105 }
106 }
107}
108
109impl InputElementImpl for TreeViewItem {
110 fn on_key_down(this: &Self, e: &KeyEventArgs) {
111 if !e.handled() {
112 let handler = match e.key {
113 Key::Left => {
114 Some(apply_to_item_or_recursively_if_ctrl(focus_aware_collapse_item, e.key_modifiers))
115 }
116 Key::Right => Some(apply_to_item_or_recursively_if_ctrl(expand_item, e.key_modifiers)),
117 Key::Enter => Some(apply_to_item_or_recursively_if_ctrl(
118 if this.is_expanded() { collapse_item } else { expand_item },
119 e.key_modifiers,
120 )),
121
122 Key::Subtract => Some(KeyHandler::Item(focus_aware_collapse_item)),
124 Key::Add => Some(KeyHandler::Item(expand_item)),
125 Key::Divide => Some(KeyHandler::Subtree(collapse_item)),
126 Key::Multiply => Some(KeyHandler::Subtree(expand_item)),
127 _ => None,
128 };
129
130 match handler {
131 Some(handler) => e.set_handled(handler.invoke(this)),
132 None => {
133 if let Some(owner) = this.tree_view_owner() {
134 owner.update_selection_from_event(&this.to_ref().upcast(), e);
135 }
136 }
137 }
138 }
139
140 }
143
144 fn on_pointer_pressed(this: &Self, e: &PointerPressedEventArgs) {
145 Self::parent_on_pointer_pressed(this, e);
146
147 this.items_panel_pointer_pressed.set(this.is_items_panel_event(e));
148
149 if !this.items_panel_pointer_pressed.get() {
151 if let Some(owner) = this.tree_view_owner() {
152 owner.update_selection_from_event(&this.to_ref().upcast(), e);
153 }
154 }
155 }
156
157 fn on_pointer_released(this: &Self, e: &PointerReleasedEventArgs) {
158 Self::parent_on_pointer_released(this, e);
159
160 if !this.items_panel_pointer_pressed.get() && !this.is_items_panel_event(e) {
163 if let Some(owner) = this.tree_view_owner() {
164 owner.update_selection_from_event(&this.to_ref().upcast(), e);
165 }
166 }
167
168 this.items_panel_pointer_pressed.set(false);
169 }
170}
171
172impl TemplatedControlImpl for TreeViewItem {
173 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
174 let previous = this.header_presenter.take();
175 if let (Some(previous), Some(token)) = (previous, this.header_double_tapped_token.take()) {
176 previous.remove_handler(InputElement::double_tapped_event(), token);
177 }
178
179 *this.header.borrow_mut() = e.name_scope().find_as::<Control>("PART_Header");
180 let header_presenter = e.name_scope().find_as::<Control>("PART_HeaderPresenter");
181 *this.header_presenter.borrow_mut() = header_presenter.clone();
182 this.template_applied.set(true);
183
184 if let Some(header_presenter) = header_presenter {
185 let weak = this.to_ref().downgrade();
186 let token = header_presenter.add_handler(InputElement::double_tapped_event(), move |_, e| {
187 if let Some(this) = weak.upgrade() {
188 this.header_double_tapped(e);
189 }
190 });
191 this.header_double_tapped_token.set(Some(token));
192 }
193
194 if this.deferred_bring_into_view_flag.get() {
195 this.deferred_bring_into_view_flag.set(false);
196 this.bring_into_view();
197 }
198 }
199}
200
201impl ItemsControlImpl for TreeViewItem {
202 fn create_container_for_item_override(
203 this: &Self,
204 item: &Option<BoxedValue>,
205 index: i32,
206 recycle_key: Option<RecycleKey>,
207 ) -> Ref<Control> {
208 this.ensure_tree_view().create_container_for_item_override(item, index, recycle_key)
209 }
210
211 fn needs_container_override(this: &Self, item: &Option<BoxedValue>, index: i32) -> (bool, Option<RecycleKey>) {
212 this.ensure_tree_view().needs_container_override(item, index)
213 }
214
215 fn prepare_container_for_item_override(
216 this: &Self,
217 container: &Ref<Control>,
218 item: &Option<BoxedValue>,
219 index: i32,
220 ) {
221 this.ensure_tree_view().prepare_container_for_item_override(container, item, index)
222 }
223
224 fn container_for_item_prepared_override(
225 this: &Self,
226 container: &Ref<Control>,
227 item: &Option<BoxedValue>,
228 index: i32,
229 ) {
230 this.ensure_tree_view().container_for_item_prepared_override(container, item, index)
231 }
232
233 fn on_items_view_collection_changed(this: &Self, e: &ItemsChangedEventArgs<'_>) {
234 Self::parent_on_items_view_collection_changed(this, e);
235
236 let Some(tree_view) = this.tree_view_owner() else { return };
237
238 match e.action {
239 NotifyCollectionChangedAction::Remove | NotifyCollectionChangedAction::Replace => {
240 for i in e.old_items {
241 tree_view.selected_items().remove(&i);
242 }
243 }
244 NotifyCollectionChangedAction::Reset => {
245 for i in this.get_realized_containers() {
246 if i.downcast_ref::<TreeViewItem>().is_some_and(|tvi| tvi.is_selected()) {
247 tree_view.selected_items().remove(&i.data_context());
248 }
249 }
250 }
251 _ => {}
252 }
253 }
254}
255
256impl TreeViewItemImpl for TreeViewItem {
257 fn on_request_bring_into_view(this: &Self, e: &RequestBringIntoViewEventArgs) {
258 let this_visual: Ref<ferroui_base::Visual> = this.to_ref().upcast();
259 let is_target = e.target_object.as_ref() == Some(&this_visual);
260 if !is_target {
261 return;
262 }
263
264 if !this.template_applied.get() {
265 this.deferred_bring_into_view_flag.set(true);
266 return;
267 }
268
269 let header = this.header.borrow().clone();
270 if let Some(header) = header {
271 if let Some(m) = header.transform_to_visual(this) {
272 let bounds = Rect::from_size(Self::get_header_target_size(&header));
273 let rect = bounds.transform_to_aabb(m);
274 e.set_target_rect(rect);
275 }
276 }
277 }
278
279 fn on_header_double_tapped(this: &Self, e: &TappedEventArgs) {
280 if this.item_count() > 0 {
281 this.set_current_value(Self::is_expanded_property(), !this.is_expanded());
282 e.set_handled(true);
283 }
284 }
285}
286
287impl ISelectable for TreeViewItem {
288 fn is_selected(&self) -> bool {
289 TreeViewItem::is_selected(self)
290 }
291
292 fn set_is_selected(&self, value: bool) {
293 TreeViewItem::set_is_selected(self, value)
294 }
295}
296
297enum KeyHandler {
300 Item(fn(&TreeViewItem) -> bool),
301 Subtree(fn(&TreeViewItem) -> bool),
302}
303
304impl KeyHandler {
305 fn invoke(&self, tree_view_item: &TreeViewItem) -> bool {
310 match self {
311 KeyHandler::Item(f) => f(tree_view_item),
312 KeyHandler::Subtree(f) => {
313 let mut items = Vec::new();
318 sub_tree(&tree_view_item.to_ref(), &mut items);
319
320 let mut result = false;
321 for item in &items {
322 let applied = f(item);
323 result = result || applied;
324 }
325 result
326 }
327 }
328 }
329}
330
331fn apply_to_item_or_recursively_if_ctrl(f: fn(&TreeViewItem) -> bool, key_modifiers: KeyModifiers) -> KeyHandler {
332 if key_modifiers.contains(KeyModifiers::CONTROL) {
333 return KeyHandler::Subtree(f);
334 }
335
336 KeyHandler::Item(f)
337}
338
339fn expand_item(tree_view_item: &TreeViewItem) -> bool {
340 if tree_view_item.item_count() > 0 && !tree_view_item.is_expanded() {
341 tree_view_item.set_current_value(TreeViewItem::is_expanded_property(), true);
342 return true;
343 }
344
345 false
346}
347
348fn collapse_item(tree_view_item: &TreeViewItem) -> bool {
349 if tree_view_item.item_count() > 0 && tree_view_item.is_expanded() {
350 tree_view_item.set_current_value(TreeViewItem::is_expanded_property(), false);
351 return true;
352 }
353
354 false
355}
356
357fn focus_aware_collapse_item(tree_view_item: &TreeViewItem) -> bool {
358 if tree_view_item.item_count() > 0 && tree_view_item.is_expanded() {
359 if tree_view_item.is_focused() {
360 tree_view_item.set_current_value(TreeViewItem::is_expanded_property(), false);
361 } else {
362 tree_view_item.focus_with(NavigationMethod::Directional, KeyModifiers::NONE);
363 }
364
365 return true;
366 }
367
368 false
369}
370
371fn sub_tree(tree_view_item: &Ref<TreeViewItem>, result: &mut Vec<Ref<TreeViewItem>>) {
372 result.push(tree_view_item.clone());
373
374 for child in StyledElement::logical_children(tree_view_item).snapshot().iter() {
375 if let Some(child) = child.clone().cast::<TreeViewItem>() {
376 sub_tree(&child, result);
377 }
378 }
379}
380
381impl TreeViewItem {
382 pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
384 &[TemplatePartAttribute::new("PART_Header", <Control as StaticType>::TYPE)];
385
386 pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[":pressed", ":selected"]);
388}
389
390ferroui_base::ferro_properties! { impl TreeViewItem {
391 ferro_property!(
392 pub fn is_expanded_property() -> StyledProperty<bool> {
394 FerroProperty::register_with::<TreeViewItem, _>(
395 "IsExpanded",
396 StyledPropertyOptions::new(false).default_binding_mode(BindingMode::TwoWay),
397 )
398 }
399 );
400
401 ferro_property!(
402 pub fn is_selected_property() -> StyledProperty<bool> {
404 SelectingItemsControl::is_selected_property().add_owner::<TreeViewItem>()
405 }
406 );
407
408 ferro_property!(
409 pub fn level_property() -> DirectProperty<TreeViewItem, i32> {
411 FerroProperty::register_direct::<TreeViewItem, _>("Level", |o| o.level(), None, 0)
412 }
413 );
414} }
415
416impl TreeViewItem {
417 ferro_routed_event!(
418 pub fn expanded_event() -> RoutedEvent<RoutedEventArgs> {
420 RoutedEvent::register::<TreeViewItem, _>("Expanded", RoutingStrategies::BUBBLE | RoutingStrategies::TUNNEL)
421 }
422 );
423
424 ferro_routed_event!(
425 pub fn collapsed_event() -> RoutedEvent<RoutedEventArgs> {
427 RoutedEvent::register::<TreeViewItem, _>(
428 "Collapsed",
429 RoutingStrategies::BUBBLE | RoutingStrategies::TUNNEL,
430 )
431 }
432 );
433
434 pub(crate) fn static_constructor() {
435 register_selectable::<TreeViewItem>();
436 SelectableMixin::attach::<TreeViewItem>(Self::is_selected_property());
437 PressedMixin::attach::<TreeViewItem>();
438 InputElement::focusable_property().override_default_value::<TreeViewItem>(true);
439 let default_panel: Rc<dyn ITemplateOf<Option<Ref<Panel>>>> =
440 FuncTemplate::new(|| Some(StackPanel::new().upcast::<Panel>()));
441 ItemsControl::items_panel_property().override_default_value::<TreeViewItem>(default_panel);
442 crate::platform::PlatformFeedback::feedback_type_property()
443 .override_default_value::<TreeViewItem>(crate::platform::FeedbackType::Auto);
444 crate::automation::AutomationProperties::is_offscreen_behavior_property()
445 .override_default_value::<TreeViewItem>(crate::automation::IsOffscreenBehavior::FromClip);
446 Control::request_bring_into_view_event()
447 .add_class_handler::<TreeViewItem>(|x, e| x.on_request_bring_into_view(e));
448 Self::is_expanded_property().changed().add_class_handler::<TreeViewItem>(|x, e| x.on_is_expanded_changed(e));
449 }
450
451 pub fn construct() -> Self {
453 Self {
454 base: HeaderedItemsControl::construct(),
455 tree_view: RefCell::new(None),
456 header: RefCell::new(None),
457 header_presenter: RefCell::new(None),
458 header_double_tapped_token: Cell::new(None),
459 level: Cell::new(0),
460 template_applied: Cell::new(false),
461 deferred_bring_into_view_flag: Cell::new(false),
462 items_panel_pointer_pressed: Cell::new(false),
463 }
464 }
465
466 pub fn new() -> Ref<Self> {
467 instantiate(Self::construct())
468 }
469
470 fn on_is_expanded_changed(&self, args: &FerroPropertyChangedEventArgs<'_>) {
471 let routed_event = if args.get_new_value::<bool>() { Self::expanded_event() } else { Self::collapsed_event() };
472 let event_args = RoutedEventArgs::with_event_and_source(routed_event, self.to_ref());
473 self.raise_event(&event_args);
474 }
475
476 pub fn is_expanded(&self) -> bool {
479 self.get_value(Self::is_expanded_property())
480 }
481
482 pub fn set_is_expanded(&self, value: bool) {
483 self.set_value(Self::is_expanded_property(), value)
484 }
485
486 pub fn is_selected(&self) -> bool {
488 self.get_value(Self::is_selected_property())
489 }
490
491 pub fn set_is_selected(&self, value: bool) {
492 self.set_value(Self::is_selected_property(), value)
493 }
494
495 pub fn level(&self) -> i32 {
497 self.level.get()
498 }
499
500 fn set_level(&self, value: i32) {
501 self.set_and_raise_cell(Self::level_property(), &self.level, value);
502 }
503
504 pub fn expanded(
506 &self,
507 handler: impl Fn(&ferroui_base::interactivity::Interactive, &RoutedEventArgs) + 'static,
508 ) -> RoutedEventHandlerToken {
509 self.add_handler(Self::expanded_event(), handler)
510 }
511
512 pub fn collapsed(
514 &self,
515 handler: impl Fn(&ferroui_base::interactivity::Interactive, &RoutedEventArgs) + 'static,
516 ) -> RoutedEventHandlerToken {
517 self.add_handler(Self::collapsed_event(), handler)
518 }
519
520 pub(crate) fn tree_view_owner(&self) -> Option<Ref<TreeView>> {
521 self.tree_view.borrow().as_ref().and_then(WeakRef::upgrade)
522 }
523
524 fn get_header_target_size(header: &Control) -> Size {
525 let margin = header.margin();
530 let desired_width = header.desired_size().width - margin.left - margin.right;
531 let bounds_size = header.bounds().size();
532
533 bounds_size.with_width(desired_width.clamp(0.0, bounds_size.width))
534 }
535
536 fn is_items_panel_event(&self, e: &PointerEventArgs) -> bool {
540 match self.items_panel_root() {
541 Some(items_panel_root) => {
542 items_panel_root.input_hit_test(e.get_position(Some(&items_panel_root))).is_some()
543 }
544 None => false,
545 }
546 }
547
548 fn calculate_distance_from_logical_parent<T: ferroui_base::ObjectType>(
549 logical: Option<Ref<StyledElement>>,
550 default: i32,
551 ) -> i32 {
552 let mut result = 0;
553 let mut logical = logical;
554
555 while let Some(current) = logical.take() {
556 if current.is::<T>() {
557 return result;
558 }
559
560 result += 1;
561 logical = current.parent();
562 }
563
564 default
565 }
566
567 fn ensure_tree_view(&self) -> Ref<TreeView> {
568 match self.tree_view_owner() {
569 Some(tree_view) => tree_view,
570 None => panic!("The TreeViewItem is not part of a TreeView."),
571 }
572 }
573
574 fn header_double_tapped(&self, e: &TappedEventArgs) {
575 self.on_header_double_tapped(e);
576 }
577}