1use crate::{
2 AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
3 Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
4 Point, PromptButton, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId,
5 WindowAppearance, 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 set_input_handler(&mut self, input_handler: PlatformInputHandler) {
157 self.0.lock().input_handler = Some(input_handler);
158 }
159
160 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
161 self.0.lock().input_handler.take()
162 }
163
164 fn prompt(
165 &self,
166 _level: crate::PromptLevel,
167 msg: &str,
168 detail: Option<&str>,
169 answers: &[PromptButton],
170 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
171 Some(
172 self.0
173 .lock()
174 .platform
175 .upgrade()
176 .expect("platform dropped")
177 .prompt(msg, detail, answers),
178 )
179 }
180
181 fn activate(&self) {
182 self.0
183 .lock()
184 .platform
185 .upgrade()
186 .unwrap()
187 .set_active_window(Some(self.clone()))
188 }
189
190 fn is_active(&self) -> bool {
191 false
192 }
193
194 fn is_hovered(&self) -> bool {
195 false
196 }
197
198 fn set_title(&mut self, title: &str) {
199 self.0.lock().title = Some(title.to_owned());
200 }
201
202 fn set_app_id(&mut self, _app_id: &str) {}
203
204 fn set_background_appearance(&self, _background: WindowBackgroundAppearance) {}
205
206 fn set_edited(&mut self, edited: bool) {
207 self.0.lock().edited = edited;
208 }
209
210 fn show_character_palette(&self) {
211 unimplemented!()
212 }
213
214 fn minimize(&self) {
215 unimplemented!()
216 }
217
218 fn zoom(&self) {
219 unimplemented!()
220 }
221
222 fn toggle_fullscreen(&self) {
223 let mut lock = self.0.lock();
224 lock.is_fullscreen = !lock.is_fullscreen;
225 }
226
227 fn is_fullscreen(&self) -> bool {
228 self.0.lock().is_fullscreen
229 }
230
231 fn on_request_frame(&self, _callback: Box<dyn FnMut(RequestFrameOptions)>) {}
232
233 fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>) {
234 self.0.lock().input_callback = Some(callback)
235 }
236
237 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
238 self.0.lock().active_status_change_callback = Some(callback)
239 }
240
241 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
242 self.0.lock().hover_status_change_callback = Some(callback)
243 }
244
245 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
246 self.0.lock().resize_callback = Some(callback)
247 }
248
249 fn on_moved(&self, callback: Box<dyn FnMut()>) {
250 self.0.lock().moved_callback = Some(callback)
251 }
252
253 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
254 self.0.lock().should_close_handler = Some(callback);
255 }
256
257 fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
258
259 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
260 self.0.lock().hit_test_window_control_callback = Some(callback);
261 }
262
263 fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
264
265 fn draw(&self, _scene: &crate::Scene) {}
266
267 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
268 self.0.lock().sprite_atlas.clone()
269 }
270
271 fn as_test(&mut self) -> Option<&mut TestWindow> {
272 Some(self)
273 }
274
275 #[cfg(target_os = "windows")]
276 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND {
277 unimplemented!()
278 }
279
280 fn show_window_menu(&self, _position: Point<Pixels>) {
281 unimplemented!()
282 }
283
284 fn start_window_move(&self) {
285 unimplemented!()
286 }
287
288 fn update_ime_position(&self, _bounds: Bounds<ScaledPixels>) {}
289
290 fn gpu_specs(&self) -> Option<GpuSpecs> {
291 None
292 }
293}
294
295pub(crate) struct TestAtlasState {
296 next_id: u32,
297 tiles: HashMap<AtlasKey, AtlasTile>,
298}
299
300pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
301
302impl TestAtlas {
303 pub fn new() -> Self {
304 TestAtlas(Mutex::new(TestAtlasState {
305 next_id: 0,
306 tiles: HashMap::default(),
307 }))
308 }
309}
310
311impl PlatformAtlas for TestAtlas {
312 fn get_or_insert_with<'a>(
313 &self,
314 key: &crate::AtlasKey,
315 build: &mut dyn FnMut() -> anyhow::Result<
316 Option<(Size<crate::DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
317 >,
318 ) -> anyhow::Result<Option<crate::AtlasTile>> {
319 let mut state = self.0.lock();
320 if let Some(tile) = state.tiles.get(key) {
321 return Ok(Some(tile.clone()));
322 }
323 drop(state);
324
325 let Some((size, _)) = build()? else {
326 return Ok(None);
327 };
328
329 let mut state = self.0.lock();
330 state.next_id += 1;
331 let texture_id = state.next_id;
332 state.next_id += 1;
333 let tile_id = state.next_id;
334
335 state.tiles.insert(
336 key.clone(),
337 crate::AtlasTile {
338 texture_id: AtlasTextureId {
339 index: texture_id,
340 kind: crate::AtlasTextureKind::Path,
341 },
342 tile_id: TileId(tile_id),
343 padding: 0,
344 bounds: crate::Bounds {
345 origin: Point::default(),
346 size,
347 },
348 },
349 );
350
351 Ok(Some(state.tiles[key].clone()))
352 }
353
354 fn remove(&self, key: &AtlasKey) {
355 let mut state = self.0.lock();
356 state.tiles.remove(key);
357 }
358}