From 39d219c898ba9e2036e8a087c26bf08401624f3c Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 6 Sep 2022 17:35:56 -0700 Subject: [PATCH 01/26] Start moving terminal modal into dock UI --- crates/terminal/src/modal.rs | 12 ++++++------ crates/workspace/src/programs.rs | 10 ++++------ crates/workspace/src/workspace.rs | 4 ++-- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index bf83196a97eb4e2e713248b6287565e504a65990..63cc4316cf0f174aa157c7bcd1f1735990f247df 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -1,6 +1,6 @@ use gpui::{ModelHandle, ViewContext}; use settings::{Settings, WorkingDirectory}; -use workspace::{programs::ProgramManager, Workspace}; +use workspace::{programs::Dock, Workspace}; use crate::{ terminal_container_view::{ @@ -13,7 +13,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon let window = cx.window_id(); // Pull the terminal connection out of the global if it has been stored - let possible_terminal = ProgramManager::remove::(window, cx); + let possible_terminal = Dock::remove::(window, cx); if let Some(terminal_handle) = possible_terminal { workspace.toggle_modal(cx, |_, cx| { @@ -22,7 +22,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon }); // Toggle Modal will dismiss the terminal modal if it is currently shown, so we must // store the terminal back in the global - ProgramManager::insert_or_replace::(window, terminal_handle, cx); + Dock::insert_or_replace::(window, terminal_handle, cx); } else { // No connection was stored, create a new terminal if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| { @@ -43,7 +43,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon cx.subscribe(&terminal_handle, on_event).detach(); // Set the global immediately if terminal construction was successful, // in case the user opens the command palette - ProgramManager::insert_or_replace::(window, terminal_handle, cx); + Dock::insert_or_replace::(window, terminal_handle, cx); } this @@ -55,7 +55,7 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon let terminal_handle = connected.read(cx).handle(); // Set the global immediately if terminal construction was successful, // in case the user opens the command palette - ProgramManager::insert_or_replace::(window, terminal_handle, cx); + Dock::insert_or_replace::(window, terminal_handle, cx); } } } @@ -69,7 +69,7 @@ pub fn on_event( ) { // Dismiss the modal if the terminal quit if let Event::CloseTerminal = event { - ProgramManager::remove::(cx.window_id(), cx); + Dock::remove::(cx.window_id(), cx); if workspace.modal::().is_some() { workspace.dismiss_modal(cx) diff --git a/crates/workspace/src/programs.rs b/crates/workspace/src/programs.rs index 36169ea4c70ad174e666669349656ea2c70e9aa9..015d37f00e27e9ed92dbb2d90d416c0d7f43f9a2 100644 --- a/crates/workspace/src/programs.rs +++ b/crates/workspace/src/programs.rs @@ -19,27 +19,25 @@ use gpui::{AnyModelHandle, Entity, ModelHandle, View, ViewContext}; /// This struct is going to be the starting point for the 'program manager' feature that will /// eventually be implemented to provide a collaborative way of engaging with identity-having /// features like the terminal. -pub struct ProgramManager { +pub struct Dock { // TODO: Make this a hashset or something modals: HashMap, } -impl ProgramManager { +impl Dock { pub fn insert_or_replace( window: usize, program: ModelHandle, cx: &mut ViewContext, ) -> Option { - cx.update_global::(|pm, _| { - pm.insert_or_replace_internal::(window, program) - }) + cx.update_global::(|pm, _| pm.insert_or_replace_internal::(window, program)) } pub fn remove( window: usize, cx: &mut ViewContext, ) -> Option> { - cx.update_global::(|pm, _| pm.remove_internal::(window)) + cx.update_global::(|pm, _| pm.remove_internal::(window)) } pub fn new() -> Self { diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 643ade23a748aa3e60a070fe09385720b4d1f8d6..5e010a1d253c3ac073be1b3dd8a4f4edfaeb1fef 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -37,7 +37,7 @@ use log::error; pub use pane::*; pub use pane_group::*; use postage::prelude::Stream; -use programs::ProgramManager; +use programs::Dock; use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId}; use searchable::SearchableItemHandle; use serde::Deserialize; @@ -147,7 +147,7 @@ impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]); pub fn init(app_state: Arc, cx: &mut MutableAppContext) { // Initialize the program manager immediately - cx.set_global(ProgramManager::new()); + cx.set_global(Dock::new()); pane::init(cx); From b9a63369957d269646d468d252567674fc4ca067 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Tue, 6 Sep 2022 18:44:16 -0700 Subject: [PATCH 02/26] Initial explorations into docks --- crates/terminal/src/modal.rs | 100 +++++++++++++++--------------- crates/workspace/src/dock.rs | 35 +++++++++++ crates/workspace/src/pane.rs | 4 ++ crates/workspace/src/programs.rs | 75 ---------------------- crates/workspace/src/workspace.rs | 44 ++++++++++--- 5 files changed, 123 insertions(+), 135 deletions(-) create mode 100644 crates/workspace/src/dock.rs delete mode 100644 crates/workspace/src/programs.rs diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index 63cc4316cf0f174aa157c7bcd1f1735990f247df..2180883ad45030dfb73500734c97cf4cb94eb249 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -1,6 +1,6 @@ use gpui::{ModelHandle, ViewContext}; use settings::{Settings, WorkingDirectory}; -use workspace::{programs::Dock, Workspace}; +use workspace::{dock::Dock, Workspace}; use crate::{ terminal_container_view::{ @@ -10,55 +10,55 @@ use crate::{ }; pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewContext) { - let window = cx.window_id(); + // let window = cx.window_id(); - // Pull the terminal connection out of the global if it has been stored - let possible_terminal = Dock::remove::(window, cx); + // // Pull the terminal connection out of the global if it has been stored + // let possible_terminal = Dock::remove::(window, cx); - if let Some(terminal_handle) = possible_terminal { - workspace.toggle_modal(cx, |_, cx| { - // Create a view from the stored connection if the terminal modal is not already shown - cx.add_view(|cx| TerminalContainer::from_terminal(terminal_handle.clone(), true, cx)) - }); - // Toggle Modal will dismiss the terminal modal if it is currently shown, so we must - // store the terminal back in the global - Dock::insert_or_replace::(window, terminal_handle, cx); - } else { - // No connection was stored, create a new terminal - if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| { - // No terminal modal visible, construct a new one. - let wd_strategy = cx - .global::() - .terminal_overrides - .working_directory - .clone() - .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + // if let Some(terminal_handle) = possible_terminal { + // workspace.toggle_modal(cx, |_, cx| { + // // Create a view from the stored connection if the terminal modal is not already shown + // cx.add_view(|cx| TerminalContainer::from_terminal(terminal_handle.clone(), true, cx)) + // }); + // // Toggle Modal will dismiss the terminal modal if it is currently shown, so we must + // // store the terminal back in the global + // Dock::insert_or_replace::(window, terminal_handle, cx); + // } else { + // // No connection was stored, create a new terminal + // if let Some(closed_terminal_handle) = workspace.toggle_modal(cx, |workspace, cx| { + // // No terminal modal visible, construct a new one. + // let wd_strategy = cx + // .global::() + // .terminal_overrides + // .working_directory + // .clone() + // .unwrap_or(WorkingDirectory::CurrentProjectDirectory); - let working_directory = get_working_directory(workspace, cx, wd_strategy); + // let working_directory = get_working_directory(workspace, cx, wd_strategy); - let this = cx.add_view(|cx| TerminalContainer::new(working_directory, true, cx)); + // let this = cx.add_view(|cx| TerminalContainer::new(working_directory, true, cx)); - if let TerminalContainerContent::Connected(connected) = &this.read(cx).content { - let terminal_handle = connected.read(cx).handle(); - cx.subscribe(&terminal_handle, on_event).detach(); - // Set the global immediately if terminal construction was successful, - // in case the user opens the command palette - Dock::insert_or_replace::(window, terminal_handle, cx); - } + // if let TerminalContainerContent::Connected(connected) = &this.read(cx).content { + // let terminal_handle = connected.read(cx).handle(); + // cx.subscribe(&terminal_handle, on_event).detach(); + // // Set the global immediately if terminal construction was successful, + // // in case the user opens the command palette + // Dock::insert_or_replace::(window, terminal_handle, cx); + // } - this - }) { - // Terminal modal was dismissed and the terminal view is connected, store the terminal - if let TerminalContainerContent::Connected(connected) = - &closed_terminal_handle.read(cx).content - { - let terminal_handle = connected.read(cx).handle(); - // Set the global immediately if terminal construction was successful, - // in case the user opens the command palette - Dock::insert_or_replace::(window, terminal_handle, cx); - } - } - } + // this + // }) { + // // Terminal modal was dismissed and the terminal view is connected, store the terminal + // if let TerminalContainerContent::Connected(connected) = + // &closed_terminal_handle.read(cx).content + // { + // let terminal_handle = connected.read(cx).handle(); + // // Set the global immediately if terminal construction was successful, + // // in case the user opens the command palette + // Dock::insert_or_replace::(window, terminal_handle, cx); + // } + // } + // } } pub fn on_event( @@ -68,11 +68,11 @@ pub fn on_event( cx: &mut ViewContext, ) { // Dismiss the modal if the terminal quit - if let Event::CloseTerminal = event { - Dock::remove::(cx.window_id(), cx); + // if let Event::CloseTerminal = event { + // Dock::remove::(cx.window_id(), cx); - if workspace.modal::().is_some() { - workspace.dismiss_modal(cx) - } - } + // if workspace.modal::().is_some() { + // workspace.dismiss_modal(cx) + // } + // } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs new file mode 100644 index 0000000000000000000000000000000000000000..9fe55a99ecf620a52cf60df197fad5652e01935c --- /dev/null +++ b/crates/workspace/src/dock.rs @@ -0,0 +1,35 @@ +use gpui::{elements::ChildView, Element, ElementBox, ViewContext, ViewHandle}; +use theme::Theme; + +use crate::{Pane, Workspace}; + +#[derive(PartialEq, Eq)] +pub enum DockPosition { + Bottom, + Right, + Fullscreen, + Hidden, +} + +pub struct Dock { + position: DockPosition, + pane: ViewHandle, +} + +impl Dock { + pub fn new(cx: &mut ViewContext) -> Self { + let pane = cx.add_view(Pane::new); + Self { + pane, + position: DockPosition::Bottom, + } + } + + pub fn render(&self, _theme: &Theme, position: DockPosition) -> Option { + if position == self.position { + Some(ChildView::new(self.pane.clone()).boxed()) + } else { + None + } + } +} diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index ceb2385ab878da08e0bf0e99adee5ae8256a91c5..671f6a9f9240b53783124f4c294b47ae7fab5fee 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1412,6 +1412,10 @@ impl View for Pane { .on_down(MouseButton::Left, |_, cx| { cx.focus_parent_view(); }) + .on_up(MouseButton::Left, { + let pane = this.clone(); + move |_, cx: &mut EventContext| Pane::handle_dropped_item(&pane, 0, cx) + }) .boxed() }) .on_navigate_mouse_down(move |direction, cx| { diff --git a/crates/workspace/src/programs.rs b/crates/workspace/src/programs.rs deleted file mode 100644 index 015d37f00e27e9ed92dbb2d90d416c0d7f43f9a2..0000000000000000000000000000000000000000 --- a/crates/workspace/src/programs.rs +++ /dev/null @@ -1,75 +0,0 @@ -// TODO: Need to put this basic structure in workspace, and make 'program handles' -// based off of the 'searchable item' pattern except with models. This way, the workspace's clients -// can register their models as programs with a specific identity and capable of notifying the workspace -// Programs are: -// - Kept alive by the program manager, they need to emit an event to get dropped from it -// - Can be interacted with directly, (closed, activated, etc.) by the program manager, bypassing -// associated view(s) -// - Have special rendering methods that the program manager requires them to implement to fill out -// the status bar -// - Can emit events for the program manager which: -// - Add a jewel (notification, change, etc.) -// - Drop the program -// - ??? -// - Program Manager is kept in a global, listens for window drop so it can drop all it's program handles - -use collections::HashMap; -use gpui::{AnyModelHandle, Entity, ModelHandle, View, ViewContext}; - -/// This struct is going to be the starting point for the 'program manager' feature that will -/// eventually be implemented to provide a collaborative way of engaging with identity-having -/// features like the terminal. -pub struct Dock { - // TODO: Make this a hashset or something - modals: HashMap, -} - -impl Dock { - pub fn insert_or_replace( - window: usize, - program: ModelHandle, - cx: &mut ViewContext, - ) -> Option { - cx.update_global::(|pm, _| pm.insert_or_replace_internal::(window, program)) - } - - pub fn remove( - window: usize, - cx: &mut ViewContext, - ) -> Option> { - cx.update_global::(|pm, _| pm.remove_internal::(window)) - } - - pub fn new() -> Self { - Self { - modals: Default::default(), - } - } - - /// Inserts or replaces the model at the given location. - fn insert_or_replace_internal( - &mut self, - window: usize, - program: ModelHandle, - ) -> Option { - self.modals.insert(window, AnyModelHandle::from(program)) - } - - /// Remove the program associated with this window, if it's of the given type - fn remove_internal(&mut self, window: usize) -> Option> { - let program = self.modals.remove(&window); - if let Some(program) = program { - if program.is::() { - // Guaranteed to be some, but leave it in the option - // anyway for the API - program.downcast() - } else { - // Model is of the incorrect type, put it back - self.modals.insert(window, program); - None - } - } else { - None - } - } -} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5e010a1d253c3ac073be1b3dd8a4f4edfaeb1fef..5e782a50e8d66b3b6a4393e55d56fbb829660c23 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,3 +1,4 @@ +pub mod dock; /// NOTE: Focus only 'takes' after an update has flushed_effects. Pane sends an event in on_focus_in /// which the workspace uses to change the activated pane. /// @@ -5,7 +6,6 @@ /// specific locations. pub mod pane; pub mod pane_group; -pub mod programs; pub mod searchable; pub mod sidebar; mod status_bar; @@ -18,6 +18,7 @@ use client::{ }; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; +use dock::{Dock, DockPosition}; use drag_and_drop::DragAndDrop; use futures::{channel::oneshot, FutureExt}; use gpui::{ @@ -37,7 +38,6 @@ use log::error; pub use pane::*; pub use pane_group::*; use postage::prelude::Stream; -use programs::Dock; use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId}; use searchable::SearchableItemHandle; use serde::Deserialize; @@ -146,9 +146,6 @@ impl_internal_actions!( impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]); pub fn init(app_state: Arc, cx: &mut MutableAppContext) { - // Initialize the program manager immediately - cx.set_global(Dock::new()); - pane::init(cx); cx.add_global_action(open); @@ -893,6 +890,7 @@ pub struct Workspace { panes_by_item: HashMap>, active_pane: ViewHandle, status_bar: ViewHandle, + dock: Dock, notifications: Vec<(TypeId, usize, Box)>, project: ModelHandle, leader_state: LeaderState, @@ -998,10 +996,13 @@ impl Workspace { drag_and_drop.register_container(weak_self.clone()); }); + let dock = Dock::new(cx); + let mut this = Workspace { modal: None, weak_self, center: PaneGroup::new(pane.clone()), + dock, panes: vec![pane.clone()], panes_by_item: Default::default(), active_pane: pane.clone(), @@ -2557,14 +2558,36 @@ impl View for Workspace { }, ) .with_child( - FlexItem::new(self.center.render( - &theme, - &self.follower_states_by_leader, - self.project.read(cx).collaborators(), - )) + FlexItem::new( + Flex::column() + .with_child( + FlexItem::new(self.center.render( + &theme, + &self.follower_states_by_leader, + self.project.read(cx).collaborators(), + )) + .flex(1., true) + .boxed(), + ) + .with_children( + self.dock + .render(&theme, DockPosition::Bottom) + .map(|dock| { + FlexItem::new(dock) + .flex(1., true) + .boxed() + }), + ) + .boxed(), + ) .flex(1., true) .boxed(), ) + .with_children( + self.dock + .render(&theme, DockPosition::Right) + .map(|dock| FlexItem::new(dock).flex(1., true).boxed()), + ) .with_children( if self.right_sidebar.read(cx).active_item().is_some() { Some( @@ -2578,6 +2601,7 @@ impl View for Workspace { ) .boxed() }) + .with_children(self.dock.render(&theme, DockPosition::Fullscreen)) .with_children(self.modal.as_ref().map(|m| { ChildView::new(m) .contained() From d87fb20170003799d81bf68f1a4cf3570b749e21 Mon Sep 17 00:00:00 2001 From: Mikayla Maki Date: Wed, 7 Sep 2022 10:53:50 -0700 Subject: [PATCH 03/26] In progress, working on building out the dock UI experience --- crates/workspace/src/dock.rs | 67 +++++++++++++++++++++++++++---- crates/workspace/src/workspace.rs | 8 ++-- 2 files changed, 65 insertions(+), 10 deletions(-) diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 9fe55a99ecf620a52cf60df197fad5652e01935c..e0a8ee4bb5f2726b291fcdb4977a2bfc102e28c2 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,18 +1,20 @@ -use gpui::{elements::ChildView, Element, ElementBox, ViewContext, ViewHandle}; +use std::sync::Arc; + +use gpui::{elements::ChildView, Element, ElementBox, Entity, View, ViewContext, ViewHandle}; use theme::Theme; -use crate::{Pane, Workspace}; +use crate::{Pane, StatusItemView, Workspace}; -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Default, Copy, Clone)] pub enum DockPosition { + #[default] Bottom, Right, Fullscreen, - Hidden, } pub struct Dock { - position: DockPosition, + position: Option, pane: ViewHandle, } @@ -21,15 +23,66 @@ impl Dock { let pane = cx.add_view(Pane::new); Self { pane, - position: DockPosition::Bottom, + position: None, } } pub fn render(&self, _theme: &Theme, position: DockPosition) -> Option { - if position == self.position { + if self.position.is_some() && self.position.unwrap() == position { Some(ChildView::new(self.pane.clone()).boxed()) } else { None } } } + +pub struct ToggleDock { + dock: Arc, +} + +impl ToggleDock { + pub fn new(dock: Arc, _cx: &mut ViewContext) -> Self { + Self { dock } + } +} + +impl Entity for ToggleDock { + type Event = (); +} + +impl View for ToggleDock { + fn ui_name() -> &'static str { + "Dock Toggle" + } + // Shift-escape ON + // Get or insert the dock's last focused terminal + // Open the dock in fullscreen + // Focus that terminal + + // Shift-escape OFF + // Close the dock + // Return focus to center + + // Behaviors: + // If the dock is shown, hide it + // If the dock is hidden, show it + // If the dock was full screen, open it in last position (bottom or right) + // If the dock was bottom or right, re-open it in that context (and with the previous % width) + // On hover, change color and background + // On shown, change color and background + // On hidden, change color and background + // Show tool tip + fn render(&mut self, _cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { + todo!() + } +} + +impl StatusItemView for ToggleDock { + fn set_active_pane_item( + &mut self, + _active_pane_item: Option<&dyn crate::ItemHandle>, + _cx: &mut ViewContext, + ) { + //Not applicable + } +} diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 5e782a50e8d66b3b6a4393e55d56fbb829660c23..cb8d57ad1f6cbcea19886f8a2f9a80bcb888d533 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -18,7 +18,7 @@ use client::{ }; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; -use dock::{Dock, DockPosition}; +use dock::{Dock, DockPosition, ToggleDock}; use drag_and_drop::DragAndDrop; use futures::{channel::oneshot, FutureExt}; use gpui::{ @@ -980,15 +980,19 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_self.clone())); + let dock = Dock::new(cx); + let left_sidebar = cx.add_view(|_| Sidebar::new(Side::Left)); let right_sidebar = cx.add_view(|_| Sidebar::new(Side::Right)); let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx)); + let toggle_dock = cx.add_view(|cx| ToggleDock::new(Arc::new(dock), cx)); let right_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx)); let status_bar = cx.add_view(|cx| { let mut status_bar = StatusBar::new(&pane.clone(), cx); status_bar.add_left_item(left_sidebar_buttons, cx); status_bar.add_right_item(right_sidebar_buttons, cx); + status_bar.add_right_item(toggle_dock, cx); status_bar }); @@ -996,8 +1000,6 @@ impl Workspace { drag_and_drop.register_container(weak_self.clone()); }); - let dock = Dock::new(cx); - let mut this = Workspace { modal: None, weak_self, From b88abcacac87864be64f44342a0ded578e822ccb Mon Sep 17 00:00:00 2001 From: K Simmons Date: Wed, 7 Sep 2022 19:44:36 -0700 Subject: [PATCH 04/26] WIP dock split button and default item --- crates/collab/src/integration_tests.rs | 14 +- crates/command_palette/src/command_palette.rs | 3 +- crates/contacts_panel/src/contacts_panel.rs | 3 +- crates/diagnostics/src/diagnostics.rs | 3 +- crates/editor/src/editor.rs | 2 +- crates/editor/src/test.rs | 3 +- crates/file_finder/src/file_finder.rs | 18 +- crates/project_panel/src/project_panel.rs | 6 +- crates/terminal/src/modal.rs | 18 +- .../src/tests/terminal_test_context.rs | 4 +- crates/vim/src/vim_test_context.rs | 3 +- crates/workspace/src/dock.rs | 215 ++++++++++++++---- crates/workspace/src/pane.rs | 66 +++++- crates/workspace/src/waiting_room.rs | 142 ++++++------ crates/workspace/src/workspace.rs | 79 ++++--- crates/zed/src/main.rs | 25 +- crates/zed/src/zed.rs | 24 +- 17 files changed, 435 insertions(+), 193 deletions(-) diff --git a/crates/collab/src/integration_tests.rs b/crates/collab/src/integration_tests.rs index 905aa328f2a6bc3211006606a628f09d434fa964..6b512d950ff6f9a0e7a2c782a88bce7c60d918ee 100644 --- a/crates/collab/src/integration_tests.rs +++ b/crates/collab/src/integration_tests.rs @@ -298,7 +298,8 @@ async fn test_host_disconnect( let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared())); - let (_, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx)); + let (_, workspace_b) = + cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "b.txt"), true, cx) @@ -2786,7 +2787,8 @@ async fn test_collaborating_with_code_actions( // Join the project as client B. let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx)); + let (_window_b, workspace_b) = + cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "main.rs"), true, cx) @@ -3001,7 +3003,8 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await; let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await; - let (_window_b, workspace_b) = cx_b.add_window(|cx| Workspace::new(project_b.clone(), cx)); + let (_window_b, workspace_b) = + cx_b.add_window(|cx| Workspace::new(project_b.clone(), |_, _| unimplemented!(), cx)); let editor_b = workspace_b .update(cx_b, |workspace, cx| { workspace.open_path((worktree_id, "one.rs"), true, cx) @@ -5224,6 +5227,7 @@ impl TestServer { fs: fs.clone(), build_window_options: Default::default, initialize_workspace: |_, _, _| unimplemented!(), + default_item_factory: |_, _| unimplemented!(), }); Channel::init(&client); @@ -5459,7 +5463,9 @@ impl TestClient { cx: &mut TestAppContext, ) -> ViewHandle { let (_, root_view) = cx.add_window(|_| EmptyView); - cx.add_view(&root_view, |cx| Workspace::new(project.clone(), cx)) + cx.add_view(&root_view, |cx| { + Workspace::new(project.clone(), |_, _| unimplemented!(), cx) + }) } async fn simulate_host( diff --git a/crates/command_palette/src/command_palette.rs b/crates/command_palette/src/command_palette.rs index 9b51415069e5df8fbaa82d44b4e68de8376835a1..c12e68a85417099e81f6f44af168e442db516ac5 100644 --- a/crates/command_palette/src/command_palette.rs +++ b/crates/command_palette/src/command_palette.rs @@ -350,7 +350,8 @@ mod tests { }); let project = Project::test(app_state.fs.clone(), [], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let editor = cx.add_view(&workspace, |cx| { let mut editor = Editor::single_line(None, cx); editor.set_text("abc", cx); diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index fde304cd358414c6ac83e91a0ddf6cbde21c524e..672730cf229447dc931fd5e4a015601e61be4d63 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -1247,7 +1247,8 @@ mod tests { .0 .read_with(cx, |worktree, _| worktree.id().to_proto()); - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); let panel = cx.add_view(&workspace, |cx| { ContactsPanel::new( user_store.clone(), diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 271843dc69a5f572264ee02ab40c921d856759d8..7387ec8fdc2d7bb8f8f3f68a6d2df1fd85aaf4a2 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -776,7 +776,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/test".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); // Create some diagnostics project.update(cx, |project, cx| { diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ce31e68dae5f6471488159d32e01964a80d8eb87..7c7e3391d531a8bcbf68087d5eaf9460b43884d2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -7100,7 +7100,7 @@ mod tests { fn test_navigation_history(cx: &mut gpui::MutableAppContext) { cx.set_global(Settings::test(cx)); use workspace::Item; - let (_, pane) = cx.add_window(Default::default(), Pane::new); + let (_, pane) = cx.add_window(Default::default(), |cx| Pane::new(false, cx)); let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx); cx.add_view(&pane, |cx| { diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 43e50829f55ae47299abbe41ad33066dcc3be653..74b82a20bda6d1e783b5f1fd0c4c9951b99a259e 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -364,7 +364,8 @@ impl<'a> EditorLspTestContext<'a> { .insert_tree("/root", json!({ "dir": { file_name: "" }})) .await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); project .update(cx, |project, cx| { project.find_or_create_local_worktree("/root", true, cx) diff --git a/crates/file_finder/src/file_finder.rs b/crates/file_finder/src/file_finder.rs index 768e58407e98deae848cdbc812e44d3b972a014f..aa2174b9598a8cb94e0917c58cde07a537852cf6 100644 --- a/crates/file_finder/src/file_finder.rs +++ b/crates/file_finder/src/file_finder.rs @@ -316,7 +316,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); cx.dispatch_action(window_id, Toggle); let finder = cx.read(|cx| workspace.read(cx).modal::().unwrap()); @@ -370,7 +371,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/dir".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); @@ -444,7 +446,8 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); finder @@ -468,7 +471,8 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); @@ -520,7 +524,8 @@ mod tests { cx, ) .await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); @@ -558,7 +563,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let (_, finder) = cx.add_window(|cx| FileFinder::new(workspace.read(cx).project().clone(), cx)); finder diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index bc314d81be4770bbfdcda88c5c43a26684177b6c..afd911233b3eb926f540034114eb6af41dcb779c 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -1243,7 +1243,8 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx)); assert_eq!( visible_entries_as_strings(&panel, 0..50, cx), @@ -1335,7 +1336,8 @@ mod tests { .await; let project = Project::test(fs.clone(), ["/root1".as_ref(), "/root2".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); let panel = workspace.update(cx, |_, cx| ProjectPanel::new(project, cx)); select_path(&panel, "root1", cx); diff --git a/crates/terminal/src/modal.rs b/crates/terminal/src/modal.rs index 2180883ad45030dfb73500734c97cf4cb94eb249..309a11d41f39ec0bad22c020678133f9cbb86c40 100644 --- a/crates/terminal/src/modal.rs +++ b/crates/terminal/src/modal.rs @@ -1,15 +1,9 @@ use gpui::{ModelHandle, ViewContext}; -use settings::{Settings, WorkingDirectory}; -use workspace::{dock::Dock, Workspace}; +use workspace::Workspace; -use crate::{ - terminal_container_view::{ - get_working_directory, DeployModal, TerminalContainer, TerminalContainerContent, - }, - Event, Terminal, -}; +use crate::{terminal_container_view::DeployModal, Event, Terminal}; -pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewContext) { +pub fn deploy_modal(_workspace: &mut Workspace, _: &DeployModal, _cx: &mut ViewContext) { // let window = cx.window_id(); // // Pull the terminal connection out of the global if it has been stored @@ -62,10 +56,10 @@ pub fn deploy_modal(workspace: &mut Workspace, _: &DeployModal, cx: &mut ViewCon } pub fn on_event( - workspace: &mut Workspace, + _workspace: &mut Workspace, _: ModelHandle, - event: &Event, - cx: &mut ViewContext, + _event: &Event, + _cx: &mut ViewContext, ) { // Dismiss the modal if the terminal quit // if let Event::CloseTerminal = event { diff --git a/crates/terminal/src/tests/terminal_test_context.rs b/crates/terminal/src/tests/terminal_test_context.rs index bee78e3ce0b3feeef432215ee6e02d5213668223..f9ee6e808266092f5767faaf4401c3392ca5cc31 100644 --- a/crates/terminal/src/tests/terminal_test_context.rs +++ b/crates/terminal/src/tests/terminal_test_context.rs @@ -21,7 +21,9 @@ impl<'a> TerminalTestContext<'a> { let params = self.cx.update(AppState::test); let project = Project::test(params.fs.clone(), [], self.cx).await; - let (_, workspace) = self.cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = self + .cx + .add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); (project, workspace) } diff --git a/crates/vim/src/vim_test_context.rs b/crates/vim/src/vim_test_context.rs index 2be2eb8d6f2ff6f6b7b74f94b1c820d12a97a99f..0e77b05ba2b5df719e2b52bff8149bc738000937 100644 --- a/crates/vim/src/vim_test_context.rs +++ b/crates/vim/src/vim_test_context.rs @@ -39,7 +39,8 @@ impl<'a> VimTestContext<'a> { .insert_tree("/root", json!({ "dir": { "test.txt": "" } })) .await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); // Setup search toolbars workspace.update(cx, |workspace, cx| { diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index e0a8ee4bb5f2726b291fcdb4977a2bfc102e28c2..f1e8aa8539acc379bb0d61bb938afec2ee0746b4 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,83 +1,210 @@ -use std::sync::Arc; - -use gpui::{elements::ChildView, Element, ElementBox, Entity, View, ViewContext, ViewHandle}; +use gpui::{ + actions, + elements::{ChildView, MouseEventHandler, Svg}, + impl_internal_actions, CursorStyle, Element, ElementBox, Entity, MouseButton, + MutableAppContext, View, ViewContext, ViewHandle, WeakViewHandle, +}; +use serde::Deserialize; +use settings::Settings; use theme::Theme; -use crate::{Pane, StatusItemView, Workspace}; +use crate::{pane, ItemHandle, Pane, StatusItemView, Workspace}; -#[derive(PartialEq, Eq, Default, Copy, Clone)] -pub enum DockPosition { +#[derive(PartialEq, Clone, Deserialize)] +pub struct MoveDock(pub DockAnchor); + +#[derive(PartialEq, Clone)] +pub struct AddDefaultItemToDock; + +actions!(workspace, [ToggleDock]); +impl_internal_actions!(workspace, [MoveDock, AddDefaultItemToDock]); + +pub fn init(cx: &mut MutableAppContext) { + cx.add_action(Dock::toggle); + cx.add_action(Dock::move_dock); +} + +#[derive(PartialEq, Eq, Default, Copy, Clone, Deserialize)] +pub enum DockAnchor { #[default] Bottom, Right, - Fullscreen, + Expanded, +} + +#[derive(Copy, Clone)] +pub enum DockPosition { + Shown(DockAnchor), + Hidden(DockAnchor), +} + +impl Default for DockPosition { + fn default() -> Self { + DockPosition::Hidden(Default::default()) + } +} + +impl DockPosition { + fn toggle(self) -> Self { + match self { + DockPosition::Shown(anchor) => DockPosition::Hidden(anchor), + DockPosition::Hidden(anchor) => DockPosition::Shown(anchor), + } + } + + fn visible(&self) -> Option { + match self { + DockPosition::Shown(anchor) => Some(*anchor), + DockPosition::Hidden(_) => None, + } + } + + fn hide(self) -> Self { + match self { + DockPosition::Shown(anchor) => DockPosition::Hidden(anchor), + DockPosition::Hidden(_) => self, + } + } } +pub type DefaultItemFactory = + fn(&mut Workspace, &mut ViewContext) -> Box; + pub struct Dock { - position: Option, + position: DockPosition, pane: ViewHandle, + default_item_factory: DefaultItemFactory, } impl Dock { - pub fn new(cx: &mut ViewContext) -> Self { - let pane = cx.add_view(Pane::new); + pub fn new(cx: &mut ViewContext, default_item_factory: DefaultItemFactory) -> Self { + let pane = cx.add_view(|cx| Pane::new(true, cx)); + + cx.subscribe(&pane.clone(), |workspace, _, event, cx| { + if let pane::Event::Remove = event { + workspace.dock.hide(); + cx.notify(); + } + }) + .detach(); + Self { pane, - position: None, + position: Default::default(), + default_item_factory, + } + } + + pub fn pane(&self) -> ViewHandle { + self.pane.clone() + } + + fn hide(&mut self) { + self.position = self.position.hide(); + } + + fn ensure_not_empty(workspace: &mut Workspace, cx: &mut ViewContext) { + let pane = workspace.dock.pane.clone(); + if !pane.read(cx).items().next().is_none() { + let item_to_add = (workspace.dock.default_item_factory)(workspace, cx); + Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx); } } - pub fn render(&self, _theme: &Theme, position: DockPosition) -> Option { - if self.position.is_some() && self.position.unwrap() == position { - Some(ChildView::new(self.pane.clone()).boxed()) - } else { - None + fn toggle(workspace: &mut Workspace, _: &ToggleDock, cx: &mut ViewContext) { + // Shift-escape ON + // Get or insert the dock's last focused terminal + // Open the dock in fullscreen + // Focus that terminal + + // Shift-escape OFF + // Close the dock + // Return focus to center + + // Behaviors: + // If the dock is shown, hide it + // If the dock is hidden, show it + // If the dock was full screen, open it in last position (bottom or right) + // If the dock was bottom or right, re-open it in that context (and with the previous % width) + + workspace.dock.position = workspace.dock.position.toggle(); + if workspace.dock.position.visible().is_some() { + Self::ensure_not_empty(workspace, cx); } + cx.notify(); + } + + fn move_dock( + workspace: &mut Workspace, + &MoveDock(new_anchor): &MoveDock, + cx: &mut ViewContext, + ) { + // Clear the previous position if the dock is not visible. + workspace.dock.position = DockPosition::Shown(new_anchor); + Self::ensure_not_empty(workspace, cx); + cx.notify(); + } + + pub fn render(&self, _theme: &Theme, anchor: DockAnchor) -> Option { + self.position + .visible() + .filter(|current_anchor| *current_anchor == anchor) + .map(|_| ChildView::new(self.pane.clone()).boxed()) } } -pub struct ToggleDock { - dock: Arc, +pub struct ToggleDockButton { + workspace: WeakViewHandle, } -impl ToggleDock { - pub fn new(dock: Arc, _cx: &mut ViewContext) -> Self { - Self { dock } +impl ToggleDockButton { + pub fn new(workspace: WeakViewHandle, _cx: &mut ViewContext) -> Self { + Self { workspace } } } -impl Entity for ToggleDock { +impl Entity for ToggleDockButton { type Event = (); } -impl View for ToggleDock { +impl View for ToggleDockButton { fn ui_name() -> &'static str { "Dock Toggle" } - // Shift-escape ON - // Get or insert the dock's last focused terminal - // Open the dock in fullscreen - // Focus that terminal - - // Shift-escape OFF - // Close the dock - // Return focus to center - - // Behaviors: - // If the dock is shown, hide it - // If the dock is hidden, show it - // If the dock was full screen, open it in last position (bottom or right) - // If the dock was bottom or right, re-open it in that context (and with the previous % width) - // On hover, change color and background - // On shown, change color and background - // On hidden, change color and background - // Show tool tip - fn render(&mut self, _cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { - todo!() + + fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> ElementBox { + let dock_is_open = self + .workspace + .upgrade(cx) + .map(|workspace| workspace.read(cx).dock.position.visible().is_some()) + .unwrap_or(false); + + MouseEventHandler::new::(0, cx, |state, cx| { + let theme = &cx + .global::() + .theme + .workspace + .status_bar + .sidebar_buttons; + let style = theme.item.style_for(state, dock_is_open); + + Svg::new("icons/terminal_16.svg") + .with_color(style.icon_color) + .constrained() + .with_width(style.icon_size) + .with_height(style.icon_size) + .contained() + .with_style(style.container) + .boxed() + }) + .with_cursor_style(CursorStyle::PointingHand) + .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(ToggleDock)) + // TODO: Add tooltip + .boxed() } } -impl StatusItemView for ToggleDock { +impl StatusItemView for ToggleDockButton { fn set_active_pane_item( &mut self, _active_pane_item: Option<&dyn crate::ItemHandle>, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 671f6a9f9240b53783124f4c294b47ae7fab5fee..fc95deca902d6d6679e34f52c4bf44c0bf623c30 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,5 +1,9 @@ use super::{ItemHandle, SplitDirection}; -use crate::{toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace}; +use crate::{ + dock::{DockAnchor, MoveDock}, + toolbar::Toolbar, + Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace, +}; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; use context_menu::{ContextMenu, ContextMenuItem}; @@ -76,13 +80,27 @@ pub struct DeploySplitMenu { position: Vector2F, } +#[derive(Clone, PartialEq)] +pub struct DeployDockMenu { + position: Vector2F, +} + #[derive(Clone, PartialEq)] pub struct DeployNewMenu { position: Vector2F, } impl_actions!(pane, [GoBack, GoForward, ActivateItem]); -impl_internal_actions!(pane, [CloseItem, DeploySplitMenu, DeployNewMenu, MoveItem]); +impl_internal_actions!( + pane, + [ + CloseItem, + DeploySplitMenu, + DeployNewMenu, + DeployDockMenu, + MoveItem + ] +); const MAX_NAVIGATION_HISTORY_LEN: usize = 1024; @@ -141,6 +159,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(|pane: &mut Pane, _: &SplitDown, cx| pane.split(SplitDirection::Down, cx)); cx.add_action(Pane::deploy_split_menu); cx.add_action(Pane::deploy_new_menu); + cx.add_action(Pane::deploy_dock_menu); cx.add_action(|workspace: &mut Workspace, _: &ReopenClosedItem, cx| { Pane::reopen_closed_item(workspace, cx).detach(); }); @@ -186,6 +205,7 @@ pub struct Pane { nav_history: Rc>, toolbar: ViewHandle, context_menu: ViewHandle, + is_dock: bool, } pub struct ItemNavHistory { @@ -235,7 +255,7 @@ pub enum ReorderBehavior { } impl Pane { - pub fn new(cx: &mut ViewContext) -> Self { + pub fn new(is_dock: bool, cx: &mut ViewContext) -> Self { let handle = cx.weak_handle(); let context_menu = cx.add_view(ContextMenu::new); Self { @@ -254,6 +274,7 @@ impl Pane { })), toolbar: cx.add_view(|_| Toolbar::new(handle)), context_menu, + is_dock, } } @@ -976,6 +997,20 @@ impl Pane { }); } + fn deploy_dock_menu(&mut self, action: &DeployDockMenu, cx: &mut ViewContext) { + self.context_menu.update(cx, |menu, cx| { + menu.show( + action.position, + vec![ + ContextMenuItem::item("Move Dock Right", MoveDock(DockAnchor::Right)), + ContextMenuItem::item("Move Dock Bottom", MoveDock(DockAnchor::Bottom)), + ContextMenuItem::item("Move Dock Maximized", MoveDock(DockAnchor::Expanded)), + ], + cx, + ); + }); + } + fn deploy_new_menu(&mut self, action: &DeployNewMenu, cx: &mut ViewContext) { self.context_menu.update(cx, |menu, cx| { menu.show( @@ -1320,6 +1355,8 @@ impl View for Pane { let this = cx.handle(); + let is_dock = self.is_dock; + Stack::new() .with_child( EventHandler::new(if let Some(active_item) = self.active_item() { @@ -1382,10 +1419,16 @@ impl View for Pane { }, ) .with_cursor_style(CursorStyle::PointingHand) - .on_down(MouseButton::Left, |e, cx| { - cx.dispatch_action(DeploySplitMenu { - position: e.position, - }); + .on_down(MouseButton::Left, move |e, cx| { + if is_dock { + cx.dispatch_action(DeployDockMenu { + position: e.position, + }); + } else { + cx.dispatch_action(DeploySplitMenu { + position: e.position, + }); + } }) .boxed(), ]) @@ -1570,7 +1613,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, crate::tests::default_item_factory, cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1658,7 +1702,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, crate::tests::default_item_factory, cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1734,7 +1779,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, crate::tests::default_item_factory, cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // singleton view diff --git a/crates/workspace/src/waiting_room.rs b/crates/workspace/src/waiting_room.rs index e05e0fb5ffdafd7616c1d5fa992ba6b1b5342700..8102bb7bb23fb684249f873c165d4a127568a2e9 100644 --- a/crates/workspace/src/waiting_room.rs +++ b/crates/workspace/src/waiting_room.rs @@ -74,82 +74,84 @@ impl WaitingRoom { ) -> Self { let project_id = contact.projects[project_index].id; let client = app_state.client.clone(); - let _join_task = - cx.spawn_weak({ - let contact = contact.clone(); - |this, mut cx| async move { - let project = Project::remote( - project_id, - app_state.client.clone(), - app_state.user_store.clone(), - app_state.project_store.clone(), - app_state.languages.clone(), - app_state.fs.clone(), - cx.clone(), - ) - .await; + let _join_task = cx.spawn_weak({ + let contact = contact.clone(); + |this, mut cx| async move { + let project = Project::remote( + project_id, + app_state.client.clone(), + app_state.user_store.clone(), + app_state.project_store.clone(), + app_state.languages.clone(), + app_state.fs.clone(), + cx.clone(), + ) + .await; - if let Some(this) = this.upgrade(&cx) { - this.update(&mut cx, |this, cx| { - this.waiting = false; - match project { - Ok(project) => { - cx.replace_root_view(|cx| { - let mut workspace = Workspace::new(project, cx); - (app_state.initialize_workspace)( - &mut workspace, - &app_state, - cx, - ); - workspace.toggle_sidebar(Side::Left, cx); - if let Some((host_peer_id, _)) = - workspace.project.read(cx).collaborators().iter().find( - |(_, collaborator)| collaborator.replica_id == 0, - ) + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |this, cx| { + this.waiting = false; + match project { + Ok(project) => { + cx.replace_root_view(|cx| { + let mut workspace = + Workspace::new(project, app_state.default_item_factory, cx); + (app_state.initialize_workspace)( + &mut workspace, + &app_state, + cx, + ); + workspace.toggle_sidebar(Side::Left, cx); + if let Some((host_peer_id, _)) = workspace + .project + .read(cx) + .collaborators() + .iter() + .find(|(_, collaborator)| collaborator.replica_id == 0) + { + if let Some(follow) = workspace + .toggle_follow(&ToggleFollow(*host_peer_id), cx) { - if let Some(follow) = workspace - .toggle_follow(&ToggleFollow(*host_peer_id), cx) - { - follow.detach_and_log_err(cx); - } - } - workspace - }); - } - Err(error) => { - let login = &contact.user.github_login; - let message = match error { - project::JoinProjectError::HostDeclined => { - format!("@{} declined your request.", login) + follow.detach_and_log_err(cx); } - project::JoinProjectError::HostClosedProject => { - format!( - "@{} closed their copy of {}.", - login, - humanize_list( - &contact.projects[project_index] - .visible_worktree_root_names - ) + } + workspace + }); + } + Err(error) => { + let login = &contact.user.github_login; + let message = match error { + project::JoinProjectError::HostDeclined => { + format!("@{} declined your request.", login) + } + project::JoinProjectError::HostClosedProject => { + format!( + "@{} closed their copy of {}.", + login, + humanize_list( + &contact.projects[project_index] + .visible_worktree_root_names ) - } - project::JoinProjectError::HostWentOffline => { - format!("@{} went offline.", login) - } - project::JoinProjectError::Other(error) => { - log::error!("error joining project: {}", error); - "An error occurred.".to_string() - } - }; - this.message = message; - cx.notify(); - } + ) + } + project::JoinProjectError::HostWentOffline => { + format!("@{} went offline.", login) + } + project::JoinProjectError::Other(error) => { + log::error!("error joining project: {}", error); + "An error occurred.".to_string() + } + }; + this.message = message; + cx.notify(); } - }) - } - - Ok(()) + } + }) } - }); + + Ok(()) + } + }); Self { project_id, diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index cb8d57ad1f6cbcea19886f8a2f9a80bcb888d533..56b771020d3e8f4fa349337b01fbffc0b99eb75e 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -1,9 +1,9 @@ -pub mod dock; /// NOTE: Focus only 'takes' after an update has flushed_effects. Pane sends an event in on_focus_in /// which the workspace uses to change the activated pane. /// /// This may cause issues when you're trying to write tests that use workspace focus to add items at /// specific locations. +pub mod dock; pub mod pane; pub mod pane_group; pub mod searchable; @@ -18,7 +18,7 @@ use client::{ }; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; -use dock::{Dock, DockPosition, ToggleDock}; +use dock::{DefaultItemFactory, Dock, DockAnchor, ToggleDockButton}; use drag_and_drop::DragAndDrop; use futures::{channel::oneshot, FutureExt}; use gpui::{ @@ -147,6 +147,7 @@ impl_actions!(workspace, [ToggleProjectOnline, ActivatePane]); pub fn init(app_state: Arc, cx: &mut MutableAppContext) { pane::init(cx); + dock::init(cx); cx.add_global_action(open); cx.add_global_action({ @@ -262,6 +263,7 @@ pub struct AppState { pub fs: Arc, pub build_window_options: fn() -> WindowOptions<'static>, pub initialize_workspace: fn(&mut Workspace, &Arc, &mut ViewContext), + pub default_item_factory: DefaultItemFactory, } #[derive(Eq, PartialEq, Hash)] @@ -867,6 +869,7 @@ impl AppState { project_store, initialize_workspace: |_, _, _| {}, build_window_options: Default::default, + default_item_factory: |_, _| unimplemented!(), }) } } @@ -920,7 +923,11 @@ enum FollowerItem { } impl Workspace { - pub fn new(project: ModelHandle, cx: &mut ViewContext) -> Self { + pub fn new( + project: ModelHandle, + dock_default_factory: DefaultItemFactory, + cx: &mut ViewContext, + ) -> Self { cx.observe_fullscreen(|_, _, cx| cx.notify()).detach(); cx.observe_window_activation(Self::on_window_activation_changed) @@ -947,14 +954,14 @@ impl Workspace { }) .detach(); - let pane = cx.add_view(Pane::new); - let pane_id = pane.id(); - cx.subscribe(&pane, move |this, _, event, cx| { + let center_pane = cx.add_view(|cx| Pane::new(false, cx)); + let pane_id = center_pane.id(); + cx.subscribe(¢er_pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) }) .detach(); - cx.focus(&pane); - cx.emit(Event::PaneAdded(pane.clone())); + cx.focus(¢er_pane); + cx.emit(Event::PaneAdded(center_pane.clone())); let fs = project.read(cx).fs().clone(); let user_store = project.read(cx).user_store(); @@ -977,19 +984,18 @@ impl Workspace { }); let weak_self = cx.weak_handle(); - cx.emit_global(WorkspaceCreated(weak_self.clone())); - let dock = Dock::new(cx); + let dock = Dock::new(cx, dock_default_factory); let left_sidebar = cx.add_view(|_| Sidebar::new(Side::Left)); let right_sidebar = cx.add_view(|_| Sidebar::new(Side::Right)); let left_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(left_sidebar.clone(), cx)); - let toggle_dock = cx.add_view(|cx| ToggleDock::new(Arc::new(dock), cx)); + let toggle_dock = cx.add_view(|cx| ToggleDockButton::new(weak_self.clone(), cx)); let right_sidebar_buttons = cx.add_view(|cx| SidebarButtons::new(right_sidebar.clone(), cx)); let status_bar = cx.add_view(|cx| { - let mut status_bar = StatusBar::new(&pane.clone(), cx); + let mut status_bar = StatusBar::new(¢er_pane.clone(), cx); status_bar.add_left_item(left_sidebar_buttons, cx); status_bar.add_right_item(right_sidebar_buttons, cx); status_bar.add_right_item(toggle_dock, cx); @@ -1003,11 +1009,11 @@ impl Workspace { let mut this = Workspace { modal: None, weak_self, - center: PaneGroup::new(pane.clone()), + center: PaneGroup::new(center_pane.clone()), dock, - panes: vec![pane.clone()], + panes: vec![center_pane.clone()], panes_by_item: Default::default(), - active_pane: pane.clone(), + active_pane: center_pane.clone(), status_bar, notifications: Default::default(), client, @@ -1081,6 +1087,7 @@ impl Workspace { app_state.fs.clone(), cx, ), + app_state.default_item_factory, cx, ); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); @@ -1532,7 +1539,7 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - let pane = cx.add_view(Pane::new); + let pane = cx.add_view(|cx| Pane::new(false, cx)); let pane_id = pane.id(); cx.subscribe(&pane, move |this, _, event, cx| { this.handle_pane_event(pane_id, event, cx) @@ -1549,6 +1556,10 @@ impl Workspace { Pane::add_item(self, &active_pane, item, true, true, None, cx); } + pub fn add_item_to_dock(&mut self, item: Box, cx: &mut ViewContext) { + Pane::add_item(self, &self.dock.pane(), item, true, true, None, cx); + } + pub fn open_path( &mut self, path: impl Into, @@ -2573,7 +2584,7 @@ impl View for Workspace { ) .with_children( self.dock - .render(&theme, DockPosition::Bottom) + .render(&theme, DockAnchor::Bottom) .map(|dock| { FlexItem::new(dock) .flex(1., true) @@ -2587,7 +2598,7 @@ impl View for Workspace { ) .with_children( self.dock - .render(&theme, DockPosition::Right) + .render(&theme, DockAnchor::Right) .map(|dock| FlexItem::new(dock).flex(1., true).boxed()), ) .with_children( @@ -2603,7 +2614,7 @@ impl View for Workspace { ) .boxed() }) - .with_children(self.dock.render(&theme, DockPosition::Fullscreen)) + .with_children(self.dock.render(&theme, DockAnchor::Expanded)) .with_children(self.modal.as_ref().map(|m| { ChildView::new(m) .contained() @@ -2811,7 +2822,7 @@ pub fn open_paths( cx, ); new_project = Some(project.clone()); - let mut workspace = Workspace::new(project, cx); + let mut workspace = Workspace::new(project, app_state.default_item_factory, cx); (app_state.initialize_workspace)(&mut workspace, &app_state, cx); if contains_directory { workspace.toggle_sidebar(Side::Left, cx); @@ -2872,6 +2883,7 @@ fn open_new(app_state: &Arc, cx: &mut MutableAppContext) { app_state.fs.clone(), cx, ), + app_state.default_item_factory, cx, ); (app_state.initialize_workspace)(&mut workspace, app_state, cx); @@ -2889,6 +2901,13 @@ mod tests { use project::{FakeFs, Project, ProjectEntryId}; use serde_json::json; + pub fn default_item_factory( + _workspace: &mut Workspace, + _cx: &mut ViewContext, + ) -> Box { + unimplemented!(); + } + #[gpui::test] async fn test_tab_disambiguation(cx: &mut TestAppContext) { cx.foreground().forbid_parking(); @@ -2896,7 +2915,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx)); // Adding an item with no ambiguity renders the tab without detail. let item1 = cx.add_view(&workspace, |_| { @@ -2960,7 +2980,8 @@ mod tests { .await; let project = Project::test(fs, ["root1".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx)); let worktree_id = project.read_with(cx, |project, cx| { project.worktrees(cx).next().unwrap().read(cx).id() }); @@ -3056,7 +3077,8 @@ mod tests { fs.insert_tree("/root", json!({ "one": "" })).await; let project = Project::test(fs, ["root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), default_item_factory, cx)); // When there are no dirty items, there's nothing to do. let item1 = cx.add_view(&workspace, |_| TestItem::new()); @@ -3096,7 +3118,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); let item1 = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); @@ -3191,7 +3214,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); // Create several workspace items with single project entries, and two // workspace items with multiple project entries. @@ -3292,7 +3316,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); let item = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); @@ -3409,7 +3434,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, [], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); let item = cx.add_view(&workspace, |_| { let mut item = TestItem::new(); diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index fc3616d59287f184d79c4895fda4bd6ce7368ea7..3bfd5e6e1a08791e262eab09ad6ede4771382278 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -19,20 +19,21 @@ use futures::{ channel::{mpsc, oneshot}, FutureExt, SinkExt, StreamExt, }; -use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task}; +use gpui::{executor::Background, App, AssetSource, AsyncAppContext, Task, ViewContext}; use isahc::{config::Configurable, AsyncBody, Request}; use language::LanguageRegistry; use log::LevelFilter; use parking_lot::Mutex; use project::{Fs, ProjectStore}; use serde_json::json; -use settings::{self, KeymapFileContent, Settings, SettingsFileContent}; +use settings::{self, KeymapFileContent, Settings, SettingsFileContent, WorkingDirectory}; use smol::process::Command; use std::{env, ffi::OsStr, fs, panic, path::PathBuf, sync::Arc, thread, time::Duration}; +use terminal::terminal_container_view::{get_working_directory, TerminalContainer}; use theme::ThemeRegistry; use util::{ResultExt, TryFutureExt}; -use workspace::{self, AppState, NewFile, OpenPaths}; +use workspace::{self, AppState, ItemHandle, NewFile, OpenPaths, Workspace}; use zed::{ self, build_window_options, fs::RealFs, @@ -148,6 +149,7 @@ fn main() { fs, build_window_options, initialize_workspace, + default_item_factory, }); auto_update::init(db, http, client::ZED_SERVER_URL.clone(), cx); workspace::init(app_state.clone(), cx); @@ -591,3 +593,20 @@ async fn handle_cli_connection( } } } + +pub fn default_item_factory( + workspace: &mut Workspace, + cx: &mut ViewContext, +) -> Box { + let strategy = cx + .global::() + .terminal_overrides + .working_directory + .clone() + .unwrap_or(WorkingDirectory::CurrentProjectDirectory); + + let working_directory = get_working_directory(workspace, cx, strategy); + + let terminal_handle = cx.add_view(|cx| TerminalContainer::new(working_directory, false, cx)); + Box::new(terminal_handle) +} diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index a8f10a1579f09ecdaaced6763d2305307ff8fc88..103db1b51517598addaa1bfed7645a67322ab2c8 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -723,7 +723,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -842,7 +843,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/dir1".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); // Open a file within an existing worktree. cx.update(|cx| { @@ -1001,7 +1003,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); // Open a file within an existing worktree. cx.update(|cx| { @@ -1043,7 +1046,8 @@ mod tests { let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let worktree = cx.read(|cx| workspace.read(cx).worktrees(cx).next().unwrap()); // Create a new untitled buffer @@ -1132,7 +1136,8 @@ mod tests { let project = Project::test(app_state.fs.clone(), [], cx).await; project.update(cx, |project, _| project.languages().add(rust_lang())); - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); // Create a new untitled buffer cx.dispatch_action(window_id, NewFile); @@ -1185,7 +1190,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (window_id, workspace) = cx.add_window(|cx| Workspace::new(project, cx)); + let (window_id, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1258,7 +1264,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); let entries = cx.read(|cx| workspace.file_project_paths(cx)); let file1 = entries[0].clone(); @@ -1522,7 +1529,8 @@ mod tests { .await; let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project.clone(), cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project.clone(), |_, _| unimplemented!(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); let entries = cx.read(|cx| workspace.file_project_paths(cx)); From 59fd967793b1376ee83503612108c0939742208f Mon Sep 17 00:00:00 2001 From: K Simmons Date: Wed, 7 Sep 2022 20:32:28 -0700 Subject: [PATCH 05/26] Swapped keyboard binding and did some minor tweaks to style and focus --- assets/keymaps/default.json | 2 +- crates/theme/src/theme.rs | 1 + crates/workspace/src/dock.rs | 10 ++++++++-- crates/workspace/src/pane.rs | 9 +++------ crates/workspace/src/workspace.rs | 8 +++++++- styles/src/styleTree/workspace.ts | 4 ++++ 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 1d95c33cbf1edc3fc4ee4a8bd1c4a7f8a3118418..b11a7900880ba44cc3772ab88135670ebbaf29d7 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -310,7 +310,7 @@ "cmd-shift-m": "diagnostics::Deploy", "cmd-shift-e": "project_panel::ToggleFocus", "cmd-alt-s": "workspace::SaveAll", - "shift-escape": "terminal::DeployModal" + "shift-escape": "workspace::ToggleDock" } }, // Bindings from Sublime Text diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 9d90b44402c41d0128af41669c4eb19ad9e827c9..b4ddd8ed94cbde2a4eb8b7cb31dfc3eee9e94b55 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -57,6 +57,7 @@ pub struct Workspace { pub notifications: Notifications, pub joining_project_avatar: ImageStyle, pub joining_project_message: ContainedText, + pub fullscreen_dock: ContainerStyle, } #[derive(Clone, Deserialize, Default)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index f1e8aa8539acc379bb0d61bb938afec2ee0746b4..7bc407e50847e2bd02072c7cee6e009db6b102a4 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -105,7 +105,7 @@ impl Dock { fn ensure_not_empty(workspace: &mut Workspace, cx: &mut ViewContext) { let pane = workspace.dock.pane.clone(); - if !pane.read(cx).items().next().is_none() { + if pane.read(cx).items().next().is_none() { let item_to_add = (workspace.dock.default_item_factory)(workspace, cx); Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx); } @@ -130,8 +130,12 @@ impl Dock { workspace.dock.position = workspace.dock.position.toggle(); if workspace.dock.position.visible().is_some() { Self::ensure_not_empty(workspace, cx); + cx.focus(workspace.dock.pane.clone()); + } else { + cx.focus_self(); } cx.notify(); + workspace.status_bar().update(cx, |_, cx| cx.notify()); } fn move_dock( @@ -198,7 +202,9 @@ impl View for ToggleDockButton { .boxed() }) .with_cursor_style(CursorStyle::PointingHand) - .on_click(MouseButton::Left, |_, cx| cx.dispatch_action(ToggleDock)) + .on_click(MouseButton::Left, |_, cx| { + cx.dispatch_action(ToggleDock); + }) // TODO: Add tooltip .boxed() } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index fc95deca902d6d6679e34f52c4bf44c0bf623c30..f8e95dcb3325796a47263fc3dfb6c5c38bb3a35b 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1613,8 +1613,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = - cx.add_window(|cx| Workspace::new(project, crate::tests::default_item_factory, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1702,8 +1701,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = - cx.add_window(|cx| Workspace::new(project, crate::tests::default_item_factory, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1779,8 +1777,7 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = - cx.add_window(|cx| Workspace::new(project, crate::tests::default_item_factory, cx)); + let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // singleton view diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 56b771020d3e8f4fa349337b01fbffc0b99eb75e..a67910d694b69b2f8c310519a13ec8c7e0bacdb4 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -2614,7 +2614,13 @@ impl View for Workspace { ) .boxed() }) - .with_children(self.dock.render(&theme, DockAnchor::Expanded)) + .with_children(self.dock.render(&theme, DockAnchor::Expanded).map( + |dock| { + Container::new(dock) + .with_style(theme.workspace.fullscreen_dock) + .boxed() + }, + )) .with_children(self.modal.as_ref().map(|m| { ChildView::new(m) .contained() diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index 875b2bb2d58c966bb7b63d5d05ee6baadec59c99..d47b76cd056c8905d03bc1d265cceb972162965d 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -156,5 +156,9 @@ export default function workspace(theme: Theme) { width: 400, margin: { right: 10, bottom: 10 }, }, + fullscreenDock: { + background: withOpacity(theme.backgroundColor[500].base, 0.8), + padding: 25, + } }; } From 69ecbb644d2482e9a31dce3e4466aa2c5bf9f811 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Thu, 8 Sep 2022 19:32:38 -0700 Subject: [PATCH 06/26] DOCK WORKING! Update editor element to use mouse regions instead of dispatch event for mouse events Fix bug in presenter where mouse region handlers were stored on click and called instead of more up to date handlers from subsequent renders Changed MouseRegion to require discriminants in all cases Add scroll wheel event to MouseRegion Polished a bunch of dock inconsistencies Co-Authored-By: Mikayla Maki --- assets/settings/default.json | 10 + crates/editor/src/element.rs | 592 ++++++++++-------- crates/gpui/src/app.rs | 4 +- crates/gpui/src/elements/event_handler.rs | 18 +- .../gpui/src/elements/mouse_event_handler.rs | 16 +- crates/gpui/src/elements/overlay.rs | 7 +- crates/gpui/src/presenter.rs | 116 ++-- crates/gpui/src/scene.rs | 8 +- crates/gpui/src/scene/mouse_region.rs | 71 ++- crates/gpui/src/scene/mouse_region_event.rs | 2 +- crates/settings/src/settings.rs | 16 + crates/terminal/src/terminal_element.rs | 2 +- crates/theme/src/theme.rs | 10 +- crates/workspace/src/dock.rs | 138 ++-- crates/workspace/src/pane.rs | 23 +- crates/workspace/src/workspace.rs | 70 +-- crates/zed/src/zed.rs | 1 + styles/src/styleTree/workspace.ts | 14 +- 18 files changed, 619 insertions(+), 499 deletions(-) diff --git a/assets/settings/default.json b/assets/settings/default.json index 95ba08c9d56ba11a4107897a08be64a0e00ba98c..127e0e1401474779fab22fae0388945323b26ac5 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -32,6 +32,16 @@ // 4. Save when idle for a certain amount of time: // "autosave": { "after_delay": {"milliseconds": 500} }, "autosave": "off", + // Where to place the dock by default. This setting can take three + // values: + // + // 1. Position the dock attached to the bottom of the workspace + // "default_dock_anchor": "bottom" + // 2. Position the dock to the right of the workspace like a side panel + // "default_dock_anchor": "right" + // 3. Position the dock full screen over the entire workspace" + // "default_dock_anchor": "expanded" + "default_dock_anchor": "right", // How to auto-format modified buffers when saving them. This // setting can take three values: // diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index d9d0c3990cce3960d5c88749bf0a0aefe2609d13..7e3b8ead9b73142a28a01f483f3476e2ae8e8643 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -28,7 +28,7 @@ use gpui::{ text_layout::{self, Line, RunStyle, TextLayoutCache}, AppContext, Axis, Border, CursorRegion, Element, ElementBox, Event, EventContext, LayoutContext, ModifiersChangedEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, - MutableAppContext, PaintContext, Quad, Scene, ScrollWheelEvent, SizeConstraint, ViewContext, + MouseRegion, MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; @@ -41,6 +41,7 @@ use std::{ fmt::Write, iter, ops::Range, + sync::Arc, }; const MIN_POPOVER_CHARACTER_WIDTH: f32 = 20.; @@ -76,9 +77,10 @@ impl SelectionLayout { } } +#[derive(Clone)] pub struct EditorElement { view: WeakViewHandle, - style: EditorStyle, + style: Arc, cursor_shape: CursorShape, } @@ -90,7 +92,7 @@ impl EditorElement { ) -> Self { Self { view, - style, + style: Arc::new(style), cursor_shape, } } @@ -110,8 +112,98 @@ impl EditorElement { self.update_view(cx, |view, cx| view.snapshot(cx)) } + fn attach_mouse_handlers( + view: &WeakViewHandle, + position_map: &Arc, + visible_bounds: RectF, + text_bounds: RectF, + gutter_bounds: RectF, + bounds: RectF, + cx: &mut PaintContext, + ) { + enum EditorElementMouseHandlers {} + cx.scene.push_mouse_region( + MouseRegion::new::(view.id(), view.id(), visible_bounds) + .on_down(MouseButton::Left, { + let position_map = position_map.clone(); + move |e, cx| { + if !Self::mouse_down( + e.platform_event, + position_map.as_ref(), + text_bounds, + gutter_bounds, + cx, + ) { + cx.propogate_event(); + } + } + }) + .on_down(MouseButton::Right, { + let position_map = position_map.clone(); + move |e, cx| { + if !Self::mouse_right_down( + e.position, + position_map.as_ref(), + text_bounds, + cx, + ) { + cx.propogate_event(); + } + } + }) + .on_up(MouseButton::Left, { + let view = view.clone(); + let position_map = position_map.clone(); + move |e, cx| { + if !Self::mouse_up( + view.clone(), + e.position, + e.cmd, + e.shift, + position_map.as_ref(), + text_bounds, + cx, + ) { + cx.propogate_event() + } + } + }) + .on_drag(MouseButton::Left, { + let view = view.clone(); + let position_map = position_map.clone(); + move |e, cx| { + if !Self::mouse_dragged( + view.clone(), + e.platform_event, + position_map.as_ref(), + text_bounds, + cx, + ) { + cx.propogate_event() + } + } + }) + .on_move({ + let position_map = position_map.clone(); + move |e, cx| { + if !Self::mouse_moved(e.platform_event, &position_map, text_bounds, cx) { + cx.propogate_event() + } + } + }) + .on_scroll({ + let position_map = position_map.clone(); + move |e, cx| { + if !Self::scroll(e.position, e.delta, e.precise, &position_map, bounds, cx) + { + cx.propogate_event() + } + } + }), + ); + } + fn mouse_down( - &self, MouseButtonEvent { position, ctrl, @@ -121,18 +213,18 @@ impl EditorElement { mut click_count, .. }: MouseButtonEvent, - layout: &mut LayoutState, - paint: &mut PaintState, + position_map: &PositionMap, + text_bounds: RectF, + gutter_bounds: RectF, cx: &mut EventContext, ) -> bool { - if paint.gutter_bounds.contains_point(position) { + if gutter_bounds.contains_point(position) { click_count = 3; // Simulate triple-click when clicking the gutter to select lines - } else if !paint.text_bounds.contains_point(position) { + } else if !text_bounds.contains_point(position) { return false; } - let snapshot = self.snapshot(cx.app); - let (position, target_position) = paint.point_for_position(&snapshot, layout, position); + let (position, target_position) = position_map.point_for_position(text_bounds, position); if shift && alt { cx.dispatch_action(Select(SelectPhase::BeginColumnar { @@ -156,33 +248,31 @@ impl EditorElement { } fn mouse_right_down( - &self, position: Vector2F, - layout: &mut LayoutState, - paint: &mut PaintState, + position_map: &PositionMap, + text_bounds: RectF, cx: &mut EventContext, ) -> bool { - if !paint.text_bounds.contains_point(position) { + if !text_bounds.contains_point(position) { return false; } - let snapshot = self.snapshot(cx.app); - let (point, _) = paint.point_for_position(&snapshot, layout, position); + let (point, _) = position_map.point_for_position(text_bounds, position); cx.dispatch_action(DeployMouseContextMenu { position, point }); true } fn mouse_up( - &self, + view: WeakViewHandle, position: Vector2F, cmd: bool, shift: bool, - layout: &mut LayoutState, - paint: &mut PaintState, + position_map: &PositionMap, + text_bounds: RectF, cx: &mut EventContext, ) -> bool { - let view = self.view(cx.app.as_ref()); + let view = view.upgrade(cx.app).unwrap().read(cx.app); let end_selection = view.has_pending_selection(); let pending_nonempty_selections = view.has_pending_nonempty_selection(); @@ -190,9 +280,8 @@ impl EditorElement { cx.dispatch_action(Select(SelectPhase::End)); } - if !pending_nonempty_selections && cmd && paint.text_bounds.contains_point(position) { - let (point, target_point) = - paint.point_for_position(&self.snapshot(cx), layout, position); + if !pending_nonempty_selections && cmd && text_bounds.contains_point(position) { + let (point, target_point) = position_map.point_for_position(text_bounds, position); if point == target_point { if shift { @@ -209,22 +298,21 @@ impl EditorElement { } fn mouse_dragged( - &self, + view: WeakViewHandle, MouseMovedEvent { cmd, shift, position, .. }: MouseMovedEvent, - layout: &mut LayoutState, - paint: &mut PaintState, + position_map: &PositionMap, + text_bounds: RectF, cx: &mut EventContext, ) -> bool { // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed // Don't trigger hover popover if mouse is hovering over context menu - let point = if paint.text_bounds.contains_point(position) { - let (point, target_point) = - paint.point_for_position(&self.snapshot(cx), layout, position); + let point = if text_bounds.contains_point(position) { + let (point, target_point) = position_map.point_for_position(text_bounds, position); if point == target_point { Some(point) } else { @@ -240,14 +328,13 @@ impl EditorElement { shift_held: shift, }); - let view = self.view(cx.app); + let view = view.upgrade(cx.app).unwrap().read(cx.app); if view.has_pending_selection() { - let rect = paint.text_bounds; let mut scroll_delta = Vector2F::zero(); - let vertical_margin = layout.line_height.min(rect.height() / 3.0); - let top = rect.origin_y() + vertical_margin; - let bottom = rect.lower_left().y() - vertical_margin; + let vertical_margin = position_map.line_height.min(text_bounds.height() / 3.0); + let top = text_bounds.origin_y() + vertical_margin; + let bottom = text_bounds.lower_left().y() - vertical_margin; if position.y() < top { scroll_delta.set_y(-scale_vertical_mouse_autoscroll_delta(top - position.y())) } @@ -255,9 +342,9 @@ impl EditorElement { scroll_delta.set_y(scale_vertical_mouse_autoscroll_delta(position.y() - bottom)) } - let horizontal_margin = layout.line_height.min(rect.width() / 3.0); - let left = rect.origin_x() + horizontal_margin; - let right = rect.upper_right().x() - horizontal_margin; + let horizontal_margin = position_map.line_height.min(text_bounds.width() / 3.0); + let left = text_bounds.origin_x() + horizontal_margin; + let right = text_bounds.upper_right().x() - horizontal_margin; if position.x() < left { scroll_delta.set_x(-scale_horizontal_mouse_autoscroll_delta( left - position.x(), @@ -269,14 +356,14 @@ impl EditorElement { )) } - let snapshot = self.snapshot(cx.app); - let (position, target_position) = paint.point_for_position(&snapshot, layout, position); + let (position, target_position) = + position_map.point_for_position(text_bounds, position); cx.dispatch_action(Select(SelectPhase::Update { position, goal_column: target_position.column(), - scroll_position: (snapshot.scroll_position() + scroll_delta) - .clamp(Vector2F::zero(), layout.scroll_max), + scroll_position: (position_map.snapshot.scroll_position() + scroll_delta) + .clamp(Vector2F::zero(), position_map.scroll_max), })); cx.dispatch_action(HoverAt { point }); @@ -288,22 +375,20 @@ impl EditorElement { } fn mouse_moved( - &self, MouseMovedEvent { cmd, shift, position, .. }: MouseMovedEvent, - layout: &LayoutState, - paint: &PaintState, + position_map: &PositionMap, + text_bounds: RectF, cx: &mut EventContext, ) -> bool { // This will be handled more correctly once https://github.com/zed-industries/zed/issues/1218 is completed // Don't trigger hover popover if mouse is hovering over context menu - let point = if paint.text_bounds.contains_point(position) { - let (point, target_point) = - paint.point_for_position(&self.snapshot(cx), layout, position); + let point = if text_bounds.contains_point(position) { + let (point, target_point) = position_map.point_for_position(text_bounds, position); if point == target_point { Some(point) } else { @@ -319,23 +404,6 @@ impl EditorElement { shift_held: shift, }); - if paint - .context_menu_bounds - .map_or(false, |context_menu_bounds| { - context_menu_bounds.contains_point(position) - }) - { - return false; - } - - if paint - .hover_popover_bounds - .iter() - .any(|hover_bounds| hover_bounds.contains_point(position)) - { - return false; - } - cx.dispatch_action(HoverAt { point }); true } @@ -349,28 +417,27 @@ impl EditorElement { } fn scroll( - &self, position: Vector2F, mut delta: Vector2F, precise: bool, - layout: &mut LayoutState, - paint: &mut PaintState, + position_map: &PositionMap, + bounds: RectF, cx: &mut EventContext, ) -> bool { - if !paint.bounds.contains_point(position) { + if !bounds.contains_point(position) { return false; } - let snapshot = self.snapshot(cx.app); - let max_glyph_width = layout.em_width; + let max_glyph_width = position_map.em_width; if !precise { - delta *= vec2f(max_glyph_width, layout.line_height); + delta *= vec2f(max_glyph_width, position_map.line_height); } - let scroll_position = snapshot.scroll_position(); + let scroll_position = position_map.snapshot.scroll_position(); let x = (scroll_position.x() * max_glyph_width - delta.x()) / max_glyph_width; - let y = (scroll_position.y() * layout.line_height - delta.y()) / layout.line_height; - let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), layout.scroll_max); + let y = + (scroll_position.y() * position_map.line_height - delta.y()) / position_map.line_height; + let scroll_position = vec2f(x, y).clamp(Vector2F::zero(), position_map.scroll_max); cx.dispatch_action(Scroll(scroll_position)); @@ -385,7 +452,8 @@ impl EditorElement { cx: &mut PaintContext, ) { let bounds = gutter_bounds.union_rect(text_bounds); - let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; + let scroll_top = + layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height; let editor = self.view(cx.app); cx.scene.push_quad(Quad { bounds: gutter_bounds, @@ -414,11 +482,12 @@ impl EditorElement { if !contains_non_empty_selection { let origin = vec2f( bounds.origin_x(), - bounds.origin_y() + (layout.line_height * *start_row as f32) - scroll_top, + bounds.origin_y() + (layout.position_map.line_height * *start_row as f32) + - scroll_top, ); let size = vec2f( bounds.width(), - layout.line_height * (end_row - start_row + 1) as f32, + layout.position_map.line_height * (end_row - start_row + 1) as f32, ); cx.scene.push_quad(Quad { bounds: RectF::new(origin, size), @@ -432,12 +501,13 @@ impl EditorElement { if let Some(highlighted_rows) = &layout.highlighted_rows { let origin = vec2f( bounds.origin_x(), - bounds.origin_y() + (layout.line_height * highlighted_rows.start as f32) + bounds.origin_y() + + (layout.position_map.line_height * highlighted_rows.start as f32) - scroll_top, ); let size = vec2f( bounds.width(), - layout.line_height * highlighted_rows.len() as f32, + layout.position_map.line_height * highlighted_rows.len() as f32, ); cx.scene.push_quad(Quad { bounds: RectF::new(origin, size), @@ -456,23 +526,30 @@ impl EditorElement { layout: &mut LayoutState, cx: &mut PaintContext, ) { - let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; + let scroll_top = + layout.position_map.snapshot.scroll_position().y() * layout.position_map.line_height; for (ix, line) in layout.line_number_layouts.iter().enumerate() { if let Some(line) = line { let line_origin = bounds.origin() + vec2f( bounds.width() - line.width() - layout.gutter_padding, - ix as f32 * layout.line_height - (scroll_top % layout.line_height), + ix as f32 * layout.position_map.line_height + - (scroll_top % layout.position_map.line_height), ); - line.paint(line_origin, visible_bounds, layout.line_height, cx); + line.paint( + line_origin, + visible_bounds, + layout.position_map.line_height, + cx, + ); } } if let Some((row, indicator)) = layout.code_actions_indicator.as_mut() { let mut x = bounds.width() - layout.gutter_padding; - let mut y = *row as f32 * layout.line_height - scroll_top; + let mut y = *row as f32 * layout.position_map.line_height - scroll_top; x += ((layout.gutter_padding + layout.gutter_margin) - indicator.size().x()) / 2.; - y += (layout.line_height - indicator.size().y()) / 2.; + y += (layout.position_map.line_height - indicator.size().y()) / 2.; indicator.paint(bounds.origin() + vec2f(x, y), visible_bounds, cx); } } @@ -488,11 +565,12 @@ impl EditorElement { let view = self.view(cx.app); let style = &self.style; let local_replica_id = view.replica_id(cx); - let scroll_position = layout.snapshot.scroll_position(); + let scroll_position = layout.position_map.snapshot.scroll_position(); let start_row = scroll_position.y() as u32; - let scroll_top = scroll_position.y() * layout.line_height; - let end_row = ((scroll_top + bounds.height()) / layout.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen - let max_glyph_width = layout.em_width; + let scroll_top = scroll_position.y() * layout.position_map.line_height; + let end_row = + ((scroll_top + bounds.height()) / layout.position_map.line_height).ceil() as u32 + 1; // Add 1 to ensure selections bleed off screen + let max_glyph_width = layout.position_map.em_width; let scroll_left = scroll_position.x() * max_glyph_width; let content_origin = bounds.origin() + vec2f(layout.gutter_margin, 0.); @@ -514,7 +592,7 @@ impl EditorElement { end_row, *color, 0., - 0.15 * layout.line_height, + 0.15 * layout.position_map.line_height, layout, content_origin, scroll_top, @@ -527,7 +605,7 @@ impl EditorElement { let mut cursors = SmallVec::<[Cursor; 32]>::new(); for (replica_id, selections) in &layout.selections { let selection_style = style.replica_selection_style(*replica_id); - let corner_radius = 0.15 * layout.line_height; + let corner_radius = 0.15 * layout.position_map.line_height; for selection in selections { self.paint_highlighted_range( @@ -548,50 +626,52 @@ impl EditorElement { if view.show_local_cursors() || *replica_id != local_replica_id { let cursor_position = selection.head; if (start_row..end_row).contains(&cursor_position.row()) { - let cursor_row_layout = - &layout.line_layouts[(cursor_position.row() - start_row) as usize]; + let cursor_row_layout = &layout.position_map.line_layouts + [(cursor_position.row() - start_row) as usize]; let cursor_column = cursor_position.column() as usize; let cursor_character_x = cursor_row_layout.x_for_index(cursor_column); let mut block_width = cursor_row_layout.x_for_index(cursor_column + 1) - cursor_character_x; if block_width == 0.0 { - block_width = layout.em_width; + block_width = layout.position_map.em_width; } - - let block_text = - if let CursorShape::Block = self.cursor_shape { - layout.snapshot.chars_at(cursor_position).next().and_then( - |character| { - let font_id = - cursor_row_layout.font_for_index(cursor_column)?; - let text = character.to_string(); - - Some(cx.text_layout_cache.layout_str( - &text, - cursor_row_layout.font_size(), - &[( - text.len(), - RunStyle { - font_id, - color: style.background, - underline: Default::default(), - }, - )], - )) - }, - ) - } else { - None - }; + let block_text = if let CursorShape::Block = self.cursor_shape { + layout + .position_map + .snapshot + .chars_at(cursor_position) + .next() + .and_then(|character| { + let font_id = + cursor_row_layout.font_for_index(cursor_column)?; + let text = character.to_string(); + + Some(cx.text_layout_cache.layout_str( + &text, + cursor_row_layout.font_size(), + &[( + text.len(), + RunStyle { + font_id, + color: style.background, + underline: Default::default(), + }, + )], + )) + }) + } else { + None + }; let x = cursor_character_x - scroll_left; - let y = cursor_position.row() as f32 * layout.line_height - scroll_top; + let y = cursor_position.row() as f32 * layout.position_map.line_height + - scroll_top; cursors.push(Cursor { color: selection_style.cursor, block_width, origin: vec2f(x, y), - line_height: layout.line_height, + line_height: layout.position_map.line_height, shape: self.cursor_shape, block_text, }); @@ -602,13 +682,16 @@ impl EditorElement { if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) { // Draw glyphs - for (ix, line) in layout.line_layouts.iter().enumerate() { + for (ix, line) in layout.position_map.line_layouts.iter().enumerate() { let row = start_row + ix as u32; line.paint( content_origin - + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top), + + vec2f( + -scroll_left, + row as f32 * layout.position_map.line_height - scroll_top, + ), visible_text_bounds, - layout.line_height, + layout.position_map.line_height, cx, ); } @@ -622,9 +705,10 @@ impl EditorElement { if let Some((position, context_menu)) = layout.context_menu.as_mut() { cx.scene.push_stacking_context(None); - let cursor_row_layout = &layout.line_layouts[(position.row() - start_row) as usize]; + let cursor_row_layout = + &layout.position_map.line_layouts[(position.row() - start_row) as usize]; let x = cursor_row_layout.x_for_index(position.column() as usize) - scroll_left; - let y = (position.row() + 1) as f32 * layout.line_height - scroll_top; + let y = (position.row() + 1) as f32 * layout.position_map.line_height - scroll_top; let mut list_origin = content_origin + vec2f(x, y); let list_width = context_menu.size().x(); let list_height = context_menu.size().y(); @@ -636,7 +720,7 @@ impl EditorElement { } if list_origin.y() + list_height > bounds.max_y() { - list_origin.set_y(list_origin.y() - layout.line_height - list_height); + list_origin.set_y(list_origin.y() - layout.position_map.line_height - list_height); } context_menu.paint( @@ -654,18 +738,19 @@ impl EditorElement { cx.scene.push_stacking_context(None); // This is safe because we check on layout whether the required row is available - let hovered_row_layout = &layout.line_layouts[(position.row() - start_row) as usize]; + let hovered_row_layout = + &layout.position_map.line_layouts[(position.row() - start_row) as usize]; // Minimum required size: Take the first popover, and add 1.5 times the minimum popover // height. This is the size we will use to decide whether to render popovers above or below // the hovered line. let first_size = hover_popovers[0].size(); - let height_to_reserve = - first_size.y() + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.line_height; + let height_to_reserve = first_size.y() + + 1.5 * MIN_POPOVER_LINE_HEIGHT as f32 * layout.position_map.line_height; // Compute Hovered Point let x = hovered_row_layout.x_for_index(position.column() as usize) - scroll_left; - let y = position.row() as f32 * layout.line_height - scroll_top; + let y = position.row() as f32 * layout.position_map.line_height - scroll_top; let hovered_point = content_origin + vec2f(x, y); paint.hover_popover_bounds.clear(); @@ -697,7 +782,7 @@ impl EditorElement { } } else { // There is not enough space above. Render popovers below the hovered point - let mut current_y = hovered_point.y() + layout.line_height; + let mut current_y = hovered_point.y() + layout.position_map.line_height; for hover_popover in hover_popovers { let size = hover_popover.size(); let mut popover_origin = vec2f(hovered_point.x(), current_y); @@ -753,14 +838,16 @@ impl EditorElement { let highlighted_range = HighlightedRange { color, - line_height: layout.line_height, + line_height: layout.position_map.line_height, corner_radius, - start_y: content_origin.y() + row_range.start as f32 * layout.line_height + start_y: content_origin.y() + + row_range.start as f32 * layout.position_map.line_height - scroll_top, lines: row_range .into_iter() .map(|row| { - let line_layout = &layout.line_layouts[(row - start_row) as usize]; + let line_layout = + &layout.position_map.line_layouts[(row - start_row) as usize]; HighlightedRangeLine { start_x: if row == range.start.row() { content_origin.x() @@ -793,13 +880,16 @@ impl EditorElement { layout: &mut LayoutState, cx: &mut PaintContext, ) { - let scroll_position = layout.snapshot.scroll_position(); - let scroll_left = scroll_position.x() * layout.em_width; - let scroll_top = scroll_position.y() * layout.line_height; + let scroll_position = layout.position_map.snapshot.scroll_position(); + let scroll_left = scroll_position.x() * layout.position_map.em_width; + let scroll_top = scroll_position.y() * layout.position_map.line_height; for block in &mut layout.blocks { - let mut origin = - bounds.origin() + vec2f(0., block.row as f32 * layout.line_height - scroll_top); + let mut origin = bounds.origin() + + vec2f( + 0., + block.row as f32 * layout.position_map.line_height - scroll_top, + ); if !matches!(block.style, BlockStyle::Sticky) { origin += vec2f(-scroll_left, 0.); } @@ -1483,22 +1573,24 @@ impl Element for EditorElement { ( size, LayoutState { - size, - scroll_max, + position_map: Arc::new(PositionMap { + size, + scroll_max, + line_layouts, + line_height, + em_width, + em_advance, + snapshot, + }), gutter_size, gutter_padding, text_size, gutter_margin, - snapshot, active_rows, highlighted_rows, highlighted_ranges, - line_layouts, line_number_layouts, blocks, - line_height, - em_width, - em_advance, selections, context_menu, code_actions_indicator, @@ -1523,13 +1615,20 @@ impl Element for EditorElement { ); let mut paint_state = PaintState { - bounds, - gutter_bounds, - text_bounds, context_menu_bounds: None, hover_popover_bounds: Default::default(), }; + Self::attach_mouse_handlers( + &self.view, + &layout.position_map, + visible_bounds, + text_bounds, + gutter_bounds, + bounds, + cx, + ); + self.paint_background(gutter_bounds, text_bounds, layout, cx); if layout.gutter_size.x() > 0. { self.paint_gutter(gutter_bounds, visible_bounds, layout, cx); @@ -1552,78 +1651,15 @@ impl Element for EditorElement { event: &Event, _: RectF, _: RectF, - layout: &mut LayoutState, - paint: &mut PaintState, + _: &mut LayoutState, + _: &mut PaintState, cx: &mut EventContext, ) -> bool { - if let Some((_, context_menu)) = &mut layout.context_menu { - if context_menu.dispatch_event(event, cx) { - return true; - } - } - - if let Some((_, indicator)) = &mut layout.code_actions_indicator { - if indicator.dispatch_event(event, cx) { - return true; - } + if let Event::ModifiersChanged(event) = event { + self.modifiers_changed(*event, cx); } - if let Some((_, popover_elements)) = &mut layout.hover_popovers { - for popover_element in popover_elements.iter_mut() { - if popover_element.dispatch_event(event, cx) { - return true; - } - } - } - - for block in &mut layout.blocks { - if block.element.dispatch_event(event, cx) { - return true; - } - } - - match event { - &Event::MouseDown( - event @ MouseButtonEvent { - button: MouseButton::Left, - .. - }, - ) => self.mouse_down(event, layout, paint, cx), - - &Event::MouseDown(MouseButtonEvent { - button: MouseButton::Right, - position, - .. - }) => self.mouse_right_down(position, layout, paint, cx), - - &Event::MouseUp(MouseButtonEvent { - button: MouseButton::Left, - position, - cmd, - shift, - .. - }) => self.mouse_up(position, cmd, shift, layout, paint, cx), - - Event::MouseMoved( - event @ MouseMovedEvent { - pressed_button: Some(MouseButton::Left), - .. - }, - ) => self.mouse_dragged(*event, layout, paint, cx), - - Event::ScrollWheel(ScrollWheelEvent { - position, - delta, - precise, - .. - }) => self.scroll(*position, *delta, *precise, layout, paint, cx), - - &Event::ModifiersChanged(event) => self.modifiers_changed(event, cx), - - &Event::MouseMoved(event) => self.mouse_moved(event, layout, paint, cx), - - _ => false, - } + false } fn rect_for_text_range( @@ -1640,26 +1676,34 @@ impl Element for EditorElement { layout.text_size, ); let content_origin = text_bounds.origin() + vec2f(layout.gutter_margin, 0.); - let scroll_position = layout.snapshot.scroll_position(); + let scroll_position = layout.position_map.snapshot.scroll_position(); let start_row = scroll_position.y() as u32; - let scroll_top = scroll_position.y() * layout.line_height; - let scroll_left = scroll_position.x() * layout.em_width; + let scroll_top = scroll_position.y() * layout.position_map.line_height; + let scroll_left = scroll_position.x() * layout.position_map.em_width; - let range_start = - OffsetUtf16(range_utf16.start).to_display_point(&layout.snapshot.display_snapshot); + let range_start = OffsetUtf16(range_utf16.start) + .to_display_point(&layout.position_map.snapshot.display_snapshot); if range_start.row() < start_row { return None; } let line = layout + .position_map .line_layouts .get((range_start.row() - start_row) as usize)?; let range_start_x = line.x_for_index(range_start.column() as usize); - let range_start_y = range_start.row() as f32 * layout.line_height; + let range_start_y = range_start.row() as f32 * layout.position_map.line_height; Some(RectF::new( - content_origin + vec2f(range_start_x, range_start_y + layout.line_height) + content_origin + + vec2f( + range_start_x, + range_start_y + layout.position_map.line_height, + ) - vec2f(scroll_left, scroll_top), - vec2f(layout.em_width, layout.line_height), + vec2f( + layout.position_map.em_width, + layout.position_map.line_height, + ), )) } @@ -1678,21 +1722,15 @@ impl Element for EditorElement { } pub struct LayoutState { - size: Vector2F, - scroll_max: Vector2F, + position_map: Arc, gutter_size: Vector2F, gutter_padding: f32, gutter_margin: f32, text_size: Vector2F, - snapshot: EditorSnapshot, active_rows: BTreeMap, highlighted_rows: Option>, - line_layouts: Vec, line_number_layouts: Vec>, blocks: Vec, - line_height: f32, - em_width: f32, - em_advance: f32, highlighted_ranges: Vec<(Range, Color)>, selections: Vec<(ReplicaId, Vec)>, context_menu: Option<(DisplayPoint, ElementBox)>, @@ -1700,6 +1738,52 @@ pub struct LayoutState { hover_popovers: Option<(DisplayPoint, Vec)>, } +pub struct PositionMap { + size: Vector2F, + line_height: f32, + scroll_max: Vector2F, + em_width: f32, + em_advance: f32, + line_layouts: Vec, + snapshot: EditorSnapshot, +} + +impl PositionMap { + /// Returns two display points: + /// 1. The nearest *valid* position in the editor + /// 2. An unclipped, potentially *invalid* position that maps directly to + /// the given pixel position. + fn point_for_position( + &self, + text_bounds: RectF, + position: Vector2F, + ) -> (DisplayPoint, DisplayPoint) { + let scroll_position = self.snapshot.scroll_position(); + let position = position - text_bounds.origin(); + let y = position.y().max(0.0).min(self.size.y()); + let x = position.x() + (scroll_position.x() * self.em_width); + let row = (y / self.line_height + scroll_position.y()) as u32; + let (column, x_overshoot) = if let Some(line) = self + .line_layouts + .get(row as usize - scroll_position.y() as usize) + { + if let Some(ix) = line.index_for_x(x) { + (ix as u32, 0.0) + } else { + (line.len() as u32, 0f32.max(x - line.width())) + } + } else { + (0, x) + }; + + let mut target_point = DisplayPoint::new(row, column); + let point = self.snapshot.clip_point(target_point, Bias::Left); + *target_point.column_mut() += (x_overshoot / self.em_advance) as u32; + + (point, target_point) + } +} + struct BlockLayout { row: u32, element: ElementBox, @@ -1738,50 +1822,10 @@ fn layout_line( } pub struct PaintState { - bounds: RectF, - gutter_bounds: RectF, - text_bounds: RectF, context_menu_bounds: Option, hover_popover_bounds: Vec, } -impl PaintState { - /// Returns two display points: - /// 1. The nearest *valid* position in the editor - /// 2. An unclipped, potentially *invalid* position that maps directly to - /// the given pixel position. - fn point_for_position( - &self, - snapshot: &EditorSnapshot, - layout: &LayoutState, - position: Vector2F, - ) -> (DisplayPoint, DisplayPoint) { - let scroll_position = snapshot.scroll_position(); - let position = position - self.text_bounds.origin(); - let y = position.y().max(0.0).min(layout.size.y()); - let x = position.x() + (scroll_position.x() * layout.em_width); - let row = (y / layout.line_height + scroll_position.y()) as u32; - let (column, x_overshoot) = if let Some(line) = layout - .line_layouts - .get(row as usize - scroll_position.y() as usize) - { - if let Some(ix) = line.index_for_x(x) { - (ix as u32, 0.0) - } else { - (line.len() as u32, 0f32.max(x - line.width())) - } - } else { - (0, x) - }; - - let mut target_point = DisplayPoint::new(row, column); - let point = snapshot.clip_point(target_point, Bias::Left); - *target_point.column_mut() += (x_overshoot / layout.em_advance) as u32; - - (point, target_point) - } -} - #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum CursorShape { Bar, @@ -2090,7 +2134,7 @@ mod tests { &mut layout_cx, ); - assert_eq!(state.line_layouts.len(), 4); + assert_eq!(state.position_map.line_layouts.len(), 4); assert_eq!( state .line_number_layouts diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index e9091d74c8001c59c03beb6c2e1f4101ae567035..19446ab0cc94f4d83b738dae0a3881221ec26fc7 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4028,7 +4028,7 @@ pub struct RenderParams { pub view_id: usize, pub titlebar_height: f32, pub hovered_region_ids: HashSet, - pub clicked_region_ids: Option<(Vec, MouseButton)>, + pub clicked_region_ids: Option<(HashSet, MouseButton)>, pub refreshing: bool, } @@ -4037,7 +4037,7 @@ pub struct RenderContext<'a, T: View> { pub(crate) view_id: usize, pub(crate) view_type: PhantomData, pub(crate) hovered_region_ids: HashSet, - pub(crate) clicked_region_ids: Option<(Vec, MouseButton)>, + pub(crate) clicked_region_ids: Option<(HashSet, MouseButton)>, pub app: &'a mut MutableAppContext, pub titlebar_height: f32, pub refreshing: bool, diff --git a/crates/gpui/src/elements/event_handler.rs b/crates/gpui/src/elements/event_handler.rs index 015e778314110f7ed6bbaf893513b8ecc3716fc4..064f00e1f31c2fa5a4107acee26b65ed1b6f644d 100644 --- a/crates/gpui/src/elements/event_handler.rs +++ b/crates/gpui/src/elements/event_handler.rs @@ -1,7 +1,7 @@ use crate::{ - geometry::vector::Vector2F, presenter::MeasurementContext, CursorRegion, DebugContext, Element, - ElementBox, Event, EventContext, LayoutContext, MouseButton, MouseButtonEvent, MouseRegion, - NavigationDirection, PaintContext, SizeConstraint, + geometry::vector::Vector2F, presenter::MeasurementContext, scene::HandlerSet, CursorRegion, + DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MouseButton, + MouseButtonEvent, MouseRegion, NavigationDirection, PaintContext, SizeConstraint, }; use pathfinder_geometry::rect::RectF; use serde_json::json; @@ -82,11 +82,13 @@ impl Element for EventHandler { bounds: visible_bounds, style: Default::default(), }); - cx.scene.push_mouse_region(MouseRegion::handle_all( - cx.current_view_id(), - Some(discriminant), - visible_bounds, - )); + cx.scene.push_mouse_region(MouseRegion { + view_id: cx.current_view_id(), + discriminant, + bounds: visible_bounds, + handlers: HandlerSet::capture_all(), + hoverable: true, + }); cx.scene.pop_stacking_context(); } self.child.paint(bounds.origin(), visible_bounds, cx); diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 9e5465e91a28da9d053757531b55794db9534efb..25e2ba2fde7fc1aedf3ac76cba76d796f7d18081 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -167,15 +167,13 @@ impl Element for MouseEventHandler { }); } - cx.scene.push_mouse_region( - MouseRegion::from_handlers( - cx.current_view_id(), - Some(self.discriminant), - hit_bounds, - self.handlers.clone(), - ) - .with_hoverable(self.hoverable), - ); + cx.scene.push_mouse_region(MouseRegion { + view_id: cx.current_view_id(), + discriminant: self.discriminant, + bounds: hit_bounds, + handlers: self.handlers.clone(), + hoverable: self.hoverable, + }); self.child.paint(bounds.origin(), visible_bounds, cx); } diff --git a/crates/gpui/src/elements/overlay.rs b/crates/gpui/src/elements/overlay.rs index d81c939061380f52f673cd9974723a331dcf7617..b4022759192732164180b2d43fb27c350bbd023d 100644 --- a/crates/gpui/src/elements/overlay.rs +++ b/crates/gpui/src/elements/overlay.rs @@ -1,4 +1,4 @@ -use std::ops::Range; +use std::{any::TypeId, ops::Range}; use crate::{ geometry::{rect::RectF, vector::Vector2F}, @@ -78,10 +78,13 @@ impl Element for Overlay { cx.scene.push_stacking_context(None); if self.hoverable { + enum OverlayHoverCapture {} cx.scene.push_mouse_region(MouseRegion { view_id: cx.current_view_id(), bounds, - ..Default::default() + discriminant: (TypeId::of::(), cx.current_view_id()), + handlers: Default::default(), + hoverable: true, }); } diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 859ba463754cade5e4af05847cb4d0445adba9ee..91cd58f7077043de870833f9d5c3c4f48bc1df84 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -8,7 +8,8 @@ use crate::{ platform::{CursorStyle, Event}, scene::{ ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, - HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, + HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, ScrollWheelRegionEvent, + UpOutRegionEvent, UpRegionEvent, }, text_layout::TextLayoutCache, Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity, @@ -36,7 +37,7 @@ pub struct Presenter { asset_cache: Arc, last_mouse_moved_event: Option, hovered_region_ids: HashSet, - clicked_regions: Vec, + clicked_region_ids: HashSet, clicked_button: Option, mouse_position: Vector2F, titlebar_height: f32, @@ -61,7 +62,7 @@ impl Presenter { asset_cache, last_mouse_moved_event: None, hovered_region_ids: Default::default(), - clicked_regions: Vec::new(), + clicked_region_ids: Default::default(), clicked_button: None, mouse_position: vec2f(0., 0.), titlebar_height, @@ -75,7 +76,6 @@ impl Presenter { ) { cx.start_frame(); for view_id in &invalidation.removed { - invalidation.updated.remove(view_id); self.rendered_views.remove(view_id); } for view_id in &invalidation.updated { @@ -86,15 +86,9 @@ impl Presenter { view_id: *view_id, titlebar_height: self.titlebar_height, hovered_region_ids: self.hovered_region_ids.clone(), - clicked_region_ids: self.clicked_button.map(|button| { - ( - self.clicked_regions - .iter() - .filter_map(MouseRegion::id) - .collect(), - button, - ) - }), + clicked_region_ids: self + .clicked_button + .map(|button| (self.clicked_region_ids.clone(), button)), refreshing: false, }) .unwrap(), @@ -112,15 +106,9 @@ impl Presenter { view_id: *view_id, titlebar_height: self.titlebar_height, hovered_region_ids: self.hovered_region_ids.clone(), - clicked_region_ids: self.clicked_button.map(|button| { - ( - self.clicked_regions - .iter() - .filter_map(MouseRegion::id) - .collect(), - button, - ) - }), + clicked_region_ids: self + .clicked_button + .map(|button| (self.clicked_region_ids.clone(), button)), refreshing: true, }) .unwrap(); @@ -184,15 +172,9 @@ impl Presenter { view_stack: Vec::new(), refreshing, hovered_region_ids: self.hovered_region_ids.clone(), - clicked_region_ids: self.clicked_button.map(|button| { - ( - self.clicked_regions - .iter() - .filter_map(MouseRegion::id) - .collect(), - button, - ) - }), + clicked_region_ids: self + .clicked_button + .map(|button| (self.clicked_region_ids.clone(), button)), titlebar_height: self.titlebar_height, window_size, app: cx, @@ -248,14 +230,15 @@ impl Presenter { // If there is already clicked_button stored, don't replace it. if self.clicked_button.is_none() { - self.clicked_regions = self + self.clicked_region_ids = self .mouse_regions .iter() .filter_map(|(region, _)| { - region - .bounds - .contains_point(e.position) - .then(|| region.clone()) + if region.bounds.contains_point(e.position) { + Some(region.id()) + } else { + None + } }) .collect(); self.clicked_button = Some(e.button); @@ -341,6 +324,12 @@ impl Presenter { self.last_mouse_moved_event = Some(event.clone()); } + Event::ScrollWheel(e) => { + events_to_send.push(MouseRegionEvent::ScrollWheel(ScrollWheelRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })) + } _ => {} } @@ -375,23 +364,21 @@ impl Presenter { top_most_depth = Some(depth); } - if let Some(region_id) = region.id() { - // This unwrap relies on short circuiting boolean expressions - // The right side of the && is only executed when contains_mouse - // is true, and we know above that when contains_mouse is true - // top_most_depth is set - if contains_mouse && depth == top_most_depth.unwrap() { - //Ensure that hover entrance events aren't sent twice - if self.hovered_region_ids.insert(region_id) { - valid_regions.push(region.clone()); - invalidated_views.insert(region.view_id); - } - } else { - // Ensure that hover exit events aren't sent twice - if self.hovered_region_ids.remove(®ion_id) { - valid_regions.push(region.clone()); - invalidated_views.insert(region.view_id); - } + // This unwrap relies on short circuiting boolean expressions + // The right side of the && is only executed when contains_mouse + // is true, and we know above that when contains_mouse is true + // top_most_depth is set + if contains_mouse && depth == top_most_depth.unwrap() { + //Ensure that hover entrance events aren't sent twice + if self.hovered_region_ids.insert(region.id()) { + valid_regions.push(region.clone()); + invalidated_views.insert(region.view_id); + } + } else { + // Ensure that hover exit events aren't sent twice + if self.hovered_region_ids.remove(®ion.id()) { + valid_regions.push(region.clone()); + invalidated_views.insert(region.view_id); } } } @@ -404,21 +391,23 @@ impl Presenter { .unwrap_or(false) { // Clear clicked regions and clicked button - let clicked_regions = - std::mem::replace(&mut self.clicked_regions, Vec::new()); + let clicked_region_ids = + std::mem::replace(&mut self.clicked_region_ids, Default::default()); self.clicked_button = None; // Find regions which still overlap with the mouse since the last MouseDown happened - for clicked_region in clicked_regions.into_iter().rev() { - if clicked_region.bounds.contains_point(e.position) { - valid_regions.push(clicked_region); + for (mouse_region, _) in self.mouse_regions.iter().rev() { + if clicked_region_ids.contains(&mouse_region.id()) { + valid_regions.push(mouse_region.clone()); } } } } MouseRegionEvent::Drag(_) => { - for clicked_region in self.clicked_regions.iter().rev() { - valid_regions.push(clicked_region.clone()); + for (mouse_region, _) in self.mouse_regions.iter().rev() { + if self.clicked_region_ids.contains(&mouse_region.id()) { + valid_regions.push(mouse_region.clone()); + } } } @@ -447,10 +436,7 @@ impl Presenter { region_event.set_region(valid_region.bounds); if let MouseRegionEvent::Hover(e) = &mut region_event { - e.started = valid_region - .id() - .map(|region_id| hovered_region_ids.contains(®ion_id)) - .unwrap_or(false) + e.started = hovered_region_ids.contains(&valid_region.id()) } // Handle Down events if the MouseRegion has a Click handler. This makes the api more intuitive as you would // not expect a MouseRegion to be transparent to Down events if it also has a Click handler. @@ -546,7 +532,7 @@ pub struct LayoutContext<'a> { pub window_size: Vector2F, titlebar_height: f32, hovered_region_ids: HashSet, - clicked_region_ids: Option<(Vec, MouseButton)>, + clicked_region_ids: Option<(HashSet, MouseButton)>, } impl<'a> LayoutContext<'a> { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 086af5f64d1e93170a55ec8f0733f860cc098c1c..a00f354d3dcf2b3d0fda1879c278b46277c1f4c0 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -536,11 +536,11 @@ impl ToJson for Border { } impl MouseRegion { - pub fn id(&self) -> Option { - self.discriminant.map(|discriminant| MouseRegionId { + pub fn id(&self) -> MouseRegionId { + MouseRegionId { view_id: self.view_id, - discriminant, - }) + discriminant: self.discriminant, + } } } diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 362818134e91e26e31b27b1da03387ee91ee9321..e9c0ed31ca42d5a8687c84669b6c6390faf2d4c9 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -6,50 +6,47 @@ use pathfinder_geometry::rect::RectF; use crate::{EventContext, MouseButton}; -use super::mouse_region_event::{ - ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent, - MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, +use super::{ + mouse_region_event::{ + ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent, + MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, + }, + ScrollWheelRegionEvent, }; -#[derive(Clone, Default)] +#[derive(Clone)] pub struct MouseRegion { pub view_id: usize, - pub discriminant: Option<(TypeId, usize)>, + pub discriminant: (TypeId, usize), pub bounds: RectF, pub handlers: HandlerSet, pub hoverable: bool, } impl MouseRegion { - pub fn new(view_id: usize, discriminant: Option<(TypeId, usize)>, bounds: RectF) -> Self { - Self::from_handlers(view_id, discriminant, bounds, Default::default()) + /// Region ID is used to track semantically equivalent mouse regions across render passes. + /// e.g. if you have mouse handlers attached to a list item type, then each item of the list + /// should pass a different (consistent) region_id. If you have one big region that covers your + /// whole component, just pass the view_id again. + pub fn new(view_id: usize, region_id: usize, bounds: RectF) -> Self { + Self::from_handlers::(view_id, region_id, bounds, Default::default()) } - pub fn from_handlers( - view_id: usize, - discriminant: Option<(TypeId, usize)>, - bounds: RectF, - handlers: HandlerSet, - ) -> Self { - Self { - view_id, - discriminant, - bounds, - handlers, - hoverable: true, - } + pub fn handle_all(view_id: usize, region_id: usize, bounds: RectF) -> Self { + Self::from_handlers::(view_id, region_id, bounds, HandlerSet::capture_all()) } - pub fn handle_all( + pub fn from_handlers( view_id: usize, - discriminant: Option<(TypeId, usize)>, + region_id: usize, bounds: RectF, + handlers: HandlerSet, ) -> Self { Self { view_id, - discriminant, + discriminant: (TypeId::of::(), region_id), bounds, - handlers: HandlerSet::capture_all(), + handlers, hoverable: true, } } @@ -124,6 +121,14 @@ impl MouseRegion { self } + pub fn on_scroll( + mut self, + handler: impl Fn(ScrollWheelRegionEvent, &mut EventContext) + 'static, + ) -> Self { + self.handlers = self.handlers.on_scroll(handler); + self + } + pub fn with_hoverable(mut self, is_hoverable: bool) -> Self { self.hoverable = is_hoverable; self @@ -345,4 +350,22 @@ impl HandlerSet { })); self } + + pub fn on_scroll( + mut self, + handler: impl Fn(ScrollWheelRegionEvent, &mut EventContext) + 'static, + ) -> Self { + self.set.insert((MouseRegionEvent::scroll_wheel_disc(), None), + Rc::new(move |region_event, cx| { + if let MouseRegionEvent::ScrollWheel(e) = region_event { + handler(e, cx); + } else { + panic!( + "Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::ScrollWheel, found {:?}", + region_event + ); + } + })); + self + } } diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs index 4aff9f1ab95a1d78711d18ecd3f51313b9b41bc3..4d89cd5b6f06a9aafd98d168a0c1defbd55fd801 100644 --- a/crates/gpui/src/scene/mouse_region_event.rs +++ b/crates/gpui/src/scene/mouse_region_event.rs @@ -168,7 +168,7 @@ impl MouseRegionEvent { pub fn is_capturable(&self) -> bool { match self { MouseRegionEvent::Move(_) => true, - MouseRegionEvent::Drag(_) => false, + MouseRegionEvent::Drag(_) => true, MouseRegionEvent::Hover(_) => false, MouseRegionEvent::Down(_) => true, MouseRegionEvent::Up(_) => true, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 1095f289ebb94382449ea346e6af827331fc0e32..fa5fa9d2a91fcf05fbd64992edee550fd7691ea9 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -29,6 +29,7 @@ pub struct Settings { pub show_completions_on_input: bool, pub vim_mode: bool, pub autosave: Autosave, + pub default_dock_anchor: DockAnchor, pub editor_defaults: EditorSettings, pub editor_overrides: EditorSettings, pub terminal_defaults: TerminalSettings, @@ -150,6 +151,15 @@ pub enum WorkingDirectory { Always { directory: String }, } +#[derive(PartialEq, Eq, Debug, Default, Copy, Clone, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum DockAnchor { + #[default] + Bottom, + Right, + Expanded, +} + #[derive(Clone, Debug, Default, Deserialize, JsonSchema)] pub struct SettingsFileContent { pub experiments: Option, @@ -167,6 +177,8 @@ pub struct SettingsFileContent { pub vim_mode: Option, #[serde(default)] pub autosave: Option, + #[serde(default)] + pub default_dock_anchor: Option, #[serde(flatten)] pub editor: EditorSettings, #[serde(default)] @@ -216,6 +228,7 @@ impl Settings { projects_online_by_default: defaults.projects_online_by_default.unwrap(), vim_mode: defaults.vim_mode.unwrap(), autosave: defaults.autosave.unwrap(), + default_dock_anchor: defaults.default_dock_anchor.unwrap(), editor_defaults: EditorSettings { tab_size: required(defaults.editor.tab_size), hard_tabs: required(defaults.editor.hard_tabs), @@ -268,6 +281,8 @@ impl Settings { merge(&mut self.autosave, data.autosave); merge(&mut self.experiments, data.experiments); merge(&mut self.staff_mode, data.staff_mode); + merge(&mut self.default_dock_anchor, data.default_dock_anchor); + // Ensure terminal font is loaded, so we can request it in terminal_element layout if let Some(terminal_font) = &data.terminal.font_family { font_cache.load_family(&[terminal_font]).log_err(); @@ -336,6 +351,7 @@ impl Settings { show_completions_on_input: true, vim_mode: false, autosave: Autosave::Off, + default_dock_anchor: DockAnchor::Bottom, editor_defaults: EditorSettings { tab_size: Some(4.try_into().unwrap()), hard_tabs: Some(false), diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs index d88511cbce408acf9b14a34fc98b218193f8b75d..fb2fb0211ab72c6adf55a7a453cbc9690f8dbcbb 100644 --- a/crates/terminal/src/terminal_element.rs +++ b/crates/terminal/src/terminal_element.rs @@ -366,7 +366,7 @@ impl TerminalElement { ) { let connection = self.terminal; - let mut region = MouseRegion::new(view_id, None, visible_bounds); + let mut region = MouseRegion::new::(view_id, view_id, visible_bounds); // Terminal Emulator controlled behavior: region = region diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index b4ddd8ed94cbde2a4eb8b7cb31dfc3eee9e94b55..3bf643ec24811c99d213bc447be294d7ec5518c5 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -57,7 +57,7 @@ pub struct Workspace { pub notifications: Notifications, pub joining_project_avatar: ImageStyle, pub joining_project_message: ContainedText, - pub fullscreen_dock: ContainerStyle, + pub dock: Dock, } #[derive(Clone, Deserialize, Default)] @@ -150,6 +150,14 @@ pub struct Toolbar { pub nav_button: Interactive, } +#[derive(Clone, Deserialize, Default)] +pub struct Dock { + pub wash_color: Color, + pub flex: f32, + pub panel: ContainerStyle, + pub maximized: ContainerStyle, +} + #[derive(Clone, Deserialize, Default)] pub struct Notifications { #[serde(flatten)] diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 7bc407e50847e2bd02072c7cee6e009db6b102a4..c928d72c76dbba207aceef3ab5165e9b5743b0a8 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,14 +1,14 @@ use gpui::{ actions, - elements::{ChildView, MouseEventHandler, Svg}, + elements::{ChildView, Container, FlexItem, Margin, MouseEventHandler, Svg}, impl_internal_actions, CursorStyle, Element, ElementBox, Entity, MouseButton, - MutableAppContext, View, ViewContext, ViewHandle, WeakViewHandle, + MutableAppContext, RenderContext, View, ViewContext, ViewHandle, WeakViewHandle, }; use serde::Deserialize; -use settings::Settings; +use settings::{DockAnchor, Settings}; use theme::Theme; -use crate::{pane, ItemHandle, Pane, StatusItemView, Workspace}; +use crate::{ItemHandle, Pane, StatusItemView, Workspace}; #[derive(PartialEq, Clone, Deserialize)] pub struct MoveDock(pub DockAnchor); @@ -24,14 +24,6 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Dock::move_dock); } -#[derive(PartialEq, Eq, Default, Copy, Clone, Deserialize)] -pub enum DockAnchor { - #[default] - Bottom, - Right, - Expanded, -} - #[derive(Copy, Clone)] pub enum DockPosition { Shown(DockAnchor), @@ -79,63 +71,56 @@ pub struct Dock { impl Dock { pub fn new(cx: &mut ViewContext, default_item_factory: DefaultItemFactory) -> Self { let pane = cx.add_view(|cx| Pane::new(true, cx)); - - cx.subscribe(&pane.clone(), |workspace, _, event, cx| { - if let pane::Event::Remove = event { - workspace.dock.hide(); - cx.notify(); - } + let pane_id = pane.id(); + cx.subscribe(&pane, move |workspace, _, event, cx| { + workspace.handle_pane_event(pane_id, event, cx); }) .detach(); Self { pane, - position: Default::default(), + position: DockPosition::Hidden(cx.global::().default_dock_anchor), default_item_factory, } } - pub fn pane(&self) -> ViewHandle { - self.pane.clone() + pub fn pane(&self) -> &ViewHandle { + &self.pane } - fn hide(&mut self) { - self.position = self.position.hide(); + pub fn visible_pane(&self) -> Option<&ViewHandle> { + self.position.visible().map(|_| self.pane()) } - fn ensure_not_empty(workspace: &mut Workspace, cx: &mut ViewContext) { - let pane = workspace.dock.pane.clone(); - if pane.read(cx).items().next().is_none() { - let item_to_add = (workspace.dock.default_item_factory)(workspace, cx); - Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx); + fn set_dock_position( + workspace: &mut Workspace, + new_position: DockPosition, + cx: &mut ViewContext, + ) { + workspace.dock.position = new_position; + let now_visible = workspace.dock.visible_pane().is_some(); + if now_visible { + // Ensure that the pane has at least one item or construct a default item to put in it + let pane = workspace.dock.pane.clone(); + if pane.read(cx).items().next().is_none() { + let item_to_add = (workspace.dock.default_item_factory)(workspace, cx); + Pane::add_item(workspace, &pane, item_to_add, true, true, None, cx); + } + cx.focus(pane); + } else { + if let Some(last_active_center_pane) = workspace.last_active_center_pane.clone() { + cx.focus(last_active_center_pane); + } } + cx.notify(); + } + + pub fn hide(workspace: &mut Workspace, cx: &mut ViewContext) { + Self::set_dock_position(workspace, workspace.dock.position.hide(), cx); } fn toggle(workspace: &mut Workspace, _: &ToggleDock, cx: &mut ViewContext) { - // Shift-escape ON - // Get or insert the dock's last focused terminal - // Open the dock in fullscreen - // Focus that terminal - - // Shift-escape OFF - // Close the dock - // Return focus to center - - // Behaviors: - // If the dock is shown, hide it - // If the dock is hidden, show it - // If the dock was full screen, open it in last position (bottom or right) - // If the dock was bottom or right, re-open it in that context (and with the previous % width) - - workspace.dock.position = workspace.dock.position.toggle(); - if workspace.dock.position.visible().is_some() { - Self::ensure_not_empty(workspace, cx); - cx.focus(workspace.dock.pane.clone()); - } else { - cx.focus_self(); - } - cx.notify(); - workspace.status_bar().update(cx, |_, cx| cx.notify()); + Self::set_dock_position(workspace, workspace.dock.position.toggle(), cx); } fn move_dock( @@ -143,17 +128,54 @@ impl Dock { &MoveDock(new_anchor): &MoveDock, cx: &mut ViewContext, ) { - // Clear the previous position if the dock is not visible. - workspace.dock.position = DockPosition::Shown(new_anchor); - Self::ensure_not_empty(workspace, cx); - cx.notify(); + Self::set_dock_position(workspace, DockPosition::Shown(new_anchor), cx); } - pub fn render(&self, _theme: &Theme, anchor: DockAnchor) -> Option { + pub fn render( + &self, + theme: &Theme, + anchor: DockAnchor, + cx: &mut RenderContext, + ) -> Option { + let style = &theme.workspace.dock; self.position .visible() .filter(|current_anchor| *current_anchor == anchor) - .map(|_| ChildView::new(self.pane.clone()).boxed()) + .map(|anchor| match anchor { + DockAnchor::Bottom | DockAnchor::Right => { + let mut panel_style = style.panel.clone(); + if anchor == DockAnchor::Bottom { + panel_style.margin = Margin { + top: panel_style.margin.top, + ..Default::default() + }; + } else { + panel_style.margin = Margin { + left: panel_style.margin.left, + ..Default::default() + }; + } + FlexItem::new( + Container::new(ChildView::new(self.pane.clone()).boxed()) + .with_style(style.panel) + .boxed(), + ) + .flex(style.flex, true) + .boxed() + } + DockAnchor::Expanded => Container::new( + MouseEventHandler::new::(0, cx, |_state, _cx| { + Container::new(ChildView::new(self.pane.clone()).boxed()) + .with_style(style.maximized) + .boxed() + }) + .capture_all() + .with_cursor_style(CursorStyle::Arrow) + .boxed(), + ) + .with_background_color(style.wash_color) + .boxed(), + }) } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index f8e95dcb3325796a47263fc3dfb6c5c38bb3a35b..5f4a7a1962b5cbefda56b7e87ea5c3b20b20aa17 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1,8 +1,7 @@ use super::{ItemHandle, SplitDirection}; use crate::{ - dock::{DockAnchor, MoveDock}, - toolbar::Toolbar, - Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, Workspace, + dock::MoveDock, toolbar::Toolbar, Item, NewFile, NewSearch, NewTerminal, WeakItemHandle, + Workspace, }; use anyhow::Result; use collections::{HashMap, HashSet, VecDeque}; @@ -25,7 +24,7 @@ use gpui::{ }; use project::{Project, ProjectEntryId, ProjectPath}; use serde::Deserialize; -use settings::{Autosave, Settings}; +use settings::{Autosave, DockAnchor, Settings}; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use theme::Theme; use util::ResultExt; @@ -187,6 +186,7 @@ pub fn init(cx: &mut MutableAppContext) { }); } +#[derive(Debug)] pub enum Event { Focused, ActivateItem { local: bool }, @@ -1392,7 +1392,7 @@ impl View for Pane { .with_cursor_style(CursorStyle::PointingHand) .on_down(MouseButton::Left, |e, cx| { cx.dispatch_action(DeployNewMenu { - position: e.position, + position: e.region.lower_right(), }); }) .boxed(), @@ -1422,11 +1422,11 @@ impl View for Pane { .on_down(MouseButton::Left, move |e, cx| { if is_dock { cx.dispatch_action(DeployDockMenu { - position: e.position, + position: e.region.lower_right(), }); } else { cx.dispatch_action(DeploySplitMenu { - position: e.position, + position: e.region.lower_right(), }); } }) @@ -1613,7 +1613,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1701,7 +1702,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // 1. Add with a destination index @@ -1777,7 +1779,8 @@ mod tests { let fs = FakeFs::new(cx.background()); let project = Project::test(fs, None, cx).await; - let (_, workspace) = cx.add_window(|cx| Workspace::new(project, default_item_factory, cx)); + let (_, workspace) = + cx.add_window(|cx| Workspace::new(project, |_, _| unimplemented!(), cx)); let pane = workspace.read_with(cx, |workspace, _| workspace.active_pane().clone()); // singleton view diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index a67910d694b69b2f8c310519a13ec8c7e0bacdb4..1b5cd413b7b46ebd5c81eae080d198e89b88ed2a 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -18,7 +18,7 @@ use client::{ }; use clock::ReplicaId; use collections::{hash_map, HashMap, HashSet}; -use dock::{DefaultItemFactory, Dock, DockAnchor, ToggleDockButton}; +use dock::{DefaultItemFactory, Dock, ToggleDockButton}; use drag_and_drop::DragAndDrop; use futures::{channel::oneshot, FutureExt}; use gpui::{ @@ -41,7 +41,7 @@ use postage::prelude::Stream; use project::{fs, Fs, Project, ProjectEntryId, ProjectPath, ProjectStore, Worktree, WorktreeId}; use searchable::SearchableItemHandle; use serde::Deserialize; -use settings::{Autosave, Settings}; +use settings::{Autosave, DockAnchor, Settings}; use sidebar::{Side, Sidebar, SidebarButtons, ToggleSidebarItem}; use smallvec::SmallVec; use status_bar::StatusBar; @@ -892,6 +892,7 @@ pub struct Workspace { panes: Vec>, panes_by_item: HashMap>, active_pane: ViewHandle, + last_active_center_pane: Option>, status_bar: ViewHandle, dock: Dock, notifications: Vec<(TypeId, usize, Box)>, @@ -987,6 +988,7 @@ impl Workspace { cx.emit_global(WorkspaceCreated(weak_self.clone())); let dock = Dock::new(cx, dock_default_factory); + let dock_pane = dock.pane().clone(); let left_sidebar = cx.add_view(|_| Sidebar::new(Side::Left)); let right_sidebar = cx.add_view(|_| Sidebar::new(Side::Right)); @@ -1011,9 +1013,10 @@ impl Workspace { weak_self, center: PaneGroup::new(center_pane.clone()), dock, - panes: vec![center_pane.clone()], + panes: vec![center_pane.clone(), dock_pane], panes_by_item: Default::default(), active_pane: center_pane.clone(), + last_active_center_pane: Some(center_pane.clone()), status_bar, notifications: Default::default(), client, @@ -1556,10 +1559,6 @@ impl Workspace { Pane::add_item(self, &active_pane, item, true, true, None, cx); } - pub fn add_item_to_dock(&mut self, item: Box, cx: &mut ViewContext) { - Pane::add_item(self, &self.dock.pane(), item, true, true, None, cx); - } - pub fn open_path( &mut self, path: impl Into, @@ -1696,6 +1695,10 @@ impl Workspace { status_bar.set_active_pane(&self.active_pane, cx); }); self.active_item_path_changed(cx); + + if &pane != self.dock.pane() { + self.last_active_center_pane = Some(pane.clone()); + } cx.notify(); } @@ -1715,21 +1718,19 @@ impl Workspace { cx: &mut ViewContext, ) { if let Some(pane) = self.pane(pane_id) { + let is_dock = &pane == self.dock.pane(); match event { - pane::Event::Split(direction) => { + pane::Event::Split(direction) if !is_dock => { self.split_pane(pane, *direction, cx); } - pane::Event::Remove => { - self.remove_pane(pane, cx); - } - pane::Event::Focused => { - self.handle_pane_focused(pane, cx); - } + pane::Event::Remove if !is_dock => self.remove_pane(pane, cx), + pane::Event::Remove if is_dock => Dock::hide(self, cx), + pane::Event::Focused => self.handle_pane_focused(pane, cx), pane::Event::ActivateItem { local } => { if *local { self.unfollow(&pane, cx); } - if pane == self.active_pane { + if &pane == self.active_pane() { self.active_item_path_changed(cx); } } @@ -1747,8 +1748,9 @@ impl Workspace { } } } + _ => {} } - } else { + } else if self.dock.visible_pane().is_none() { error!("pane {} not found", pane_id); } } @@ -1779,6 +1781,10 @@ impl Workspace { for removed_item in pane.read(cx).items() { self.panes_by_item.remove(&removed_item.id()); } + if self.last_active_center_pane == Some(pane) { + self.last_active_center_pane = None; + } + cx.notify(); } else { self.active_item_path_changed(cx); @@ -1797,6 +1803,10 @@ impl Workspace { &self.active_pane } + pub fn dock_pane(&self) -> &ViewHandle { + self.dock.pane() + } + fn project_remote_id_changed(&mut self, remote_id: Option, cx: &mut ViewContext) { if let Some(remote_id) = remote_id { self.remote_entity_subscription = @@ -2582,25 +2592,17 @@ impl View for Workspace { .flex(1., true) .boxed(), ) - .with_children( - self.dock - .render(&theme, DockAnchor::Bottom) - .map(|dock| { - FlexItem::new(dock) - .flex(1., true) - .boxed() - }), - ) + .with_children(self.dock.render( + &theme, + DockAnchor::Bottom, + cx, + )) .boxed(), ) .flex(1., true) .boxed(), ) - .with_children( - self.dock - .render(&theme, DockAnchor::Right) - .map(|dock| FlexItem::new(dock).flex(1., true).boxed()), - ) + .with_children(self.dock.render(&theme, DockAnchor::Right, cx)) .with_children( if self.right_sidebar.read(cx).active_item().is_some() { Some( @@ -2614,13 +2616,7 @@ impl View for Workspace { ) .boxed() }) - .with_children(self.dock.render(&theme, DockAnchor::Expanded).map( - |dock| { - Container::new(dock) - .with_style(theme.workspace.fullscreen_dock) - .boxed() - }, - )) + .with_children(self.dock.render(&theme, DockAnchor::Expanded, cx)) .with_children(self.modal.as_ref().map(|m| { ChildView::new(m) .contained() diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 103db1b51517598addaa1bfed7645a67322ab2c8..73e87b0fce95f1f7d5ba18f7ce1856c03c026c17 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -243,6 +243,7 @@ pub fn initialize_workspace( .detach(); cx.emit(workspace::Event::PaneAdded(workspace.active_pane().clone())); + cx.emit(workspace::Event::PaneAdded(workspace.dock_pane().clone())); let settings = cx.global::(); diff --git a/styles/src/styleTree/workspace.ts b/styles/src/styleTree/workspace.ts index d47b76cd056c8905d03bc1d265cceb972162965d..b775578e3a324dfcab59f9f664544931c2a7daac 100644 --- a/styles/src/styleTree/workspace.ts +++ b/styles/src/styleTree/workspace.ts @@ -156,9 +156,17 @@ export default function workspace(theme: Theme) { width: 400, margin: { right: 10, bottom: 10 }, }, - fullscreenDock: { - background: withOpacity(theme.backgroundColor[500].base, 0.8), - padding: 25, + dock: { + wash_color: withOpacity(theme.backgroundColor[500].base, 0.5), + flex: 0.5, + panel: { + margin: 4, + }, + maximized: { + margin: 32, + border: border(theme, "secondary"), + shadow: modalShadow(theme), + } } }; } From 6b26965074faa9793cb28e05ab816bb917e76102 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Fri, 9 Sep 2022 17:29:52 -0700 Subject: [PATCH 07/26] Permanent fix to repeat MouseRegion Tag failure in Workspace Polish tab bar buttons Co-Authored-By: Mikayla Maki --- .../src/activity_indicator.rs | 2 +- crates/auto_update/src/update_notification.rs | 4 +- crates/chat_panel/src/chat_panel.rs | 2 +- crates/contacts_panel/src/contacts_panel.rs | 72 ++--- crates/contacts_panel/src/notifications.rs | 4 +- crates/context_menu/src/context_menu.rs | 24 +- crates/diagnostics/src/items.rs | 4 +- crates/drag_and_drop/src/drag_and_drop.rs | 5 +- crates/editor/src/editor.rs | 8 +- crates/editor/src/element.rs | 2 +- crates/editor/src/hover_popover.rs | 4 +- crates/editor/src/mouse_context_menu.rs | 6 +- crates/gpui/src/app.rs | 13 +- crates/gpui/src/elements.rs | 7 +- crates/gpui/src/elements/event_handler.rs | 179 ---------- .../gpui/src/elements/mouse_event_handler.rs | 35 +- crates/gpui/src/elements/overlay.rs | 153 ++++++--- crates/gpui/src/elements/tooltip.rs | 53 ++- crates/gpui/src/presenter.rs | 10 +- crates/gpui/src/scene.rs | 32 +- crates/gpui/src/scene/mouse_region.rs | 42 ++- crates/gpui/src/views/select.rs | 30 +- crates/picker/src/picker.rs | 2 +- crates/project_panel/src/project_panel.rs | 10 +- crates/search/src/buffer_search.rs | 4 +- crates/search/src/project_search.rs | 6 +- crates/terminal/src/terminal_view.rs | 7 +- crates/workspace/src/dock.rs | 20 +- crates/workspace/src/pane.rs | 305 ++++++++++-------- crates/workspace/src/sidebar.rs | 4 +- crates/workspace/src/toolbar.rs | 2 +- crates/workspace/src/workspace.rs | 14 +- crates/zed/src/feedback.rs | 2 +- 33 files changed, 532 insertions(+), 535 deletions(-) delete mode 100644 crates/gpui/src/elements/event_handler.rs diff --git a/crates/activity_indicator/src/activity_indicator.rs b/crates/activity_indicator/src/activity_indicator.rs index 7d4d01e8a18893616eac2189e5ccbf559e3d7c10..8f6f4bf627f13b876a30aba743643aa85319634c 100644 --- a/crates/activity_indicator/src/activity_indicator.rs +++ b/crates/activity_indicator/src/activity_indicator.rs @@ -278,7 +278,7 @@ impl View for ActivityIndicator { fn render(&mut self, cx: &mut RenderContext) -> ElementBox { let (icon, message, action) = self.content_to_render(cx); - let mut element = MouseEventHandler::new::(0, cx, |state, cx| { + let mut element = MouseEventHandler::::new(0, cx, |state, cx| { let theme = &cx .global::() .theme diff --git a/crates/auto_update/src/update_notification.rs b/crates/auto_update/src/update_notification.rs index b0dff3e40c1a51b0f917e973ced424f2145286ef..bbc9b0ea7f6f7e4812a5573d4cb0f7f7e7cd3e6b 100644 --- a/crates/auto_update/src/update_notification.rs +++ b/crates/auto_update/src/update_notification.rs @@ -29,7 +29,7 @@ impl View for UpdateNotification { let theme = cx.global::().theme.clone(); let theme = &theme.update_notification; - MouseEventHandler::new::(0, cx, |state, cx| { + MouseEventHandler::::new(0, cx, |state, cx| { Flex::column() .with_child( Flex::row() @@ -47,7 +47,7 @@ impl View for UpdateNotification { .boxed(), ) .with_child( - MouseEventHandler::new::(0, cx, |state, _| { + MouseEventHandler::::new(0, cx, |state, _| { let style = theme.dismiss_button.style_for(state, false); Svg::new("icons/x_mark_thin_8.svg") .with_color(style.color) diff --git a/crates/chat_panel/src/chat_panel.rs b/crates/chat_panel/src/chat_panel.rs index 3ff7062f40f49630ae31ca1ab1211ca981a55262..6744ae9339ec027623673901218bd53d90d860d6 100644 --- a/crates/chat_panel/src/chat_panel.rs +++ b/crates/chat_panel/src/chat_panel.rs @@ -308,7 +308,7 @@ impl ChatPanel { enum SignInPromptLabel {} Align::new( - MouseEventHandler::new::(0, cx, |mouse_state, _| { + MouseEventHandler::::new(0, cx, |mouse_state, _| { Label::new( "Sign in to use chat".to_string(), if mouse_state.hovered { diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 672730cf229447dc931fd5e4a015601e61be4d63..b5460f4d063714f2107d56e94f04f0d61d8b159b 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -276,7 +276,7 @@ impl ContactsPanel { Section::Offline => "Offline", }; let icon_size = theme.section_icon_size; - MouseEventHandler::new::(section as usize, cx, |_, _| { + MouseEventHandler::
::new(section as usize, cx, |_, _| { Flex::row() .with_child( Svg::new(if is_collapsed { @@ -375,7 +375,7 @@ impl ContactsPanel { let baseline_offset = row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.; - MouseEventHandler::new::(project_id as usize, cx, |mouse_state, cx| { + MouseEventHandler::::new(project_id as usize, cx, |mouse_state, cx| { let tree_branch = *tree_branch.style_for(mouse_state, is_selected); let row = theme.project_row.style_for(mouse_state, is_selected); @@ -424,7 +424,7 @@ impl ContactsPanel { return None; } - let button = MouseEventHandler::new::( + let button = MouseEventHandler::::new( project_id as usize, cx, |state, _| { @@ -529,7 +529,7 @@ impl ContactsPanel { enum ToggleOnline {} let project_id = project_handle.id(); - MouseEventHandler::new::(project_id, cx, |state, cx| { + MouseEventHandler::::new(project_id, cx, |state, cx| { let row = theme.project_row.style_for(state, is_selected); let mut worktree_root_names = String::new(); let project = if let Some(project) = project_handle.upgrade(cx.deref_mut()) { @@ -548,7 +548,7 @@ impl ContactsPanel { Flex::row() .with_child({ let button = - MouseEventHandler::new::(project_id, cx, |state, _| { + MouseEventHandler::::new(project_id, cx, |state, _| { let mut style = *theme.private_button.style_for(state, false); if is_going_online { style.color = theme.disabled_button.color; @@ -636,7 +636,7 @@ impl ContactsPanel { if is_incoming { row.add_children([ - MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + MouseEventHandler::::new(user.id as usize, cx, |mouse_state, _| { let button_style = if is_contact_request_pending { &theme.disabled_button } else { @@ -658,7 +658,7 @@ impl ContactsPanel { .contained() .with_margin_right(button_spacing) .boxed(), - MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + MouseEventHandler::::new(user.id as usize, cx, |mouse_state, _| { let button_style = if is_contact_request_pending { &theme.disabled_button } else { @@ -680,7 +680,7 @@ impl ContactsPanel { ]); } else { row.add_child( - MouseEventHandler::new::(user.id as usize, cx, |mouse_state, _| { + MouseEventHandler::::new(user.id as usize, cx, |mouse_state, _| { let button_style = if is_contact_request_pending { &theme.disabled_button } else { @@ -1071,7 +1071,7 @@ impl View for ContactsPanel { .boxed(), ) .with_child( - MouseEventHandler::new::(0, cx, |_, _| { + MouseEventHandler::::new(0, cx, |_, _| { Svg::new("icons/user_plus_16.svg") .with_color(theme.add_contact_button.color) .constrained() @@ -1102,35 +1102,31 @@ impl View for ContactsPanel { if info.count > 0 { Some( - MouseEventHandler::new::( - 0, - cx, - |state, cx| { - let style = - theme.invite_row.style_for(state, false).clone(); - - let copied = - cx.read_from_clipboard().map_or(false, |item| { - item.text().as_str() == info.url.as_ref() - }); - - Label::new( - format!( - "{} invite link ({} left)", - if copied { "Copied" } else { "Copy" }, - info.count - ), - style.label.clone(), - ) - .aligned() - .left() - .constrained() - .with_height(theme.row_height) - .contained() - .with_style(style.container) - .boxed() - }, - ) + MouseEventHandler::::new(0, cx, |state, cx| { + let style = + theme.invite_row.style_for(state, false).clone(); + + let copied = + cx.read_from_clipboard().map_or(false, |item| { + item.text().as_str() == info.url.as_ref() + }); + + Label::new( + format!( + "{} invite link ({} left)", + if copied { "Copied" } else { "Copy" }, + info.count + ), + style.label.clone(), + ) + .aligned() + .left() + .constrained() + .with_height(theme.row_height) + .contained() + .with_style(style.container) + .boxed() + }) .with_cursor_style(CursorStyle::PointingHand) .on_click(MouseButton::Left, move |_, cx| { cx.write_to_clipboard(ClipboardItem::new( diff --git a/crates/contacts_panel/src/notifications.rs b/crates/contacts_panel/src/notifications.rs index 4cc30560d25b7d7cf9fe60ee512fc1cef95c1bfd..b9a6dba545b820e38fcaf55ae5767e498a3a52c4 100644 --- a/crates/contacts_panel/src/notifications.rs +++ b/crates/contacts_panel/src/notifications.rs @@ -52,7 +52,7 @@ pub fn render_user_notification( .boxed(), ) .with_child( - MouseEventHandler::new::(user.id as usize, cx, |state, _| { + MouseEventHandler::::new(user.id as usize, cx, |state, _| { render_icon_button( theme.dismiss_button.style_for(state, false), "icons/x_mark_thin_8.svg", @@ -90,7 +90,7 @@ pub fn render_user_notification( Flex::row() .with_children(buttons.into_iter().enumerate().map( |(ix, (message, action))| { - MouseEventHandler::new::(ix, cx, |state, _| { + MouseEventHandler::