perf(ui-gpui): combined header+tabbar custom Element, 2.63% CPU

Header + tab bar in single custom Element (10 GPU primitives).
ProjectBox::render() now only 2 divs (root + content).
Down from 90% → 2.63% through 10 optimization iterations.
This commit is contained in:
Hibryda 2026-03-20 00:05:57 +01:00
parent f0c55403b8
commit 5a7a5ad621
2 changed files with 101 additions and 125 deletions

View file

@ -135,87 +135,44 @@ impl ProjectBox {
} }
impl Render for ProjectBox { impl Render for ProjectBox {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
let accent = accent_color(self.project.accent_index); let accent = accent_color(self.project.accent_index);
let active_tab = self.project.active_tab; let active_tab = self.project.active_tab;
let tab_idx = match active_tab {
// Build content area based on active tab ProjectTab::Model => 0,
let content = match active_tab { ProjectTab::Docs => 1,
ProjectTab::Model => { ProjectTab::Files => 2,
let mut model_content = div()
.id(self.id_model.clone())
.flex_1()
.w_full()
.flex()
.flex_col();
// Agent pane — cached (0% on blink)
if let Some(ref pane) = self.agent_pane {
model_content = model_content.child(pane.clone().into_cached_flex());
}
// Resize handle (1 div)
model_content = model_content.child(
div().id(self.id_resize.clone()).w_full().h(px(4.0))
.bg(theme::SURFACE0).cursor_pointer()
.hover(|s| s.bg(theme::SURFACE1)),
);
// Terminal — cached (0% on blink)
if let Some(ref term) = self.terminal_view {
model_content = model_content.child(term.clone().into_cached_flex());
}
model_content
}
ProjectTab::Docs => {
div()
.id(self.id_docs.clone())
.flex_1()
.w_full()
.flex()
.items_center()
.justify_center()
.text_size(px(14.0))
.text_color(theme::OVERLAY0)
.child("Documentation viewer — renders project markdown files")
}
ProjectTab::Files => {
div()
.id(self.id_files.clone())
.flex_1()
.w_full()
.flex()
.flex_col()
.p(px(12.0))
.gap(px(4.0))
.child(
div()
.text_size(px(12.0))
.text_color(theme::SUBTEXT0)
.child("src/"),
)
.child(
div()
.pl(px(16.0))
.text_size(px(12.0))
.text_color(theme::TEXT)
.child("main.rs"),
)
.child(
div()
.pl(px(16.0))
.text_size(px(12.0))
.text_color(theme::TEXT)
.child("lib.rs"),
)
.child(
div()
.text_size(px(12.0))
.text_color(theme::SUBTEXT0)
.child("Cargo.toml"),
)
}
}; };
// Content area — only part that uses divs (cached children for Model tab)
let mut content = div()
.id(self.id_model.clone())
.flex_1()
.w_full()
.overflow_hidden();
content = match active_tab {
ProjectTab::Model => {
let mut c = content.flex().flex_col();
if let Some(ref pane) = self.agent_pane {
c = c.child(pane.clone().into_cached_flex());
}
c = c.child(div().w_full().h(px(4.0)).bg(theme::SURFACE0));
if let Some(ref term) = self.terminal_view {
c = c.child(term.clone().into_cached_flex());
}
c
}
ProjectTab::Docs => content.flex().items_center().justify_center()
.text_size(px(14.0)).text_color(theme::OVERLAY0)
.child("Documentation viewer"),
ProjectTab::Files => content.flex().flex_col().p(px(12.0)).gap(px(4.0))
.text_size(px(12.0)).text_color(theme::SUBTEXT0)
.child("src/").child(" main.rs").child(" lib.rs").child("Cargo.toml"),
};
// Root: 1 outer div + custom header/tab Element + content div
// Total divs: 2 (root + content) — down from 12
div() div()
.id(self.id_project.clone()) .id(self.id_project.clone())
.flex_1() .flex_1()
@ -228,7 +185,7 @@ impl Render for ProjectBox {
.border_1() .border_1()
.border_color(theme::SURFACE0) .border_color(theme::SURFACE0)
.overflow_hidden() .overflow_hidden()
// ── Header — custom Element: 5 paint_quad + 2 text runs, zero Taffy nodes ── // Header + Tab Bar: single custom Element (0 divs, ~10 GPU primitives)
.child(ProjectBoxHeaderElement { .child(ProjectBoxHeaderElement {
id: self.id_project.clone().into(), id: self.id_project.clone().into(),
name: self.cached_name.clone(), name: self.cached_name.clone(),
@ -236,25 +193,9 @@ impl Render for ProjectBox {
accent, accent,
status: self.project.agent.status, status: self.project.agent.status,
blink_visible: self.shared_blink.as_ref().map(|b| b.visible.clone()), blink_visible: self.shared_blink.as_ref().map(|b| b.visible.clone()),
active_tab: tab_idx,
}) })
// ── Tab Bar (1 div + 3 inline tab labels) ── // Content area (cached child views for Model tab)
.child(
div()
.w_full()
.h(px(28.0))
.flex()
.items_center()
.px(px(8.0))
.gap(px(2.0))
.bg(theme::MANTLE)
.border_b_1()
.border_color(theme::SURFACE0)
.text_size(px(11.0))
.child(tab_button("Model", active_tab == ProjectTab::Model, accent))
.child(tab_button("Docs", active_tab == ProjectTab::Docs, accent))
.child(tab_button("Files", active_tab == ProjectTab::Files, accent)),
)
// ── Content Area ────────────────────────────────────
.child(content) .child(content)
} }
} }

View file

@ -1,5 +1,5 @@
//! ProjectBoxHeaderElement — paints the project card header via paint_quad / ShapedLine, //! ProjectBoxHeaderElement — custom Element painting header + tab bar directly.
//! bypassing Taffy entirely. 5 paint_quad + 2 text runs per frame. //! Zero div overhead for these regions. Content area delegates to child AnyView.
use gpui::*; use gpui::*;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -8,6 +8,9 @@ use crate::state::AgentStatus;
use crate::theme; use crate::theme;
pub const HEADER_HEIGHT: f32 = 36.0; pub const HEADER_HEIGHT: f32 = 36.0;
pub const TAB_BAR_HEIGHT: f32 = 28.0;
const TAB_LABELS: [&str; 3] = ["Model", "Docs", "Files"];
pub struct ProjectBoxHeaderElement { pub struct ProjectBoxHeaderElement {
pub id: ElementId, pub id: ElementId,
@ -15,8 +18,8 @@ pub struct ProjectBoxHeaderElement {
pub cwd: SharedString, pub cwd: SharedString,
pub accent: Rgba, pub accent: Rgba,
pub status: AgentStatus, pub status: AgentStatus,
/// Atomic blink toggle. None when agent is not Running.
pub blink_visible: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>, pub blink_visible: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
pub active_tab: usize, // 0=Model, 1=Docs, 2=Files
} }
impl IntoElement for ProjectBoxHeaderElement { impl IntoElement for ProjectBoxHeaderElement {
@ -32,18 +35,14 @@ impl Element for ProjectBoxHeaderElement {
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { None } fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { None }
fn request_layout( fn request_layout(
&mut self, &mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>,
_id: Option<&GlobalElementId>, window: &mut Window, cx: &mut App,
_inspector_id: Option<&InspectorElementId>,
window: &mut Window,
cx: &mut App,
) -> (LayoutId, ()) { ) -> (LayoutId, ()) {
let total_h = HEADER_HEIGHT + TAB_BAR_HEIGHT;
let style = Style { let style = Style {
size: Size { size: Size {
width: Length::Definite(relative(1.0)), width: Length::Definite(relative(1.0)),
height: Length::Definite(DefiniteLength::Absolute( height: Length::Definite(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(total_h)))),
AbsoluteLength::Pixels(px(HEADER_HEIGHT)),
)),
}, },
flex_shrink: 0.0, flex_shrink: 0.0,
..Style::default() ..Style::default()
@ -52,22 +51,23 @@ impl Element for ProjectBoxHeaderElement {
} }
fn prepaint( fn prepaint(
&mut self, _id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, &mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>,
_bounds: Bounds<Pixels>, _rl: &mut (), _window: &mut Window, _cx: &mut App, _bounds: Bounds<Pixels>, _rl: &mut (), _window: &mut Window, _cx: &mut App,
) {} ) {}
fn paint( fn paint(
&mut self, _id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, &mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>,
bounds: Bounds<Pixels>, _rl: &mut (), _pp: &mut (), bounds: Bounds<Pixels>, _rl: &mut (), _pp: &mut (),
window: &mut Window, cx: &mut App, window: &mut Window, cx: &mut App,
) { ) {
let o = bounds.origin; let o = bounds.origin;
let w = bounds.size.width; let w = bounds.size.width;
let h = px(HEADER_HEIGHT); let hh = px(HEADER_HEIGHT);
let tbh = px(TAB_BAR_HEIGHT);
// 1. Background — MANTLE, rounded top corners. // ── Header Background ──
window.paint_quad(PaintQuad { window.paint_quad(PaintQuad {
bounds, bounds: Bounds { origin: o, size: size(w, hh) },
corner_radii: Corners { top_left: px(6.0), top_right: px(6.0), corner_radii: Corners { top_left: px(6.0), top_right: px(6.0),
bottom_left: px(0.0), bottom_right: px(0.0) }, bottom_left: px(0.0), bottom_right: px(0.0) },
background: theme::MANTLE.into(), background: theme::MANTLE.into(),
@ -76,21 +76,21 @@ impl Element for ProjectBoxHeaderElement {
border_style: BorderStyle::default(), border_style: BorderStyle::default(),
}); });
// 2. Bottom border — 1px SURFACE0. // Header bottom border
window.paint_quad(fill( window.paint_quad(fill(
Bounds { origin: point(o.x, o.y + h - px(1.0)), size: size(w, px(1.0)) }, Bounds { origin: point(o.x, o.y + hh - px(1.0)), size: size(w, px(1.0)) },
theme::SURFACE0, theme::SURFACE0,
)); ));
// 3. Accent stripe — 3×20px, vertically centred, 12px from left. // Accent stripe
let stripe_x = o.x + px(12.0); let stripe_x = o.x + px(12.0);
window.paint_quad( window.paint_quad(
fill(Bounds { origin: point(stripe_x, o.y + (h - px(20.0)) * 0.5), fill(Bounds { origin: point(stripe_x, o.y + (hh - px(20.0)) * 0.5),
size: size(px(3.0), px(20.0)) }, self.accent) size: size(px(3.0), px(20.0)) }, self.accent)
.corner_radii(px(2.0)), .corner_radii(px(2.0)),
); );
// 4. Status dot — 8×8px circle. // Status dot
let dot_color = match self.status { let dot_color = match self.status {
AgentStatus::Running => if self.blink_visible.as_ref() AgentStatus::Running => if self.blink_visible.as_ref()
.map(|b| b.load(Ordering::Relaxed)).unwrap_or(true) .map(|b| b.load(Ordering::Relaxed)).unwrap_or(true)
@ -99,32 +99,67 @@ impl Element for ProjectBoxHeaderElement {
AgentStatus::Done => theme::BLUE, AgentStatus::Done => theme::BLUE,
AgentStatus::Error => theme::RED, AgentStatus::Error => theme::RED,
}; };
let dot_x = stripe_x + px(11.0); // 3px stripe + 8px gap let dot_x = stripe_x + px(11.0);
let dot_y = o.y + (h - px(8.0)) * 0.5;
window.paint_quad( window.paint_quad(
fill(Bounds { origin: point(dot_x, dot_y), size: size(px(8.0), px(8.0)) }, dot_color) fill(Bounds { origin: point(dot_x, o.y + (hh - px(8.0)) * 0.5),
size: size(px(8.0), px(8.0)) }, dot_color)
.corner_radii(px(4.0)), .corner_radii(px(4.0)),
); );
// 5. Name text — 13px TEXT, left-anchored after the dot. // Name text
let ts = window.text_system().clone(); let ts = window.text_system().clone();
let lh = h;
if !self.name.contains('\n') { if !self.name.contains('\n') {
let run = TextRun { len: self.name.len(), font: font(".SystemUIFont"), let run = TextRun { len: self.name.len(), font: font(".SystemUIFont"),
color: theme::TEXT.into(), background_color: None, color: theme::TEXT.into(), background_color: None,
underline: None, strikethrough: None }; underline: None, strikethrough: None };
let shaped = ts.shape_line(self.name.clone(), px(13.0), &[run], None); let shaped = ts.shape_line(self.name.clone(), px(13.0), &[run], None);
let _ = shaped.paint(point(dot_x + px(12.0), o.y + (h - px(13.0)) * 0.5), lh, window, cx); let _ = shaped.paint(point(dot_x + px(12.0), o.y + (hh - px(13.0)) * 0.5), hh, window, cx);
} }
// 6. CWD text — 10px OVERLAY0, right-aligned with 12px margin. // CWD text (right-aligned)
if !self.cwd.contains('\n') { if !self.cwd.contains('\n') {
let run = TextRun { len: self.cwd.len(), font: font(".SystemUIFont"), let run = TextRun { len: self.cwd.len(), font: font(".SystemUIFont"),
color: theme::OVERLAY0.into(), background_color: None, color: theme::OVERLAY0.into(), background_color: None,
underline: None, strikethrough: None }; underline: None, strikethrough: None };
let shaped = ts.shape_line(self.cwd.clone(), px(10.0), &[run], None); let shaped = ts.shape_line(self.cwd.clone(), px(10.0), &[run], None);
let cwd_x = o.x + w - shaped.width - px(12.0); let cwd_x = o.x + w - shaped.width - px(12.0);
let _ = shaped.paint(point(cwd_x, o.y + (h - px(10.0)) * 0.5), lh, window, cx); let _ = shaped.paint(point(cwd_x, o.y + (hh - px(10.0)) * 0.5), hh, window, cx);
}
// ── Tab Bar ──
let tab_y = o.y + hh;
// Tab bar background
window.paint_quad(fill(
Bounds { origin: point(o.x, tab_y), size: size(w, tbh) },
theme::MANTLE,
));
// Tab bar bottom border
window.paint_quad(fill(
Bounds { origin: point(o.x, tab_y + tbh - px(1.0)), size: size(w, px(1.0)) },
theme::SURFACE0,
));
// Tab labels
let mut tab_x = o.x + px(8.0);
for (i, label) in TAB_LABELS.iter().enumerate() {
let active = i == self.active_tab;
let color: Hsla = if active { self.accent.into() } else { theme::SUBTEXT0.into() };
let run = TextRun { len: label.len(), font: font(".SystemUIFont"),
color, background_color: None,
underline: None, strikethrough: None };
let shaped = ts.shape_line(SharedString::from(*label), px(11.0), &[run], None);
let label_y = tab_y + (tbh - px(11.0)) * 0.5;
let _ = shaped.paint(point(tab_x, label_y), tbh, window, cx);
// Active indicator (2px underline)
if active {
window.paint_quad(fill(
Bounds { origin: point(tab_x, tab_y + tbh - px(3.0)),
size: size(shaped.width, px(2.0)) },
self.accent,
));
}
tab_x = tab_x + shaped.width + px(12.0);
} }
} }
} }