1use crate::{
2 AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DevicePixels,
3 DispatchEventResult, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
4 PlatformInputHandler, PlatformWindow, Point, 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<DevicePixels>,
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.map(|pixels| (pixels.0 as i32).into());
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<DevicePixels> {
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.into()
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) -> std::rc::Rc<dyn crate::PlatformDisplay> {
136 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
262pub(crate) struct TestAtlasState {
263 next_id: u32,
264 tiles: HashMap<AtlasKey, AtlasTile>,
265}
266
267pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
268
269impl TestAtlas {
270 pub fn new() -> Self {
271 TestAtlas(Mutex::new(TestAtlasState {
272 next_id: 0,
273 tiles: HashMap::default(),
274 }))
275 }
276}
277
278impl PlatformAtlas for TestAtlas {
279 fn get_or_insert_with<'a>(
280 &self,
281 key: &crate::AtlasKey,
282 build: &mut dyn FnMut() -> anyhow::Result<(
283 Size<crate::DevicePixels>,
284 std::borrow::Cow<'a, [u8]>,
285 )>,
286 ) -> anyhow::Result<crate::AtlasTile> {
287 let mut state = self.0.lock();
288 if let Some(tile) = state.tiles.get(key) {
289 return Ok(tile.clone());
290 }
291
292 state.next_id += 1;
293 let texture_id = state.next_id;
294 state.next_id += 1;
295 let tile_id = state.next_id;
296
297 drop(state);
298 let (size, _) = build()?;
299 let mut state = self.0.lock();
300
301 state.tiles.insert(
302 key.clone(),
303 crate::AtlasTile {
304 texture_id: AtlasTextureId {
305 index: texture_id,
306 kind: crate::AtlasTextureKind::Path,
307 },
308 tile_id: TileId(tile_id),
309 padding: 0,
310 bounds: crate::Bounds {
311 origin: Point::default(),
312 size,
313 },
314 },
315 );
316
317 Ok(state.tiles[key].clone())
318 }
319}