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