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