Update split view and web surface to glass morphism styling

Split panes use rounded corners, glass backgrounds, and accent dot
indicators matching the design reference. Web surface error page
uses centered layout with proper typography. Remove opaque white
backgrounds in favor of transparent glass surfaces.
This commit is contained in:
2026-05-09 15:47:00 -04:00
parent 16e8cf2e07
commit e621c43757
2 changed files with 70 additions and 139 deletions
+43 -57
View File
@@ -3,7 +3,7 @@ use ely_design_system::{colors, spacing};
use ely_domain::{BrowserTab, SplitAxis, SplitLayout}; use ely_domain::{BrowserTab, SplitAxis, SplitLayout};
use gpui::{ use gpui::{
AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString, AnyElement, Context, InteractiveElement, IntoElement, ParentElement, SharedString,
StatefulInteractiveElement, Styled, Window, div, px, rgb, StatefulInteractiveElement, Styled, Window, div, px, rgb, rgba,
}; };
use gpui_component::{ use gpui_component::{
IconName, Selectable, Sizable, StyledExt, IconName, Selectable, Sizable, StyledExt,
@@ -130,12 +130,11 @@ impl ElyShell {
.flex_1() .flex_1()
.h_full() .h_full()
.min_w_0() .min_w_0()
.p_3() .p(px(14.0))
.gap_3() .gap(px(10.0))
.flex() .flex()
.flex_col() .flex_col()
.overflow_hidden() .overflow_hidden();
.bg(rgb(colors::CANVAS));
let pane_area = div().flex_1().min_h_0().gap_3().overflow_hidden(); let pane_area = div().flex_1().min_h_0().gap_3().overflow_hidden();
let compact_canvas = layout.axis() == &SplitAxis::Vertical && layout.pane_count() >= 4; let compact_canvas = layout.axis() == &SplitAxis::Vertical && layout.pane_count() >= 4;
@@ -176,7 +175,7 @@ impl ElyShell {
fn render_split_pane( fn render_split_pane(
&mut self, &mut self,
index: usize, _index: usize,
tab: &BrowserTab, tab: &BrowserTab,
snapshot: &BrowserSnapshot, snapshot: &BrowserSnapshot,
compact_canvas: bool, compact_canvas: bool,
@@ -184,9 +183,8 @@ 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 pane_number = index + 1; let border = if active { colors::ACCENT } else { colors::HAIRLINE_STRONG };
let border = if active { colors::PRIMARY } else { colors::HAIRLINE_STRONG }; let title_color = if active { colors::INK } else { colors::INK_2 };
let title_color = if active { colors::INK } else { colors::BODY };
div() div()
.id(SharedString::from(format!("split-pane-{}", tab.id().as_str()))) .id(SharedString::from(format!("split-pane-{}", tab.id().as_str())))
@@ -196,41 +194,37 @@ impl ElyShell {
.flex() .flex()
.flex_col() .flex_col()
.overflow_hidden() .overflow_hidden()
.rounded_md() .rounded(px(10.0))
.border_1() .border_1()
.border_color(rgb(border)) .border_color(rgb(border))
.bg(rgb(colors::SURFACE_CARD)) .bg(rgba(colors::GLASS_2))
.cursor_pointer() .cursor_pointer()
.hover(|style| style.bg(rgb(colors::CANVAS_SOFT))) .hover(|style| style.bg(rgba(colors::GLASS_3)))
.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(
div() div()
.h(px(spacing::COMMAND_BAR_HEIGHT - spacing::MD)) .h(px(30.0))
.px_3() .px(px(10.0))
.gap_2() .gap(px(6.0))
.flex() .flex()
.items_center() .items_center()
.border_b_1() .flex_shrink_0()
.border_color(rgb(colors::HAIRLINE))
.bg(rgb(colors::CANVAS_SOFT))
.child(div().text_color(rgb(colors::MUTED)).child(IconName::Frame))
.child( .child(
div() div()
.text_xs() .size(px(6.0))
.font_semibold() .rounded_full()
.text_color(rgb(colors::MUTED)) .bg(rgb(if active { colors::ACCENT } else { colors::INK_4 })),
.child(format!("Pane {pane_number}")),
) )
.child( .child(
div() div()
.flex_1() .flex_1()
.min_w_0() .min_w_0()
.truncate() .truncate()
.text_sm() .text_size(px(12.0))
.font_semibold() .font_weight(gpui::FontWeight(500.0))
.text_color(rgb(title_color)) .text_color(rgb(title_color))
.child(tab.title().to_string()), .child(tab.title().to_string()),
), ),
@@ -255,10 +249,8 @@ impl ElyShell {
.flex() .flex()
.items_center() .items_center()
.justify_center() .justify_center()
.border_1() .rounded(px(10.0))
.border_color(rgb(colors::HAIRLINE)) .bg(rgba(colors::GLASS_2))
.rounded_md()
.bg(rgb(colors::CANVAS_SOFT))
.child( .child(
div() div()
.text_xs() .text_xs()
@@ -391,28 +383,25 @@ impl ElyShell {
cx: &mut Context<Self>, cx: &mut Context<Self>,
) -> Option<AnyElement> { ) -> Option<AnyElement> {
let first_tab_id = layout.panes().first()?.tab_id().clone(); let first_tab_id = layout.panes().first()?.tab_id().clone();
let background = if active { colors::SURFACE_CARD } else { colors::CANVAS }; let bg = if active { colors::GLASS_3 } else { 0x00000000 };
let border = if active { colors::PRIMARY } else { colors::HAIRLINE };
Some( Some(
div() div()
.id(SharedString::from(format!("saved-split-{}", layout.id().as_str()))) .id(SharedString::from(format!("saved-split-{}", layout.id().as_str())))
.rounded_md() .rounded(px(spacing::RADIUS_NAV))
.border_1() .bg(rgba(bg))
.border_color(rgb(border)) .px(px(10.0))
.bg(rgb(background)) .py(px(7.0))
.px_3() .gap(px(10.0))
.py_2()
.gap_2()
.flex() .flex()
.items_center() .items_center()
.cursor_pointer() .cursor_pointer()
.hover(|style| style.bg(rgb(colors::SURFACE_CARD))) .hover(|style| style.bg(rgba(colors::GLASS_3)))
.active(|style| style.opacity(0.82)) .active(|style| style.opacity(0.82))
.on_click(cx.listener(move |shell, _, window, cx| { .on_click(cx.listener(move |shell, _, window, cx| {
shell.select_tab(&first_tab_id, window, cx); shell.select_tab(&first_tab_id, window, cx);
})) }))
.child(div().text_color(rgb(colors::PRIMARY)).child(IconName::Frame)) .child(div().text_color(rgb(colors::ACCENT)).child(IconName::Frame))
.child( .child(
div() div()
.min_w_0() .min_w_0()
@@ -421,16 +410,16 @@ impl ElyShell {
.gap_1() .gap_1()
.child( .child(
div() div()
.text_sm() .text_size(px(13.0))
.font_semibold() .font_weight(gpui::FontWeight(500.0))
.truncate() .truncate()
.text_color(rgb(colors::INK)) .text_color(rgb(colors::INK))
.child(layout.title().to_string()), .child(layout.title().to_string()),
) )
.child( .child(
div() div()
.text_xs() .text_size(px(11.0))
.text_color(rgb(colors::MUTED)) .text_color(rgb(colors::INK_4))
.child(format!("{} panes", layout.pane_count())), .child(format!("{} panes", layout.pane_count())),
), ),
) )
@@ -452,21 +441,18 @@ fn render_compact_split_canvas(tab: &BrowserTab) -> AnyElement {
.flex_1() .flex_1()
.min_h_0() .min_h_0()
.p_2() .p_2()
.bg(rgb(colors::CANVAS_SOFT))
.child( .child(
div() div()
.size_full() .size_full()
.min_h_0() .min_h_0()
.rounded_md() .rounded(px(10.0))
.border_1() .bg(rgba(colors::GLASS_2))
.border_color(rgb(colors::HAIRLINE)) .px(px(10.0))
.bg(rgb(colors::SURFACE_CARD)) .py(px(8.0))
.px_3() .gap(px(8.0))
.py_2()
.gap_2()
.flex() .flex()
.items_center() .items_center()
.child(div().text_color(rgb(colors::MUTED)).child(IconName::Globe)) .child(div().text_color(rgb(colors::INK_4)).child(IconName::Globe))
.child( .child(
div() div()
.min_w_0() .min_w_0()
@@ -475,15 +461,15 @@ fn render_compact_split_canvas(tab: &BrowserTab) -> AnyElement {
.child( .child(
div() div()
.truncate() .truncate()
.text_sm() .text_size(px(13.0))
.font_semibold() .font_weight(gpui::FontWeight(500.0))
.child(tab.title().to_string()), .child(tab.title().to_string()),
) )
.child( .child(
div() div()
.truncate() .truncate()
.text_xs() .text_size(px(11.0))
.text_color(rgb(colors::MUTED)) .text_color(rgb(colors::INK_4))
.child(split_canvas_status(tab)), .child(split_canvas_status(tab)),
), ),
), ),
+27 -82
View File
@@ -1,12 +1,11 @@
use ely_domain::{BrowserTab, TabId}; use ely_domain::{BrowserTab, TabId};
use gpui::{ use gpui::{
AnyElement, App, Entity, ImageSource, InteractiveElement, IntoElement, MouseButton, ObjectFit, AnyElement, App, Entity, ImageSource, InteractiveElement, IntoElement, MouseButton, ObjectFit,
ParentElement, Styled, StyledImage, Window, canvas, div, img, prelude::FluentBuilder, px, rgb, ParentElement, Styled, StyledImage, Window, canvas, div, img, px, rgb,
}; };
use gpui_component::StyledExt;
use super::{ElyShell, web_surface_frame::WebSurfaceFrame}; use super::{ElyShell, web_surface_frame::WebSurfaceFrame};
use ely_design_system::{colors, spacing}; use ely_design_system::colors;
pub(super) fn render_ready_web_surface( pub(super) fn render_ready_web_surface(
frame: &WebSurfaceFrame, frame: &WebSurfaceFrame,
@@ -16,10 +15,7 @@ pub(super) fn render_ready_web_surface(
render_web_surface( render_web_surface(
tab, tab,
state_entity, state_entity,
frame.title_label(), img(ImageSource::Render(frame.image.clone())).size_full().object_fit(ObjectFit::Fill),
frame.url_label().to_string(),
Some(frame.detail_label()),
img(ImageSource::Render(frame.image.clone())).size_full().object_fit(ObjectFit::Contain),
) )
} }
@@ -27,14 +23,7 @@ pub(super) fn render_loading_web_surface(
tab: &BrowserTab, tab: &BrowserTab,
state_entity: Entity<ElyShell>, state_entity: Entity<ElyShell>,
) -> AnyElement { ) -> AnyElement {
render_web_surface( render_web_surface(tab, state_entity, div().size_full())
tab,
state_entity,
tab.title().to_string(),
tab.url().as_str().to_string(),
Some("Rendering".to_string()),
centered_status(tab.title(), tab.url().as_str(), "Rendering page with Servo", colors::BODY),
)
} }
pub(super) fn render_failed_web_surface( pub(super) fn render_failed_web_surface(
@@ -42,66 +31,36 @@ pub(super) fn render_failed_web_surface(
message: &str, message: &str,
state_entity: Entity<ElyShell>, state_entity: Entity<ElyShell>,
) -> AnyElement { ) -> AnyElement {
render_web_surface( render_web_surface(tab, state_entity, error_page(message))
tab,
state_entity,
tab.title().to_string(),
tab.url().as_str().to_string(),
Some("Render failed".to_string()),
centered_status(tab.title(), tab.url().as_str(), message, colors::ERROR),
)
} }
fn render_web_surface_header(title: String, url: String, detail: Option<String>) -> AnyElement { fn error_page(message: &str) -> impl IntoElement {
div()
.h(px(34.0))
.px_3()
.gap_3()
.flex()
.items_center()
.border_b_1()
.border_color(rgb(colors::HAIRLINE))
.bg(rgb(colors::CANVAS_SOFT))
.child(
div()
.min_w_0()
.flex_1()
.truncate()
.text_sm()
.font_semibold()
.text_color(rgb(colors::INK))
.child(title),
)
.when_some(detail, |this, detail| {
this.child(div().text_xs().text_color(rgb(colors::MUTED)).child(detail))
})
.child(
div().max_w(px(420.0)).truncate().text_xs().text_color(rgb(colors::MUTED)).child(url),
)
.into_any_element()
}
fn centered_status(title: &str, url: &str, detail: &str, detail_color: u32) -> impl IntoElement {
div() div()
.size_full() .size_full()
.p_8() .p(px(48.0))
.flex() .flex()
.flex_col() .flex_col()
.items_center() .items_center()
.justify_center() .justify_center()
.gap_2() .gap(px(12.0))
.bg(rgb(colors::SURFACE_CARD)) .child(
.child(div().text_size(px(26.0)).text_color(rgb(colors::INK)).child(title.to_string())) div()
.child(div().text_sm().text_color(rgb(colors::MUTED)).child(url.to_string())) .text_size(px(22.0))
.child(div().text_sm().text_color(rgb(detail_color)).child(detail.to_string())) .font_weight(gpui::FontWeight(400.0))
.text_color(rgb(colors::INK))
.child("Page unavailable"),
)
.child(
div()
.text_size(px(14.0))
.text_color(rgb(colors::INK_3))
.child(message.to_string()),
)
} }
fn render_web_surface( fn render_web_surface(
tab: &BrowserTab, tab: &BrowserTab,
state_entity: Entity<ElyShell>, state_entity: Entity<ElyShell>,
title: String,
url: String,
detail: Option<String>,
content: impl IntoElement, content: impl IntoElement,
) -> AnyElement { ) -> AnyElement {
let input_tab_id = tab.id().clone(); let input_tab_id = tab.id().clone();
@@ -112,30 +71,16 @@ fn render_web_surface(
div() div()
.flex_1() .flex_1()
.h_full() .h_full()
.p(px(spacing::SM)) .min_w_0()
.bg(rgb(colors::CANVAS_SOFT)) .overflow_hidden()
.child( .child(
div() div()
.relative()
.size_full() .size_full()
.overflow_hidden() .overflow_hidden()
.rounded_md() .child(content)
.border_1() .child(render_viewport_tracker(tab.id().clone(), tracker_entity))
.border_color(rgb(colors::HAIRLINE)) .child(render_input_overlay(input_tab_id, input_url, input_entity)),
.bg(rgb(colors::SURFACE_CARD))
.flex()
.flex_col()
.child(render_web_surface_header(title, url, detail))
.child(
div()
.relative()
.flex_1()
.min_h_0()
.overflow_hidden()
.bg(rgb(colors::SURFACE_CARD))
.child(content)
.child(render_viewport_tracker(tab.id().clone(), tracker_entity))
.child(render_input_overlay(input_tab_id, input_url, input_entity)),
),
) )
.into_any_element() .into_any_element()
} }