diff --git a/crates/ely_app/src/shell/chrome/appearance_layout_cards.rs b/crates/ely_app/src/shell/chrome/appearance_layout_cards.rs index 40e954b..34ecced 100644 --- a/crates/ely_app/src/shell/chrome/appearance_layout_cards.rs +++ b/crates/ely_app/src/shell/chrome/appearance_layout_cards.rs @@ -1,6 +1,8 @@ use ely_browser_core::BrowserSnapshot; use ely_design_system::colors; -use ely_domain::{COLLAPSED_SIDEBAR_WIDTH_PX, DEFAULT_SIDEBAR_WIDTH_PX}; +use ely_domain::{ + COLLAPSED_SIDEBAR_WIDTH_PX, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX, +}; use gpui::{ AnyElement, Context, FontWeight, InteractiveElement, IntoElement, ParentElement, SharedString, StatefulInteractiveElement, Styled, div, prelude::FluentBuilder, px, rgb, rgba, @@ -19,7 +21,7 @@ pub(crate) fn render_sidebar_layout_section( .find(|space| space.id() == &snapshot.active_space_id) .map(|space| space.sidebar_width_px()) .unwrap_or(DEFAULT_SIDEBAR_WIDTH_PX); - let compact = active_width <= COLLAPSED_SIDEBAR_WIDTH_PX; + let mode = LayoutMode::from_width(active_width); div() .flex() @@ -43,30 +45,74 @@ pub(crate) fn render_sidebar_layout_section( .child( div() .grid() - .grid_cols(2) + .grid_cols(3) .gap(px(10.0)) .child(render_layout_card( "Single column", "Default ยท workspace + tabs.", - !compact, + mode == LayoutMode::Single, LayoutMode::Single, cx, )) .child(render_layout_card( "Compact", "Icons-only with launcher rail.", - compact, + mode == LayoutMode::Compact, LayoutMode::Compact, cx, + )) + .child(render_layout_card( + "Hidden on hover", + "Slide in on cursor reach.", + mode == LayoutMode::Hidden, + LayoutMode::Hidden, + cx, )), ) .into_any_element() } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Eq, PartialEq)] enum LayoutMode { Single, Compact, + Hidden, +} + +impl LayoutMode { + fn from_width(width: u16) -> Self { + if width <= HIDDEN_SIDEBAR_WIDTH_PX { + Self::Hidden + } else if width <= COLLAPSED_SIDEBAR_WIDTH_PX { + Self::Compact + } else { + Self::Single + } + } + + fn width(self) -> u16 { + match self { + Self::Single => DEFAULT_SIDEBAR_WIDTH_PX, + Self::Compact => COLLAPSED_SIDEBAR_WIDTH_PX, + Self::Hidden => HIDDEN_SIDEBAR_WIDTH_PX, + } + } + + fn id(self) -> &'static str { + match self { + Self::Single => "layout-single", + Self::Compact => "layout-compact", + Self::Hidden => "layout-hidden", + } + } + + fn preview_sidebar(self) -> f32 { + match self { + Self::Single => 56.0, + Self::Compact => 18.0, + Self::Hidden => 6.0, + } + } } fn render_layout_card( @@ -76,13 +122,8 @@ fn render_layout_card( mode: LayoutMode, cx: &mut Context, ) -> AnyElement { - let id = SharedString::from(match mode { - LayoutMode::Single => "layout-single", - LayoutMode::Compact => "layout-compact", - }); - div() - .id(id) + .id(SharedString::from(mode.id())) .p(px(14.0)) .rounded(px(14.0)) .bg(rgba(LAYOUT_CARD_BG)) @@ -95,11 +136,7 @@ fn render_layout_card( .hover(|style| style.opacity(0.94)) .active(|style| style.opacity(0.85)) .on_click(cx.listener(move |shell, _, _, cx| { - let width = match mode { - LayoutMode::Single => DEFAULT_SIDEBAR_WIDTH_PX, - LayoutMode::Compact => COLLAPSED_SIDEBAR_WIDTH_PX, - }; - shell.set_active_sidebar_width(width, cx); + shell.set_active_sidebar_width(mode.width(), cx); })) .child(render_layout_preview(mode)) .child( @@ -125,10 +162,7 @@ fn render_layout_card( } fn render_layout_preview(mode: LayoutMode) -> AnyElement { - let sidebar_width = match mode { - LayoutMode::Single => 56.0, - LayoutMode::Compact => 18.0, - }; + let sidebar_width = mode.preview_sidebar(); div() .h(px(96.0)) diff --git a/crates/ely_app/src/shell/mod.rs b/crates/ely_app/src/shell/mod.rs index c2793e8..f9bb479 100644 --- a/crates/ely_app/src/shell/mod.rs +++ b/crates/ely_app/src/shell/mod.rs @@ -66,6 +66,7 @@ pub struct ElyShell { pub(crate) plugin_search_input: Entity, pub(crate) translucency_slider: Entity, pub(crate) workspace_picker_open: bool, + pub(crate) sidebar_hover_expanded: bool, download_action_error: Option, download_clear_confirmation: bool, download_security_confirmation: Option, @@ -177,6 +178,7 @@ impl ElyShell { plugin_search_input, translucency_slider, workspace_picker_open: false, + sidebar_hover_expanded: false, download_action_error: None, download_clear_confirmation: false, download_security_confirmation: None, diff --git a/crates/ely_app/src/shell/render.rs b/crates/ely_app/src/shell/render.rs index 38cc56a..ec740eb 100644 --- a/crates/ely_app/src/shell/render.rs +++ b/crates/ely_app/src/shell/render.rs @@ -1,9 +1,9 @@ use ely_browser_core::BrowserSnapshot; use ely_design_system::{colors, spacing}; -use ely_domain::BrowserTab; +use ely_domain::{BrowserTab, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX}; use gpui::{ - AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, Styled, Window, - div, px, rgb, rgba, + AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString, + StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, rgba, }; use super::chrome::{ @@ -36,7 +36,9 @@ impl ElyShell { Ok(sidebar_width) => sidebar_width, Err(message) => return render_error(message), }; - let sidebar_collapsed = collapsed_sidebar_active(sidebar_width); + let sidebar_hidden = sidebar_width <= f32::from(HIDDEN_SIDEBAR_WIDTH_PX); + let sidebar_collapsed = collapsed_sidebar_active(sidebar_width) && !sidebar_hidden; + let hover_expanded = sidebar_hidden && self.sidebar_hover_expanded; div() .size_full() @@ -72,13 +74,52 @@ impl ElyShell { .p(px(spacing::SHELL_INSET)) .gap(px(spacing::SIDEBAR_MAIN_GAP)) .flex() - .child(self.render_sidebar(&snapshot, sidebar_width, sidebar_collapsed, cx)) + .child(self.render_sidebar( + &snapshot, + sidebar_width, + sidebar_collapsed, + sidebar_hidden, + cx, + )) .child(self.render_main_pane(&snapshot, &active_tab, sidebar_collapsed, cx)), ) + .when(hover_expanded, |el| { + el.child(self.render_hidden_sidebar_overlay(&snapshot, cx)) + }) .children(render_command_overlay(&snapshot, cx)) .into_any_element() } + fn render_hidden_sidebar_overlay( + &mut self, + snapshot: &BrowserSnapshot, + cx: &mut Context, + ) -> AnyElement { + div() + .absolute() + .inset_0() + .child( + div() + .id(SharedString::from("hidden-sidebar-backdrop")) + .absolute() + .inset_0() + .on_click(cx.listener(|shell, _, _, cx| shell.collapse_hidden_sidebar(cx))), + ) + .child( + div() + .absolute() + .top(px(spacing::SHELL_INSET)) + .left(px(spacing::SHELL_INSET)) + .bottom(px(spacing::SHELL_INSET)) + .child(self.render_expanded_sidebar( + snapshot, + f32::from(DEFAULT_SIDEBAR_WIDTH_PX), + cx, + )), + ) + .into_any_element() + } + fn render_main_pane( &mut self, snapshot: &BrowserSnapshot, @@ -112,13 +153,31 @@ impl ElyShell { snapshot: &BrowserSnapshot, sidebar_width: f32, sidebar_collapsed: bool, + sidebar_hidden: bool, cx: &mut Context, ) -> AnyElement { + if sidebar_hidden { + return self.render_hidden_sidebar_rail(cx); + } if sidebar_collapsed { return self.render_compact_sidebar(snapshot, sidebar_width, cx); } self.render_expanded_sidebar(snapshot, sidebar_width, cx) } + + fn render_hidden_sidebar_rail(&mut self, cx: &mut Context) -> AnyElement { + div() + .id(SharedString::from("hidden-sidebar-rail")) + .w(px(f32::from(HIDDEN_SIDEBAR_WIDTH_PX))) + .h_full() + .rounded(px(spacing::RADIUS_PANE)) + .bg(rgba(0xffffff8c)) + .cursor_pointer() + .hover(|style| style.bg(rgba(0xffffffd9))) + .active(|style| style.opacity(0.78)) + .on_click(cx.listener(|shell, _, _, cx| shell.expand_hidden_sidebar(cx))) + .into_any_element() + } } fn render_error(message: String) -> AnyElement { diff --git a/crates/ely_app/src/shell/sidebar.rs b/crates/ely_app/src/shell/sidebar.rs index ee38d1d..8965e99 100644 --- a/crates/ely_app/src/shell/sidebar.rs +++ b/crates/ely_app/src/shell/sidebar.rs @@ -1,7 +1,8 @@ use ely_browser_core::BrowserSnapshot; use ely_design_system::{colors, spacing}; use ely_domain::{ - ArchivedTab, BrowserTab, COLLAPSED_SIDEBAR_WIDTH_PX, DEFAULT_SIDEBAR_WIDTH_PX, Space, + ArchivedTab, BrowserTab, COLLAPSED_SIDEBAR_WIDTH_PX, DEFAULT_SIDEBAR_WIDTH_PX, + HIDDEN_SIDEBAR_WIDTH_PX, Space, }; use gpui::{ AnyElement, BoxShadow, Context, IntoElement, ParentElement, Styled, Window, div, hsla, point, @@ -124,6 +125,23 @@ impl ElyShell { }; if core.set_space_sidebar_width(&active_space_id, width_px).is_ok() { + if width_px > HIDDEN_SIDEBAR_WIDTH_PX { + self.sidebar_hover_expanded = false; + } + cx.notify(); + } + } + + pub(crate) fn expand_hidden_sidebar(&mut self, cx: &mut Context) { + if !self.sidebar_hover_expanded { + self.sidebar_hover_expanded = true; + cx.notify(); + } + } + + pub(crate) fn collapse_hidden_sidebar(&mut self, cx: &mut Context) { + if self.sidebar_hover_expanded { + self.sidebar_hover_expanded = false; cx.notify(); } } diff --git a/crates/ely_domain/src/lib.rs b/crates/ely_domain/src/lib.rs index ad67f4e..f32b7f5 100644 --- a/crates/ely_domain/src/lib.rs +++ b/crates/ely_domain/src/lib.rs @@ -58,7 +58,10 @@ pub use site_permission::{ SiteOrigin, SitePermissionAuditAction, SitePermissionAuditEvent, SitePermissionDecision, SitePermissionEntry, SitePermissionFeature, }; -pub use space::{ArchivePolicy, COLLAPSED_SIDEBAR_WIDTH_PX, DEFAULT_SIDEBAR_WIDTH_PX, Space}; +pub use space::{ + ArchivePolicy, COLLAPSED_SIDEBAR_WIDTH_PX, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX, + Space, +}; pub use split::{MAX_SPLIT_PANES, SplitAxis, SplitLayout, SplitPane}; pub use sync::{ SyncConnectionState, SyncObjectKind, SyncObjectPolicy, SyncObjectState, SyncObjectStatus, diff --git a/crates/ely_domain/src/space.rs b/crates/ely_domain/src/space.rs index 49e703e..f43e2f7 100644 --- a/crates/ely_domain/src/space.rs +++ b/crates/ely_domain/src/space.rs @@ -4,6 +4,7 @@ use crate::{ProfileId, SpaceId}; pub const DEFAULT_SIDEBAR_WIDTH_PX: u16 = 280; pub const COLLAPSED_SIDEBAR_WIDTH_PX: u16 = 56; +pub const HIDDEN_SIDEBAR_WIDTH_PX: u16 = 8; #[derive(Clone, Debug, Eq, PartialEq)] pub enum ArchivePolicy {