window.rs

  1use crate::{
  2    AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
  3    Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
  4    Point, PromptButton, RequestFrameOptions, Size, TestPlatform, TileId, WindowAppearance,
  5    WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowParams,
  6};
  7use collections::HashMap;
  8use parking_lot::Mutex;
  9use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
 10use std::{
 11    rc::{Rc, Weak},
 12    sync::{self, Arc},
 13};
 14
 15pub(crate) struct TestWindowState {
 16    pub(crate) bounds: Bounds<Pixels>,
 17    pub(crate) handle: AnyWindowHandle,
 18    display: Rc<dyn PlatformDisplay>,
 19    pub(crate) title: Option<String>,
 20    pub(crate) edited: bool,
 21    platform: Weak<TestPlatform>,
 22    // TODO: Replace with `Rc`
 23    sprite_atlas: Arc<dyn PlatformAtlas>,
 24    pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
 25    hit_test_window_control_callback: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
 26    input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
 27    active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
 28    hover_status_change_callback: Option<Box<dyn FnMut(bool)>>,
 29    resize_callback: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
 30    moved_callback: Option<Box<dyn FnMut()>>,
 31    input_handler: Option<PlatformInputHandler>,
 32    is_fullscreen: bool,
 33}
 34
 35#[derive(Clone)]
 36pub struct TestWindow(pub(crate) Rc<Mutex<TestWindowState>>);
 37
 38impl HasWindowHandle for TestWindow {
 39    fn window_handle(
 40        &self,
 41    ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
 42        unimplemented!("Test Windows are not backed by a real platform window")
 43    }
 44}
 45
 46impl HasDisplayHandle for TestWindow {
 47    fn display_handle(
 48        &self,
 49    ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
 50        unimplemented!("Test Windows are not backed by a real platform window")
 51    }
 52}
 53
 54impl TestWindow {
 55    pub(crate) fn new(
 56        handle: AnyWindowHandle,
 57        params: WindowParams,
 58        platform: Weak<TestPlatform>,
 59        display: Rc<dyn PlatformDisplay>,
 60    ) -> Self {
 61        Self(Rc::new(Mutex::new(TestWindowState {
 62            bounds: params.bounds,
 63            display,
 64            platform,
 65            handle,
 66            sprite_atlas: Arc::new(TestAtlas::new()),
 67            title: Default::default(),
 68            edited: false,
 69            should_close_handler: None,
 70            hit_test_window_control_callback: None,
 71            input_callback: None,
 72            active_status_change_callback: None,
 73            hover_status_change_callback: None,
 74            resize_callback: None,
 75            moved_callback: None,
 76            input_handler: None,
 77            is_fullscreen: false,
 78        })))
 79    }
 80
 81    pub fn simulate_resize(&mut self, size: Size<Pixels>) {
 82        let scale_factor = self.scale_factor();
 83        let mut lock = self.0.lock();
 84        let Some(mut callback) = lock.resize_callback.take() else {
 85            return;
 86        };
 87        lock.bounds.size = size;
 88        drop(lock);
 89        callback(size, scale_factor);
 90        self.0.lock().resize_callback = Some(callback);
 91    }
 92
 93    pub(crate) fn simulate_active_status_change(&self, active: bool) {
 94        let mut lock = self.0.lock();
 95        let Some(mut callback) = lock.active_status_change_callback.take() else {
 96            return;
 97        };
 98        drop(lock);
 99        callback(active);
100        self.0.lock().active_status_change_callback = Some(callback);
101    }
102
103    pub fn simulate_input(&mut self, event: PlatformInput) -> bool {
104        let mut lock = self.0.lock();
105        let Some(mut callback) = lock.input_callback.take() else {
106            return false;
107        };
108        drop(lock);
109        let result = callback(event);
110        self.0.lock().input_callback = Some(callback);
111        !result.propagate
112    }
113}
114
115impl PlatformWindow for TestWindow {
116    fn bounds(&self) -> Bounds<Pixels> {
117        self.0.lock().bounds
118    }
119
120    fn window_bounds(&self) -> WindowBounds {
121        WindowBounds::Windowed(self.bounds())
122    }
123
124    fn is_maximized(&self) -> bool {
125        false
126    }
127
128    fn content_size(&self) -> Size<Pixels> {
129        self.bounds().size
130    }
131
132    fn resize(&mut self, size: Size<Pixels>) {
133        let mut lock = self.0.lock();
134        lock.bounds.size = size;
135    }
136
137    fn scale_factor(&self) -> f32 {
138        2.0
139    }
140
141    fn appearance(&self) -> WindowAppearance {
142        WindowAppearance::Light
143    }
144
145    fn display(&self) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
146        Some(self.0.lock().display.clone())
147    }
148
149    fn mouse_position(&self) -> Point<Pixels> {
150        Point::default()
151    }
152
153    fn modifiers(&self) -> crate::Modifiers {
154        crate::Modifiers::default()
155    }
156
157    fn capslock(&self) -> crate::Capslock {
158        crate::Capslock::default()
159    }
160
161    fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
162        self.0.lock().input_handler = Some(input_handler);
163    }
164
165    fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
166        self.0.lock().input_handler.take()
167    }
168
169    fn prompt(
170        &self,
171        _level: crate::PromptLevel,
172        msg: &str,
173        detail: Option<&str>,
174        answers: &[PromptButton],
175    ) -> Option<futures::channel::oneshot::Receiver<usize>> {
176        Some(
177            self.0
178                .lock()
179                .platform
180                .upgrade()
181                .expect("platform dropped")
182                .prompt(msg, detail, answers),
183        )
184    }
185
186    fn activate(&self) {
187        self.0
188            .lock()
189            .platform
190            .upgrade()
191            .unwrap()
192            .set_active_window(Some(self.clone()))
193    }
194
195    fn is_active(&self) -> bool {
196        false
197    }
198
199    fn is_hovered(&self) -> bool {
200        false
201    }
202
203    fn background_appearance(&self) -> WindowBackgroundAppearance {
204        WindowBackgroundAppearance::Opaque
205    }
206
207    fn is_subpixel_rendering_supported(&self) -> bool {
208        false
209    }
210
211    fn set_title(&mut self, title: &str) {
212        self.0.lock().title = Some(title.to_owned());
213    }
214
215    fn set_app_id(&mut self, _app_id: &str) {}
216
217    fn set_background_appearance(&self, _background: WindowBackgroundAppearance) {}
218
219    fn set_edited(&mut self, edited: bool) {
220        self.0.lock().edited = edited;
221    }
222
223    fn show_character_palette(&self) {
224        unimplemented!()
225    }
226
227    fn minimize(&self) {
228        unimplemented!()
229    }
230
231    fn zoom(&self) {
232        unimplemented!()
233    }
234
235    fn toggle_fullscreen(&self) {
236        let mut lock = self.0.lock();
237        lock.is_fullscreen = !lock.is_fullscreen;
238    }
239
240    fn is_fullscreen(&self) -> bool {
241        self.0.lock().is_fullscreen
242    }
243
244    fn on_request_frame(&self, _callback: Box<dyn FnMut(RequestFrameOptions)>) {}
245
246    fn on_input(&self, callback: Box<dyn FnMut(crate::PlatformInput) -> DispatchEventResult>) {
247        self.0.lock().input_callback = Some(callback)
248    }
249
250    fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
251        self.0.lock().active_status_change_callback = Some(callback)
252    }
253
254    fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
255        self.0.lock().hover_status_change_callback = Some(callback)
256    }
257
258    fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
259        self.0.lock().resize_callback = Some(callback)
260    }
261
262    fn on_moved(&self, callback: Box<dyn FnMut()>) {
263        self.0.lock().moved_callback = Some(callback)
264    }
265
266    fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
267        self.0.lock().should_close_handler = Some(callback);
268    }
269
270    fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
271
272    fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
273        self.0.lock().hit_test_window_control_callback = Some(callback);
274    }
275
276    fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
277
278    fn draw(&self, _scene: &crate::Scene) {}
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    #[cfg(target_os = "windows")]
289    fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND {
290        unimplemented!()
291    }
292
293    fn show_window_menu(&self, _position: Point<Pixels>) {
294        unimplemented!()
295    }
296
297    fn start_window_move(&self) {
298        unimplemented!()
299    }
300
301    fn update_ime_position(&self, _bounds: Bounds<Pixels>) {}
302
303    fn gpu_specs(&self) -> Option<GpuSpecs> {
304        None
305    }
306}
307
308pub(crate) struct TestAtlasState {
309    next_id: u32,
310    tiles: HashMap<AtlasKey, AtlasTile>,
311}
312
313pub(crate) struct TestAtlas(Mutex<TestAtlasState>);
314
315impl TestAtlas {
316    pub fn new() -> Self {
317        TestAtlas(Mutex::new(TestAtlasState {
318            next_id: 0,
319            tiles: HashMap::default(),
320        }))
321    }
322}
323
324impl PlatformAtlas for TestAtlas {
325    fn get_or_insert_with<'a>(
326        &self,
327        key: &crate::AtlasKey,
328        build: &mut dyn FnMut() -> anyhow::Result<
329            Option<(Size<crate::DevicePixels>, std::borrow::Cow<'a, [u8]>)>,
330        >,
331    ) -> anyhow::Result<Option<crate::AtlasTile>> {
332        let mut state = self.0.lock();
333        if let Some(tile) = state.tiles.get(key) {
334            return Ok(Some(tile.clone()));
335        }
336        drop(state);
337
338        let Some((size, _)) = build()? else {
339            return Ok(None);
340        };
341
342        let mut state = self.0.lock();
343        state.next_id += 1;
344        let texture_id = state.next_id;
345        state.next_id += 1;
346        let tile_id = state.next_id;
347
348        state.tiles.insert(
349            key.clone(),
350            crate::AtlasTile {
351                texture_id: AtlasTextureId {
352                    index: texture_id,
353                    kind: crate::AtlasTextureKind::Monochrome,
354                },
355                tile_id: TileId(tile_id),
356                padding: 0,
357                bounds: crate::Bounds {
358                    origin: Point::default(),
359                    size,
360                },
361            },
362        );
363
364        Ok(Some(state.tiles[key].clone()))
365    }
366
367    fn remove(&self, key: &AtlasKey) {
368        let mut state = self.0.lock();
369        state.tiles.remove(key);
370    }
371}