Detailed changes
@@ -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<T: 'static>(&mut self, entity: &Model<T>) -> impl Stream<Item = ()> {
let (tx, rx) = futures::channel::mpsc::unbounded();
@@ -209,15 +209,15 @@ where
cx: &mut ViewContext<V>,
) -> Self::ElementState {
let mut element_state = element_state.unwrap_or_default();
- self.focus
- .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
- element_state.focus_handle = focus_handle;
- self.interaction.initialize(cx, |cx| {
+ self.interaction.initialize(cx, |cx| {
+ self.focus
+ .initialize(element_state.focus_handle.take(), cx, |focus_handle, cx| {
+ element_state.focus_handle = focus_handle;
for child in &mut self.children {
child.initialize(view_state, cx);
}
})
- });
+ });
element_state
}
@@ -25,6 +25,10 @@ impl<T: Clone + Debug + Default> Point<T> {
Self { x, y }
}
+ pub fn zero() -> Self {
+ Self::new(T::default(), T::default())
+ }
+
pub fn map<U: Clone + Default + Debug>(&self, f: impl Fn(T) -> U) -> Point<U> {
Point {
x: f(self.x.clone()),
@@ -1230,3 +1230,73 @@ pub type KeyListener<V> = Box<
) -> Option<Box<dyn Action>>
+ 'static,
>;
+
+#[cfg(test)]
+mod test {
+ use serde_derive::Deserialize;
+
+ use crate::{
+ self as gpui, div, Div, FocusHandle, KeyBinding, Keystroke, ParentElement, Render,
+ StatefulInteraction, StatelessInteractive, TestAppContext, VisualContext,
+ };
+
+ struct TestView {
+ saw_key_down: bool,
+ saw_action: bool,
+ focus_handle: FocusHandle,
+ }
+
+ #[derive(PartialEq, Clone, Default, Deserialize)]
+ struct TestAction;
+
+ impl Render for TestView {
+ type Element = Div<Self, StatefulInteraction<Self>>;
+
+ fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> Self::Element {
+ div().id("testview").child(
+ div()
+ .on_key_down(|this: &mut TestView, _, _, _| {
+ dbg!("ola!");
+ this.saw_key_down = true
+ })
+ .on_action(|this: &mut TestView, _: &TestAction, _, _| {
+ dbg!("ola!");
+ this.saw_action = true
+ })
+ .track_focus(&self.focus_handle),
+ )
+ }
+ }
+
+ #[gpui::test]
+ fn test_on_events(cx: &mut TestAppContext) {
+ let window = cx.update(|cx| {
+ cx.open_window(Default::default(), |cx| {
+ cx.build_view(|cx| TestView {
+ saw_key_down: false,
+ saw_action: false,
+ focus_handle: cx.focus_handle(),
+ })
+ })
+ });
+
+ cx.update(|cx| {
+ cx.bind_keys(vec![KeyBinding::new("ctrl-g", TestAction, None)]);
+ });
+
+ window
+ .update(cx, |test_view, cx| cx.focus(&test_view.focus_handle))
+ .unwrap();
+
+ cx.dispatch_keystroke(*window, Keystroke::parse("space").unwrap(), false);
+ cx.dispatch_keystroke(*window, Keystroke::parse("ctrl-g").unwrap(), false);
+
+ window
+ .update(cx, |test_view, _| {
+ assert!(test_view.saw_key_down || test_view.saw_action);
+ assert!(test_view.saw_key_down);
+ assert!(test_view.saw_action);
+ })
+ .unwrap();
+ }
+}
@@ -1,5 +1,9 @@
mod dispatcher;
+mod display;
mod platform;
+mod window;
pub use dispatcher::*;
+pub use display::*;
pub use platform::*;
+pub use window::*;
@@ -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<GlobalPixels>,
+}
+
+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<uuid::Uuid> {
+ Ok(self.uuid)
+ }
+
+ fn as_any(&self) -> &dyn std::any::Any {
+ todo!()
+ }
+
+ fn bounds(&self) -> crate::Bounds<crate::GlobalPixels> {
+ self.bounds
+ }
+}
@@ -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<Mutex<Option<AnyWindowHandle>>>,
+ active_display: Rc<dyn PlatformDisplay>,
+ active_cursor: Mutex<CursorStyle>,
}
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<std::rc::Rc<dyn crate::PlatformDisplay>> {
- unimplemented!()
+ vec![self.active_display.clone()]
}
- fn display(&self, _id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
- unimplemented!()
+ fn display(&self, id: DisplayId) -> Option<std::rc::Rc<dyn crate::PlatformDisplay>> {
+ self.displays().iter().find(|d| d.id() == id).cloned()
}
fn main_window(&self) -> Option<crate::AnyWindowHandle> {
@@ -72,10 +84,11 @@ impl Platform for TestPlatform {
fn open_window(
&self,
- _handle: crate::AnyWindowHandle,
- _options: crate::WindowOptions,
+ handle: AnyWindowHandle,
+ options: WindowOptions,
) -> Box<dyn crate::PlatformWindow> {
- 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 {
@@ -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<Box<dyn FnMut(bool)>>,
+ input: Vec<Box<dyn FnMut(crate::InputEvent) -> bool>>,
+ moved: Vec<Box<dyn FnMut()>>,
+ resize: Vec<Box<dyn FnMut(Size<Pixels>, f32)>>,
+}
+
+pub struct TestWindow {
+ bounds: WindowBounds,
+ current_scene: Mutex<Option<Scene>>,
+ display: Rc<dyn PlatformDisplay>,
+
+ handlers: Mutex<Handlers>,
+ sprite_atlas: Arc<dyn PlatformAtlas>,
+}
+impl TestWindow {
+ pub fn new(options: WindowOptions, display: Rc<dyn PlatformDisplay>) -> 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<Pixels> {
+ 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<dyn crate::PlatformDisplay> {
+ self.display.clone()
+ }
+
+ fn mouse_position(&self) -> Point<Pixels> {
+ Point::zero()
+ }
+
+ fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
+ todo!()
+ }
+
+ fn set_input_handler(&mut self, _input_handler: Box<dyn crate::PlatformInputHandler>) {
+ todo!()
+ }
+
+ fn prompt(
+ &self,
+ _level: crate::PromptLevel,
+ _msg: &str,
+ _answers: &[&str],
+ ) -> futures::channel::oneshot::Receiver<usize> {
+ 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<dyn FnMut(crate::InputEvent) -> bool>) {
+ self.handlers.lock().input.push(callback)
+ }
+
+ fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
+ self.handlers.lock().active_status_change.push(callback)
+ }
+
+ fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
+ self.handlers.lock().resize.push(callback)
+ }
+
+ fn on_fullscreen(&self, _callback: Box<dyn FnMut(bool)>) {
+ todo!()
+ }
+
+ fn on_moved(&self, callback: Box<dyn FnMut()>) {
+ self.handlers.lock().moved.push(callback)
+ }
+
+ fn on_should_close(&self, _callback: Box<dyn FnMut() -> bool>) {
+ todo!()
+ }
+
+ fn on_close(&self, _callback: Box<dyn FnOnce()>) {
+ todo!()
+ }
+
+ fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {
+ todo!()
+ }
+
+ fn is_topmost_for_position(&self, _position: crate::Point<Pixels>) -> bool {
+ todo!()
+ }
+
+ fn draw(&self, scene: crate::Scene) {
+ self.current_scene.lock().replace(scene);
+ }
+
+ fn sprite_atlas(&self) -> std::sync::Arc<dyn crate::PlatformAtlas> {
+ 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<crate::DevicePixels>,
+ std::borrow::Cow<'a, [u8]>,
+ )>,
+ ) -> anyhow::Result<crate::AtlasTile> {
+ todo!()
+ }
+
+ fn clear(&self) {
+ todo!()
+ }
+}
@@ -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.