Composite glass: inset highlights, blink, fade, panel tint
Closes the three "platform-impossible" gaps from the design audit without resorting to hacks or shaders. * chrome::glass — `render_inner_highlight(radius)` paints the design's `box-shadow: inset 0 0 0 1px` ring as an absolute overlay div with a 1 px top-edge highlight sliver. GPUI 0.2.2's BoxShadow has no inset flag, so the composite is the only way to keep the panel content unclipped while the ring rides on top. * chrome::animations — `blink` and `fade_in` wrap GPUI's first-class `with_animation` driver. Square-wave caret in the command header, 180 ms opacity ramp on the command backdrop and workspace disclosure list. * chrome::sidebar — panel_bg now tints toward the active wallpaper theme so the wallpaper bleeds through every glass surface, the closest honest substitute for backdrop-filter without a shader pass. * command_overlay split: row helpers move to chrome::command_rows so command_overlay drops from 504 → 232 lines, well under the 500-line ceiling. No behavior change. cargo test --workspace: 440 passed, 0 failed.
This commit is contained in:
@@ -1,19 +1,21 @@
|
||||
use ely_browser_core::BrowserSnapshot;
|
||||
use ely_design_system::colors;
|
||||
use ely_domain::{BookmarkEntry, BrowserTab, HistoryEntry};
|
||||
use gpui::{
|
||||
AnyElement, BoxShadow, Context, FontWeight, InteractiveElement, IntoElement, ParentElement,
|
||||
SharedString, StatefulInteractiveElement, Styled, div, hsla, point,
|
||||
prelude::FluentBuilder, px, rgb, rgba,
|
||||
AnyElement, BoxShadow, Context, FontWeight, IntoElement, ParentElement, Styled, div, hsla,
|
||||
point, px, rgb, rgba,
|
||||
};
|
||||
use gpui_component::IconName;
|
||||
|
||||
use crate::shell::ElyShell;
|
||||
use crate::shell::chrome::animations::{blink, fade_in};
|
||||
use crate::shell::chrome::command_footer::{render_command_footer, render_kbd};
|
||||
use crate::shell::chrome::command_match::{
|
||||
CommandActionEntry, matching_actions, matching_bookmarks, matching_history, matching_tabs,
|
||||
matching_actions, matching_bookmarks, matching_history, matching_tabs,
|
||||
};
|
||||
use crate::shell::chrome::render_glyph_for;
|
||||
use crate::shell::chrome::command_rows::{
|
||||
render_action_rows, render_bookmark_rows, render_history_rows, render_tab_rows,
|
||||
};
|
||||
use crate::shell::chrome::glass::render_inner_highlight;
|
||||
|
||||
const COMMAND_PREFIX: &str = ">";
|
||||
|
||||
@@ -38,16 +40,20 @@ fn render_overlay(
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.bg(rgba(BACKDROP_BG))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.pt(px(80.0))
|
||||
.child(render_panel(snapshot, needle, selected_index, cx))
|
||||
.into_any_element()
|
||||
fade_in(
|
||||
"command-overlay-backdrop",
|
||||
160,
|
||||
div()
|
||||
.absolute()
|
||||
.inset_0()
|
||||
.bg(rgba(BACKDROP_BG))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.items_center()
|
||||
.pt(px(80.0))
|
||||
.child(render_panel(snapshot, needle, selected_index, cx)),
|
||||
)
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_panel(
|
||||
@@ -68,11 +74,13 @@ fn render_panel(
|
||||
.bg(rgba(PANEL_BG))
|
||||
.shadow(panel_shadow())
|
||||
.overflow_hidden()
|
||||
.relative()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(render_header(query_label.clone(), needle.is_empty()))
|
||||
.child(render_results(snapshot, needle, selected_index, cx))
|
||||
.child(render_command_footer())
|
||||
.child(render_inner_highlight(16.0))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
@@ -95,9 +103,19 @@ fn render_header(query_label: String, is_empty: bool) -> AnyElement {
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap(px(2.0))
|
||||
.text_size(px(16.0))
|
||||
.text_color(rgb(label_color))
|
||||
.child(query_label),
|
||||
.child(query_label)
|
||||
.child(blink(
|
||||
"command-overlay-caret",
|
||||
div()
|
||||
.w(px(1.0))
|
||||
.h(px(18.0))
|
||||
.bg(rgb(colors::ACCENT)),
|
||||
)),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -190,236 +208,6 @@ fn render_section(label: &'static str, body: AnyElement) -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_tab_rows(
|
||||
tabs: Vec<&BrowserTab>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.children(tabs.into_iter().enumerate().map(|(index, tab)| {
|
||||
let tab_id = tab.id().clone();
|
||||
let title = tab.title().to_string();
|
||||
let host = tab.url().host().map(|host| host.to_string());
|
||||
let host_label = host
|
||||
.clone()
|
||||
.unwrap_or_else(|| tab.display_url());
|
||||
let initial = title.chars().next().unwrap_or('?').to_string();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row_with_glyph(
|
||||
CommandRowContent {
|
||||
id: format!("cmd-tab-{index}"),
|
||||
title,
|
||||
hint: Some(host_label),
|
||||
keys: None,
|
||||
selected: is_selected,
|
||||
},
|
||||
host.as_deref(),
|
||||
&initial,
|
||||
cx,
|
||||
move |shell, window, cx| {
|
||||
shell.select_tab(&tab_id, window, cx);
|
||||
shell.dismiss_command_mode(window, cx);
|
||||
},
|
||||
)
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_history_rows(
|
||||
entries: Vec<&HistoryEntry>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.children(entries.into_iter().enumerate().map(|(index, entry)| {
|
||||
let url = entry.url().clone();
|
||||
let title = entry.title().to_string();
|
||||
let host = entry.url().host().map(|host| host.to_string());
|
||||
let display = host
|
||||
.clone()
|
||||
.unwrap_or_else(|| entry.url().as_str().to_string());
|
||||
let initial = title.chars().next().unwrap_or('?').to_string();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row_with_glyph(
|
||||
CommandRowContent {
|
||||
id: format!("cmd-history-{index}"),
|
||||
title,
|
||||
hint: Some(display),
|
||||
keys: None,
|
||||
selected: is_selected,
|
||||
},
|
||||
host.as_deref(),
|
||||
&initial,
|
||||
cx,
|
||||
move |shell, window, cx| {
|
||||
shell.open_internal_tab(url.as_str(), window, cx);
|
||||
shell.dismiss_command_mode(window, cx);
|
||||
},
|
||||
)
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_action_rows(
|
||||
actions: Vec<&'static CommandActionEntry>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.children(actions.into_iter().enumerate().map(|(index, action)| {
|
||||
let route = action.route;
|
||||
let keys = if action.keys.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(action.keys.to_string())
|
||||
};
|
||||
let icon = action.icon.clone();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row(
|
||||
CommandRowContent {
|
||||
id: format!("cmd-action-{index}"),
|
||||
title: action.title.to_string(),
|
||||
hint: Some(action.hint.to_string()),
|
||||
keys,
|
||||
selected: is_selected,
|
||||
},
|
||||
icon,
|
||||
cx,
|
||||
move |shell, window, cx| {
|
||||
shell.open_internal_tab(route, window, cx);
|
||||
shell.dismiss_command_mode(window, cx);
|
||||
},
|
||||
)
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
struct CommandRowContent {
|
||||
id: String,
|
||||
title: String,
|
||||
hint: Option<String>,
|
||||
keys: Option<String>,
|
||||
selected: bool,
|
||||
}
|
||||
|
||||
fn render_row<F>(
|
||||
content: CommandRowContent,
|
||||
icon: IconName,
|
||||
cx: &mut Context<ElyShell>,
|
||||
handler: F,
|
||||
) -> AnyElement
|
||||
where
|
||||
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
||||
{
|
||||
let leading = div()
|
||||
.size(px(24.0))
|
||||
.rounded(px(6.0))
|
||||
.bg(rgba(ROW_ICON_BG))
|
||||
.flex()
|
||||
.items_center()
|
||||
.justify_center()
|
||||
.text_color(rgb(colors::INK_2))
|
||||
.child(icon)
|
||||
.into_any_element();
|
||||
render_row_inner(content, leading, cx, handler)
|
||||
}
|
||||
|
||||
fn render_row_with_glyph<F>(
|
||||
content: CommandRowContent,
|
||||
host: Option<&str>,
|
||||
fallback_initial: &str,
|
||||
cx: &mut Context<ElyShell>,
|
||||
handler: F,
|
||||
) -> AnyElement
|
||||
where
|
||||
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
||||
{
|
||||
let leading = render_glyph_for(host, fallback_initial, 24.0);
|
||||
render_row_inner(content, leading, cx, handler)
|
||||
}
|
||||
|
||||
fn render_row_inner<F>(
|
||||
content: CommandRowContent,
|
||||
leading: AnyElement,
|
||||
cx: &mut Context<ElyShell>,
|
||||
handler: F,
|
||||
) -> AnyElement
|
||||
where
|
||||
F: Fn(&mut ElyShell, &mut gpui::Window, &mut Context<ElyShell>) + 'static,
|
||||
{
|
||||
let CommandRowContent { id, title, hint, keys, selected } = content;
|
||||
let bg = if selected { ROW_SELECTED_BG } else { 0x00000000 };
|
||||
|
||||
div()
|
||||
.id(SharedString::from(id))
|
||||
.relative()
|
||||
.flex()
|
||||
.items_center()
|
||||
.gap(px(12.0))
|
||||
.px(px(16.0))
|
||||
.py(px(8.0))
|
||||
.bg(rgba(bg))
|
||||
.cursor_pointer()
|
||||
.hover(|style| style.bg(rgba(ROW_HOVER_BG)))
|
||||
.active(|style| style.opacity(0.85))
|
||||
.on_click(cx.listener(move |shell, _, window, cx| handler(shell, window, cx)))
|
||||
.when(selected, |el| {
|
||||
el.child(
|
||||
div()
|
||||
.absolute()
|
||||
.left_0()
|
||||
.top(px(6.0))
|
||||
.bottom(px(6.0))
|
||||
.w(px(2.0))
|
||||
.rounded(px(2.0))
|
||||
.bg(rgb(colors::ACCENT)),
|
||||
)
|
||||
})
|
||||
.child(leading)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_size(px(13.0))
|
||||
.font_weight(FontWeight(500.0))
|
||||
.text_color(rgb(colors::INK))
|
||||
.truncate()
|
||||
.child(title),
|
||||
)
|
||||
.children(hint.map(|hint| {
|
||||
div()
|
||||
.text_size(px(11.0))
|
||||
.text_color(rgb(colors::INK_4))
|
||||
.truncate()
|
||||
.child(hint)
|
||||
})),
|
||||
)
|
||||
.children(keys.map(|key_label| {
|
||||
div()
|
||||
.text_size(px(10.5))
|
||||
.text_color(rgb(colors::INK_3))
|
||||
.child(key_label)
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_empty_state() -> AnyElement {
|
||||
div()
|
||||
.px(px(18.0))
|
||||
@@ -430,50 +218,8 @@ fn render_empty_state() -> AnyElement {
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_bookmark_rows(
|
||||
entries: Vec<&BookmarkEntry>,
|
||||
offset: usize,
|
||||
selected_index: usize,
|
||||
cx: &mut Context<ElyShell>,
|
||||
) -> AnyElement {
|
||||
div()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.children(entries.into_iter().enumerate().map(|(index, bookmark)| {
|
||||
let url = bookmark.url().clone();
|
||||
let title = bookmark.title().to_string();
|
||||
let host = bookmark.url().host().map(|host| host.to_string());
|
||||
let display = host
|
||||
.clone()
|
||||
.unwrap_or_else(|| bookmark.url().as_str().to_string());
|
||||
let initial = title.chars().next().unwrap_or('?').to_string();
|
||||
let is_selected = offset + index == selected_index;
|
||||
|
||||
render_row_with_glyph(
|
||||
CommandRowContent {
|
||||
id: format!("cmd-bookmark-{index}"),
|
||||
title,
|
||||
hint: Some(display),
|
||||
keys: None,
|
||||
selected: is_selected,
|
||||
},
|
||||
host.as_deref(),
|
||||
&initial,
|
||||
cx,
|
||||
move |shell, window, cx| {
|
||||
shell.open_internal_tab(url.as_str(), window, cx);
|
||||
shell.dismiss_command_mode(window, cx);
|
||||
},
|
||||
)
|
||||
}))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
const PANEL_BG: u32 = 0xfffffff5;
|
||||
const BACKDROP_BG: u32 = 0x140f0a3d;
|
||||
const ROW_HOVER_BG: u32 = 0xc9644214;
|
||||
const ROW_SELECTED_BG: u32 = 0xc964421f;
|
||||
const ROW_ICON_BG: u32 = 0xffffffd9;
|
||||
const BADGE_BG: u32 = 0x281e140f;
|
||||
|
||||
fn panel_shadow() -> Vec<BoxShadow> {
|
||||
|
||||
Reference in New Issue
Block a user