ferroui_native/
ferro_native_application_platform.rs1use crate::ferro_native_platform::FerroNativePlatform;
2use crate::interop::*;
3use crate::frn_string::frn_string_array_to_vec;
4use crate::mac_os_activatable_lifetime::MacOSActivatableLifetime;
5use crate::storage_provider_api::StorageProviderApi;
6use ferroui_base::platform::storage::file_io::StorageProviderHelpers;
7use ferroui_base::platform::storage::IStorageItem;
8use ferroui_base::reactive::{Disposable, IDisposable};
9use ferroui_base::utilities::{HandlerList, Uri, UriKind};
10use ferroui_base::{FerroLocator, LocatorExtensions};
11use ferroui_controls::application_lifetimes::{
12 ActivationKind, FileActivatedEventArgs, ProtocolActivatedEventArgs, ShutdownRequestedEventArgs,
13};
14use ferroui_controls::platform::IPlatformLifetimeEventsImpl;
15use std::rc::{Rc, Weak};
16
17pub struct FerroNativeApplicationPlatform {
20 weak_self: Weak<FerroNativeApplicationPlatform>,
21 platform: Weak<FerroNativePlatform>,
22 shutdown_requested: HandlerList<dyn Fn(&ShutdownRequestedEventArgs)>,
23}
24
25impl FerroNativeApplicationPlatform {
26 pub(crate) fn new(platform: Weak<FerroNativePlatform>) -> Rc<FerroNativeApplicationPlatform> {
27 Rc::new_cyclic(|weak_self| FerroNativeApplicationPlatform {
28 weak_self: weak_self.clone(),
29 platform,
30 shutdown_requested: HandlerList::new(),
31 })
32 }
33
34 fn shutdown_reply(e: &ShutdownRequestedEventArgs, is_os_shutdown: bool) -> FrnShutdownReply {
37 if e.cancel() {
38 return FrnShutdownReply::ShutdownReplyCancel;
39 }
40
41 if e.will_exit_main_loop() && !is_os_shutdown {
44 return FrnShutdownReply::ShutdownReplyDeferToManagedLoop;
45 }
46
47 FrnShutdownReply::ShutdownReplyTerminateNow
48 }
49}
50
51impl IPlatformLifetimeEventsImpl for FerroNativeApplicationPlatform {
52 fn shutdown_requested(&self, handler: Rc<dyn Fn(&ShutdownRequestedEventArgs)>) -> Rc<dyn IDisposable> {
53 let token = self.shutdown_requested.add(handler);
54 let this = self.weak_self.clone();
55 Disposable::create(move || {
56 if let Some(this) = this.upgrade() {
57 this.shutdown_requested.remove(token);
58 }
59 })
60 }
61}
62
63fn activatable_lifetime() -> Option<Rc<MacOSActivatableLifetime>> {
65 FerroLocator::current().get_service::<MacOSActivatableLifetime>()
66}
67
68fn storage_api() -> Option<Rc<StorageProviderApi>> {
71 FerroLocator::current().get_service::<StorageProviderApi>()
72}
73
74impl IFrnApplicationEventsImpl for FerroNativeApplicationPlatform {
75 fn files_opened(&self, urls: Option<&IFrnStringArray>) {
76 crate::callback_base::guard((), || {
77 if let (Some(lifetime), Some(storage_api)) = (activatable_lifetime(), storage_api()) {
78 let file_paths = urls.map(frn_string_array_to_vec).unwrap_or_default();
79 let mut files: Vec<Rc<dyn IStorageItem>> = Vec::with_capacity(file_paths.len());
80 for file_path in &file_paths {
81 if let Some(file) = StorageProviderHelpers::try_get_uri_from_file_path(file_path, false)
82 .and_then(|file_uri| storage_api.try_get_storage_item(Some(&file_uri), false))
83 {
84 files.push(file.into_item());
85 }
86 }
87
88 if !files.is_empty() {
89 lifetime.on_activated_with(FileActivatedEventArgs::new(files).into());
90 }
91 }
92 })
93 }
94
95 fn urls_opened(&self, urls: Option<&IFrnStringArray>) {
96 crate::callback_base::guard((), || {
97 if let (Some(lifetime), Some(storage_api)) = (activatable_lifetime(), storage_api()) {
98 let mut files: Vec<Rc<dyn IStorageItem>> = Vec::new();
99 let mut uris: Vec<Uri> = Vec::new();
100 for url in urls.map(frn_string_array_to_vec).unwrap_or_default() {
101 if let Some(uri) = Uri::try_create(&url, UriKind::RelativeOrAbsolute) {
102 if uri.scheme() == "file" {
103 if let Some(file) = storage_api.try_get_storage_item(Some(&uri), false) {
104 files.push(file.into_item());
105 }
106 } else {
107 uris.push(uri);
108 }
109 }
110 }
111
112 for uri in uris {
113 lifetime.on_activated_with(ProtocolActivatedEventArgs::new(uri).into());
114 }
115 if !files.is_empty() {
116 lifetime.on_activated_with(FileActivatedEventArgs::new(files).into());
117 }
118 }
119 })
120 }
121
122 fn try_shutdown(&self, is_os_shutdown: bool) -> FrnShutdownReply {
123 crate::callback_base::guard(FrnShutdownReply::ShutdownReplyTerminateNow, || {
124 if self.shutdown_requested.is_empty() {
125 return FrnShutdownReply::ShutdownReplyTerminateNow;
126 }
127
128 let e = ShutdownRequestedEventArgs::with_is_os_shutdown(is_os_shutdown);
129 for (_, handler) in self.shutdown_requested.snapshot().iter() {
130 handler(&e);
131 }
132
133 Self::shutdown_reply(&e, is_os_shutdown)
134 })
135 }
136
137 fn on_reopen(&self) {
138 crate::callback_base::guard((), || {
139 if let Some(lifetime) = activatable_lifetime() {
140 lifetime.on_activated(ActivationKind::Reopen);
141 }
142 })
143 }
144
145 fn on_hide(&self) {}
146
147 fn on_unhide(&self) {}
148
149 fn on_activate(&self) {
150 crate::callback_base::guard((), || {
151 if let Some(lifetime) = activatable_lifetime() {
152 lifetime.on_activated(ActivationKind::Background);
153 }
154 })
155 }
156
157 fn on_deactivate(&self) {
158 crate::callback_base::guard((), || {
159 if let Some(lifetime) = activatable_lifetime() {
160 lifetime.on_deactivated(ActivationKind::Background);
161 }
162 })
163 }
164
165 fn on_terminating(&self) {
166 crate::callback_base::guard((), || {
167 if let Some(platform) = self.platform.upgrade() {
169 platform.dispose();
170 }
171 })
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 fn reply(cancel: bool, will_exit_main_loop: bool, is_os_shutdown: bool) -> FrnShutdownReply {
180 let e = ShutdownRequestedEventArgs::with_is_os_shutdown(is_os_shutdown);
181 e.set_cancel(cancel);
182 e.set_will_exit_main_loop(will_exit_main_loop);
183 FerroNativeApplicationPlatform::shutdown_reply(&e, is_os_shutdown)
184 }
185
186 #[test]
187 fn shutdown_replies() {
188 assert_eq!(reply(true, false, false), FrnShutdownReply::ShutdownReplyCancel);
189 assert_eq!(reply(true, true, true), FrnShutdownReply::ShutdownReplyCancel);
190 assert_eq!(reply(false, true, false), FrnShutdownReply::ShutdownReplyDeferToManagedLoop);
191 assert_eq!(reply(false, true, true), FrnShutdownReply::ShutdownReplyTerminateNow);
193 assert_eq!(reply(false, false, false), FrnShutdownReply::ShutdownReplyTerminateNow);
194 }
195
196 #[test]
197 fn shutdown_without_subscribers_terminates_now() {
198 let platform = FerroNativeApplicationPlatform::new(Weak::new());
199 assert_eq!(platform.try_shutdown(false), FrnShutdownReply::ShutdownReplyTerminateNow);
200
201 let subscription = platform.shutdown_requested(Rc::new(|e| e.set_cancel(true)));
202 assert_eq!(platform.try_shutdown(false), FrnShutdownReply::ShutdownReplyCancel);
203
204 subscription.dispose();
205 assert_eq!(platform.try_shutdown(false), FrnShutdownReply::ShutdownReplyTerminateNow);
206 }
207}