Fold the workspace picker into one pill and widen the traffic-light reserve
Drops the separate dashboard tile and the top-level "+" button so the sidebar's first row holds a single workspace identity. "+ New workspace" lives inside the disclosure popover next to the existing spaces list, matching that row's geometry instead of inventing a new square tile. The previous 76 px traffic-light reserve only cleared the buttons when the row also carried the tile and "+" — with the pill alone plus its drop-shadow, it slid back behind the green button on the default sidebar width. Widen `TRAFFIC_LIGHT_RESERVE` to 100 px and document the derivation: pill outer left = `29 + RESERVE`, traffic- light group right edge ≈ 112 px, so 100 leaves 17 px of breathing room. Anchor solver and tests track the new geometry.
This commit is contained in:
@@ -25,17 +25,28 @@ const HEADER_PX: f32 = 10.0;
|
|||||||
const PICKER_ROW_PY: f32 = 4.0;
|
const PICKER_ROW_PY: f32 = 4.0;
|
||||||
/// Horizontal padding inside the picker row (left/right).
|
/// Horizontal padding inside the picker row (left/right).
|
||||||
const PICKER_ROW_PX: f32 = 2.0;
|
const PICKER_ROW_PX: f32 = 2.0;
|
||||||
/// Gap between the tile, the pill, and the add button.
|
/// Effective square height of the picker pill row content (matches
|
||||||
const PICKER_ROW_GAP: f32 = 6.0;
|
/// the prior tile/add-button size so vertical rhythm is preserved
|
||||||
/// Square size of the workspaces tile and the add button.
|
/// after the tile and add button were folded into a single pill).
|
||||||
const PICKER_BUTTON_SIZE: f32 = 32.0;
|
const PICKER_BUTTON_SIZE: f32 = 32.0;
|
||||||
/// Visual lift applied to the popover so it nestles just below the
|
/// Visual lift applied to the popover so it nestles just below the
|
||||||
/// picker pill instead of sitting flush on its bottom edge.
|
/// picker pill instead of sitting flush on its bottom edge.
|
||||||
const DISCLOSURE_LIFT: f32 = 6.0;
|
const DISCLOSURE_LIFT: f32 = 6.0;
|
||||||
/// Width reserved at the start of the sidebar's picker row for the
|
/// Width reserved at the start of the sidebar's picker row for the
|
||||||
/// macOS traffic lights so the workspaces tile never collides with
|
/// macOS traffic lights so the picker pill never collides with the
|
||||||
/// the close / minimize / maximize circles.
|
/// close / minimize / maximize circles.
|
||||||
pub(crate) const TRAFFIC_LIGHT_RESERVE: f32 = 76.0;
|
///
|
||||||
|
/// Derivation: traffic-light origin is `SHELL_INSET + 34 = 50 px` from
|
||||||
|
/// the window edge (see `main.rs::TRAFFIC_LIGHT_ORIGIN_X`). The macOS
|
||||||
|
/// system traffic-light group renders ~62 px wide (three ~14 px
|
||||||
|
/// circles with ~10 px gaps), so the rightmost circle reaches
|
||||||
|
/// `50 + 62 = 112 px`. The pill's outer left edge sits at
|
||||||
|
/// `SHELL_INSET + 1 px border + HEADER_PX + PICKER_ROW_PX + RESERVE`
|
||||||
|
/// = `29 + RESERVE`. Setting RESERVE to 100 px puts the pill at
|
||||||
|
/// `129 px` — 17 px of breathing room past the green button, enough
|
||||||
|
/// that the pill's drop-shadow doesn't visually bleed into the
|
||||||
|
/// circles even on Retina sub-pixel positioning.
|
||||||
|
pub(crate) const TRAFFIC_LIGHT_RESERVE: f32 = 100.0;
|
||||||
|
|
||||||
pub(crate) fn render_sidebar_header(
|
pub(crate) fn render_sidebar_header(
|
||||||
shell: &ElyShell,
|
shell: &ElyShell,
|
||||||
@@ -62,46 +73,20 @@ fn render_workspace_picker(
|
|||||||
picker_open: bool,
|
picker_open: bool,
|
||||||
cx: &mut Context<ElyShell>,
|
cx: &mut Context<ElyShell>,
|
||||||
) -> AnyElement {
|
) -> AnyElement {
|
||||||
// The disclosure renders at the window root (see render.rs) so it can
|
// The disclosure renders at the window root (see render.rs) so it
|
||||||
// sit above a fullscreen click-out backdrop and extend past the
|
// can sit above a fullscreen click-out backdrop and extend past
|
||||||
// sidebar's overflow_hidden clip. Picker row keeps just the tile, the
|
// the sidebar's overflow_hidden clip. After folding the dashboard
|
||||||
// pill, and the add button — and reserves space at the start for the
|
// tile and the "+" button into a single workspace identity, the
|
||||||
// macOS traffic lights that float in this same horizontal band.
|
// pill is the sole occupant of the picker row — it just reserves
|
||||||
|
// space at the start for the macOS traffic lights that float in
|
||||||
|
// this same horizontal band.
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.gap(px(PICKER_ROW_GAP))
|
|
||||||
.py(px(PICKER_ROW_PY))
|
.py(px(PICKER_ROW_PY))
|
||||||
.px(px(PICKER_ROW_PX))
|
.px(px(PICKER_ROW_PX))
|
||||||
.pl(px(TRAFFIC_LIGHT_RESERVE))
|
.pl(px(TRAFFIC_LIGHT_RESERVE))
|
||||||
.child(render_workspaces_tile(cx))
|
|
||||||
.child(render_picker_pill(active_space, picker_open, cx))
|
.child(render_picker_pill(active_space, picker_open, 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(PICKER_BUTTON_SIZE))
|
|
||||||
.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.92))
|
|
||||||
.active(|style| style.opacity(0.82))
|
|
||||||
.on_click(cx.listener(|shell, _, window, cx| {
|
|
||||||
shell.open_internal_tab("ely://settings/spaces", window, cx);
|
|
||||||
}))
|
|
||||||
.child(IconName::LayoutDashboard)
|
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,12 +152,7 @@ impl WorkspaceDisclosureAnchor {
|
|||||||
/// upstream, so changing one of those constants moves the popover
|
/// upstream, so changing one of those constants moves the popover
|
||||||
/// with it.
|
/// with it.
|
||||||
pub(crate) fn solve(sidebar_width_px: f32) -> Self {
|
pub(crate) fn solve(sidebar_width_px: f32) -> Self {
|
||||||
let left_px = spacing::SHELL_INSET
|
let left_px = spacing::SHELL_INSET + HEADER_PX + PICKER_ROW_PX + TRAFFIC_LIGHT_RESERVE;
|
||||||
+ HEADER_PX
|
|
||||||
+ PICKER_ROW_PX
|
|
||||||
+ TRAFFIC_LIGHT_RESERVE
|
|
||||||
+ PICKER_BUTTON_SIZE
|
|
||||||
+ PICKER_ROW_GAP;
|
|
||||||
|
|
||||||
let top_px = spacing::SHELL_INSET
|
let top_px = spacing::SHELL_INSET
|
||||||
+ HEADER_PT
|
+ HEADER_PT
|
||||||
@@ -181,17 +161,11 @@ impl WorkspaceDisclosureAnchor {
|
|||||||
+ PICKER_ROW_PY
|
+ PICKER_ROW_PY
|
||||||
+ DISCLOSURE_LIFT;
|
+ DISCLOSURE_LIFT;
|
||||||
|
|
||||||
// Pill spans flex_1 between tile and add button inside the
|
// Pill now owns the picker row alone, so:
|
||||||
// padded picker row. So:
|
|
||||||
// pill_w = sidebar_w
|
// pill_w = sidebar_w
|
||||||
// - 2 * (HEADER_PX + PICKER_ROW_PX) (row insets)
|
// - 2 * (HEADER_PX + PICKER_ROW_PX) (row insets)
|
||||||
// - TRAFFIC_LIGHT_RESERVE (left reserve)
|
// - TRAFFIC_LIGHT_RESERVE (left reserve)
|
||||||
// - 2 * PICKER_BUTTON_SIZE (tile + add)
|
let chrome = 2.0 * (HEADER_PX + PICKER_ROW_PX) + TRAFFIC_LIGHT_RESERVE;
|
||||||
// - 2 * PICKER_ROW_GAP (two gaps)
|
|
||||||
let chrome = 2.0 * (HEADER_PX + PICKER_ROW_PX)
|
|
||||||
+ TRAFFIC_LIGHT_RESERVE
|
|
||||||
+ 2.0 * PICKER_BUTTON_SIZE
|
|
||||||
+ 2.0 * PICKER_ROW_GAP;
|
|
||||||
let width_px = (sidebar_width_px - chrome).max(0.0);
|
let width_px = (sidebar_width_px - chrome).max(0.0);
|
||||||
|
|
||||||
Self { top_px, left_px, width_px }
|
Self { top_px, left_px, width_px }
|
||||||
@@ -230,6 +204,7 @@ pub(crate) fn render_workspace_disclosure(
|
|||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, space)| render_disclosure_row(index, space, &active_id, cx)),
|
.map(|(index, space)| render_disclosure_row(index, space, &active_id, cx)),
|
||||||
)
|
)
|
||||||
|
.child(render_new_workspace_row(cx))
|
||||||
.child(render_disclosure_footer(cx));
|
.child(render_disclosure_footer(cx));
|
||||||
|
|
||||||
fade_in("workspace-disclosure", 140, body).into_any_element()
|
fade_in("workspace-disclosure", 140, body).into_any_element()
|
||||||
@@ -338,24 +313,31 @@ fn render_workspace_glyph(emoji: String) -> AnyElement {
|
|||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_add_workspace_button(cx: &mut Context<ElyShell>) -> AnyElement {
|
fn render_new_workspace_row(cx: &mut Context<ElyShell>) -> AnyElement {
|
||||||
|
// The "+" now lives inside the disclosure as a peer row to the
|
||||||
|
// existing spaces, keeping a single source of "workspace identity"
|
||||||
|
// visible at rest. Click semantics mirror the old top-of-sidebar
|
||||||
|
// button so existing flows keep working.
|
||||||
div()
|
div()
|
||||||
.id(SharedString::from("workspace-add"))
|
.id(SharedString::from("workspace-add"))
|
||||||
.size(px(PICKER_BUTTON_SIZE))
|
|
||||||
.rounded(px(9.0))
|
|
||||||
.bg(rgba(add_button_bg()))
|
|
||||||
.shadow(soft_shadow())
|
|
||||||
.flex()
|
.flex()
|
||||||
.items_center()
|
.items_center()
|
||||||
.justify_center()
|
.gap(px(8.0))
|
||||||
.text_color(rgb(colors::ink_3()))
|
.px(px(8.0))
|
||||||
|
.py(px(6.0))
|
||||||
|
.rounded(px(8.0))
|
||||||
|
.text_size(px(13.0))
|
||||||
|
.font_weight(gpui::FontWeight(500.0))
|
||||||
|
.text_color(rgb(colors::ink_2()))
|
||||||
.cursor_pointer()
|
.cursor_pointer()
|
||||||
.hover(|style| style.bg(rgba(picker_bg())).text_color(rgb(colors::ink())))
|
.hover(|style| style.bg(rgba(disclosure_row_hover_bg())).text_color(rgb(colors::ink())))
|
||||||
.active(|style| style.opacity(0.82))
|
.active(|style| style.opacity(0.85))
|
||||||
.on_click(cx.listener(|shell, _, window, cx| {
|
.on_click(cx.listener(|shell, _, window, cx| {
|
||||||
|
shell.close_workspace_picker(cx);
|
||||||
shell.open_internal_tab("ely://settings/spaces", window, cx);
|
shell.open_internal_tab("ely://settings/spaces", window, cx);
|
||||||
}))
|
}))
|
||||||
.child(IconName::Plus)
|
.child(div().text_color(rgb(colors::ink_3())).child(IconName::Plus))
|
||||||
|
.child(div().flex_1().min_w_0().truncate().child("New workspace"))
|
||||||
.into_any_element()
|
.into_any_element()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,9 +347,6 @@ fn picker_bg() -> u32 {
|
|||||||
fn picker_bg_hover() -> u32 {
|
fn picker_bg_hover() -> u32 {
|
||||||
colors::pick(0xffffffd9, 0x1f1d1bd9)
|
colors::pick(0xffffffd9, 0x1f1d1bd9)
|
||||||
}
|
}
|
||||||
fn add_button_bg() -> u32 {
|
|
||||||
colors::pick(0xffffff66, 0x1f1d1b66)
|
|
||||||
}
|
|
||||||
fn disclosure_bg() -> u32 {
|
fn disclosure_bg() -> u32 {
|
||||||
colors::pick(0xffffffd9, 0x1f1d1bd9)
|
colors::pick(0xffffffd9, 0x1f1d1bd9)
|
||||||
}
|
}
|
||||||
@@ -406,12 +385,13 @@ mod tests {
|
|||||||
fn anchor_lines_up_with_default_sidebar_picker_pill() {
|
fn anchor_lines_up_with_default_sidebar_picker_pill() {
|
||||||
// Default sidebar (280 px) should land the popover at the
|
// Default sidebar (280 px) should land the popover at the
|
||||||
// window-relative position the visual design was tuned for.
|
// window-relative position the visual design was tuned for.
|
||||||
// After the title-row removal the picker is the only header
|
// The picker pill now owns the row alone (tile + add-button
|
||||||
// row, so the anchor sits closer to the top edge.
|
// folded into the disclosure), so the anchor sits flush to
|
||||||
|
// the traffic-light reserve with no extra tile offset.
|
||||||
let anchor = WorkspaceDisclosureAnchor::solve(280.0);
|
let anchor = WorkspaceDisclosureAnchor::solve(280.0);
|
||||||
assert_eq!(anchor.left_px, 142.0);
|
assert_eq!(anchor.left_px, 128.0);
|
||||||
assert_eq!(anchor.top_px, 70.0);
|
assert_eq!(anchor.top_px, 70.0);
|
||||||
assert_eq!(anchor.width_px, 104.0);
|
assert_eq!(anchor.width_px, 156.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -422,8 +402,8 @@ mod tests {
|
|||||||
let wide = WorkspaceDisclosureAnchor::solve(360.0);
|
let wide = WorkspaceDisclosureAnchor::solve(360.0);
|
||||||
assert_eq!(narrow.left_px, wide.left_px);
|
assert_eq!(narrow.left_px, wide.left_px);
|
||||||
assert_eq!(narrow.top_px, wide.top_px);
|
assert_eq!(narrow.top_px, wide.top_px);
|
||||||
assert_eq!(narrow.width_px, 64.0);
|
assert_eq!(narrow.width_px, 116.0);
|
||||||
assert_eq!(wide.width_px, 184.0);
|
assert_eq!(wide.width_px, 236.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user