From 4403eacf1502a9e51eb582218a6eaa11b13af58d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Sat, 9 May 2026 20:06:11 -0400 Subject: [PATCH] Auto-reveal hidden sidebar on cursor reach Cursor-reach reveal now fulfils the "Slide in on cursor reach" description on the design's Hidden-on-hover layout card. - BrowserCore::active_space_sidebar_width() returns the current sidebar tier without cloning a full BrowserSnapshot, so the mouse_move hot path stays cheap. - on_window_mouse_move on the shell root hits-tests the cursor x against REVEAL_THRESHOLD_PX (24 px from the left edge). Outside the reveal/collapse zones it returns immediately, so 99 % of mouse moves never even read the snapshot. - When in HIDDEN mode and the cursor crosses the reveal threshold, expand_hidden_sidebar fires (its early-return on already-expanded state prevents notify spam). When the cursor passes COLLAPSE_THRESHOLD_PX (shell inset + default sidebar width + 24 px buffer), collapse_hidden_sidebar fires. - Click-to-expand on the rail and click-on-backdrop-to-collapse remain as predictable fallbacks. --- crates/ely_app/src/shell/render.rs | 46 ++++++++++++++++++++++++++-- crates/ely_browser_core/src/state.rs | 5 +++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/ely_app/src/shell/render.rs b/crates/ely_app/src/shell/render.rs index ec740eb..4ea3666 100644 --- a/crates/ely_app/src/shell/render.rs +++ b/crates/ely_app/src/shell/render.rs @@ -2,8 +2,9 @@ use ely_browser_core::BrowserSnapshot; use ely_design_system::{colors, spacing}; use ely_domain::{BrowserTab, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX}; use gpui::{ - AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString, - StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, rgba, + AnyElement, Context, InteractiveElement, IntoElement, MouseMoveEvent, ParentElement, Render, + SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, + rgb, rgba, }; use super::chrome::{ @@ -65,6 +66,7 @@ impl ElyShell { .on_action(cx.listener(Self::on_toggle_sidebar)) .on_action(cx.listener(Self::on_zoom_in)) .on_action(cx.listener(Self::on_zoom_out)) + .on_mouse_move(cx.listener(Self::on_window_mouse_move)) .text_color(rgb(colors::INK)) .child(render_wallpaper(snapshot.appearance.wallpaper())) .child( @@ -165,6 +167,35 @@ impl ElyShell { self.render_expanded_sidebar(snapshot, sidebar_width, cx) } + fn on_window_mouse_move( + &mut self, + event: &MouseMoveEvent, + _window: &mut Window, + cx: &mut Context, + ) { + let cursor_x = f32::from(event.position.x); + + if cursor_x >= REVEAL_THRESHOLD_PX && cursor_x <= COLLAPSE_THRESHOLD_PX { + return; + } + + let ShellState::Ready(core) = &self.state else { + return; + }; + let Some(width) = core.active_space_sidebar_width() else { + return; + }; + if width > HIDDEN_SIDEBAR_WIDTH_PX { + return; + } + + if !self.sidebar_hover_expanded && cursor_x < REVEAL_THRESHOLD_PX { + self.expand_hidden_sidebar(cx); + } else if self.sidebar_hover_expanded && cursor_x > COLLAPSE_THRESHOLD_PX { + self.collapse_hidden_sidebar(cx); + } + } + fn render_hidden_sidebar_rail(&mut self, cx: &mut Context) -> AnyElement { div() .id(SharedString::from("hidden-sidebar-rail")) @@ -180,6 +211,17 @@ impl ElyShell { } } +/// Cursor x within this px from the left edge auto-reveals the hidden sidebar. +/// Sized so the user only triggers the reveal when they actually approach the +/// rail, not when they hover the page content. +const REVEAL_THRESHOLD_PX: f32 = 24.0; + +/// Once revealed, cursor x past this px collapses the hidden sidebar back to +/// the rail. Sits past the right edge of the expanded sidebar plus a small +/// buffer so brief overshoots don't ping-pong the state. +const COLLAPSE_THRESHOLD_PX: f32 = + spacing::SHELL_INSET + DEFAULT_SIDEBAR_WIDTH_PX as f32 + 24.0; + fn render_error(message: String) -> AnyElement { div() .size_full() diff --git a/crates/ely_browser_core/src/state.rs b/crates/ely_browser_core/src/state.rs index dff16bb..458d7df 100644 --- a/crates/ely_browser_core/src/state.rs +++ b/crates/ely_browser_core/src/state.rs @@ -465,6 +465,11 @@ impl BrowserCore { .ok_or_else(|| CoreError::SpaceNotFound { id: self.active_space_id.clone() }) } + #[must_use] + pub fn active_space_sidebar_width(&self) -> Option { + self.active_space().ok().map(|space| space.sidebar_width_px()) + } + fn favorites(&self) -> Vec { self.tabs.iter().filter(|tab| tab.flags().favorite).cloned().collect() }