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