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(&mut self, _background: WindowBackgroundAppearance) {
192 unimplemented!()
193 }
194
195 fn set_edited(&mut self, edited: bool) {
196 self.0.lock().edited = edited;
197 }
198
199 fn show_character_palette(&self) {
200 unimplemented!()
201 }
202
203 fn minimize(&self) {
204 unimplemented!()
205 }
206
207 fn zoom(&self) {
208 unimplemented!()
209 }
210
211 fn toggle_fullscreen(&self) {
212 let mut lock = self.0.lock();
213 lock.is_fullscreen = !lock.is_fullscreen;
214 }
215
216 fn is_fullscreen(&self) -> bool {
217 self.0.lock().is_fullscreen
218 }
219
220 fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
221
222 fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>) {
223 self.0.lock().input_callback = Some(callback)
224 }
225
226 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
227 self.0.lock().active_status_change_callback = Some(callback)
228 }
229
230 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
231 self.0.lock().resize_callback = Some(callback)
232 }
233
234 fn on_moved(&self, callback: Box<dyn FnMut()>) {
235 self.0.lock().moved_callback = Some(callback)
236 }
237
238 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
239 self.0.lock().should_close_handler = Some(callback);
240 }
241
242 fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
243
244 fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
245
246 fn draw(&self, _scene: &crate::Scene) {}
247
248 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
249 self.0.lock().sprite_atlas.clone()
250 }
251
252 fn as_test(&mut self) -> Option<&mut TestWindow> {
253 Some(self)
254 }
255
256 #[cfg(target_os = "windows")]
257 fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND {
258 unimplemented!()
259 }
260
261 fn show_window_menu(&self, _position: Point<Pixels>) {
262 unimplemented!()
263 }
264
265 fn start_system_move(&self) {
266 unimplemented!()
267 }
268
269 fn should_render_window_controls(&self) -> bool {
270 false
271 }
272}
273
274pub(crate) struct TestAtlasState {
275 next_id: u32,
276 tiles: HashMap<AtlasKey, AtlasTile>,
277}
278
279pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
280
281impl TestAtlas {
282 pub fn new() -> Self {
283 TestAtlas(Mutex::new(TestAtlasState {
284 next_id: 0,
285 tiles: HashMap::default(),
286 }))
287 }
288}
289
290impl PlatformAtlas for TestAtlas {
291 fn get_or_insert_with<'a>(
292 &self,
293 key: &crate::AtlasKey,
294 build: &mut dyn FnMut() -> anyhow::Result<
295 Option<(Size<crate::DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
296 >,
297 ) -> anyhow::Result<Option<crate::AtlasTile>> {
298 let mut state = self.0.lock();
299 if let Some(tile) = state.tiles.get(key) {
300 return Ok(Some(tile.clone()));
301 }
302 drop(state);
303
304 let Some((size, _)) = build()? else {
305 return Ok(None);
306 };
307
308 let mut state = self.0.lock();
309 state.next_id += 1;
310 let texture_id = state.next_id;
311 state.next_id += 1;
312 let tile_id = state.next_id;
313
314 state.tiles.insert(
315 key.clone(),
316 crate::AtlasTile {
317 texture_id: AtlasTextureId {
318 index: texture_id,
319 kind: crate::AtlasTextureKind::Path,
320 },
321 tile_id: TileId(tile_id),
322 padding: 0,
323 bounds: crate::Bounds {
324 origin: Point::default(),
325 size,
326 },
327 },
328 );
329
330 Ok(Some(state.tiles[key].clone()))
331 }
332}