window.rs

  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}