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, keystroke: Keystroke, is_held: bool) {
100 if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent {
101 keystroke: keystroke.clone(),
102 is_held,
103 })) {
104 return;
105 }
106
107 let mut lock = self.0.lock();
108 let Some(mut input_handler) = lock.input_handler.take() else {
109 panic!(
110 "simulate_keystroke {:?} input event was not handled and there was no active input",
111 &keystroke
112 );
113 };
114 drop(lock);
115 let text = keystroke.ime_key.unwrap_or(keystroke.key);
116 input_handler.replace_text_in_range(None, &text);
117
118 self.0.lock().input_handler = Some(input_handler);
119 }
120}
121
122impl PlatformWindow for TestWindow {
123 fn bounds(&self) -> WindowBounds {
124 self.0.lock().bounds
125 }
126
127 fn content_size(&self) -> Size<Pixels> {
128 let bounds = match self.bounds() {
129 WindowBounds::Fixed(bounds) => bounds,
130 WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
131 };
132 bounds.size.map(|p| px(p.0))
133 }
134
135 fn scale_factor(&self) -> f32 {
136 2.0
137 }
138
139 fn titlebar_height(&self) -> Pixels {
140 unimplemented!()
141 }
142
143 fn appearance(&self) -> WindowAppearance {
144 unimplemented!()
145 }
146
147 fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
148 self.0.lock().display.clone()
149 }
150
151 fn mouse_position(&self) -> Point<Pixels> {
152 Point::default()
153 }
154
155 fn modifiers(&self) -> crate::Modifiers {
156 crate::Modifiers::default()
157 }
158
159 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
160 self
161 }
162
163 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
164 self.0.lock().input_handler = Some(input_handler);
165 }
166
167 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
168 self.0.lock().input_handler.take()
169 }
170
171 fn prompt(
172 &self,
173 _level: crate::PromptLevel,
174 _msg: &str,
175 _answers: &[&str],
176 ) -> futures::channel::oneshot::Receiver<usize> {
177 self.0
178 .lock()
179 .platform
180 .upgrade()
181 .expect("platform dropped")
182 .prompt()
183 }
184
185 fn activate(&self) {
186 self.0
187 .lock()
188 .platform
189 .upgrade()
190 .unwrap()
191 .set_active_window(Some(self.clone()))
192 }
193
194 fn set_title(&mut self, title: &str) {
195 self.0.lock().title = Some(title.to_owned());
196 }
197
198 fn set_edited(&mut self, edited: bool) {
199 self.0.lock().edited = edited;
200 }
201
202 fn show_character_palette(&self) {
203 unimplemented!()
204 }
205
206 fn minimize(&self) {
207 unimplemented!()
208 }
209
210 fn zoom(&self) {
211 unimplemented!()
212 }
213
214 fn toggle_full_screen(&self) {
215 unimplemented!()
216 }
217
218 fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
219
220 fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
221 self.0.lock().input_callback = Some(callback)
222 }
223
224 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
225 self.0.lock().active_status_change_callback = Some(callback)
226 }
227
228 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
229 self.0.lock().resize_callback = Some(callback)
230 }
231
232 fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
233 unimplemented!()
234 }
235
236 fn on_moved(&self, callback: Box<dyn FnMut()>) {
237 self.0.lock().moved_callback = Some(callback)
238 }
239
240 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
241 self.0.lock().should_close_handler = Some(callback);
242 }
243
244 fn on_close(&self, _callback: Box<dyn FnOnce()>) {
245 unimplemented!()
246 }
247
248 fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
249 unimplemented!()
250 }
251
252 fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
253 unimplemented!()
254 }
255
256 fn invalidate(&self) {}
257
258 fn draw(&self, _scene: &crate::Scene) {}
259
260 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
261 self.0.lock().sprite_atlas.clone()
262 }
263
264 fn as_test(&mut self) -> Option<&mut TestWindow> {
265 Some(self)
266 }
267}
268
269pub(crate) struct TestAtlasState {
270 next_id: u32,
271 tiles: HashMap<AtlasKey, AtlasTile>,
272}
273
274pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
275
276impl TestAtlas {
277 pub fn new() -> Self {
278 TestAtlas(Mutex::new(TestAtlasState {
279 next_id: 0,
280 tiles: HashMap::default(),
281 }))
282 }
283}
284
285impl PlatformAtlas for TestAtlas {
286 fn get_or_insert_with<'a>(
287 &self,
288 key: &crate::AtlasKey,
289 build: &mut dyn FnMut() -> anyhow::Result<(
290 Size<crate::DevicePixels>,
291 std::borrow::Cow<'a, [u8]>,
292 )>,
293 ) -> anyhow::Result<crate::AtlasTile> {
294 let mut state = self.0.lock();
295 if let Some(tile) = state.tiles.get(key) {
296 return Ok(tile.clone());
297 }
298
299 state.next_id += 1;
300 let texture_id = state.next_id;
301 state.next_id += 1;
302 let tile_id = state.next_id;
303
304 drop(state);
305 let (size, _) = build()?;
306 let mut state = self.0.lock();
307
308 state.tiles.insert(
309 key.clone(),
310 crate::AtlasTile {
311 texture_id: AtlasTextureId {
312 index: texture_id,
313 kind: crate::AtlasTextureKind::Path,
314 },
315 tile_id: TileId(tile_id),
316 bounds: crate::Bounds {
317 origin: Point::default(),
318 size,
319 },
320 },
321 );
322
323 Ok(state.tiles[key].clone())
324 }
325}