From f03c0f6e6386c6aeff9b94c699c44205b328c448 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Mon, 11 Dec 2023 13:17:42 -0800 Subject: [PATCH] Add double click handling --- crates/gpui2/src/app.rs | 4 ++ crates/gpui2/src/platform.rs | 1 + crates/gpui2/src/platform/mac/platform.rs | 8 ++++ crates/gpui2/src/platform/test/platform.rs | 5 ++ crates/workspace2/src/dock.rs | 53 +++++++++++++++++++-- crates/workspace2/src/workspace2.rs | 54 +++++++++++++++++++--- 6 files changed, 115 insertions(+), 10 deletions(-) diff --git a/crates/gpui2/src/app.rs b/crates/gpui2/src/app.rs index 842ac0b7044a6b88e68a7fb8c331f6c0db9ebb31..0d70458d370fdf75e09bac251986311c3b8bd966 100644 --- a/crates/gpui2/src/app.rs +++ b/crates/gpui2/src/app.rs @@ -514,6 +514,10 @@ impl AppContext { self.platform.path_for_auxiliary_executable(name) } + pub fn double_click_interval(&self) -> Duration { + self.platform.double_click_interval() + } + pub fn prompt_for_paths( &self, options: PathPromptOptions, diff --git a/crates/gpui2/src/platform.rs b/crates/gpui2/src/platform.rs index 006640af4f193c149b97cd3279ada54bbdc183bb..2442841f054482b9673829b7609e89fd9b42cb56 100644 --- a/crates/gpui2/src/platform.rs +++ b/crates/gpui2/src/platform.rs @@ -102,6 +102,7 @@ pub(crate) trait Platform: 'static { fn app_version(&self) -> Result; fn app_path(&self) -> Result; fn local_timezone(&self) -> UtcOffset; + fn double_click_interval(&self) -> Duration; fn path_for_auxiliary_executable(&self, name: &str) -> Result; fn set_cursor_style(&self, style: CursorStyle); diff --git a/crates/gpui2/src/platform/mac/platform.rs b/crates/gpui2/src/platform/mac/platform.rs index 2deea545e164b3445dcd6388048eaf97b66e9102..6b6c0237526975661f9d78f14a865b0ac3ba2e45 100644 --- a/crates/gpui2/src/platform/mac/platform.rs +++ b/crates/gpui2/src/platform/mac/platform.rs @@ -48,6 +48,7 @@ use std::{ rc::Rc, slice, str, sync::Arc, + time::Duration, }; use time::UtcOffset; @@ -650,6 +651,13 @@ impl Platform for MacPlatform { "macOS" } + fn double_click_interval(&self) -> Duration { + unsafe { + let double_click_interval: f64 = msg_send![class!(NSEvent), doubleClickInterval]; + Duration::from_secs_f64(double_click_interval) + } + } + fn os_version(&self) -> Result { unsafe { let process_info = NSProcessInfo::processInfo(nil); diff --git a/crates/gpui2/src/platform/test/platform.rs b/crates/gpui2/src/platform/test/platform.rs index 876120b62626657541fabd1b790e857e16bd5865..751cc4f7533277e6ff2943908bf01c86b9a83594 100644 --- a/crates/gpui2/src/platform/test/platform.rs +++ b/crates/gpui2/src/platform/test/platform.rs @@ -11,6 +11,7 @@ use std::{ path::PathBuf, rc::{Rc, Weak}, sync::Arc, + time::Duration, }; pub struct TestPlatform { @@ -272,4 +273,8 @@ impl Platform for TestPlatform { fn delete_credentials(&self, _url: &str) -> Result<()> { Ok(()) } + + fn double_click_interval(&self) -> std::time::Duration { + Duration::from_millis(500) + } } diff --git a/crates/workspace2/src/dock.rs b/crates/workspace2/src/dock.rs index 817a89df9e09cd73103273166504a6f1d52c26b4..672484d3b57ee197180c3eee4825562ebfd18041 100644 --- a/crates/workspace2/src/dock.rs +++ b/crates/workspace2/src/dock.rs @@ -1,8 +1,9 @@ use crate::{status_bar::StatusItemView, Workspace}; +use crate::{DockClickReset, DockDragState}; use gpui::{ - div, px, Action, AnchorCorner, AnyView, AppContext, Axis, Div, Entity, EntityId, EventEmitter, - FocusHandle, FocusableView, IntoElement, ParentElement, Render, SharedString, Styled, - Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, + div, px, Action, AnchorCorner, AnyView, AppContext, Axis, ClickEvent, Div, Entity, EntityId, + EventEmitter, FocusHandle, FocusableView, IntoElement, MouseButton, ParentElement, Render, + SharedString, Styled, Subscription, View, ViewContext, VisualContext, WeakView, WindowContext, }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -364,7 +365,7 @@ impl Dock { this.set_open(false, cx); } } - PanelEvent::Focus => todo!(), + PanelEvent::Focus => {} }), ]; @@ -485,6 +486,48 @@ impl Render for Dock { if let Some(entry) = self.visible_entry() { let size = entry.panel.size(cx); + let mut pre_resize_handle = None; + let mut post_resize_handle = None; + let position = self.position; + let handler = div() + .id("resize-handle") + .bg(gpui::red()) + .on_mouse_down(gpui::MouseButton::Left, move |_, cx| { + cx.update_global(|drag: &mut DockDragState, cx| drag.0 = Some(position)) + }) + .on_click(cx.listener(|v, e: &ClickEvent, cx| { + if e.down.button == MouseButton::Left { + cx.update_global(|state: &mut DockClickReset, cx| { + if state.0.is_some() { + state.0 = None; + v.resize_active_panel(None, cx) + } else { + let double_click = cx.double_click_interval(); + let timer = cx.background_executor().timer(double_click); + state.0 = Some(cx.spawn(|_, mut cx| async move { + timer.await; + cx.update_global(|state: &mut DockClickReset, cx| { + state.0 = None; + }) + .ok(); + })); + } + }) + } + })); + + match self.position() { + DockPosition::Left => { + post_resize_handle = Some(handler.w_2().h_full().cursor_col_resize()) + } + DockPosition::Bottom => { + pre_resize_handle = Some(handler.w_full().h_2().cursor_row_resize()) + } + DockPosition::Right => { + pre_resize_handle = Some(handler.w_full().h_1().cursor_col_resize()) + } + } + div() .border_color(cx.theme().colors().border) .map(|this| match self.position().axis() { @@ -496,7 +539,9 @@ impl Render for Dock { DockPosition::Right => this.border_l(), DockPosition::Bottom => this.border_t(), }) + .children(pre_resize_handle) .child(entry.panel.to_any()) + .children(post_resize_handle) } else { div() } diff --git a/crates/workspace2/src/workspace2.rs b/crates/workspace2/src/workspace2.rs index 409798573e23a350707d2935f1a9b2fb5efc3303..43aa13847d937540dbd7c16658de347abb0521b5 100644 --- a/crates/workspace2/src/workspace2.rs +++ b/crates/workspace2/src/workspace2.rs @@ -29,12 +29,12 @@ use futures::{ Future, FutureExt, StreamExt, }; use gpui::{ - actions, div, impl_actions, point, size, Action, AnyModel, AnyView, AnyWeakView, + actions, canvas, div, impl_actions, point, size, Action, AnyModel, AnyView, AnyWeakView, AnyWindowHandle, AppContext, AsyncAppContext, AsyncWindowContext, Bounds, Context, Div, Entity, EntityId, EventEmitter, FocusHandle, FocusableView, GlobalPixels, InteractiveElement, - KeyContext, ManagedView, Model, ModelContext, ParentElement, PathPromptOptions, Point, - PromptLevel, Render, Size, Styled, Subscription, Task, View, ViewContext, VisualContext, - WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, + KeyContext, ManagedView, Model, ModelContext, MouseMoveEvent, ParentElement, PathPromptOptions, + Pixels, Point, PromptLevel, Render, Size, Styled, Subscription, Task, View, ViewContext, + VisualContext, WeakView, WindowBounds, WindowContext, WindowHandle, WindowOptions, }; use item::{FollowableItem, FollowableItemHandle, Item, ItemHandle, ItemSettings, ProjectItem}; use itertools::Itertools; @@ -227,6 +227,9 @@ pub fn init_settings(cx: &mut AppContext) { } pub fn init(app_state: Arc, cx: &mut AppContext) { + cx.default_global::(); + cx.default_global::(); + init_settings(cx); notifications::init(cx); @@ -480,8 +483,6 @@ struct FollowerState { items_by_leader_view_id: HashMap>, } -enum WorkspaceBounds {} - impl Workspace { pub fn new( workspace_id: WorkspaceId, @@ -3571,6 +3572,16 @@ impl FocusableView for Workspace { } } +struct WorkspaceBounds(Bounds); + +//todo!("remove this when better drag APIs are in GPUI2") +#[derive(Default)] +struct DockDragState(Option); + +//todo!("remove this when better double APIs are in GPUI2") +#[derive(Default)] +struct DockClickReset(Option>); + impl Render for Workspace { type Element = Div; @@ -3614,6 +3625,37 @@ impl Render for Workspace { .border_t() .border_b() .border_color(cx.theme().colors().border) + .on_mouse_up(gpui::MouseButton::Left, |_, cx| { + cx.update_global(|drag: &mut DockDragState, cx| { + drag.0 = None; + }) + }) + .on_mouse_move(cx.listener(|workspace, e: &MouseMoveEvent, cx| { + if let Some(types) = &cx.global::().0 { + let workspace_bounds = cx.global::().0; + match types { + DockPosition::Left => { + let size = e.position.x; + workspace.left_dock.update(cx, |left_dock, cx| { + left_dock.resize_active_panel(Some(size.0), cx); + }); + } + DockPosition::Right => { + let size = workspace_bounds.size.width - e.position.x; + workspace.right_dock.update(cx, |right_dock, cx| { + right_dock.resize_active_panel(Some(size.0), cx); + }); + } + DockPosition::Bottom => { + let size = workspace_bounds.size.height - e.position.y; + workspace.bottom_dock.update(cx, |bottom_dock, cx| { + bottom_dock.resize_active_panel(Some(size.0), cx); + }); + } + } + } + })) + .child(canvas(|bounds, cx| cx.set_global(WorkspaceBounds(bounds)))) .child(self.modal_layer.clone()) .child( div()