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