@@ -17,7 +17,7 @@ use gpui::{
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
-use settings::Settings;
+use settings::{settings_file::SettingsFile, Settings};
use std::{
cmp::Ordering,
collections::{hash_map, HashMap},
@@ -28,7 +28,10 @@ use std::{
};
use theme::ProjectPanelEntry;
use unicase::UniCase;
-use workspace::{dock::DockPosition, Workspace};
+use workspace::{
+ dock::{DockPosition, Panel},
+ Workspace,
+};
const NEW_ENTRY_ID: ProjectEntryId = ProjectEntryId::MAX;
@@ -142,17 +145,6 @@ impl ProjectPanel {
pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
let project = workspace.project().clone();
let project_panel = cx.add_view(|cx: &mut ViewContext<Self>| {
- // Update the dock position when the setting changes.
- let mut old_dock_position = cx.global::<Settings>().project_panel_overrides.dock;
- cx.observe_global::<Settings, _>(move |_, cx| {
- let new_dock_position = cx.global::<Settings>().project_panel_overrides.dock;
- if new_dock_position != old_dock_position {
- old_dock_position = new_dock_position;
- cx.emit(Event::DockPositionChanged);
- }
- })
- .detach();
-
cx.observe(&project, |this, _, cx| {
this.update_visible_entries(None, cx);
cx.notify();
@@ -224,6 +216,18 @@ impl ProjectPanel {
workspace: workspace.weak_handle(),
};
this.update_visible_entries(None, cx);
+
+ // Update the dock position when the setting changes.
+ let mut old_dock_position = this.position(cx);
+ cx.observe_global::<Settings, _>(move |this, cx| {
+ let new_dock_position = this.position(cx);
+ if new_dock_position != old_dock_position {
+ old_dock_position = new_dock_position;
+ cx.emit(Event::DockPositionChanged);
+ }
+ })
+ .detach();
+
this
});
@@ -1342,17 +1346,25 @@ impl Entity for ProjectPanel {
impl workspace::dock::Panel for ProjectPanel {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
- cx.global::<Settings>()
+ let settings = cx.global::<Settings>();
+ settings
.project_panel_overrides
.dock
- .map(Into::into)
- .unwrap_or(DockPosition::Left)
+ .or(settings.project_panel_defaults.dock)
+ .unwrap()
+ .into()
}
fn position_is_valid(&self, position: DockPosition) -> bool {
matches!(position, DockPosition::Left | DockPosition::Right)
}
+ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
+ SettingsFile::update(cx, move |settings| {
+ settings.project_panel.dock = Some(position.into())
+ })
+ }
+
fn icon_path(&self) -> &'static str {
"icons/folder_tree_16.svg"
}
@@ -252,7 +252,7 @@ impl Default for HourFormat {
}
}
-#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
pub struct TerminalSettings {
pub shell: Option<Shell>,
pub working_directory: Option<WorkingDirectory>,
@@ -265,26 +265,7 @@ pub struct TerminalSettings {
pub alternate_scroll: Option<AlternateScroll>,
pub option_as_meta: Option<bool>,
pub copy_on_select: Option<bool>,
- pub dock: DockPosition,
-}
-
-impl Default for TerminalSettings {
- fn default() -> Self {
- Self {
- shell: Default::default(),
- working_directory: Default::default(),
- font_size: Default::default(),
- font_family: Default::default(),
- line_height: Default::default(),
- font_features: Default::default(),
- env: Default::default(),
- blinking: Default::default(),
- alternate_scroll: Default::default(),
- option_as_meta: Default::default(),
- copy_on_select: Default::default(),
- dock: DockPosition::Bottom,
- }
- }
+ pub dock: Option<DockPosition>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
@@ -398,7 +379,8 @@ pub struct SettingsFileContent {
pub autosave: Option<Autosave>,
#[serde(flatten)]
pub editor: EditorSettings,
- pub project_panel: Option<ProjectPanelSettings>,
+ #[serde(default)]
+ pub project_panel: ProjectPanelSettings,
#[serde(default)]
pub journal: JournalSettings,
#[serde(default)]
@@ -493,7 +475,7 @@ impl Settings {
show_call_status_icon: defaults.show_call_status_icon.unwrap(),
vim_mode: defaults.vim_mode.unwrap(),
autosave: defaults.autosave.unwrap(),
- project_panel_defaults: defaults.project_panel.unwrap(),
+ project_panel_defaults: defaults.project_panel,
project_panel_overrides: Default::default(),
editor_defaults: EditorSettings {
tab_size: required(defaults.editor.tab_size),
@@ -601,7 +583,7 @@ impl Settings {
}
}
self.editor_overrides = data.editor;
- self.project_panel_overrides = data.project_panel.unwrap_or_default();
+ self.project_panel_overrides = data.project_panel;
self.git_overrides = data.git.unwrap_or_default();
self.journal_overrides = data.journal;
self.terminal_defaults.font_size = data.terminal.font_size;
@@ -4,9 +4,12 @@ use gpui::{
WeakViewHandle,
};
use project::Project;
-use settings::{Settings, WorkingDirectory};
+use settings::{settings_file::SettingsFile, Settings, WorkingDirectory};
use util::ResultExt;
-use workspace::{dock::{Panel, DockPosition}, pane, DraggedItem, Pane, Workspace};
+use workspace::{
+ dock::{DockPosition, Panel},
+ pane, DraggedItem, Pane, Workspace,
+};
pub fn init(cx: &mut AppContext) {
cx.add_action(TerminalPanel::add_terminal);
@@ -33,7 +36,8 @@ impl TerminalPanel {
old_dock_position = new_dock_position;
cx.emit(Event::DockPositionChanged);
}
- }).detach();
+ })
+ .detach();
let this = cx.weak_handle();
let pane = cx.add_view(|cx| {
@@ -146,13 +150,25 @@ impl View for TerminalPanel {
impl Panel for TerminalPanel {
fn position(&self, cx: &gpui::WindowContext) -> DockPosition {
- cx.global::<Settings>().terminal_overrides.dock.into()
+ let settings = cx.global::<Settings>();
+ settings
+ .terminal_overrides
+ .dock
+ .or(settings.terminal_defaults.dock)
+ .unwrap()
+ .into()
}
fn position_is_valid(&self, _: DockPosition) -> bool {
true
}
+ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
+ SettingsFile::update(cx, move |settings| {
+ settings.terminal.dock = Some(position.into());
+ });
+ }
+
fn icon_path(&self) -> &'static str {
"icons/terminal_12.svg"
}
@@ -1,4 +1,5 @@
use crate::{StatusItemView, Workspace};
+use context_menu::{ContextMenu, ContextMenuItem};
use gpui::{
elements::*, impl_actions, platform::CursorStyle, platform::MouseButton, AnyViewHandle,
AppContext, Entity, Subscription, View, ViewContext, ViewHandle, WeakViewHandle, WindowContext,
@@ -10,6 +11,7 @@ use std::rc::Rc;
pub trait Panel: View {
fn position(&self, cx: &WindowContext) -> DockPosition;
fn position_is_valid(&self, position: DockPosition) -> bool;
+ fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>);
fn icon_path(&self) -> &'static str;
fn icon_tooltip(&self) -> String;
fn icon_label(&self, _: &AppContext) -> Option<String> {
@@ -24,6 +26,7 @@ pub trait PanelHandle {
fn id(&self) -> usize;
fn position(&self, cx: &WindowContext) -> DockPosition;
fn position_is_valid(&self, position: DockPosition, cx: &WindowContext) -> bool;
+ fn set_position(&self, position: DockPosition, cx: &mut WindowContext);
fn icon_path(&self, cx: &WindowContext) -> &'static str;
fn icon_tooltip(&self, cx: &WindowContext) -> String;
fn icon_label(&self, cx: &WindowContext) -> Option<String>;
@@ -47,6 +50,10 @@ where
self.read(cx).position_is_valid(position)
}
+ fn set_position(&self, position: DockPosition, cx: &mut WindowContext) {
+ self.update(cx, |this, cx| this.set_position(position, cx))
+ }
+
fn icon_path(&self, cx: &WindowContext) -> &'static str {
self.read(cx).icon_path()
}
@@ -102,7 +109,25 @@ impl From<settings::DockPosition> for DockPosition {
}
}
+impl From<DockPosition> for settings::DockPosition {
+ fn from(value: DockPosition) -> settings::DockPosition {
+ match value {
+ DockPosition::Left => settings::DockPosition::Left,
+ DockPosition::Bottom => settings::DockPosition::Bottom,
+ DockPosition::Right => settings::DockPosition::Right,
+ }
+ }
+}
+
impl DockPosition {
+ fn to_label(&self) -> &'static str {
+ match self {
+ Self::Left => "left",
+ Self::Bottom => "bottom",
+ Self::Right => "right",
+ }
+ }
+
fn to_resizable_side(self) -> Side {
match self {
Self::Left => Side::Right,
@@ -114,6 +139,7 @@ impl DockPosition {
struct PanelEntry {
panel: Rc<dyn PanelHandle>,
+ context_menu: ViewHandle<ContextMenu>,
_subscriptions: [Subscription; 2],
}
@@ -179,8 +205,14 @@ impl Dock {
}),
];
+ let dock_view_id = cx.view_id();
self.panels.push(PanelEntry {
panel: Rc::new(panel),
+ context_menu: cx.add_view(|cx| {
+ let mut menu = ContextMenu::new(dock_view_id, cx);
+ menu.set_position_mode(OverlayPositionMode::Local);
+ menu
+ }),
_subscriptions: subscriptions,
});
cx.notify()
@@ -292,66 +324,109 @@ impl View for PanelButtons {
DockPosition::Bottom => theme.group_bottom,
DockPosition::Right => theme.group_right,
};
+ let menu_corner = match dock_position {
+ DockPosition::Left => AnchorCorner::BottomLeft,
+ DockPosition::Bottom | DockPosition::Right => AnchorCorner::BottomRight,
+ };
let items = dock
.panels
.iter()
- .map(|item| item.panel.clone())
+ .map(|item| (item.panel.clone(), item.context_menu.clone()))
.collect::<Vec<_>>();
Flex::row()
- .with_children(items.into_iter().enumerate().map(|(ix, view)| {
- let action = TogglePanel {
- dock_position,
- item_index: ix,
- };
- MouseEventHandler::<Self, _>::new(ix, cx, |state, cx| {
- let is_active = is_open && ix == active_ix;
- let style = item_style.style_for(state, is_active);
- Flex::row()
- .with_child(
- Svg::new(view.icon_path(cx))
- .with_color(style.icon_color)
- .constrained()
- .with_width(style.icon_size)
- .aligned(),
- )
- .with_children(if let Some(label) = view.icon_label(cx) {
- Some(
- Label::new(label, style.label.text.clone())
- .contained()
- .with_style(style.label.container)
- .aligned(),
+ .with_children(
+ items
+ .into_iter()
+ .enumerate()
+ .map(|(ix, (view, context_menu))| {
+ let action = TogglePanel {
+ dock_position,
+ item_index: ix,
+ };
+
+ Stack::new()
+ .with_child(
+ MouseEventHandler::<Self, _>::new(ix, cx, |state, cx| {
+ let is_active = is_open && ix == active_ix;
+ let style = item_style.style_for(state, is_active);
+ Flex::row()
+ .with_child(
+ Svg::new(view.icon_path(cx))
+ .with_color(style.icon_color)
+ .constrained()
+ .with_width(style.icon_size)
+ .aligned(),
+ )
+ .with_children(if let Some(label) = view.icon_label(cx) {
+ Some(
+ Label::new(label, style.label.text.clone())
+ .contained()
+ .with_style(style.label.container)
+ .aligned(),
+ )
+ } else {
+ None
+ })
+ .constrained()
+ .with_height(style.icon_size)
+ .contained()
+ .with_style(style.container)
+ })
+ .with_cursor_style(CursorStyle::PointingHand)
+ .on_click(MouseButton::Left, {
+ let action = action.clone();
+ move |_, this, cx| {
+ if let Some(workspace) = this.workspace.upgrade(cx) {
+ let action = action.clone();
+ cx.window_context().defer(move |cx| {
+ workspace.update(cx, |workspace, cx| {
+ workspace.toggle_panel(&action, cx)
+ });
+ });
+ }
+ }
+ })
+ .on_click(MouseButton::Right, {
+ let view = view.clone();
+ let menu = context_menu.clone();
+ move |_, _, cx| {
+ const POSITIONS: [DockPosition; 3] = [
+ DockPosition::Left,
+ DockPosition::Right,
+ DockPosition::Bottom,
+ ];
+
+ menu.update(cx, |menu, cx| {
+ let items = POSITIONS
+ .into_iter()
+ .filter(|position| {
+ *position != dock_position
+ && view.position_is_valid(*position, cx)
+ })
+ .map(|position| {
+ let view = view.clone();
+ ContextMenuItem::handler(
+ format!("Dock {}", position.to_label()),
+ move |cx| view.set_position(position, cx),
+ )
+ })
+ .collect();
+ menu.show(Default::default(), menu_corner, items, cx);
+ })
+ }
+ })
+ .with_tooltip::<Self>(
+ ix,
+ view.icon_tooltip(cx),
+ Some(Box::new(action)),
+ tooltip_style.clone(),
+ cx,
+ ),
)
- } else {
- None
- })
- .constrained()
- .with_height(style.icon_size)
- .contained()
- .with_style(style.container)
- })
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(MouseButton::Left, {
- let action = action.clone();
- move |_, this, cx| {
- if let Some(workspace) = this.workspace.upgrade(cx) {
- let action = action.clone();
- cx.window_context().defer(move |cx| {
- workspace.update(cx, |workspace, cx| {
- workspace.toggle_panel(&action, cx)
- });
- });
- }
- }
- })
- .with_tooltip::<Self>(
- ix,
- view.icon_tooltip(cx),
- Some(Box::new(action)),
- tooltip_style.clone(),
- cx,
- )
- }))
+ .with_child(ChildView::new(&context_menu, cx))
+ }),
+ )
.contained()
.with_style(group_style)
.into_any()