1use crate::{
2 AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
3 Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
4 Point, PromptButton, RequestFrameOptions, Size, TestPlatform, TileId, WindowAppearance,
5 WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowParams,
6};
7use collections::HashMap;
8use parking_lot::Mutex;
9use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
10use std::{
11 rc::{Rc, Weak},
12 sync::{self, Arc},
13};
14
15pub(crate) struct TestWindowState {
16 pub(crate) bounds: Bounds<Pixels>,
17 pub(crate) handle: AnyWindowHandle,
18 display: Rc<dyn PlatformDisplay>,
19 pub(crate) title: Option<String>,
20 pub(crate) edited: bool,
21 platform: Weak<TestPlatform>,
22 // TODO: Replace with `Rc`
23 sprite_atlas: Arc<dyn PlatformAtlas>,
24 pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
25 hit_test_window_control_callback: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
26 input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
27 active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
28 hover_status_change_callback: Option<Box<dyn FnMut(bool)>>,
29 resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
30 moved_callback: Option<Box<dyn FnMut()>>,
31 input_handler: Option<PlatformInputHandler>,
32 is_fullscreen: bool,
33}
34
35#[derive(Clone)]
36pub struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
37
38impl HasWindowHandle for TestWindow {
39 fn window_handle(
40 &self,
41 ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
42 unimplemented!("Test Windows are not backed by a real platform window")
43 }
44}
45
46impl HasDisplayHandle for TestWindow {
47 fn display_handle(
48 &self,
49 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
50 unimplemented!("Test Windows are not backed by a real platform window")
51 }
52}
53
54impl TestWindow {
55 pub(crate) fn new(
56 handle: AnyWindowHandle,
57 params: WindowParams,
58 platform: Weak<TestPlatform>,
59 display: Rc<dyn PlatformDisplay>,
60 ) -> Self {
61 Self(Rc::new(Mutex::new(TestWindowState {
62 bounds: params.bounds,
63 display,
64 platform,
65 handle,
66 sprite_atlas: Arc::new(TestAtlas::new()),
67 title: Default::default(),
68 edited: false,
69 should_close_handler: None,
70 hit_test_window_control_callback: None,
71 input_callback: None,
72 active_status_change_callback: None,
73 hover_status_change_callback: None,
74 resize_callback: None,
75 moved_callback: None,
76 input_handler: None,
77 is_fullscreen: false,
78 })))
79 }
80
81 pub fn simulate_resize(&mut self, size: Size<Pixels>) {
82 let scale_factor = self.scale_factor();
83 let mut lock = self.0.lock();
84 let Some(mut callback) = lock.resize_callback.take() else {
85 return;
86 };
87 lock.bounds.size = size;
88 drop(lock);
89 callback(size, scale_factor);
90 self.0.lock().resize_callback = Some(callback);
91 }
92
93 pub(crate) fn simulate_active_status_change(&self, active: bool) {
94 let mut lock = self.0.lock();
95 let Some(mut callback) = lock.active_status_change_callback.take() else {
96 return;
97 };
98 drop(lock);
99 callback(active);
100 self.0.lock().active_status_change_callback = Some(callback);
101 }
102
103 pub fn simulate_input(&mut self, event: PlatformInput) -> bool {
104 let mut lock = self.0.lock();
105 let Some(mut callback) = lock.input_callback.take() else {
106 return false;
107 };
108 drop(lock);
109 let result = callback(event);
110 self.0.lock().input_callback = Some(callback);
111 !result.propagate
112 }
113}
114
115impl PlatformWindow for TestWindow {
116 fn bounds(&self) -> Bounds<Pixels> {
117 self.0.lock().bounds
118 }
119
120 fn window_bounds(&self) -> WindowBounds {
121 WindowBounds::Windowed(self.bounds())
122 }
123
124 fn is_maximized(&self) -> bool {
125 false
126 }
127
128 fn content_size(&self) -> Size<Pixels> {
129 self.bounds().size
130 }
131
132 fn resize(&mut self, size: Size<Pixels>) {
133 let mut lock = self.0.lock();
134 lock.bounds.size = size;
135 }
136
137 fn scale_factor(&self) -> f32 {
138 2.0
139 }
140
141 fn appearance(&self) -> WindowAppearance {
142 WindowAppearance::Light
143 }
144
145 fn display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
146 Some(self.0.lock().display.clone())
147 }
148
149 fn mouse_position(&self) -> Point<Pixels> {
150 Point::default()
151 }
152
153 fn modifiers(&self) -> crate::Modifiers {
154 crate::Modifiers::default()
155 }
156
157 fn capslock(&self) -> crate::Capslock {
158 crate::Capslock::default()
159 }
160
161 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
162 self.0.lock().input_handler = Some(input_handler);
163 }
164
165 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
166 self.0.lock().input_handler.take()
167 }
168
169 fn prompt(
170 &self,
171 _level: crate::PromptLevel,
172 msg: &str,
173 detail: Option<&str>,
174 answers: &[PromptButton],
175 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
176 Some(
177 self.0
178 .lock()
179 .platform
180 .upgrade()
181 .expect("platform dropped")
182 .prompt(msg, detail, answers),
183 )
184 }
185
186 fn activate(&self) {
187 self.0
188 .lock()
189 .platform
190 .upgrade()
191 .unwrap()
192 .set_active_window(Some(self.clone()))
193 }
194
195 fn is_active(&self) -> bool {
196 false
197 }
198
199 fn is_hovered(&self) -> bool {
200 false
201 }
202
203 fn background_appearance(&self) -> WindowBackgroundAppearance {
204 WindowBackgroundAppearance::Opaque
205 }
206
207 fn is_subpixel_rendering_supported(&self) -> bool {
208 false
209 }
210
211 fn set_title(&mut self, title: &str) {
212 self.0.lock().title = Some(title.to_owned());
213 }
214
215 fn set_app_id(&mut self, _app_id: &str) {}
216
217 fn set_background_appearance(&self, _background: WindowBackgroundAppearance) {}
218
219 fn set_edited(&mut self, edited: bool) {
220 self.0.lock().edited = edited;
221 }
222
223 fn show_character_palette(&self) {
224 unimplemented!()
225 }
226
227 fn minimize(&self) {
228 unimplemented!()
229 }
230
231 fn zoom(&self) {
232 unimplemented!()
233 }
234
235 fn toggle_fullscreen(&self) {
236 let mut lock = self.0.lock();
237 lock.is_fullscreen = !lock.is_fullscreen;
238 }
239
240 fn is_fullscreen(&self) -> bool {
241 self.0.lock().is_fullscreen
242 }
243
244 fn on_request_frame(&self, _callback: Box<dyn FnMut(RequestFrameOptions)>) {}
245
246 fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>) {
247 self.0.lock().input_callback = Some(callback)
248 }
249
250 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
251 self.0.lock().active_status_change_callback = Some(callback)
252 }
253
254 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
255 self.0.lock().hover_status_change_callback = Some(callback)
256 }
257
258 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
259 self.0.lock().resize_callback = Some(callback)
260 }
261
262 fn on_moved(&self, callback: Box<dyn FnMut()>) {
263 self.0.lock().moved_callback = Some(callback)
264 }
265
266 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
267 self.0.lock().should_close_handler = Some(callback);
268 }
269
270 fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
271
272 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
273 self.0.lock().hit_test_window_control_callback = Some(callback);
274 }
275
276 fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
277
278 fn draw(&self, _scene: &crate::Scene) {}
279
280 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
281 self.0.lock().sprite_atlas.clone()
282 }
283
284 fn as_test(&mut self) -> Option<&mut TestWindow> {
285 Some(self)
286 }
287
288 #[cfg(target_os = "windows")]
289 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND {
290 unimplemented!()
291 }
292
293 fn show_window_menu(&self, _position: Point<Pixels>) {
294 unimplemented!()
295 }
296
297 fn start_window_move(&self) {
298 unimplemented!()
299 }
300
301 fn update_ime_position(&self, _bounds: Bounds<Pixels>) {}
302
303 fn gpu_specs(&self) -> Option<GpuSpecs> {
304 None
305 }
306}
307
308pub(crate) struct TestAtlasState {
309 next_id: u32,
310 tiles: HashMap<AtlasKey, AtlasTile>,
311}
312
313pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
314
315impl TestAtlas {
316 pub fn new() -> Self {
317 TestAtlas(Mutex::new(TestAtlasState {
318 next_id: 0,
319 tiles: HashMap::default(),
320 }))
321 }
322}
323
324impl PlatformAtlas for TestAtlas {
325 fn get_or_insert_with<'a>(
326 &self,
327 key: &crate::AtlasKey,
328 build: &mut dyn FnMut() -> anyhow::Result<
329 Option<(Size<crate::DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
330 >,
331 ) -> anyhow::Result<Option<crate::AtlasTile>> {
332 let mut state = self.0.lock();
333 if let Some(tile) = state.tiles.get(key) {
334 return Ok(Some(tile.clone()));
335 }
336 drop(state);
337
338 let Some((size, _)) = build()? else {
339 return Ok(None);
340 };
341
342 let mut state = self.0.lock();
343 state.next_id += 1;
344 let texture_id = state.next_id;
345 state.next_id += 1;
346 let tile_id = state.next_id;
347
348 state.tiles.insert(
349 key.clone(),
350 crate::AtlasTile {
351 texture_id: AtlasTextureId {
352 index: texture_id,
353 kind: crate::AtlasTextureKind::Monochrome,
354 },
355 tile_id: TileId(tile_id),
356 padding: 0,
357 bounds: crate::Bounds {
358 origin: Point::default(),
359 size,
360 },
361 },
362 );
363
364 Ok(Some(state.tiles[key].clone()))
365 }
366
367 fn remove(&self, key: &AtlasKey) {
368 let mut state = self.0.lock();
369 state.tiles.remove(key);
370 }
371}