1use crate::{
2 px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent,
3 Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point,
4 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
22 input_callback: Option<Box<dyn FnMut(InputEvent) -> 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
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: InputEvent) -> 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(InputEvent::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: Box<dyn crate::PlatformInputHandler>) {
164 self.0.lock().input_handler = Some(input_handler);
165 }
166
167 fn clear_input_handler(&mut self) {
168 self.0.lock().input_handler = None;
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_input(&self, callback: Box<dyn FnMut(crate::InputEvent) -> bool>) {
219 self.0.lock().input_callback = Some(callback)
220 }
221
222 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
223 self.0.lock().active_status_change_callback = Some(callback)
224 }
225
226 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
227 self.0.lock().resize_callback = Some(callback)
228 }
229
230 fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
231 unimplemented!()
232 }
233
234 fn on_moved(&self, callback: Box<dyn FnMut()>) {
235 self.0.lock().moved_callback = Some(callback)
236 }
237
238 fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {
239 unimplemented!()
240 }
241
242 fn on_close(&self, _callback: Box<dyn FnOnce()>) {
243 unimplemented!()
244 }
245
246 fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
247 unimplemented!()
248 }
249
250 fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
251 unimplemented!()
252 }
253
254 fn invalidate(&self) {
255 // (self.draw.lock())().unwrap();
256 }
257
258 fn sprite_atlas(&self) -> sync::Arc<dyn crate::PlatformAtlas> {
259 self.0.lock().sprite_atlas.clone()
260 }
261
262 fn as_test(&mut self) -> Option<&mut TestWindow> {
263 Some(self)
264 }
265}
266
267pub struct TestAtlasState {
268 next_id: u32,
269 tiles: HashMap<AtlasKey, AtlasTile>,
270}
271
272pub struct TestAtlas(Mutex<TestAtlasState>);
273
274impl TestAtlas {
275 pub fn new() -> Self {
276 TestAtlas(Mutex::new(TestAtlasState {
277 next_id: 0,
278 tiles: HashMap::default(),
279 }))
280 }
281}
282
283impl PlatformAtlas for TestAtlas {
284 fn get_or_insert_with<'a>(
285 &self,
286 key: &crate::AtlasKey,
287 build: &mut dyn FnMut() -> anyhow::Result<(
288 Size<crate::DevicePixels>,
289 std::borrow::Cow<'a, [u8]>,
290 )>,
291 ) -> anyhow::Result<crate::AtlasTile> {
292 let mut state = self.0.lock();
293 if let Some(tile) = state.tiles.get(key) {
294 return Ok(tile.clone());
295 }
296
297 state.next_id += 1;
298 let texture_id = state.next_id;
299 state.next_id += 1;
300 let tile_id = state.next_id;
301
302 drop(state);
303 let (size, _) = build()?;
304 let mut state = self.0.lock();
305
306 state.tiles.insert(
307 key.clone(),
308 crate::AtlasTile {
309 texture_id: AtlasTextureId {
310 index: texture_id,
311 kind: crate::AtlasTextureKind::Path,
312 },
313 tile_id: TileId(tile_id),
314 bounds: crate::Bounds {
315 origin: Point::default(),
316 size,
317 },
318 },
319 );
320
321 Ok(state.tiles[key].clone())
322 }
323
324 fn clear(&self) {
325 let mut state = self.0.lock();
326 state.tiles = HashMap::default();
327 state.next_id = 0;
328 }
329}