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