From 8e799b6e2241a6620499d731c751fdd73133e889 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 6 Nov 2023 20:44:03 -0700 Subject: [PATCH] Flesh out gpui2 test support --- crates/gpui2/src/app/test_context.rs | 21 ++- crates/gpui2/src/geometry.rs | 4 + crates/gpui2/src/platform/test.rs | 4 + crates/gpui2/src/platform/test/display.rs | 41 +++++ crates/gpui2/src/platform/test/platform.rs | 33 ++-- crates/gpui2/src/platform/test/window.rs | 179 +++++++++++++++++++++ crates/gpui2/src/window.rs | 2 +- 7 files changed, 271 insertions(+), 13 deletions(-) create mode 100644 crates/gpui2/src/platform/test/display.rs create mode 100644 crates/gpui2/src/platform/test/window.rs diff --git a/crates/gpui2/src/app/test_context.rs b/crates/gpui2/src/app/test_context.rs index 856fce75b44541eb690f9cf1bad603ca7fe678c7..530138e21e042f22422749058bd255fc93d5e37d 100644 --- a/crates/gpui2/src/app/test_context.rs +++ b/crates/gpui2/src/app/test_context.rs @@ -1,7 +1,7 @@ use crate::{ AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context, - EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, - TestPlatform, WindowContext, + EventEmitter, ForegroundExecutor, InputEvent, KeyDownEvent, Keystroke, Model, ModelContext, + Result, Task, TestDispatcher, TestPlatform, WindowContext, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -129,6 +129,23 @@ impl TestAppContext { } } + pub fn dispatch_keystroke( + &mut self, + window: AnyWindowHandle, + keystroke: Keystroke, + is_held: bool, + ) { + let handled = window + .update(self, |_, cx| { + cx.dispatch_event(InputEvent::KeyDown(KeyDownEvent { keystroke, is_held })) + }) + .is_ok_and(|handled| handled); + + if !handled { + // todo!() simluate input here + } + } + pub fn notifications(&mut self, entity: &Model) -> impl Stream { let (tx, rx) = futures::channel::mpsc::unbounded(); diff --git a/crates/gpui2/src/geometry.rs b/crates/gpui2/src/geometry.rs index d6755a53973f00d5fee4fd11912f6e73da26cf71..08a661630e707c14791d96bbd6531b0cd42b3c9d 100644 --- a/crates/gpui2/src/geometry.rs +++ b/crates/gpui2/src/geometry.rs @@ -25,6 +25,10 @@ impl Point { Self { x, y } } + pub fn zero() -> Self { + Self::new(T::default(), T::default()) + } + pub fn map(&self, f: impl Fn(T) -> U) -> Point { Point { x: f(self.x.clone()), diff --git a/crates/gpui2/src/platform/test.rs b/crates/gpui2/src/platform/test.rs index 7e59ced7301d75b7784c67bb5de0fe8893074b3f..acc8ffe41ce423e7b16fd9e17e86ef3b5f71dfd4 100644 --- a/crates/gpui2/src/platform/test.rs +++ b/crates/gpui2/src/platform/test.rs @@ -1,5 +1,9 @@ mod dispatcher; +mod display; mod platform; +mod window; pub use dispatcher::*; +pub use display::*; pub use platform::*; +pub use window::*; diff --git a/crates/gpui2/src/platform/test/display.rs b/crates/gpui2/src/platform/test/display.rs new file mode 100644 index 0000000000000000000000000000000000000000..78d75296e66d3fa18f1c7ba6403537369ff013a7 --- /dev/null +++ b/crates/gpui2/src/platform/test/display.rs @@ -0,0 +1,41 @@ +use anyhow::{Ok, Result}; + +use crate::{Bounds, DisplayId, GlobalPixels, PlatformDisplay, Point}; + +#[derive(Debug)] +pub struct TestDisplay { + id: DisplayId, + uuid: uuid::Uuid, + bounds: Bounds, +} + +impl TestDisplay { + pub fn new() -> Self { + TestDisplay { + id: DisplayId(1), + uuid: uuid::Uuid::new_v4(), + bounds: Bounds::from_corners( + Point::zero(), + Point::new(GlobalPixels(1920.), GlobalPixels(1080.)), + ), + } + } +} + +impl PlatformDisplay for TestDisplay { + fn id(&self) -> crate::DisplayId { + self.id + } + + fn uuid(&self) -> Result { + Ok(self.uuid) + } + + fn as_any(&self) -> &dyn std::any::Any { + todo!() + } + + fn bounds(&self) -> crate::Bounds { + self.bounds + } +} diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index b4f3c739e60921a880827422023f8ff7d3c9e3df..37978e7ad7fe7e4b4df812cf3116f15239d6d2a8 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -1,10 +1,18 @@ -use crate::{BackgroundExecutor, DisplayId, ForegroundExecutor, Platform, PlatformTextSystem}; +use crate::{ + AnyWindowHandle, BackgroundExecutor, CursorStyle, DisplayId, ForegroundExecutor, Platform, + PlatformDisplay, PlatformTextSystem, TestDisplay, TestWindow, WindowOptions, +}; use anyhow::{anyhow, Result}; -use std::sync::Arc; +use parking_lot::Mutex; +use std::{rc::Rc, sync::Arc}; pub struct TestPlatform { background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, + + active_window: Arc>>, + active_display: Rc, + active_cursor: Mutex, } impl TestPlatform { @@ -12,6 +20,10 @@ impl TestPlatform { TestPlatform { background_executor: executor, foreground_executor, + + active_cursor: Default::default(), + active_display: Rc::new(TestDisplay::new()), + active_window: Default::default(), } } } @@ -59,11 +71,11 @@ impl Platform for TestPlatform { } fn displays(&self) -> Vec> { - unimplemented!() + vec![self.active_display.clone()] } - fn display(&self, _id: DisplayId) -> Option> { - unimplemented!() + fn display(&self, id: DisplayId) -> Option> { + self.displays().iter().find(|d| d.id() == id).cloned() } fn main_window(&self) -> Option { @@ -72,10 +84,11 @@ impl Platform for TestPlatform { fn open_window( &self, - _handle: crate::AnyWindowHandle, - _options: crate::WindowOptions, + handle: AnyWindowHandle, + options: WindowOptions, ) -> Box { - unimplemented!() + *self.active_window.lock() = Some(handle); + Box::new(TestWindow::new(options, self.active_display.clone())) } fn set_display_link_output_callback( @@ -164,8 +177,8 @@ impl Platform for TestPlatform { unimplemented!() } - fn set_cursor_style(&self, _style: crate::CursorStyle) { - unimplemented!() + fn set_cursor_style(&self, style: crate::CursorStyle) { + *self.active_cursor.lock() = style; } fn should_auto_hide_scrollbars(&self) -> bool { diff --git a/crates/gpui2/src/platform/test/window.rs b/crates/gpui2/src/platform/test/window.rs new file mode 100644 index 0000000000000000000000000000000000000000..f1327196556dbf93871ec86da50f4108d9a32846 --- /dev/null +++ b/crates/gpui2/src/platform/test/window.rs @@ -0,0 +1,179 @@ +use std::{rc::Rc, sync::Arc}; + +use parking_lot::Mutex; + +use crate::{ + px, Pixels, PlatformAtlas, PlatformDisplay, PlatformWindow, Point, Scene, Size, + WindowAppearance, WindowBounds, WindowOptions, +}; + +#[derive(Default)] +struct Handlers { + active_status_change: Vec>, + input: Vec bool>>, + moved: Vec>, + resize: Vec, f32)>>, +} + +pub struct TestWindow { + bounds: WindowBounds, + current_scene: Mutex>, + display: Rc, + + handlers: Mutex, + sprite_atlas: Arc, +} +impl TestWindow { + pub fn new(options: WindowOptions, display: Rc) -> Self { + Self { + bounds: options.bounds, + current_scene: Default::default(), + display, + + sprite_atlas: Arc::new(TestAtlas), + handlers: Default::default(), + } + } +} + +impl PlatformWindow for TestWindow { + fn bounds(&self) -> WindowBounds { + self.bounds + } + + fn content_size(&self) -> Size { + let bounds = match self.bounds { + WindowBounds::Fixed(bounds) => bounds, + WindowBounds::Maximized | WindowBounds::Fullscreen => self.display().bounds(), + }; + bounds.size.map(|p| px(p.0)) + } + + fn scale_factor(&self) -> f32 { + 2.0 + } + + fn titlebar_height(&self) -> Pixels { + todo!() + } + + fn appearance(&self) -> WindowAppearance { + todo!() + } + + fn display(&self) -> std::rc::Rc { + self.display.clone() + } + + fn mouse_position(&self) -> Point { + Point::zero() + } + + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { + todo!() + } + + fn set_input_handler(&mut self, _input_handler: Box) { + todo!() + } + + fn prompt( + &self, + _level: crate::PromptLevel, + _msg: &str, + _answers: &[&str], + ) -> futures::channel::oneshot::Receiver { + todo!() + } + + fn activate(&self) { + todo!() + } + + fn set_title(&mut self, _title: &str) { + todo!() + } + + fn set_edited(&mut self, _edited: bool) { + todo!() + } + + fn show_character_palette(&self) { + todo!() + } + + fn minimize(&self) { + todo!() + } + + fn zoom(&self) { + todo!() + } + + fn toggle_full_screen(&self) { + todo!() + } + + fn on_input(&self, callback: Box bool>) { + self.handlers.lock().input.push(callback) + } + + fn on_active_status_change(&self, callback: Box) { + self.handlers.lock().active_status_change.push(callback) + } + + fn on_resize(&self, callback: Box, f32)>) { + self.handlers.lock().resize.push(callback) + } + + fn on_fullscreen(&self, _callback: Box) { + todo!() + } + + fn on_moved(&self, callback: Box) { + self.handlers.lock().moved.push(callback) + } + + fn on_should_close(&self, _callback: Box bool>) { + todo!() + } + + fn on_close(&self, _callback: Box) { + todo!() + } + + fn on_appearance_changed(&self, _callback: Box) { + todo!() + } + + fn is_topmost_for_position(&self, _position: crate::Point) -> bool { + todo!() + } + + fn draw(&self, scene: crate::Scene) { + self.current_scene.lock().replace(scene); + } + + fn sprite_atlas(&self) -> std::sync::Arc { + self.sprite_atlas.clone() + } +} + +pub struct TestAtlas; + +impl PlatformAtlas for TestAtlas { + fn get_or_insert_with<'a>( + &self, + _key: &crate::AtlasKey, + _build: &mut dyn FnMut() -> anyhow::Result<( + Size, + std::borrow::Cow<'a, [u8]>, + )>, + ) -> anyhow::Result { + todo!() + } + + fn clear(&self) { + todo!() + } +} diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index 9cab40082b02912eced41363a2afa6506fb3d9c0..d7ee86e1c15951c5fb4daaf27cea5ca42a4f55d2 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -1047,7 +1047,7 @@ impl<'a> WindowContext<'a> { } /// Dispatch a mouse or keyboard event on the window. - fn dispatch_event(&mut self, event: InputEvent) -> bool { + pub fn dispatch_event(&mut self, event: InputEvent) -> bool { let event = match event { // Track the mouse position with our own state, since accessing the platform // API for the mouse position can only occur on the main thread.