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.
This commit is contained in:
2026-05-09 18:10:29 -04:00
parent f136ac55c1
commit 02760ffd10
+85 -16
View File
@@ -183,8 +183,12 @@ impl ElyShell {
) -> AnyElement { ) -> AnyElement {
let active = tab.id() == &snapshot.active_tab_id; let active = tab.id() == &snapshot.active_tab_id;
let tab_id = tab.id().clone(); let tab_id = tab.id().clone();
let close_tab_id = tab.id().clone();
let border = if active { colors::ACCENT } else { colors::HAIRLINE_STRONG }; 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() div()
.id(SharedString::from(format!("split-pane-{}", tab.id().as_str()))) .id(SharedString::from(format!("split-pane-{}", tab.id().as_str())))
@@ -197,43 +201,87 @@ impl ElyShell {
.rounded(px(10.0)) .rounded(px(10.0))
.border_1() .border_1()
.border_color(rgb(border)) .border_color(rgb(border))
.bg(rgba(colors::GLASS_2)) .bg(rgb(0xffffff))
.cursor_pointer() .cursor_pointer()
.hover(|style| style.bg(rgba(colors::GLASS_3))) .hover(|style| style.opacity(0.98))
.active(|style| style.opacity(0.92)) .active(|style| style.opacity(0.92))
.on_click(cx.listener(move |shell, _, window, cx| { .on_click(cx.listener(move |shell, _, window, cx| {
shell.select_tab(&tab_id, window, cx); shell.select_tab(&tab_id, window, cx);
})) }))
.child( .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 {
self.render_web_canvas(tab, snapshot, cx)
}))
.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<Self>,
) -> AnyElement {
let lock_or_globe = if secure { IconName::Search } else { IconName::Globe };
div() div()
.h(px(30.0)) .h(px(30.0))
.px(px(10.0)) .px(px(10.0))
.gap(px(6.0)) .gap(px(8.0))
.flex() .flex()
.items_center() .items_center()
.flex_shrink_0() .flex_shrink_0()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.bg(rgb(0xf6f4ef))
.child( .child(
div() div()
.size(px(6.0)) .size(px(8.0))
.rounded_full() .rounded_full()
.bg(rgb(if active { colors::ACCENT } else { colors::INK_4 })), .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( .child(
div() div()
.flex_1() .flex_1()
.min_w_0() .min_w_0()
.truncate() .truncate()
.text_size(px(12.0)) .text_size(px(11.0))
.font_weight(gpui::FontWeight(500.0)) .text_color(rgb(colors::INK_4))
.text_color(rgb(title_color)) .child(path),
.child(tab.title().to_string()),
),
) )
.child(div().flex_1().min_h_0().overflow_hidden().child(if compact_canvas { .child(
render_compact_split_canvas(tab) div()
} else { .id(SharedString::from(format!(
self.render_web_canvas(tab, snapshot, cx) "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() .into_any_element()
} }
@@ -480,3 +528,24 @@ fn render_compact_split_canvas(tab: &BrowserTab) -> AnyElement {
fn split_canvas_status(tab: &BrowserTab) -> String { fn split_canvas_status(tab: &BrowserTab) -> String {
if tab.url().as_str() == "ely://new-tab" { "Ready".to_string() } else { tab.display_url() } 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://")
}