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 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<Box<dyn PlatformInputHandler>>,
27}
28
29#[derive(Clone)]
30pub 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 pub fn edited(&self) -> bool {
121 self.0.lock().edited
122 }
123}
124
125impl PlatformWindow for TestWindow {
126 fn bounds(&self) -> WindowBounds {
127 self.0.lock().bounds
128 }
129
130 fn content_size(&self) -> Size<Pixels> {
131 let bounds = match self.bounds() {
132 WindowBounds::Fixed(bounds) => bounds,
133 WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(),
134 };
135 bounds.size.map(|p| px(p.0))
136 }
137
138 fn scale_factor(&self) -> f32 {
139 2.0
140 }
141
142 fn titlebar_height(&self) -> Pixels {
143 unimplemented!()
144 }
145
146 fn appearance(&self) -> WindowAppearance {
147 unimplemented!()
148 }
149
150 fn display(&self) -> std::rc::Rc<dyn crate::PlatformDisplay> {
151 self.0.lock().display.clone()
152 }
153
154 fn mouse_position(&self) -> Point<Pixels> {
155 Point::default()
156 }
157
158 fn modifiers(&self) -> crate::Modifiers {
159 crate::Modifiers::default()
160 }
161
162 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
163 self
164 }
165
166 fn set_input_handler(&mut self, input_handler: Box<dyn crate::PlatformInputHandler>) {
167 self.0.lock().input_handler = Some(input_handler);
168 }
169
170 fn take_input_handler(&mut self) -> Option<Box<dyn PlatformInputHandler>> {
171 self.0.lock().input_handler.take()
172 }
173
174 fn prompt(
175 &self,
176 _level: crate::PromptLevel,
177 _msg: &str,
178 _answers: &[&str],
179 ) -> futures::channel::oneshot::Receiver<usize> {
180 self.0
181 .lock()
182 .platform
183 .upgrade()
184 .expect("platform dropped")
185 .prompt()
186 }
187
188 fn activate(&self) {
189 self.0
190 .lock()
191 .platform
192 .upgrade()
193 .unwrap()
194 .set_active_window(Some(self.clone()))
195 }
196
197 fn set_title(&mut self, title: &str) {
198 self.0.lock().title = Some(title.to_owned());
199 }
200
201 fn set_edited(&mut self, edited: bool) {
202 self.0.lock().edited = edited;
203 }
204
205 fn show_character_palette(&self) {
206 unimplemented!()
207 }
208
209 fn minimize(&self) {
210 unimplemented!()
211 }
212
213 fn zoom(&self) {
214 unimplemented!()
215 }
216
217 fn toggle_full_screen(&self) {
218 unimplemented!()
219 }
220
221 fn on_request_frame(&self, _callback: Box<dyn FnMut()>) {}
222
223 fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> bool>) {
224 self.0.lock().input_callback = Some(callback)
225 }
226
227 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
228 self.0.lock().active_status_change_callback = Some(callback)
229 }
230
231 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
232 self.0.lock().resize_callback = Some(callback)
233 }
234
235 fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
236 unimplemented!()
237 }
238
239 fn on_moved(&self, callback: Box<dyn FnMut()>) {
240 self.0.lock().moved_callback = Some(callback)
241 }
242
243 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
244 self.0.lock().should_close_handler = Some(callback);
245 }
246
247 fn on_close(&self, _callback: Box<dyn FnOnce()>) {
248 unimplemented!()
249 }
250
251 fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
252 unimplemented!()
253 }
254
255 fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
256 unimplemented!()
257 }
258
259 fn invalidate(&self) {}
260
261 fn draw(&self, _scene: &crate::Scene) {}
262
263 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
264 self.0.lock().sprite_atlas.clone()
265 }
266
267 fn as_test(&mut self) -> Option<&mut TestWindow> {
268 Some(self)
269 }
270}
271
272pub struct TestAtlasState {
273 next_id: u32,
274 tiles: HashMap<AtlasKey, AtlasTile>,
275}
276
277pub 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 bounds: crate::Bounds {
320 origin: Point::default(),
321 size,
322 },
323 },
324 );
325
326 Ok(state.tiles[key].clone())
327 }
328}