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