Replace sidebar header with workspace picker row
The design's sidebar opens with an ELY title row plus a three-part workspace picker: a layout-grid tile (opens spaces settings), a glass pill showing the active space's emoji + name (cycles to next space on click), and a plus button (opens spaces settings). The previous header was a static title + space-name label that did not match the design. Cycle behaviour reuses the existing select_next_space core API to avoid introducing a new state flow; the existing SelectNextSpace shortcut delegates through the same helper.
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
pub(crate) mod sidebar_header;
|
||||||
pub(crate) mod wallpaper;
|
pub(crate) mod wallpaper;
|
||||||
|
|
||||||
|
pub(crate) use sidebar_header::render_sidebar_header;
|
||||||
pub(crate) use wallpaper::{WallpaperTheme, render_wallpaper};
|
pub(crate) use wallpaper::{WallpaperTheme, render_wallpaper};
|
||||||
|
|||||||
@@ -0,0 +1,201 @@
|
|||||||
|
use ely_browser_core::BrowserSnapshot;
|
||||||
|
use ely_design_system::colors;
|
||||||
|
use ely_domain::Space;
|
||||||
|
use gpui::{
|
||||||
|
AnyElement, BoxShadow, Context, InteractiveElement, IntoElement, ParentElement, SharedString,
|
||||||
|
StatefulInteractiveElement, Styled, div, hsla, linear_color_stop, linear_gradient, point, px,
|
||||||
|
rgb, rgba,
|
||||||
|
};
|
||||||
|
use gpui_component::IconName;
|
||||||
|
|
||||||
|
use crate::shell::ElyShell;
|
||||||
|
|
||||||
|
pub(crate) fn render_sidebar_header(
|
||||||
|
snapshot: &BrowserSnapshot,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
let active_space = snapshot
|
||||||
|
.spaces
|
||||||
|
.iter()
|
||||||
|
.find(|space| space.id() == &snapshot.active_space_id);
|
||||||
|
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.flex_col()
|
||||||
|
.pt(px(36.0))
|
||||||
|
.px(px(10.0))
|
||||||
|
.pb(px(10.0))
|
||||||
|
.gap(px(8.0))
|
||||||
|
.flex_shrink_0()
|
||||||
|
.child(render_title_row())
|
||||||
|
.child(render_workspace_picker(active_space, cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_title_row() -> AnyElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap(px(4.0))
|
||||||
|
.pl(px(6.0))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_size(px(12.5))
|
||||||
|
.font_weight(gpui::FontWeight(500.0))
|
||||||
|
.text_color(rgb(colors::INK_2))
|
||||||
|
.child("ELY Browser"),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_color(rgb(colors::INK_3))
|
||||||
|
.opacity(0.6)
|
||||||
|
.child(IconName::ChevronDown),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_workspace_picker(
|
||||||
|
active_space: Option<&Space>,
|
||||||
|
cx: &mut Context<ElyShell>,
|
||||||
|
) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap(px(6.0))
|
||||||
|
.py(px(4.0))
|
||||||
|
.px(px(2.0))
|
||||||
|
.child(render_workspaces_tile(cx))
|
||||||
|
.child(render_picker_pill(active_space, cx))
|
||||||
|
.child(render_add_workspace_button(cx))
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_workspaces_tile(cx: &mut Context<ElyShell>) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.id(SharedString::from("workspace-tile"))
|
||||||
|
.size(px(32.0))
|
||||||
|
.rounded(px(9.0))
|
||||||
|
.bg(linear_gradient(
|
||||||
|
135.0,
|
||||||
|
linear_color_stop(hsla(0.0, 0.0, 1.0, 1.0), 0.0),
|
||||||
|
linear_color_stop(hsla(20.0 / 360.0, 0.6, 0.94, 1.0), 1.0),
|
||||||
|
))
|
||||||
|
.shadow(soft_shadow())
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.text_color(rgb(colors::ACCENT))
|
||||||
|
.cursor_pointer()
|
||||||
|
.hover(|style| style.opacity(0.85))
|
||||||
|
.active(|style| style.opacity(0.7))
|
||||||
|
.on_click(cx.listener(|shell, _, window, cx| {
|
||||||
|
shell.open_internal_tab("ely://settings/spaces", window, cx);
|
||||||
|
}))
|
||||||
|
.child(IconName::LayoutDashboard)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_picker_pill(active_space: Option<&Space>, cx: &mut Context<ElyShell>) -> AnyElement {
|
||||||
|
let space_name = active_space
|
||||||
|
.map(|space| space.name().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let space_glyph = active_space
|
||||||
|
.map(|space| space.icon().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id(SharedString::from("workspace-picker"))
|
||||||
|
.flex_1()
|
||||||
|
.min_w_0()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.gap(px(8.0))
|
||||||
|
.px(px(8.0))
|
||||||
|
.py(px(6.0))
|
||||||
|
.rounded(px(9.0))
|
||||||
|
.bg(rgba(PICKER_BG))
|
||||||
|
.shadow(soft_shadow())
|
||||||
|
.cursor_pointer()
|
||||||
|
.hover(|style| style.bg(rgba(PICKER_BG_HOVER)))
|
||||||
|
.active(|style| style.opacity(0.85))
|
||||||
|
.on_click(cx.listener(|shell, _, window, cx| {
|
||||||
|
shell.cycle_to_next_space(window, cx);
|
||||||
|
}))
|
||||||
|
.child(render_workspace_glyph(space_glyph))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex_1()
|
||||||
|
.min_w_0()
|
||||||
|
.truncate()
|
||||||
|
.text_size(px(13.0))
|
||||||
|
.font_weight(gpui::FontWeight(500.0))
|
||||||
|
.text_color(rgb(colors::INK))
|
||||||
|
.child(space_name),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.text_color(rgb(colors::INK_3))
|
||||||
|
.child(IconName::ChevronDown),
|
||||||
|
)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_workspace_glyph(emoji: String) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.size(px(18.0))
|
||||||
|
.rounded(px(5.0))
|
||||||
|
.bg(linear_gradient(
|
||||||
|
135.0,
|
||||||
|
linear_color_stop(hsla(341.0 / 360.0, 0.78, 0.67, 1.0), 0.0),
|
||||||
|
linear_color_stop(hsla(15.0 / 360.0, 0.55, 0.53, 1.0), 1.0),
|
||||||
|
))
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.text_size(px(10.0))
|
||||||
|
.text_color(rgb(0xffffff))
|
||||||
|
.child(emoji)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_add_workspace_button(cx: &mut Context<ElyShell>) -> AnyElement {
|
||||||
|
div()
|
||||||
|
.id(SharedString::from("workspace-add"))
|
||||||
|
.size(px(32.0))
|
||||||
|
.rounded(px(9.0))
|
||||||
|
.bg(rgba(ADD_BUTTON_BG))
|
||||||
|
.shadow(soft_shadow())
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_center()
|
||||||
|
.text_color(rgb(colors::INK_3))
|
||||||
|
.cursor_pointer()
|
||||||
|
.hover(|style| style.bg(rgba(PICKER_BG)).text_color(rgb(colors::INK)))
|
||||||
|
.active(|style| style.opacity(0.7))
|
||||||
|
.on_click(cx.listener(|shell, _, window, cx| {
|
||||||
|
shell.open_internal_tab("ely://settings/spaces", window, cx);
|
||||||
|
}))
|
||||||
|
.child(IconName::Plus)
|
||||||
|
.into_any_element()
|
||||||
|
}
|
||||||
|
|
||||||
|
const PICKER_BG: u32 = 0xffffff99;
|
||||||
|
const PICKER_BG_HOVER: u32 = 0xffffffd9;
|
||||||
|
const ADD_BUTTON_BG: u32 = 0xffffff66;
|
||||||
|
|
||||||
|
fn soft_shadow() -> Vec<BoxShadow> {
|
||||||
|
vec![
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(0.0, 0.0, 1.0, 0.7),
|
||||||
|
offset: point(px(0.0), px(1.0)),
|
||||||
|
blur_radius: px(0.0),
|
||||||
|
spread_radius: px(0.0),
|
||||||
|
},
|
||||||
|
BoxShadow {
|
||||||
|
color: hsla(25.0 / 360.0, 0.33, 0.12, 0.08),
|
||||||
|
offset: point(px(0.0), px(0.0)),
|
||||||
|
blur_radius: px(0.0),
|
||||||
|
spread_radius: px(1.0),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ use gpui_component::{
|
|||||||
scroll::ScrollableElement,
|
scroll::ScrollableElement,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::chrome::{WallpaperTheme, render_wallpaper};
|
use super::chrome::{WallpaperTheme, render_sidebar_header, render_wallpaper};
|
||||||
use super::sidebar::{collapsed_sidebar_active, render_command_bar_identity};
|
use super::sidebar::{collapsed_sidebar_active, render_command_bar_identity};
|
||||||
use super::{ElyShell, ShellState, archive_labels::archive_detail_label};
|
use super::{ElyShell, ShellState, archive_labels::archive_detail_label};
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ impl ElyShell {
|
|||||||
.bg(rgba(PANEL_BG))
|
.bg(rgba(PANEL_BG))
|
||||||
.shadow(panel_shadow())
|
.shadow(panel_shadow())
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.child(self.render_sidebar_header(snapshot))
|
.child(render_sidebar_header(snapshot, cx))
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
@@ -265,38 +265,6 @@ impl ElyShell {
|
|||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_sidebar_header(&self, snapshot: &BrowserSnapshot) -> AnyElement {
|
|
||||||
let space_name = snapshot
|
|
||||||
.spaces
|
|
||||||
.iter()
|
|
||||||
.find(|s| s.id() == &snapshot.active_space_id)
|
|
||||||
.map(|s| s.name().to_string())
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
div()
|
|
||||||
.pt(px(48.0))
|
|
||||||
.px(px(14.0))
|
|
||||||
.pb(px(10.0))
|
|
||||||
.flex()
|
|
||||||
.flex_col()
|
|
||||||
.gap_1()
|
|
||||||
.flex_shrink_0()
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_size(px(15.0))
|
|
||||||
.font_semibold()
|
|
||||||
.text_color(rgb(colors::INK))
|
|
||||||
.child("ELY Browser"),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.text_size(px(11.0))
|
|
||||||
.text_color(rgb(colors::INK_3))
|
|
||||||
.child(space_name),
|
|
||||||
)
|
|
||||||
.into_any_element()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render_sidebar_footer(&self, snapshot: &BrowserSnapshot) -> AnyElement {
|
fn render_sidebar_footer(&self, snapshot: &BrowserSnapshot) -> AnyElement {
|
||||||
div()
|
div()
|
||||||
.px(px(14.0))
|
.px(px(14.0))
|
||||||
|
|||||||
@@ -87,6 +87,10 @@ impl ElyShell {
|
|||||||
window: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
|
self.cycle_to_next_space(window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn cycle_to_next_space(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
if let ShellState::Ready(core) = &mut self.state
|
if let ShellState::Ready(core) = &mut self.state
|
||||||
&& core.select_next_space().is_ok()
|
&& core.select_next_space().is_ok()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user