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