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.
This commit is contained in:
@@ -2,8 +2,9 @@ use ely_browser_core::BrowserSnapshot;
|
|||||||
use ely_design_system::{colors, spacing};
|
use ely_design_system::{colors, spacing};
|
||||||
use ely_domain::{BrowserTab, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX};
|
use ely_domain::{BrowserTab, DEFAULT_SIDEBAR_WIDTH_PX, HIDDEN_SIDEBAR_WIDTH_PX};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, Render, SharedString,
|
AnyElement, Context, InteractiveElement, IntoElement, MouseMoveEvent, ParentElement, Render,
|
||||||
StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px, rgb, rgba,
|
SharedString, StatefulInteractiveElement, Styled, Window, div, prelude::FluentBuilder, px,
|
||||||
|
rgb, rgba,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::chrome::{
|
use super::chrome::{
|
||||||
@@ -65,6 +66,7 @@ impl ElyShell {
|
|||||||
.on_action(cx.listener(Self::on_toggle_sidebar))
|
.on_action(cx.listener(Self::on_toggle_sidebar))
|
||||||
.on_action(cx.listener(Self::on_zoom_in))
|
.on_action(cx.listener(Self::on_zoom_in))
|
||||||
.on_action(cx.listener(Self::on_zoom_out))
|
.on_action(cx.listener(Self::on_zoom_out))
|
||||||
|
.on_mouse_move(cx.listener(Self::on_window_mouse_move))
|
||||||
.text_color(rgb(colors::INK))
|
.text_color(rgb(colors::INK))
|
||||||
.child(render_wallpaper(snapshot.appearance.wallpaper()))
|
.child(render_wallpaper(snapshot.appearance.wallpaper()))
|
||||||
.child(
|
.child(
|
||||||
@@ -165,6 +167,35 @@ impl ElyShell {
|
|||||||
self.render_expanded_sidebar(snapshot, sidebar_width, cx)
|
self.render_expanded_sidebar(snapshot, sidebar_width, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_window_mouse_move(
|
||||||
|
&mut self,
|
||||||
|
event: &MouseMoveEvent,
|
||||||
|
_window: &mut Window,
|
||||||
|
cx: &mut Context<Self>,
|
||||||
|
) {
|
||||||
|
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<Self>) -> AnyElement {
|
fn render_hidden_sidebar_rail(&mut self, cx: &mut Context<Self>) -> AnyElement {
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from("hidden-sidebar-rail"))
|
.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 {
|
fn render_error(message: String) -> AnyElement {
|
||||||
div()
|
div()
|
||||||
.size_full()
|
.size_full()
|
||||||
|
|||||||
@@ -465,6 +465,11 @@ impl BrowserCore {
|
|||||||
.ok_or_else(|| CoreError::SpaceNotFound { id: self.active_space_id.clone() })
|
.ok_or_else(|| CoreError::SpaceNotFound { id: self.active_space_id.clone() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn active_space_sidebar_width(&self) -> Option<u16> {
|
||||||
|
self.active_space().ok().map(|space| space.sidebar_width_px())
|
||||||
|
}
|
||||||
|
|
||||||
fn favorites(&self) -> Vec<BrowserTab> {
|
fn favorites(&self) -> Vec<BrowserTab> {
|
||||||
self.tabs.iter().filter(|tab| tab.flags().favorite).cloned().collect()
|
self.tabs.iter().filter(|tab| tab.flags().favorite).cloned().collect()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user