ferroui_xaml_interactions/storage_provider/
open_file_picker_action.rs1use super::utilities::FileFilterParser;
4use super::{FilePickerEventArgs, PickerActionBase};
5use crate::clipboard::visual_of;
6use crate::StorageFileRef;
7use ferroui_base::platform::storage::FilePickerOpenOptions;
8use ferroui_base::reactive::{Disposable, IDisposable};
9use ferroui_base::utilities::HandlerList;
10use ferroui_base::{
11 ferro_class, ferro_class_info, ferro_properties, instantiate, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
12 FerroProperty, Ref, StyledElementImpl, StyledProperty, Visual,
13};
14use ferroui_xaml_interactivity::StyledElementActionImpl;
15use std::rc::Rc;
16
17#[repr(C)]
25pub struct OpenFilePickerAction {
26 base: PickerActionBase,
27 pick: HandlerList<dyn Fn(&FilePickerEventArgs)>,
28}
29
30ferro_class! {
31 OpenFilePickerAction: PickerActionBase, virtuals OpenFilePickerActionImpl: StyledElementActionImpl {
32 fn on_pick(this, args: &FilePickerEventArgs);
34 }
35}
36ferro_class_info!(OpenFilePickerAction { new: OpenFilePickerAction::new });
37ferroui_base::ferro_impl_classes!(OpenFilePickerAction: StyledElementImpl);
38
39ferro_properties! {
40 impl OpenFilePickerAction {
41 pub fn allow_multiple_property() -> StyledProperty<bool> {
43 FerroProperty::register::<OpenFilePickerAction, _>("AllowMultiple", false)
44 }
45
46 pub fn file_type_filter_property() -> StyledProperty<Option<String>> {
48 FerroProperty::register::<OpenFilePickerAction, _>("FileTypeFilter", None)
49 }
50 }
51}
52
53ferroui_base::ferro_overrides! {
54 impl FerroObjectImpl for OpenFilePickerAction {
55 fn constructed(this: &Self) {
57 Self::parent_constructed(this);
58 this.set_pass_event_args_to_command(true);
59 }
60 }
61}
62
63impl StyledElementActionImpl for OpenFilePickerAction {
64 fn execute(this: &Self, sender: Option<&BoxedValue>, _parameter: Option<&BoxedValue>) -> Option<BoxedValue> {
68 let Some(visual) = visual_of(sender) else {
69 return Some(Rc::new(false));
70 };
71
72 let action: Ref<OpenFilePickerAction> = this.to_ref();
73 let task = this.track_dispatched_picker_operation(move || async move {
74 action.open_file_picker_async(&visual).await;
75 });
76 Some(Rc::new(task))
77 }
78}
79
80impl OpenFilePickerActionImpl for OpenFilePickerAction {
81 fn on_pick(this: &Self, args: &FilePickerEventArgs) {
82 for (_, handler) in this.pick.snapshot().iter() {
83 handler(args);
84 }
85 }
86}
87
88impl OpenFilePickerAction {
89 pub fn construct() -> Self {
91 Self { base: PickerActionBase::construct(), pick: HandlerList::new() }
92 }
93
94 pub fn new() -> Ref<Self> {
96 instantiate(Self::construct())
97 }
98
99 pub fn pick(&self, handler: impl Fn(&FilePickerEventArgs) + 'static) -> Rc<dyn IDisposable> {
102 let token = self.pick.add(Rc::new(handler));
103 let this = self.to_ref().downgrade();
104 Disposable::create(move || {
105 if let Some(this) = this.upgrade() {
106 this.pick.remove(token);
107 }
108 })
109 }
110
111 pub fn allow_multiple(&self) -> bool {
114 self.get_value(Self::allow_multiple_property())
115 }
116
117 pub fn set_allow_multiple(&self, value: bool) {
118 self.set_value(Self::allow_multiple_property(), value)
119 }
120
121 pub fn file_type_filter(&self) -> Option<String> {
124 self.get_value(Self::file_type_filter_property())
125 }
126
127 pub fn set_file_type_filter(&self, value: Option<String>) {
128 self.set_value(Self::file_type_filter_property(), value)
129 }
130
131 async fn open_file_picker_async(&self, visual: &Visual) {
132 let command = self.command();
133 if !self.is_enabled() || (command.is_none() && self.pick.is_empty()) {
134 return;
135 }
136
137 let Some(storage_provider) = self.resolve_storage_provider(Some(visual)) else {
138 return;
139 };
140
141 let suggested_start_location = self.resolve_suggested_start_location(Some(storage_provider.clone())).await;
142
143 let mut options = FilePickerOpenOptions::new();
144 options.set_title(self.title());
145 options.set_suggested_start_location(suggested_start_location);
146 options.set_suggested_file_name(self.suggested_file_name());
147 options.set_allow_multiple(self.allow_multiple());
148 options.set_file_type_filter(
149 self.file_type_filter().and_then(|filter| FileFilterParser::convert_to_file_picker_file_type(&filter)),
150 );
151
152 let files = storage_provider.open_file_picker_async(options).await.unwrap_or_else(|error| panic!("{error}"));
154
155 if files.is_empty() {
156 return;
157 }
158
159 let files: Vec<StorageFileRef> = files.into_iter().map(StorageFileRef::new).collect();
160 let event_args = FilePickerEventArgs::new(files.clone());
161 self.on_pick(&event_args);
162 let Some(command) = command.filter(|_| !event_args.handled()) else {
163 return;
164 };
165
166 let resolved_parameter = self.resolve_parameter(Some(&(Rc::new(files) as BoxedValue)));
167
168 if !command.can_execute(resolved_parameter.as_ref()) {
169 return;
170 }
171
172 command.execute(resolved_parameter.as_ref());
173 }
174}