1use crate::{
2 px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke,
3 Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
4 Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions,
5};
6use collections::HashMap;
7use parking_lot::Mutex;
8use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
9use std::{
10 rc::{Rc, Weak},
11 sync::{self, Arc},
12};
13
14pub(crate) struct TestWindowState {
15 pub(crate) bounds: WindowBounds,
16 pub(crate) handle: AnyWindowHandle,
17 display: Rc<dyn PlatformDisplay>,
18 pub(crate) title: Option<String>,
19 pub(crate) edited: bool,
20 platform: Weak<TestPlatform>,
21 sprite_atlas: Arc<dyn PlatformAtlas>,
22 pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
23 input_callback: Option<Box<dyn FnMut(PlatformInput) -> bool>>,
24 active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
25 resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
26 moved_callback: Option<Box<dyn FnMut()>>,
27 input_handler: Option<PlatformInputHandler>,
28}
29
30#[derive(Clone)]
31pub(crate) struct TestWindow(pub(crate) Arc<Mutex<TestWindowState>>);
32
33impl HasWindowHandle for TestWindow {
34 fn window_handle(
35 &self,
36 ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
37 unimplemented!("Test Windows are not backed by a real platform window")
38 }
39}
40
41impl HasDisplayHandle for TestWindow {
42 fn display_handle(
43 &self,
44 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
45 unimplemented!("Test Windows are not backed by a real platform window")
46 }
47}
48
49impl TestWindow {
50 pub fn new(
51 options: WindowOptions,
52 handle: AnyWindowHandle,
53 platform: Weak<TestPlatform>,
54 display: Rc<dyn PlatformDisplay>,
55 ) -> Self {
56 Self(Arc::new(Mutex::new(TestWindowState {
57 bounds: options.bounds,
58 display,
59 platform,
60 handle,
61 sprite_atlas: Arc::new(TestAtlas::new()),
62 title: Default::default(),
63 edited: false,
64 should_close_handler: None,
65 input_callback: None,
66 active_status_change_callback: None,
67 resize_callback: None,
68 moved_callback: None,
69 input_handler: None,
70 })))
71 }
72
73 pub fn simulate_resize(&mut self, size: Size<Pixels>) {
74 let scale_factor = self.scale_factor();
75 let mut lock = self.0.lock();
76 let Some(mut callback) = lock.resize_callback.take() else {
77 return;
78 };
79 match &mut lock.bounds {
80 WindowBounds::Fullscreen | WindowBounds::Maximized => {
81 lock.bounds = WindowBounds::Fixed(Bounds {
82 origin: Point::default(),
83 size: size.map(|pixels| f64::from(pixels).into()),
84 });
85 }
86 WindowBounds::Fixed(bounds) => {
87 bounds.size = size.map(|pixels| f64::from(pixels).into());
88 }
89 }
90 drop(lock);
91 callback(size, scale_factor);
92 self.0.lock().resize_callback = Some(callback);
93 }
94
95 pub(crate) fn simulate_active_status_change(&self, active: bool) {
96 let mut lock = self.0.lock();
97 let Some(mut callback) = lock.active_status_change_callback.take() else {
98 return;
99 };
100 drop(lock);
101 callback(active);
102 self.0.lock().active_status_change_callback = Some(callback);
103 }
104
105 pub fn simulate_input(&mut self, event: PlatformInput) -> bool {
106 let mut lock = self.0.lock();
107 let Some(mut callback) = lock.input_callback.take() else {
108 return false;
109 };
110 drop(lock);
111 let result = callback(event);
112 self.0.lock().input_callback = Some(callback);
113 result
114 }
115
116 pub fn simulate_keystroke(&mut self, mut keystroke: Keystroke, is_held: bool) {
117 if keystroke.ime_key.is_none()
118 && !keystroke.modifiers.command
119 && !keystroke.modifiers.control
120 && !keystroke.modifiers.function
121 {
122 keystroke.ime_key = Some(if keystroke.modifiers.shift {
123 keystroke.key.to_ascii_uppercase().clone()
124 } else {
125 keystroke.key.clone()
126 })
127 }
128
129 if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent {
130 keystroke: keystroke.clone(),
131 is_held,
132 })) {
133 return;
134 }
135
136 let mut lock = self.0.lock();
137 let Some(mut input_handler) = lock.input_handler.take() else {
138 panic!(
139 "simulate_keystroke {:?} input event was not handled and there was no active input",
140 &keystroke
141 );
142 };
143 drop(lock);
144 if let Some(text) = keystroke.ime_key.as_ref() {
145 input_handler.replace_text_in_range(None, &text);
146 }
147
148 self.0.lock().input_handler = Some(input_handler);
149 }
150}
151
152impl PlatformWindow for TestWindow {
153 fn bounds(&self) -> WindowBounds {
154 self.0.lock().bounds
155 }
156
157 fn content_size(&self) -> Size<Pixels> {
158 let bounds = match self.bounds() {
159 WindowBounds::Fixed(bounds) => bounds,
160 WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
161 };
162 bounds.size.map(|p| px(p.0))
163 }
164
165 fn scale_factor(&self) -> f32 {
166 2.0
167 }
168
169 fn titlebar_height(&self) -> Pixels {
170 unimplemented!()
171 }
172
173 fn appearance(&self) -> WindowAppearance {
174 WindowAppearance::Light
175 }
176
177 fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
178 self.0.lock().display.clone()
179 }
180
181 fn mouse_position(&self) -> Point<Pixels> {
182 Point::default()
183 }
184
185 fn modifiers(&self) -> crate::Modifiers {
186 crate::Modifiers::default()
187 }
188
189 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
190 self
191 }
192
193 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
194 self.0.lock().input_handler = Some(input_handler);
195 }
196
197 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
198 self.0.lock().input_handler.take()
199 }
200
201 fn prompt(
202 &self,
203 _level: crate::PromptLevel,
204 _msg: &str,
205 _detail: Option<&str>,
206 _answers: &[&str],
207 ) -> futures::channel::oneshot::Receiver<usize> {
208 self.0
209 .lock()
210 .platform
211 .upgrade()
212 .expect("platform dropped")
213 .prompt()
214 }
215
216 fn activate(&self) {
217 self.0
218 .lock()
219 .platform
220 .upgrade()
221 .unwrap()
222 .set_active_window(Some(self.clone()))
223 }
224
225 fn set_title(&mut self, title: &str) {
226 self.0.lock().title = Some(title.to_owned());
227 }
228
229 fn set_edited(&mut self, edited: bool) {
230 self.0.lock().edited = edited;
231 }
232
233 fn show_character_palette(&self) {
234 unimplemented!()
235 }
236
237 fn minimize(&self) {
238 unimplemented!()
239 }
240
241 fn zoom(&self) {
242 unimplemented!()
243 }
244
245 fn toggle_full_screen(&self) {
246 unimplemented!()
247 }
248
249 fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
250
251 fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
252 self.0.lock().input_callback = Some(callback)
253 }
254
255 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
256 self.0.lock().active_status_change_callback = Some(callback)
257 }
258
259 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
260 self.0.lock().resize_callback = Some(callback)
261 }
262
263 fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
264 unimplemented!()
265 }
266
267 fn on_moved(&self, callback: Box<dyn FnMut()>) {
268 self.0.lock().moved_callback = Some(callback)
269 }
270
271 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
272 self.0.lock().should_close_handler = Some(callback);
273 }
274
275 fn on_close(&self, _callback: Box<dyn FnOnce()>) {
276 unimplemented!()
277 }
278
279 fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
280
281 fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
282 unimplemented!()
283 }
284
285 fn draw(&self, _scene: &crate::Scene) {}
286
287 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
288 self.0.lock().sprite_atlas.clone()
289 }
290
291 fn set_graphics_profiler_enabled(&self, _enabled: bool) {}
292
293 fn as_test(&mut self) -> Option<&mut TestWindow> {
294 Some(self)
295 }
296}
297
298pub(crate) struct TestAtlasState {
299 next_id: u32,
300 tiles: HashMap<AtlasKey, AtlasTile>,
301}
302
303pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
304
305impl TestAtlas {
306 pub fn new() -> Self {
307 TestAtlas(Mutex::new(TestAtlasState {
308 next_id: 0,
309 tiles: HashMap::default(),
310 }))
311 }
312}
313
314impl PlatformAtlas for TestAtlas {
315 fn get_or_insert_with<'a>(
316 &self,
317 key: &crate::AtlasKey,
318 build: &mut dyn FnMut() -> anyhow::Result<(
319 Size<crate::DevicePixels>,
320 std::borrow::Cow<'a, [u8]>,
321 )>,
322 ) -> anyhow::Result<crate::AtlasTile> {
323 let mut state = self.0.lock();
324 if let Some(tile) = state.tiles.get(key) {
325 return Ok(tile.clone());
326 }
327
328 state.next_id += 1;
329 let texture_id = state.next_id;
330 state.next_id += 1;
331 let tile_id = state.next_id;
332
333 drop(state);
334 let (size, _) = build()?;
335 let mut state = self.0.lock();
336
337 state.tiles.insert(
338 key.clone(),
339 crate::AtlasTile {
340 texture_id: AtlasTextureId {
341 index: texture_id,
342 kind: crate::AtlasTextureKind::Path,
343 },
344 tile_id: TileId(tile_id),
345 padding: 0,
346 bounds: crate::Bounds {
347 origin: Point::default(),
348 size,
349 },
350 },
351 );
352
353 Ok(state.tiles[key].clone())
354 }
355}