From 02760ffd107dc535cdc797e97bbe4fa98324a977 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 18:10:29 -0400 Subject: [PATCH] Match split pane chrome to design Each split pane now opens with the design's titlebar layout: an accent status dot, a lock/globe glyph keyed off the URL scheme, the host in ink-1 + the path in ink-4, and a close action on the right that selects and closes the pane's tab. The body sits below a 1px hairline on the panel cream so the panes feel like miniature browser windows instead of borderless cards. The pane click target stays on the outer container so clicking anywhere in a pane focuses it without intercepting the close glyph. --- crates/ely_app/src/shell/splits.rs | 125 ++++++++++++++++++++++------- 1 file changed, 97 insertions(+), 28 deletions(-) diff --git a/crates/ely_app/src/shell/splits.rs b/crates/ely_app/src/shell/splits.rs index 2833540..20fe982 100644 --- a/crates/ely_app/src/shell/splits.rs +++ b/crates/ely_app/src/shell/splits.rs @@ -183,8 +183,12 @@ impl ElyShell { ) -> AnyElement { let active = tab.id() == &snapshot.active_tab_id; let tab_id = tab.id().clone(); + let close_tab_id = tab.id().clone(); let border = if active { colors::ACCENT } else { colors::HAIRLINE_STRONG }; - let title_color = if active { colors::INK } else { colors::INK_2 }; + let host = pane_host_label(tab); + let path = pane_path_label(tab); + let secure = pane_url_is_secure(tab); + let dot_color = if active { colors::ACCENT } else { colors::INK_4 }; div() .id(SharedString::from(format!("split-pane-{}", tab.id().as_str()))) @@ -197,38 +201,14 @@ impl ElyShell { .rounded(px(10.0)) .border_1() .border_color(rgb(border)) - .bg(rgba(colors::GLASS_2)) + .bg(rgb(0xffffff)) .cursor_pointer() - .hover(|style| style.bg(rgba(colors::GLASS_3))) + .hover(|style| style.opacity(0.98)) .active(|style| style.opacity(0.92)) .on_click(cx.listener(move |shell, _, window, cx| { shell.select_tab(&tab_id, window, cx); })) - .child( - div() - .h(px(30.0)) - .px(px(10.0)) - .gap(px(6.0)) - .flex() - .items_center() - .flex_shrink_0() - .child( - div() - .size(px(6.0)) - .rounded_full() - .bg(rgb(if active { colors::ACCENT } else { colors::INK_4 })), - ) - .child( - div() - .flex_1() - .min_w_0() - .truncate() - .text_size(px(12.0)) - .font_weight(gpui::FontWeight(500.0)) - .text_color(rgb(title_color)) - .child(tab.title().to_string()), - ), - ) + .child(self.render_split_pane_header(host, path, secure, dot_color, close_tab_id, cx)) .child(div().flex_1().min_h_0().overflow_hidden().child(if compact_canvas { render_compact_split_canvas(tab) } else { @@ -237,6 +217,74 @@ impl ElyShell { .into_any_element() } + fn render_split_pane_header( + &mut self, + host: String, + path: String, + secure: bool, + dot_color: u32, + close_tab_id: ely_domain::TabId, + cx: &mut Context, + ) -> AnyElement { + let lock_or_globe = if secure { IconName::Search } else { IconName::Globe }; + + div() + .h(px(30.0)) + .px(px(10.0)) + .gap(px(8.0)) + .flex() + .items_center() + .flex_shrink_0() + .border_b_1() + .border_color(rgb(colors::HAIRLINE)) + .bg(rgb(0xf6f4ef)) + .child( + div() + .size(px(8.0)) + .rounded_full() + .bg(rgb(dot_color)), + ) + .child( + div() + .text_color(rgb(colors::INK_3)) + .text_size(px(11.0)) + .child(lock_or_globe), + ) + .child( + div() + .text_size(px(11.0)) + .font_weight(gpui::FontWeight(500.0)) + .text_color(rgb(colors::INK)) + .child(host), + ) + .child( + div() + .flex_1() + .min_w_0() + .truncate() + .text_size(px(11.0)) + .text_color(rgb(colors::INK_4)) + .child(path), + ) + .child( + div() + .id(SharedString::from(format!( + "split-pane-close-{}", + close_tab_id.as_str() + ))) + .text_color(rgb(colors::INK_4)) + .text_size(px(11.0)) + .cursor_pointer() + .hover(|style| style.text_color(rgb(colors::INK))) + .on_click(cx.listener(move |shell, _, window, cx| { + shell.select_tab(&close_tab_id, window, cx); + shell.close_active_tab(window, cx); + })) + .child(IconName::Close), + ) + .into_any_element() + } + fn render_split_controls( &mut self, active_axis: &SplitAxis, @@ -480,3 +528,24 @@ fn render_compact_split_canvas(tab: &BrowserTab) -> AnyElement { fn split_canvas_status(tab: &BrowserTab) -> String { if tab.url().as_str() == "ely://new-tab" { "Ready".to_string() } else { tab.display_url() } } + +fn pane_host_label(tab: &BrowserTab) -> String { + tab.url() + .host() + .map(|host| host.to_string()) + .unwrap_or_else(|| tab.title().to_string()) +} + +fn pane_path_label(tab: &BrowserTab) -> String { + let url = tab.url().as_str(); + let path_start = url.find("://").map(|prefix| prefix + 3).unwrap_or(0); + let from_path = &url[path_start..]; + match from_path.find('/') { + Some(slash) => from_path[slash..].to_string(), + None => String::new(), + } +} + +fn pane_url_is_secure(tab: &BrowserTab) -> bool { + tab.url().as_str().starts_with("https://") || tab.url().as_str().starts_with("ely://") +}