Files
ELY-Browser/crates/ely_app/src/shell/chrome/sidebar_chrome.rs
T

187 lines
6.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use ely_browser_core::BrowserSnapshot;
use ely_design_system::colors;
use gpui::{
AnyElement, BoxShadow, Context, FontWeight, InteractiveElement, IntoElement, MouseButton,
ParentElement, SharedString, Styled, div, hsla, point, px, rgb, rgba,
};
use gpui_component::IconName;
use crate::shell::ElyShell;
/// Hover/active palette for the vertical sidebar rows.
///
/// The original code reused `ACTIVE_NAV_BG` for both `active` and
/// `hover`, which made an already-active row visually inert under the
/// cursor — the object refused to acknowledge the touch. The four
/// tokens below split that single value into a four-step ladder:
///
/// rest (transparent) → HOVER_NAV_BG → ACTIVE_NAV_BG → ACTIVE_NAV_BG_HOVER
///
/// so every state transition produces a real, perceptible change.
///
/// Values were picked by eye against the warm panel tint, then nudged
/// until the wash on a Dawn-themed panel reads as "you touched this"
/// without competing with the active selection's authority.
pub(crate) const HOVER_NAV_BG: u32 = 0xffffff66; // 40% white wash
pub(crate) const ACTIVE_NAV_BG: u32 = 0xffffffd9; // 85% white card
pub(crate) const ACTIVE_NAV_BG_HOVER: u32 = 0xfffffff2; // 95% white — active + hover
/// Hover tint behind the per-row close (×) button. Was 8% alpha, which
/// was visually indistinguishable from the panel background and made
/// the click target read as inert. Brought to ~30% alpha so the
/// hover registers as a real "press here" surface, matching the
/// confidence of close buttons in Arc/Dia/Zen.
pub(crate) const CLOSE_HOVER_BG: u32 = 0x281e144d;
pub(crate) const UNREAD_BADGE_BG: u32 = 0x281e140f;
/// 50% white inner border that traces every glass panel — the GPUI
/// substitute for the design's `box-shadow: inset 0 0 0 1px rgba(255,255,255,0.5)`.
/// Painted as the panel's own border so it stays part of the frame and
/// never participates in hit testing.
pub(crate) const HIGHLIGHT_BORDER: u32 = 0xffffff80;
pub(crate) const RESIZE_HANDLE_HOVER_BG: u32 = 0xffaa7733;
/// Diameter of the row-level close (×) button.
///
/// 16 px read as a glyph squeezed into a square; 18 px paired with
/// `rounded_full()` reads as a small physical coin you can press.
/// The extra two pixels also give the hover wash room to feel like
/// a halo around the icon instead of a tight collar.
pub(crate) const ROW_CLOSE_SIZE: f32 = 18.0;
pub(crate) fn profile_initial(name: &str) -> String {
name.chars().next().unwrap_or('P').to_uppercase().to_string()
}
pub(crate) fn render_unread_badge(count: u32) -> impl IntoElement {
let label = if count > 99 { "99+".to_string() } else { count.to_string() };
div()
.px(px(6.0))
.py(px(1.0))
.rounded(px(999.0))
.bg(rgba(UNREAD_BADGE_BG))
.text_size(px(10.0))
.font_weight(FontWeight(500.0))
.text_color(rgb(colors::INK_3))
.child(label)
}
pub(crate) fn section_tabs_label(count: usize) -> impl IntoElement {
div()
.pt(px(12.0))
.pb(px(4.0))
.px(px(10.0))
.flex()
.items_center()
.gap(px(6.0))
.child(div().text_color(rgb(colors::INK_4)).child(IconName::Frame))
.child(
div()
.text_size(px(10.5))
.font_weight(FontWeight(500.0))
.text_color(rgb(colors::INK_4))
.child(format!("TABS · {count}")),
)
}
pub(crate) fn section_label(label: &'static str) -> impl IntoElement {
div()
.pt(px(8.0))
.pb(px(4.0))
.px(px(10.0))
.text_size(px(10.5))
.font_weight(FontWeight(500.0))
.text_color(rgb(colors::INK_4))
.child(label)
}
/// Sidebar resize affordance straddling the panel's right edge. The
/// strip lives in the outer wrapper (not the rounded inner panel) so
/// it isn't swallowed by `overflow_hidden`; it spans 4 px outside the
/// panel and 4 px inside, giving the cursor a comfortably wide hit
/// surface that still reads as "edge of the panel" rather than
/// "stripe across the layout".
pub(crate) fn render_sidebar_resize_handle(cx: &mut Context<ElyShell>) -> AnyElement {
div()
.id(SharedString::from("sidebar-resize-handle"))
.absolute()
.top_0()
.bottom_0()
.right(px(-4.0))
.w(px(8.0))
.cursor_col_resize()
.hover(|style| style.bg(rgba(RESIZE_HANDLE_HOVER_BG)))
.on_mouse_down(
MouseButton::Left,
cx.listener(|shell, event: &gpui::MouseDownEvent, _, cx| {
shell.begin_sidebar_resize(f32::from(event.position.x), cx);
}),
)
.into_any_element()
}
/// Maps appearance translucency_pct to a panel rgba u32 tinted by the
/// active wallpaper theme.
///
/// GPUI 0.2.2 has no backdrop-filter, so a true sample-and-blur is
/// impossible. Instead the panel base RGB is pre-tinted with the
/// wallpaper's character (warm cream for Dawn, lavender for Violet, etc.)
/// so the wallpaper's mood reads through every translucent panel without
/// any extra layer or shader. Combined with the user-controlled
/// translucency_pct it gives the design's "frosted glass tied to the
/// wallpaper" effect entirely from real GPUI primitives.
pub(crate) fn panel_bg(snapshot: &BrowserSnapshot) -> u32 {
let pct = snapshot.appearance.translucency_pct().min(100) as u32;
let max_alpha: u32 = 0xff;
let min_alpha: u32 = 0xb3;
let alpha = max_alpha - (pct * (max_alpha - min_alpha)) / 100;
let rgb = wallpaper_panel_rgb(snapshot.appearance.wallpaper());
(rgb << 8) | alpha
}
fn wallpaper_panel_rgb(theme: ely_domain::WallpaperTheme) -> u32 {
match theme {
ely_domain::WallpaperTheme::Dawn => 0xfaf6f0,
ely_domain::WallpaperTheme::Violet => 0xf6f3f8,
ely_domain::WallpaperTheme::Mint => 0xf3f6f1,
ely_domain::WallpaperTheme::Slate => 0xeff1f4,
}
}
pub(crate) fn panel_shadow() -> Vec<BoxShadow> {
vec![
BoxShadow {
color: hsla(25.0 / 360.0, 0.33, 0.12, 0.30),
offset: point(px(0.), px(20.)),
blur_radius: px(50.),
spread_radius: px(-15.),
},
BoxShadow {
color: hsla(0., 0., 1., 0.5),
offset: point(px(0.), px(0.)),
blur_radius: px(0.),
spread_radius: px(1.),
},
]
}
pub(crate) fn soft_shadow() -> Vec<BoxShadow> {
vec![
BoxShadow {
color: hsla(0., 0., 1., 0.7),
offset: point(px(0.), px(1.)),
blur_radius: px(0.),
spread_radius: px(0.),
},
BoxShadow {
color: hsla(25.0 / 360.0, 0.33, 0.12, 0.08),
offset: point(px(0.), px(0.)),
blur_radius: px(0.),
spread_radius: px(1.),
},
]
}