ferroui_dialogs/
managed_file_chooser.rs1use crate::internal::{ManagedFileChooserItemType, ManagedFileChooserItemViewModel, ManagedFileChooserViewModel};
2use ferroui_base::input::{InputElement, InputElementImpl, PointerPressedEventArgs};
3use ferroui_base::interactivity::{InteractiveImpl, RoutingStrategies};
4use ferroui_base::layout::LayoutableImpl;
5use ferroui_base::metadata::from_markup_value;
6use ferroui_base::threading::{DispatcherPriority, DispatcherTimer};
7use ferroui_base::{
8 ferro_class, ferro_class_info, ferro_impl_classes, instantiate, FerroObjectImpl, FerroObjectImplExt, Ref,
9 StyledElement, StyledElementImpl, StyledElementImplExt, VisualImpl,
10};
11use ferroui_controls::primitives::{TemplateAppliedEventArgs, TemplatedControl, TemplatedControlImpl};
12use ferroui_controls::{Control, ControlImpl, ListBox};
13use std::cell::RefCell;
14use std::rc::Rc;
15use std::time::Duration;
16
17#[repr(C)]
21pub struct ManagedFileChooser {
22 base: TemplatedControl,
23 quick_links_root: RefCell<Option<Ref<Control>>>,
24 files_view: RefCell<Option<Ref<ListBox>>>,
25}
26
27ferro_class!(ManagedFileChooser: TemplatedControl);
28ferro_impl_classes!(ManagedFileChooser: VisualImpl, LayoutableImpl, InteractiveImpl, InputElementImpl, ControlImpl);
29ferro_class_info!(ManagedFileChooser {
30 new: ManagedFileChooser::new,
31 markup: {
32 attributes: [
33 TemplatePart("PART_QuickLinks", type(Ref<Control>), IsRequired = true),
34 TemplatePart("PART_Files", type(Ref<ListBox>), IsRequired = true),
35 ],
36 },
37});
38
39fn is_logical_ancestor_of(logical: &StyledElement, target: Option<&StyledElement>) -> bool {
42 let mut current = target.and_then(|target| target.parent());
43
44 while let Some(parent) = current {
45 if std::ptr::eq::<StyledElement>(&*parent, logical) {
46 return true;
47 }
48 current = parent.parent();
49 }
50
51 false
52}
53
54impl FerroObjectImpl for ManagedFileChooser {
55 fn constructed(this: &Self) {
56 Self::parent_constructed(this);
57
58 let weak = this.to_ref().downgrade();
59 this.add_handler_with(
60 InputElement::pointer_pressed_event(),
61 move |_, e: &PointerPressedEventArgs| {
62 if let Some(this) = weak.upgrade() {
63 this.on_pointer_pressed(e);
64 }
65 },
66 RoutingStrategies::TUNNEL,
67 false,
68 );
69 }
70}
71
72impl StyledElementImpl for ManagedFileChooser {
73 fn on_data_context_changed(this: &Self) {
74 Self::parent_on_data_context_changed(this);
75
76 let Some(model) = this.model() else {
77 return;
78 };
79
80 let Some(preselected) = model.selected_items().try_get(0) else {
81 return;
82 };
83
84 let weak = this.to_ref().downgrade();
86 DispatcherTimer::run_once(
87 move || {
88 let Some(this) = weak.upgrade() else { return };
89
90 if Some(&preselected) != model.selected_items().try_get(0).as_ref() {
91 return;
92 }
93
94 let index_of_preselected =
96 model.items().to_vec().iter().position(|item| Some(item.clone()) == from_markup_value(&preselected));
97
98 let files_view = this.files_view.borrow().clone();
99 if let (Some(files_view), Some(index_of_preselected)) = (files_view, index_of_preselected) {
100 if index_of_preselected > 1 {
101 files_view.scroll_into_view(index_of_preselected as i32 - 1);
102 }
103 }
104 },
105 Duration::from_millis(100),
106 DispatcherPriority::DEFAULT,
107 );
108 }
109}
110
111impl TemplatedControlImpl for ManagedFileChooser {
112 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
113 *this.quick_links_root.borrow_mut() = Some(e.name_scope().get_as::<Control>("PART_QuickLinks"));
114 *this.files_view.borrow_mut() = Some(e.name_scope().get_as::<ListBox>("PART_Files"));
115 }
116}
117
118impl ManagedFileChooser {
119 pub fn construct() -> Self {
120 Self { base: TemplatedControl::construct(), quick_links_root: RefCell::new(None), files_view: RefCell::new(None) }
121 }
122
123 pub fn new() -> Ref<Self> {
124 instantiate(Self::construct())
125 }
126
127 fn model(&self) -> Option<Rc<ManagedFileChooserViewModel>> {
128 from_markup_value::<Rc<ManagedFileChooserViewModel>>(&self.data_context())
129 }
130
131 fn on_pointer_pressed(&self, e: &PointerPressedEventArgs) {
132 let source = e.source();
133 let source_element = source.as_ref().and_then(|source| source.cast::<StyledElement>());
134 let Some(model) = source_element
135 .as_ref()
136 .and_then(|element| from_markup_value::<Rc<ManagedFileChooserItemViewModel>>(&element.data_context()))
137 else {
138 return;
139 };
140
141 let quick_links_root = self.quick_links_root.borrow().clone();
142 if let Some(quick_links_root) = quick_links_root {
143 let source_control = source.as_ref().and_then(|source| source.cast::<Control>());
144 let is_quick_link = is_logical_ancestor_of(&quick_links_root, source_control.as_deref().map(|c| -> &StyledElement { c }));
145
146 if e.click_count() == 2 || is_quick_link {
147 if model.item_type() == ManagedFileChooserItemType::File {
148 if let Some(chooser) = self.model() {
149 chooser.select_single_file(&model);
150 }
151 } else if let Some(chooser) = self.model() {
152 chooser.navigate(model.path().as_deref(), None);
153 }
154
155 e.set_handled(true);
156 }
157 }
158 }
159}