mirror of
https://github.com/rust-kotlin/ashell.git
synced 2026-09-22 08:01:00 +00:00
feat: add sidebar collapsible feature
This commit is contained in:
@@ -133,6 +133,7 @@ press_new_key: "Press new key..."
|
||||
settings_open_settings: "Open Settings"
|
||||
settings_open_session: "Open Session"
|
||||
settings_new_ssh: "New SSH"
|
||||
settings_toggle_sidebar: "Toggle Sidebar"
|
||||
settings_toggle_sftp_zoom: "Toggle SFTP Zoom"
|
||||
settings_focus_pane_left: "Focus Pane Left"
|
||||
settings_focus_pane_right: "Focus Pane Right"
|
||||
|
||||
@@ -136,6 +136,7 @@ press_new_key: "请按下新的快捷键..."
|
||||
settings_open_settings: "打开设置"
|
||||
settings_open_session: "打开会话"
|
||||
settings_new_ssh: "新建 SSH"
|
||||
settings_toggle_sidebar: "缩放侧边栏"
|
||||
settings_toggle_sftp_zoom: "缩放 SFTP 面板"
|
||||
settings_focus_pane_left: "向左切换焦点"
|
||||
settings_focus_pane_right: "向右切换焦点"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
pub(crate) const DEFAULT_COLS: u16 = 100;
|
||||
pub(crate) const DEFAULT_ROWS: u16 = 30;
|
||||
pub(crate) const SIDEBAR_WIDTH: f32 = 306.0;
|
||||
pub(crate) const COLLAPSED_SIDEBAR_WIDTH: f32 = 52.0;
|
||||
pub(crate) const TAB_BAR_HEIGHT: f32 = 52.0;
|
||||
pub(crate) const TERMINAL_PADDING_X: f32 = 32.0;
|
||||
pub(crate) const TERMINAL_PADDING_Y: f32 = 32.0;
|
||||
|
||||
@@ -17,6 +17,7 @@ gpui::actions!(
|
||||
OpenSettings,
|
||||
OpenSession,
|
||||
NewSsh,
|
||||
ToggleSidebar,
|
||||
ToggleSftpZoom,
|
||||
FocusPaneLeft,
|
||||
FocusPaneRight,
|
||||
@@ -55,6 +56,11 @@ pub(crate) const WORKSPACE_ACTIONS: &[WorkspaceAction] = &[
|
||||
label_key: "settings_new_ssh",
|
||||
default_suffix: "n",
|
||||
},
|
||||
WorkspaceAction {
|
||||
id: "ToggleSidebar",
|
||||
label_key: "settings_toggle_sidebar",
|
||||
default_suffix: "s",
|
||||
},
|
||||
WorkspaceAction {
|
||||
id: "ToggleSftpZoom",
|
||||
label_key: "settings_toggle_sftp_zoom",
|
||||
@@ -193,6 +199,7 @@ pub(crate) fn unbind_all_workspace_keys(cx: &mut App, config: &ConfigStore) {
|
||||
unbind_action!("OpenSettings", crate::OpenSettings);
|
||||
unbind_action!("OpenSession", crate::OpenSession);
|
||||
unbind_action!("NewSsh", crate::NewSsh);
|
||||
unbind_action!("ToggleSidebar", crate::ToggleSidebar);
|
||||
unbind_action!("ToggleSftpZoom", crate::ToggleSftpZoom);
|
||||
unbind_action!("FocusPaneLeft", crate::FocusPaneLeft);
|
||||
unbind_action!("FocusPaneRight", crate::FocusPaneRight);
|
||||
@@ -246,6 +253,7 @@ fn bind_workspace_actions(cx: &mut App, config: &ConfigStore) {
|
||||
bind_action!("OpenSettings", crate::OpenSettings);
|
||||
bind_action!("OpenSession", crate::OpenSession);
|
||||
bind_action!("NewSsh", crate::NewSsh);
|
||||
bind_action!("ToggleSidebar", crate::ToggleSidebar);
|
||||
bind_action!("ToggleSftpZoom", crate::ToggleSftpZoom);
|
||||
bind_action!("FocusPaneLeft", crate::FocusPaneLeft);
|
||||
bind_action!("FocusPaneRight", crate::FocusPaneRight);
|
||||
|
||||
@@ -245,6 +245,8 @@ pub(crate) struct Ashell {
|
||||
pub(crate) drag_split_origin: Option<gpui::Point<Pixels>>,
|
||||
pub(crate) terminal_marked_text: Option<String>,
|
||||
pub(crate) sftp_panel_minimized: bool,
|
||||
pub(crate) sidebar_collapsed: bool,
|
||||
pub(crate) collapsed_saved_scroll_handle: gpui::ScrollHandle,
|
||||
pub(crate) prev_monitoring_size: Option<Pixels>,
|
||||
pub(crate) status: SharedString,
|
||||
pub(crate) config: ConfigStore,
|
||||
@@ -467,6 +469,8 @@ impl Ashell {
|
||||
dragging_splitter: None,
|
||||
drag_split_origin: None,
|
||||
sftp_panel_minimized: false,
|
||||
sidebar_collapsed: false,
|
||||
collapsed_saved_scroll_handle: gpui::ScrollHandle::new(),
|
||||
prev_monitoring_size: None,
|
||||
status: "ready".into(),
|
||||
config,
|
||||
|
||||
+208
-22
@@ -21,7 +21,7 @@ use rust_i18n::t;
|
||||
|
||||
use crate::{
|
||||
Ashell, PaneLayout,
|
||||
app::constants::{SIDEBAR_WIDTH, TERMINAL_KEY_CONTEXT},
|
||||
app::constants::{COLLAPSED_SIDEBAR_WIDTH, SIDEBAR_WIDTH, TERMINAL_KEY_CONTEXT},
|
||||
sftp::format_mtime,
|
||||
sftp::ops::is_editable_text_file,
|
||||
system::format_bytes,
|
||||
@@ -1516,6 +1516,15 @@ impl Ashell {
|
||||
.child("Ashell"),
|
||||
)
|
||||
.child(div().flex_1())
|
||||
.child(
|
||||
Button::new("sidebar-collapse-toggle")
|
||||
.ghost()
|
||||
.icon(IconName::PanelLeftClose)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.sidebar_collapsed = true;
|
||||
cx.notify();
|
||||
})),
|
||||
)
|
||||
.child(
|
||||
Button::new("sidebar-settings")
|
||||
.ghost()
|
||||
@@ -1714,6 +1723,151 @@ impl Ashell {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_collapsed_sidebar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let sessions = self.config.sessions().to_vec();
|
||||
let active_session_id = self.active_session_id().map(ToOwned::to_owned);
|
||||
|
||||
v_flex()
|
||||
.w_full()
|
||||
.h_full()
|
||||
.min_w(px(0.))
|
||||
.p_2()
|
||||
.border_r_1()
|
||||
.border_color(cx.theme().sidebar_border)
|
||||
.bg(cx.theme().sidebar)
|
||||
.overflow_hidden()
|
||||
.items_center()
|
||||
// Top: expand button only
|
||||
.child(
|
||||
div()
|
||||
.w_full()
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.pb_2()
|
||||
.child(
|
||||
Button::new("sidebar-expand-toggle")
|
||||
.ghost()
|
||||
.icon(IconName::PanelLeftOpen)
|
||||
.on_click(cx.listener(|this, _, _, cx| {
|
||||
this.sidebar_collapsed = false;
|
||||
cx.notify();
|
||||
})),
|
||||
),
|
||||
)
|
||||
// Saved sessions as compact cards
|
||||
.child(
|
||||
div()
|
||||
.relative()
|
||||
.flex_1()
|
||||
.min_h(px(0.))
|
||||
.w_full()
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.id("collapsed-saved-sessions-scroll")
|
||||
.track_scroll(&self.collapsed_saved_scroll_handle)
|
||||
.overflow_y_scroll()
|
||||
.gap_2()
|
||||
.items_center()
|
||||
.children(sessions.into_iter().enumerate().map(
|
||||
|(ix, session)| {
|
||||
let connect_id = session.id.clone();
|
||||
let is_active = active_session_id.as_deref()
|
||||
== Some(session.id.as_str());
|
||||
let name = session.name.clone();
|
||||
|
||||
// Abbreviate: first 1 char for CJK, first 2 chars for Latin
|
||||
let abbrev = {
|
||||
let mut chars = name.chars();
|
||||
if let Some(first) = chars.next() {
|
||||
if first > '\u{2E7F}' {
|
||||
// CJK character range — show 1 char
|
||||
first.to_string()
|
||||
} else {
|
||||
// Latin / ASCII — show first 2 chars
|
||||
let mut s = first.to_string();
|
||||
if let Some(second) = chars.next() {
|
||||
s.push(second);
|
||||
}
|
||||
s
|
||||
}
|
||||
} else {
|
||||
"?".to_string()
|
||||
}
|
||||
};
|
||||
|
||||
div()
|
||||
.id(("collapsed-saved", ix))
|
||||
.w(px(36.))
|
||||
.h(px(36.))
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.rounded_md()
|
||||
.border_1()
|
||||
.border_color(if is_active {
|
||||
cx.theme().primary
|
||||
} else {
|
||||
cx.theme().border
|
||||
})
|
||||
.bg(if is_active {
|
||||
cx.theme().tab_active
|
||||
} else {
|
||||
cx.theme().muted
|
||||
})
|
||||
.cursor_pointer()
|
||||
.hover(|this| this.bg(cx.theme().secondary))
|
||||
.on_mouse_down(
|
||||
MouseButton::Left,
|
||||
cx.listener(move |this, _, _, cx| {
|
||||
this.connect_saved_session(
|
||||
connect_id.clone(),
|
||||
cx,
|
||||
)
|
||||
}),
|
||||
)
|
||||
.tooltip({
|
||||
let tooltip_text = format!("{} {}", name, session.user);
|
||||
move |window, cx| {
|
||||
gpui_component::tooltip::Tooltip::new(tooltip_text.clone()).build(window, cx)
|
||||
}
|
||||
})
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(0.833))
|
||||
.font_weight(FontWeight::BOLD)
|
||||
.text_color(if is_active {
|
||||
cx.theme().primary
|
||||
} else {
|
||||
cx.theme().foreground
|
||||
})
|
||||
.child(abbrev),
|
||||
)
|
||||
},
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.absolute()
|
||||
.top_0()
|
||||
.bottom_0()
|
||||
.left_0()
|
||||
.right_0()
|
||||
.child(
|
||||
gpui_component::scroll::Scrollbar::new(
|
||||
&self.collapsed_saved_scroll_handle,
|
||||
)
|
||||
.id("collapsed-saved-scrollbar")
|
||||
.axis(gpui_component::scroll::ScrollbarAxis::Vertical)
|
||||
.scrollbar_show(
|
||||
gpui_component::scroll::ScrollbarShow::Scrolling,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn render_tab_bar(&self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let active_tab_index = self
|
||||
.active_tab
|
||||
@@ -2187,16 +2341,6 @@ impl Render for Ashell {
|
||||
}
|
||||
}
|
||||
|
||||
let sidebar_area = resizable_panel()
|
||||
.size(px(self
|
||||
.config
|
||||
.workspace_panels()
|
||||
.and_then(|s| s.first().copied())
|
||||
.unwrap_or(SIDEBAR_WIDTH)))
|
||||
.size_range(px(240.)..px(520.))
|
||||
.flex_none()
|
||||
.child(self.sidebar(cx));
|
||||
|
||||
let monitoring_contents = v_flex()
|
||||
.size_full()
|
||||
.when(self.config.monitoring_position() == "Bottom", |this| {
|
||||
@@ -2236,19 +2380,57 @@ impl Render for Ashell {
|
||||
)
|
||||
.into_any_element();
|
||||
|
||||
let main_area = resizable_panel().child(
|
||||
v_flex()
|
||||
let workspace = if self.sidebar_collapsed {
|
||||
h_flex()
|
||||
.size_full()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.child(self.render_tab_bar(cx))
|
||||
.child(body_panel),
|
||||
);
|
||||
.child(
|
||||
div()
|
||||
.flex_none()
|
||||
.w(px(COLLAPSED_SIDEBAR_WIDTH))
|
||||
.h_full()
|
||||
.child(self.render_collapsed_sidebar(cx)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.h_full()
|
||||
.min_w(px(0.))
|
||||
.child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.child(self.render_tab_bar(cx))
|
||||
.child(body_panel),
|
||||
),
|
||||
)
|
||||
.into_any_element()
|
||||
} else {
|
||||
let sidebar_area = resizable_panel()
|
||||
.size(px(self
|
||||
.config
|
||||
.workspace_panels()
|
||||
.and_then(|s| s.first().copied())
|
||||
.unwrap_or(SIDEBAR_WIDTH)))
|
||||
.size_range(px(240.)..px(520.))
|
||||
.flex_none()
|
||||
.child(self.sidebar(cx));
|
||||
|
||||
let workspace = h_resizable("ashell-workspace")
|
||||
.with_state(&self.workspace_panels)
|
||||
.child(sidebar_area)
|
||||
.child(main_area);
|
||||
let main_area = resizable_panel().child(
|
||||
v_flex()
|
||||
.size_full()
|
||||
.relative()
|
||||
.overflow_hidden()
|
||||
.child(self.render_tab_bar(cx))
|
||||
.child(body_panel),
|
||||
);
|
||||
|
||||
h_resizable("ashell-workspace")
|
||||
.with_state(&self.workspace_panels)
|
||||
.child(sidebar_area)
|
||||
.child(main_area)
|
||||
.into_any_element()
|
||||
};
|
||||
|
||||
div()
|
||||
.id("ashell-root")
|
||||
@@ -2259,6 +2441,10 @@ impl Render for Ashell {
|
||||
.on_action(cx.listener(|this, _: &crate::OpenSettings, window, cx| this.show_settings_dialog(window, cx)))
|
||||
.on_action(cx.listener(|this, _: &crate::OpenSession, window, cx| this.show_selector_dialog(window, cx)))
|
||||
.on_action(cx.listener(|this, _: &crate::NewSsh, window, cx| this.show_ssh_dialog(window, cx)))
|
||||
.on_action(cx.listener(|this, _: &crate::ToggleSidebar, _, cx| {
|
||||
this.sidebar_collapsed = !this.sidebar_collapsed;
|
||||
cx.notify();
|
||||
}))
|
||||
.on_action(cx.listener(|this, _: &crate::ToggleSftpZoom, window, cx| {
|
||||
this.toggle_sftp_minimized(window, cx);
|
||||
}))
|
||||
|
||||
@@ -17,6 +17,7 @@ gpui::actions!(ashell_terminal, [TerminalTabKey, TerminalBacktabKey]);
|
||||
pub(crate) use app::keybinding_recorder::{
|
||||
ClosePane, FocusPaneDown, FocusPaneLeft, FocusPaneRight, FocusPaneUp, NewSsh, OpenSession,
|
||||
OpenSettings, SplitPaneDown, SplitPaneLeft, SplitPaneRight, SplitPaneUp, ToggleSftpZoom,
|
||||
ToggleSidebar,
|
||||
};
|
||||
|
||||
pub(crate) use app::{
|
||||
|
||||
Reference in New Issue
Block a user