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