Show a panel/pane as zoomed only if it's the active item in workspace

This commit is contained in:
Antonio Scandurra 2023-05-17 15:06:58 +02:00
parent f87ae6032e
commit 981129ef8e
7 changed files with 195 additions and 109 deletions

View File

@ -4224,7 +4224,7 @@ impl<T> Hash for WeakViewHandle<T> {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct AnyWeakViewHandle {
window_id: usize,
view_id: usize,

View File

@ -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<ContextMenu>,
dragged_entry_destination: Option<Arc<Path>>,
workspace: WeakViewHandle<Workspace>,
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<Self>) {
if !self.has_focus {
self.has_focus = true;
cx.emit(Event::Focus);
}
}
fn focus_out(&mut self, _: gpui::AnyViewHandle, _: &mut ViewContext<Self>) {
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::<Settings>();
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::<Settings>().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 {

View File

@ -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<String> {
fn icon_label(&self, cx: &WindowContext) -> Option<String> {
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)
}
}

View File

@ -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<String> {
fn icon_label(&self, _: &WindowContext) -> Option<String> {
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<Self>);
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<String>;
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<AnyViewHandle> {
pub fn zoomed_panel(&self) -> Option<Rc<dyn PanelHandle>> {
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<Workspace> {
if let Some(active_entry) = self.active_entry() {
let style = &cx.global::<Settings>().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<Self>) -> AnyElement<Self> {
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::<Settings>().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::<Settings>().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!()
}
}
}

View File

@ -1699,7 +1699,11 @@ impl View for Pane {
}
fn focus_in(&mut self, focused: AnyViewHandle, cx: &mut ViewContext<Self>) {
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<Self>) {

View File

@ -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<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
@ -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<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
@ -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<ActiveCall>>,
active_pane: &ViewHandle<Pane>,
zoomed: Option<&AnyViewHandle>,
app_state: &Arc<AppState>,
cx: &mut ViewContext<Workspace>,
) -> AnyElement<Workspace> {
@ -395,6 +400,7 @@ impl PaneAxis {
follower_state,
active_call,
active_pane,
zoomed,
app_state,
cx,
);

View File

@ -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<AnyViewHandle> {
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<AnyViewHandle> {
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<AnyViewHandle> {
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<AnyViewHandle> {
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<AnyElement<Self>> {
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<Workspace>, 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();
}
}