ferroui_dialogs/
managed_file_dialog_extensions.rs1use crate::{ManagedFileDialogOptions, ManagedStorageProvider};
2use ferroui_base::platform::storage::IStorageProvider;
3use ferroui_base::{FerroLocator, LocatorExtensions, ObjectType, Ref, Upcast};
4use ferroui_controls::platform::{IMountedVolumeInfoProvider, IStorageProviderFactory};
5use ferroui_controls::{AppBuilder, ContentControl, TopLevel, Window};
6use std::rc::Rc;
7
8pub(crate) struct ManagedStorageProviderFactory {
11 options: Option<ManagedFileDialogOptions>,
12}
13
14impl ManagedStorageProviderFactory {
15 pub(crate) fn new(options: Option<ManagedFileDialogOptions>) -> Self {
16 Self { options }
17 }
18}
19
20impl IStorageProviderFactory for ManagedStorageProviderFactory {
21 fn create_provider(&self, top_level: &Ref<TopLevel>) -> Rc<dyn IStorageProvider> {
22 Rc::new(ManagedStorageProvider::new(Some(top_level), self.options.clone()))
23 }
24}
25
26pub trait ManagedFileDialogExtensions {
29 fn use_managed_system_dialogs(&self) -> AppBuilder;
33
34 fn use_managed_system_dialogs_with<TWindow: ObjectType + Upcast<Window>>(&self) -> AppBuilder;
43}
44
45fn prepare_options(
48 options_override: Option<ManagedFileDialogOptions>,
49 custom_root_factory: Option<Rc<dyn Fn() -> Ref<ContentControl>>>,
50) -> Option<ManagedFileDialogOptions> {
51 let mut options =
52 options_override.or_else(|| FerroLocator::current().get_service::<ManagedFileDialogOptions>().map(|o| (*o).clone()));
53 if let (Some(options), Some(custom_root_factory)) = (options.as_mut(), custom_root_factory) {
54 options.set_content_root_factory(Some(custom_root_factory));
55 }
56
57 options
58}
59
60fn use_managed_system_dialogs(builder: &AppBuilder, custom_factory: Option<Rc<dyn Fn() -> Ref<ContentControl>>>) -> AppBuilder {
61 builder.after_setup(move |_| {
62 let options = prepare_options(None, custom_factory.clone());
63 FerroLocator::current_mutable()
64 .bind::<dyn IStorageProviderFactory>()
65 .to_constant(Rc::new(ManagedStorageProviderFactory::new(options.clone())));
66 if let Some(custom_volume_info_provider) = options.as_ref().and_then(|options| options.custom_volume_info_provider()) {
67 FerroLocator::current_mutable()
68 .bind::<dyn IMountedVolumeInfoProvider>()
69 .to_constant(custom_volume_info_provider.clone());
70 }
71 });
72 builder.clone()
73}
74
75impl ManagedFileDialogExtensions for AppBuilder {
76 fn use_managed_system_dialogs(&self) -> AppBuilder {
77 use_managed_system_dialogs(self, None)
78 }
79
80 fn use_managed_system_dialogs_with<TWindow: ObjectType + Upcast<Window>>(&self) -> AppBuilder {
81 let constructor = TWindow::TYPE
82 .default_constructor()
83 .unwrap_or_else(|| panic!("{} has no parameterless constructor.", TWindow::TYPE.name()));
84 use_managed_system_dialogs(
85 self,
86 Some(Rc::new(move || {
87 constructor().cast::<Window>().expect("the window type derives from Window").upcast::<ContentControl>()
88 })),
89 )
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
97 use ferroui_controls::platform::MountedVolumeInfo;
98 use ferroui_base::collections::FerroList;
99 use ferroui_base::reactive::{Disposable, IDisposable};
100
101 struct NoVolumes;
102
103 impl IMountedVolumeInfoProvider for NoVolumes {
104 fn listen(&self, _mounted_drives: FerroList<MountedVolumeInfo>) -> Rc<dyn IDisposable> {
105 Disposable::empty()
106 }
107 }
108
109 struct Scope {
111 locator: Rc<dyn IDisposable>,
112 _dispatcher: ferroui_base::threading::UnitTestDispatcherScope,
113 }
114
115 fn scope() -> Scope {
116 let dispatcher = ferroui_base::threading::Dispatcher::unit_test_scope();
117 Scope { locator: FerroLocator::enter_scope(), _dispatcher: dispatcher }
118 }
119
120 impl Drop for Scope {
121 fn drop(&mut self) {
122 self.locator.dispose();
123 }
124 }
125
126 #[test]
127 fn prepare_options_prefers_the_override_and_applies_the_root_factory() {
128 let _scope = scope();
129 let mut registered = ManagedFileDialogOptions::new();
130 registered.set_allow_directory_selection(true);
131 let factory: Rc<dyn Fn() -> Ref<ContentControl>> = Rc::new(ContentControl::new);
132
133 let prepared = prepare_options(Some(registered.clone()), Some(factory.clone())).unwrap();
134 assert!(prepared.allow_directory_selection());
135 assert!(std::ptr::addr_eq(Rc::as_ptr(prepared.content_root_factory().unwrap()), Rc::as_ptr(&factory)));
136
137 assert!(prepare_options(None, Some(factory.clone())).is_none());
139
140 FerroLocator::current_mutable().bind::<ManagedFileDialogOptions>().to_constant(Rc::new(registered));
142 let prepared = prepare_options(None, Some(factory)).unwrap();
143 assert!(prepared.allow_directory_selection());
144 assert!(prepared.content_root_factory().is_some());
145 }
146
147 #[test]
148 fn use_managed_system_dialogs_registers_the_factory_and_the_volume_provider_after_setup() {
149 let _scope = scope();
150 let mut options = ManagedFileDialogOptions::new();
151 let provider: Rc<dyn IMountedVolumeInfoProvider> = Rc::new(NoVolumes);
152 options.set_custom_volume_info_provider(Some(provider.clone()));
153 FerroLocator::current_mutable().bind::<ManagedFileDialogOptions>().to_constant(Rc::new(options));
154
155 let builder = AppBuilder::configure::<ferroui_controls::Application>().use_managed_system_dialogs();
156 assert!(FerroLocator::current().get_service::<dyn IStorageProviderFactory>().is_none());
157 (builder.after_setup_callback())(&builder);
158
159 assert!(FerroLocator::current().get_service::<dyn IStorageProviderFactory>().is_some());
160 let registered = FerroLocator::current().get_service::<dyn IMountedVolumeInfoProvider>().unwrap();
161 assert!(std::ptr::addr_eq(Rc::as_ptr(®istered), Rc::as_ptr(&provider)));
162 }
163}