1use crate::{
2 AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor, Keymap,
3 Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow, WindowAppearance,
4 WindowParams,
5};
6use anyhow::Result;
7use collections::VecDeque;
8use futures::channel::oneshot;
9use parking_lot::Mutex;
10use std::{
11 cell::RefCell,
12 path::{Path, PathBuf},
13 rc::{Rc, Weak},
14 sync::Arc,
15};
16#[cfg(target_os = "windows")]
17use windows::Win32::{
18 Graphics::Imaging::{CLSID_WICImagingFactory, IWICImagingFactory},
19 System::Com::{CoCreateInstance, CLSCTX_INPROC_SERVER},
20};
21
22/// TestPlatform implements the Platform trait for use in tests.
23pub(crate) struct TestPlatform {
24 background_executor: BackgroundExecutor,
25 foreground_executor: ForegroundExecutor,
26
27 pub(crate) active_window: RefCell<Option<TestWindow>>,
28 active_display: Rc<dyn PlatformDisplay>,
29 active_cursor: Mutex<CursorStyle>,
30 current_clipboard_item: Mutex<Option<ClipboardItem>>,
31 #[cfg(target_os = "linux")]
32 current_primary_item: Mutex<Option<ClipboardItem>>,
33 pub(crate) prompts: RefCell<TestPrompts>,
34 pub opened_url: RefCell<Option<String>>,
35 pub text_system: Arc<dyn PlatformTextSystem>,
36 #[cfg(target_os = "windows")]
37 bitmap_factory: std::mem::ManuallyDrop<IWICImagingFactory>,
38 weak: Weak<Self>,
39}
40
41#[derive(Default)]
42pub(crate) struct TestPrompts {
43 multiple_choice: VecDeque<oneshot::Sender<usize>>,
44 new_path: VecDeque<(PathBuf, oneshot::Sender<Result<Option<PathBuf>>>)>,
45}
46
47impl TestPlatform {
48 pub fn new(executor: BackgroundExecutor, foreground_executor: ForegroundExecutor) -> Rc<Self> {
49 #[cfg(target_os = "windows")]
50 let bitmap_factory = unsafe {
51 windows::Win32::System::Ole::OleInitialize(None)
52 .expect("unable to initialize Windows OLE");
53 std::mem::ManuallyDrop::new(
54 CoCreateInstance(&CLSID_WICImagingFactory, None, CLSCTX_INPROC_SERVER)
55 .expect("Error creating bitmap factory."),
56 )
57 };
58
59 #[cfg(target_os = "macos")]
60 let text_system = Arc::new(crate::platform::mac::MacTextSystem::new());
61
62 #[cfg(target_os = "linux")]
63 let text_system = Arc::new(crate::platform::linux::CosmicTextSystem::new());
64
65 #[cfg(target_os = "windows")]
66 let text_system = Arc::new(
67 crate::platform::windows::DirectWriteTextSystem::new(&bitmap_factory)
68 .expect("Unable to initialize direct write."),
69 );
70
71 Rc::new_cyclic(|weak| TestPlatform {
72 background_executor: executor,
73 foreground_executor,
74 prompts: Default::default(),
75 active_cursor: Default::default(),
76 active_display: Rc::new(TestDisplay::new()),
77 active_window: Default::default(),
78 current_clipboard_item: Mutex::new(None),
79 #[cfg(target_os = "linux")]
80 current_primary_item: Mutex::new(None),
81 weak: weak.clone(),
82 opened_url: Default::default(),
83 #[cfg(target_os = "windows")]
84 bitmap_factory,
85 text_system,
86 })
87 }
88
89 pub(crate) fn simulate_new_path_selection(
90 &self,
91 select_path: impl FnOnce(&std::path::Path) -> Option<std::path::PathBuf>,
92 ) {
93 let (path, tx) = self
94 .prompts
95 .borrow_mut()
96 .new_path
97 .pop_front()
98 .expect("no pending new path prompt");
99 tx.send(Ok(select_path(&path))).ok();
100 }
101
102 pub(crate) fn simulate_prompt_answer(&self, response_ix: usize) {
103 let tx = self
104 .prompts
105 .borrow_mut()
106 .multiple_choice
107 .pop_front()
108 .expect("no pending multiple choice prompt");
109 self.background_executor().set_waiting_hint(None);
110 tx.send(response_ix).ok();
111 }
112
113 pub(crate) fn has_pending_prompt(&self) -> bool {
114 !self.prompts.borrow().multiple_choice.is_empty()
115 }
116
117 pub(crate) fn prompt(&self, msg: &str, detail: Option<&str>) -> oneshot::Receiver<usize> {
118 let (tx, rx) = oneshot::channel();
119 self.background_executor()
120 .set_waiting_hint(Some(format!("PROMPT: {:?} {:?}", msg, detail)));
121 self.prompts.borrow_mut().multiple_choice.push_back(tx);
122 rx
123 }
124
125 pub(crate) fn set_active_window(&self, window: Option<TestWindow>) {
126 let executor = self.foreground_executor().clone();
127 let previous_window = self.active_window.borrow_mut().take();
128 self.active_window.borrow_mut().clone_from(&window);
129
130 executor
131 .spawn(async move {
132 if let Some(previous_window) = previous_window {
133 if let Some(window) = window.as_ref() {
134 if Rc::ptr_eq(&previous_window.0, &window.0) {
135 return;
136 }
137 }
138 previous_window.simulate_active_status_change(false);
139 }
140 if let Some(window) = window {
141 window.simulate_active_status_change(true);
142 }
143 })
144 .detach();
145 }
146
147 pub(crate) fn did_prompt_for_new_path(&self) -> bool {
148 !self.prompts.borrow().new_path.is_empty()
149 }
150}
151
152impl Platform for TestPlatform {
153 fn background_executor(&self) -> BackgroundExecutor {
154 self.background_executor.clone()
155 }
156
157 fn foreground_executor(&self) -> ForegroundExecutor {
158 self.foreground_executor.clone()
159 }
160
161 fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
162 self.text_system.clone()
163 }
164
165 fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {
166 unimplemented!()
167 }
168
169 fn quit(&self) {}
170
171 fn restart(&self, _: Option<PathBuf>) {
172 unimplemented!()
173 }
174
175 fn activate(&self, _ignoring_other_apps: bool) {
176 //
177 }
178
179 fn hide(&self) {
180 unimplemented!()
181 }
182
183 fn hide_other_apps(&self) {
184 unimplemented!()
185 }
186
187 fn unhide_other_apps(&self) {
188 unimplemented!()
189 }
190
191 fn displays(&self) -> Vec<std::rc::Rc<dyn crate::PlatformDisplay>> {
192 vec![self.active_display.clone()]
193 }
194
195 fn primary_display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
196 Some(self.active_display.clone())
197 }
198
199 fn active_window(&self) -> Option<crate::AnyWindowHandle> {
200 self.active_window
201 .borrow()
202 .as_ref()
203 .map(|window| window.0.lock().handle)
204 }
205
206 fn open_window(
207 &self,
208 handle: AnyWindowHandle,
209 params: WindowParams,
210 ) -> anyhow::Result<Box<dyn crate::PlatformWindow>> {
211 let window = TestWindow::new(
212 handle,
213 params,
214 self.weak.clone(),
215 self.active_display.clone(),
216 );
217 Ok(Box::new(window))
218 }
219
220 fn window_appearance(&self) -> WindowAppearance {
221 WindowAppearance::Light
222 }
223
224 fn open_url(&self, url: &str) {
225 *self.opened_url.borrow_mut() = Some(url.to_string())
226 }
227
228 fn on_open_urls(&self, _callback: Box<dyn FnMut(Vec<String>)>) {
229 unimplemented!()
230 }
231
232 fn prompt_for_paths(
233 &self,
234 _options: crate::PathPromptOptions,
235 ) -> oneshot::Receiver<Result<Option<Vec<std::path::PathBuf>>>> {
236 unimplemented!()
237 }
238
239 fn prompt_for_new_path(
240 &self,
241 directory: &std::path::Path,
242 ) -> oneshot::Receiver<Result<Option<std::path::PathBuf>>> {
243 let (tx, rx) = oneshot::channel();
244 self.prompts
245 .borrow_mut()
246 .new_path
247 .push_back((directory.to_path_buf(), tx));
248 rx
249 }
250
251 fn reveal_path(&self, _path: &std::path::Path) {
252 unimplemented!()
253 }
254
255 fn on_quit(&self, _callback: Box<dyn FnMut()>) {}
256
257 fn on_reopen(&self, _callback: Box<dyn FnMut()>) {
258 unimplemented!()
259 }
260
261 fn set_menus(&self, _menus: Vec<crate::Menu>, _keymap: &Keymap) {}
262 fn set_dock_menu(&self, _menu: Vec<crate::MenuItem>, _keymap: &Keymap) {}
263
264 fn add_recent_document(&self, _paths: &Path) {}
265
266 fn on_app_menu_action(&self, _callback: Box<dyn FnMut(&dyn crate::Action)>) {}
267
268 fn on_will_open_app_menu(&self, _callback: Box<dyn FnMut()>) {}
269
270 fn on_validate_app_menu_command(&self, _callback: Box<dyn FnMut(&dyn crate::Action) -> bool>) {}
271
272 fn app_path(&self) -> Result<std::path::PathBuf> {
273 unimplemented!()
274 }
275
276 fn path_for_auxiliary_executable(&self, _name: &str) -> Result<std::path::PathBuf> {
277 unimplemented!()
278 }
279
280 fn set_cursor_style(&self, style: crate::CursorStyle) {
281 *self.active_cursor.lock() = style;
282 }
283
284 fn should_auto_hide_scrollbars(&self) -> bool {
285 false
286 }
287
288 #[cfg(target_os = "linux")]
289 fn write_to_primary(&self, item: ClipboardItem) {
290 *self.current_primary_item.lock() = Some(item);
291 }
292
293 fn write_to_clipboard(&self, item: ClipboardItem) {
294 *self.current_clipboard_item.lock() = Some(item);
295 }
296
297 #[cfg(target_os = "linux")]
298 fn read_from_primary(&self) -> Option<ClipboardItem> {
299 self.current_primary_item.lock().clone()
300 }
301
302 fn read_from_clipboard(&self) -> Option<ClipboardItem> {
303 self.current_clipboard_item.lock().clone()
304 }
305
306 fn write_credentials(&self, _url: &str, _username: &str, _password: &[u8]) -> Task<Result<()>> {
307 Task::ready(Ok(()))
308 }
309
310 fn read_credentials(&self, _url: &str) -> Task<Result<Option<(String, Vec<u8>)>>> {
311 Task::ready(Ok(None))
312 }
313
314 fn delete_credentials(&self, _url: &str) -> Task<Result<()>> {
315 Task::ready(Ok(()))
316 }
317
318 fn register_url_scheme(&self, _: &str) -> Task<anyhow::Result<()>> {
319 unimplemented!()
320 }
321
322 fn open_with_system(&self, _path: &Path) {
323 unimplemented!()
324 }
325}
326
327#[cfg(target_os = "windows")]
328impl Drop for TestPlatform {
329 fn drop(&mut self) {
330 unsafe {
331 std::mem::ManuallyDrop::drop(&mut self.bitmap_factory);
332 windows::Win32::System::Ole::OleUninitialize();
333 }
334 }
335}