diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 9cc1ad908a..5cb2e8d931 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -4224,7 +4224,7 @@ impl Hash for WeakViewHandle { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Eq, PartialEq)] pub struct AnyWeakViewHandle { window_id: usize, view_id: usize, diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c448ac4e43..63263b23c7 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -13,7 +13,7 @@ use gpui::{ keymap_matcher::KeymapContext, platform::{CursorStyle, MouseButton, PromptLevel}, AnyElement, AppContext, ClipboardItem, Element, Entity, ModelHandle, Task, View, ViewContext, - ViewHandle, WeakViewHandle, + ViewHandle, WeakViewHandle, WindowContext, }; use menu::{Confirm, SelectNext, SelectPrev}; use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId}; @@ -48,6 +48,7 @@ pub struct ProjectPanel { context_menu: ViewHandle, dragged_entry_destination: Option>, workspace: WeakViewHandle, + has_focus: bool, } #[derive(Copy, Clone)] @@ -139,6 +140,7 @@ pub enum Event { focus_opened_item: bool, }, DockPositionChanged, + Focus, } impl ProjectPanel { @@ -214,6 +216,7 @@ impl ProjectPanel { context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), dragged_entry_destination: None, workspace: workspace.weak_handle(), + has_focus: false, }; this.update_visible_entries(None, cx); @@ -259,7 +262,7 @@ impl ProjectPanel { } } } - Event::DockPositionChanged => {} + _ => {} } }) .detach(); @@ -1338,6 +1341,17 @@ impl View for ProjectPanel { Self::reset_to_default_keymap_context(keymap); keymap.add_identifier("menu"); } + + fn focus_in(&mut self, _: gpui::AnyViewHandle, cx: &mut ViewContext) { + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + } + + fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext) { + self.has_focus = false; + } } impl Entity for ProjectPanel { @@ -1345,7 +1359,7 @@ impl Entity for ProjectPanel { } impl workspace::dock::Panel for ProjectPanel { - fn position(&self, cx: &gpui::WindowContext) -> DockPosition { + fn position(&self, cx: &WindowContext) -> DockPosition { let settings = cx.global::(); match settings.project_panel.dock { settings::ProjectPanelDockPosition::Left => DockPosition::Left, @@ -1369,7 +1383,7 @@ impl workspace::dock::Panel for ProjectPanel { }) } - fn default_size(&self, cx: &gpui::WindowContext) -> f32 { + fn default_size(&self, cx: &WindowContext) -> f32 { cx.global::().project_panel.default_width } @@ -1395,13 +1409,21 @@ impl workspace::dock::Panel for ProjectPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_close_on_event(_: &Self::Event) -> bool { false } + + fn has_focus(&self, _: &WindowContext) -> bool { + self.has_focus + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } impl ClipboardEntry { diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 68783a5bc1..25302d7df5 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -20,6 +20,7 @@ pub enum Event { DockPositionChanged, ZoomIn, ZoomOut, + Focus, } pub struct TerminalPanel { @@ -100,6 +101,7 @@ impl TerminalPanel { pane::Event::Remove => cx.emit(Event::Close), pane::Event::ZoomIn => cx.emit(Event::ZoomIn), pane::Event::ZoomOut => cx.emit(Event::ZoomOut), + pane::Event::Focus => cx.emit(Event::Focus), _ => {} } } @@ -149,6 +151,10 @@ impl View for TerminalPanel { if self.pane.read(cx).items_len() == 0 { self.add_terminal(&Default::default(), cx) } + + if cx.is_self_focused() { + cx.focus(&self.pane); + } } } @@ -211,7 +217,7 @@ impl Panel for TerminalPanel { "Terminals".to_string() } - fn icon_label(&self, cx: &AppContext) -> Option { + fn icon_label(&self, cx: &WindowContext) -> Option { let count = self.pane.read(cx).items_len(); if count == 0 { None @@ -224,11 +230,19 @@ impl Panel for TerminalPanel { matches!(event, Event::DockPositionChanged) } - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool { + fn should_activate_on_event(_: &Self::Event) -> bool { false } - fn should_close_on_event(&self, event: &Event, _: &AppContext) -> bool { + fn should_close_on_event(event: &Event) -> bool { matches!(event, Event::Close) } + + fn has_focus(&self, cx: &WindowContext) -> bool { + self.pane.read(cx).has_focus() + } + + fn is_focus_event(event: &Self::Event) -> bool { + matches!(event, Event::Focus) + } } diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 40e7288de0..edd6a5959f 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -1,9 +1,8 @@ use crate::{StatusItemView, Workspace}; use context_menu::{ContextMenu, ContextMenuItem}; use gpui::{ - elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, - AppContext, Axis, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, - WindowContext, + elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle, Axis, + Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext, }; use serde::Deserialize; use settings::Settings; @@ -16,15 +15,17 @@ pub trait Panel: View { fn default_size(&self, cx: &WindowContext) -> f32; fn icon_path(&self) -> &'static str; fn icon_tooltip(&self) -> String; - fn icon_label(&self, _: &AppContext) -> Option { + fn icon_label(&self, _: &WindowContext) -> Option { None } fn should_change_position_on_event(_: &Self::Event) -> bool; fn should_zoom_in_on_event(_: &Self::Event) -> bool; fn should_zoom_out_on_event(_: &Self::Event) -> bool; fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext); - fn should_activate_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; - fn should_close_on_event(&self, _: &Self::Event, _: &AppContext) -> bool; + fn should_activate_on_event(_: &Self::Event) -> bool; + fn should_close_on_event(_: &Self::Event) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; + fn is_focus_event(_: &Self::Event) -> bool; } pub trait PanelHandle { @@ -37,7 +38,7 @@ pub trait PanelHandle { fn icon_path(&self, cx: &WindowContext) -> &'static str; fn icon_tooltip(&self, cx: &WindowContext) -> String; fn icon_label(&self, cx: &WindowContext) -> Option; - fn is_focused(&self, cx: &WindowContext) -> bool; + fn has_focus(&self, cx: &WindowContext) -> bool; fn as_any(&self) -> &AnyViewHandle; } @@ -81,8 +82,8 @@ where self.read(cx).icon_label(cx) } - fn is_focused(&self, cx: &WindowContext) -> bool { - ViewHandle::is_focused(self, cx) + fn has_focus(&self, cx: &WindowContext) -> bool { + self.read(cx).has_focus(cx) } fn as_any(&self) -> &AnyViewHandle { @@ -170,6 +171,11 @@ impl Dock { self.is_open } + pub fn has_focus(&self, cx: &WindowContext) -> bool { + self.active_panel() + .map_or(false, |panel| panel.has_focus(cx)) + } + pub fn active_panel_index(&self) -> usize { self.active_panel_index } @@ -220,7 +226,7 @@ impl Dock { let subscriptions = [ cx.observe(&panel, |_, _, cx| cx.notify()), cx.subscribe(&panel, |this, panel, event, cx| { - if panel.read(cx).should_activate_on_event(event, cx) { + if T::should_activate_on_event(event) { if let Some(ix) = this .panel_entries .iter() @@ -230,7 +236,7 @@ impl Dock { this.activate_panel(ix, cx); cx.focus(&panel); } - } else if panel.read(cx).should_close_on_event(event, cx) + } else if T::should_close_on_event(event) && this.active_panel().map_or(false, |p| p.id() == panel.id()) { this.set_open(false, cx); @@ -302,10 +308,10 @@ impl Dock { } } - pub fn zoomed_panel(&self) -> Option { + pub fn zoomed_panel(&self) -> Option> { let entry = self.active_entry()?; if entry.zoomed { - Some(entry.panel.as_any().clone()) + Some(entry.panel.clone()) } else { None } @@ -344,6 +350,24 @@ impl Dock { cx.notify(); } } + + pub fn render_placeholder(&self, cx: &WindowContext) -> AnyElement { + if let Some(active_entry) = self.active_entry() { + let style = &cx.global::().theme.workspace.dock; + Empty::new() + .into_any() + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |_, _, _| {}, + ) + .into_any() + } else { + Empty::new().into_any() + } + } } impl Entity for Dock { @@ -357,21 +381,16 @@ impl View for Dock { fn render(&mut self, cx: &mut ViewContext) -> AnyElement { if let Some(active_entry) = self.active_entry() { - if active_entry.zoomed { - Empty::new().into_any() - } else { - let size = self.active_panel_size().unwrap(); - let style = &cx.global::().theme.workspace.dock; - ChildView::new(active_entry.panel.as_any(), cx) - .contained() - .with_style(style.container) - .resizable( - self.position.to_resize_handle_side(), - size, - |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), - ) - .into_any() - } + let style = &cx.global::().theme.workspace.dock; + ChildView::new(active_entry.panel.as_any(), cx) + .contained() + .with_style(style.container) + .resizable( + self.position.to_resize_handle_side(), + active_entry.size, + |dock: &mut Self, size, cx| dock.resize_active_panel(size, cx), + ) + .into_any() } else { Empty::new().into_any() } @@ -604,12 +623,20 @@ pub(crate) mod test { false } - fn should_activate_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_activate_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Activated) } - fn should_close_on_event(&self, event: &Self::Event, _: &gpui::AppContext) -> bool { + fn should_close_on_event(event: &Self::Event) -> bool { matches!(event, TestPanelEvent::Closed) } + + fn has_focus(&self, _cx: &WindowContext) -> bool { + unimplemented!() + } + + fn is_focus_event(_: &Self::Event) -> bool { + unimplemented!() + } } } diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 73738ab413..681a7eda27 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -1699,7 +1699,11 @@ impl View for Pane { } fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext) { - self.has_focus = true; + if !self.has_focus { + self.has_focus = true; + cx.emit(Event::Focus); + } + self.toolbar.update(cx, |toolbar, cx| { toolbar.pane_focus_update(true, cx); }); @@ -1725,8 +1729,6 @@ impl View for Pane { .insert(active_item.id(), focused.downgrade()); } } - - cx.emit(Event::Focus); } fn focus_out(&mut self, _: AnyViewHandle, cx: &mut ViewContext) { diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index a1b35c1d65..5f6d46aa46 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -7,7 +7,7 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::Vector2F}, platform::{CursorStyle, MouseButton}, - Axis, Border, ModelHandle, ViewContext, ViewHandle, + AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle, }; use project::Project; use serde::Deserialize; @@ -72,6 +72,7 @@ impl PaneGroup { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -81,6 +82,7 @@ impl PaneGroup { follower_states, active_call, active_pane, + zoomed, app_state, cx, ) @@ -135,6 +137,7 @@ impl Member { follower_states: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -142,7 +145,7 @@ impl Member { match self { Member::Pane(pane) => { - let pane_element = if pane.read(cx).is_zoomed() { + let pane_element = if Some(&**pane) == zoomed { Empty::new().into_any() } else { ChildView::new(pane, cx).into_any() @@ -274,6 +277,7 @@ impl Member { follower_states, active_call, active_pane, + zoomed, app_state, cx, ), @@ -378,6 +382,7 @@ impl PaneAxis { follower_state: &FollowerStatesByLeader, active_call: Option<&ModelHandle>, active_pane: &ViewHandle, + zoomed: Option<&AnyViewHandle>, app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { @@ -395,6 +400,7 @@ impl PaneAxis { follower_state, active_call, active_pane, + zoomed, app_state, cx, ); diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 06832e1385..0fd961a768 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -893,6 +893,8 @@ impl Workspace { dock.update(cx, |dock, cx| dock.set_panel_zoomed(&panel, true, cx)); } else if T::should_zoom_out_on_event(event) { this.zoom_out(cx); + } else if T::is_focus_event(event) { + cx.notify(); } } }) @@ -1309,16 +1311,42 @@ impl Workspace { } } - fn zoomed(&self, cx: &AppContext) -> Option { - self.left_dock - .read(cx) - .zoomed_panel() - .or(self.bottom_dock.read(cx).zoomed_panel()) - .or(self.right_dock.read(cx).zoomed_panel()) - .or_else(|| { - let pane = self.panes.iter().find(|pane| pane.read(cx).is_zoomed())?; - Some(pane.clone().into_any()) - }) + fn zoomed(&self, cx: &WindowContext) -> Option { + self.zoomed_panel_for_dock(DockPosition::Left, cx) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Bottom, cx)) + .or_else(|| self.zoomed_panel_for_dock(DockPosition::Right, cx)) + .or_else(|| self.zoomed_pane(cx)) + } + + fn zoomed_panel_for_dock( + &self, + position: DockPosition, + cx: &WindowContext, + ) -> Option { + let (dock, other_docks) = match position { + DockPosition::Left => (&self.left_dock, [&self.bottom_dock, &self.right_dock]), + DockPosition::Bottom => (&self.bottom_dock, [&self.left_dock, &self.right_dock]), + DockPosition::Right => (&self.right_dock, [&self.left_dock, &self.bottom_dock]), + }; + + let zoomed_panel = dock.read(&cx).zoomed_panel()?; + if other_docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) + && !self.active_pane.read(cx).has_focus() + { + Some(zoomed_panel.as_any().clone()) + } else { + None + } + } + + fn zoomed_pane(&self, cx: &WindowContext) -> Option { + let active_pane = self.active_pane.read(cx); + let docks = [&self.left_dock, &self.bottom_dock, &self.right_dock]; + if active_pane.is_zoomed() && docks.iter().all(|dock| !dock.read(cx).has_focus(cx)) { + Some(self.active_pane.clone().into_any()) + } else { + None + } } pub fn items<'a>( @@ -1433,7 +1461,7 @@ impl Workspace { }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1464,7 +1492,7 @@ impl Workspace { dock.active_panel().cloned() }); if let Some(active_item) = active_item { - if active_item.is_focused(cx) { + if active_item.has_focus(cx) { cx.focus_self(); } else { cx.focus(active_item.as_any()); @@ -1663,7 +1691,6 @@ impl Workspace { }); self.active_item_path_changed(cx); self.last_active_center_pane = Some(pane.downgrade()); - cx.notify(); } self.update_followers( @@ -1677,6 +1704,8 @@ impl Workspace { }), cx, ); + + cx.notify(); } fn handle_pane_event( @@ -1716,9 +1745,11 @@ impl Workspace { self.handle_pane_focused(pane.clone(), cx); } pane::Event::ZoomIn => { - self.zoom_out(cx); - pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); - cx.notify(); + if pane == self.active_pane { + self.zoom_out(cx); + pane.update(cx, |pane, cx| pane.set_zoomed(true, cx)); + cx.notify(); + } } pane::Event::ZoomOut => self.zoom_out(cx), } @@ -2646,6 +2677,33 @@ impl Workspace { }); Self::new(None, 0, project, app_state, cx) } + + fn render_dock(&self, position: DockPosition, cx: &WindowContext) -> Option> { + let dock = match position { + DockPosition::Left => &self.left_dock, + DockPosition::Right => &self.right_dock, + DockPosition::Bottom => &self.bottom_dock, + }; + let active_panel = dock.read(cx).active_panel()?; + let element = if Some(active_panel.as_any()) == self.zoomed(cx).as_ref() { + dock.read(cx).render_placeholder(cx) + } else { + ChildView::new(dock, cx).into_any() + }; + + Some( + element + .constrained() + .dynamically(move |constraint, _, cx| match position { + DockPosition::Left | DockPosition::Right => SizeConstraint::new( + Vector2F::new(20., constraint.min.y()), + Vector2F::new(cx.window_size().x() * 0.8, constraint.max.y()), + ), + _ => constraint, + }) + .into_any(), + ) + } } fn notify_if_database_failed(workspace: &WeakViewHandle, cx: &mut AsyncAppContext) { @@ -2702,25 +2760,7 @@ impl View for Workspace { .with_child({ let project = self.project.clone(); Flex::row() - .with_children( - if self.left_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.left_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Left, cx)) .with_child( Flex::column() .with_child( @@ -2730,44 +2770,18 @@ impl View for Workspace { &self.follower_states_by_leader, self.active_call(), self.active_pane(), + self.zoomed(cx).as_ref(), &self.app_state, cx, )) .flex(1., true), ) .with_children( - if self - .bottom_dock - .read(cx) - .active_panel() - .is_some() - { - Some(ChildView::new(&self.bottom_dock, cx)) - } else { - None - }, + self.render_dock(DockPosition::Bottom, cx), ) .flex(1., true), ) - .with_children( - if self.right_dock.read(cx).active_panel().is_some() { - Some( - ChildView::new(&self.right_dock, cx) - .constrained() - .dynamically(|constraint, _, cx| { - SizeConstraint::new( - Vector2F::new(20., constraint.min.y()), - Vector2F::new( - cx.window_size().x() * 0.8, - constraint.max.y(), - ), - ) - }), - ) - } else { - None - }, - ) + .with_children(self.render_dock(DockPosition::Right, cx)) }) .with_child(Overlay::new( Stack::new() @@ -2810,6 +2824,7 @@ impl View for Workspace { if cx.is_self_focused() { cx.focus(&self.active_pane); } + cx.notify(); } }