From 5a7a5ad621cd239bfe29ae94bcb752a671f5c8b4 Mon Sep 17 00:00:00 2001 From: Hibryda Date: Fri, 20 Mar 2026 00:05:57 +0100 Subject: [PATCH] perf(ui-gpui): combined header+tabbar custom Element, 2.63% CPU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ui-gpui/src/components/project_box.rs | 133 +++++------------- ui-gpui/src/components/project_box_element.rs | 93 ++++++++---- 2 files changed, 101 insertions(+), 125 deletions(-) diff --git a/ui-gpui/src/components/project_box.rs b/ui-gpui/src/components/project_box.rs index dff179d..1e96518 100644 --- a/ui-gpui/src/components/project_box.rs +++ b/ui-gpui/src/components/project_box.rs @@ -135,87 +135,44 @@ impl ProjectBox { } impl Render for ProjectBox { - fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + fn render(&mut self, _window: &mut Window, _cx: &mut Context) -> impl IntoElement { let accent = accent_color(self.project.accent_index); let active_tab = self.project.active_tab; - - // Build content area based on active tab - let content = match active_tab { - ProjectTab::Model => { - 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"), - ) - } + let tab_idx = match active_tab { + ProjectTab::Model => 0, + ProjectTab::Docs => 1, + ProjectTab::Files => 2, }; + // 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() .id(self.id_project.clone()) .flex_1() @@ -228,7 +185,7 @@ impl Render for ProjectBox { .border_1() .border_color(theme::SURFACE0) .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 { id: self.id_project.clone().into(), name: self.cached_name.clone(), @@ -236,25 +193,9 @@ impl Render for ProjectBox { accent, status: self.project.agent.status, blink_visible: self.shared_blink.as_ref().map(|b| b.visible.clone()), + active_tab: tab_idx, }) - // ── Tab Bar (1 div + 3 inline tab labels) ── - .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 ──────────────────────────────────── + // Content area (cached child views for Model tab) .child(content) } } diff --git a/ui-gpui/src/components/project_box_element.rs b/ui-gpui/src/components/project_box_element.rs index 7b1ca79..45b2b35 100644 --- a/ui-gpui/src/components/project_box_element.rs +++ b/ui-gpui/src/components/project_box_element.rs @@ -1,5 +1,5 @@ -//! ProjectBoxHeaderElement — paints the project card header via paint_quad / ShapedLine, -//! bypassing Taffy entirely. 5 paint_quad + 2 text runs per frame. +//! ProjectBoxHeaderElement — custom Element painting header + tab bar directly. +//! Zero div overhead for these regions. Content area delegates to child AnyView. use gpui::*; use std::sync::atomic::Ordering; @@ -8,6 +8,9 @@ use crate::state::AgentStatus; use crate::theme; 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 id: ElementId, @@ -15,8 +18,8 @@ pub struct ProjectBoxHeaderElement { pub cwd: SharedString, pub accent: Rgba, pub status: AgentStatus, - /// Atomic blink toggle. None when agent is not Running. pub blink_visible: Option>, + pub active_tab: usize, // 0=Model, 1=Docs, 2=Files } impl IntoElement for ProjectBoxHeaderElement { @@ -32,18 +35,14 @@ impl Element for ProjectBoxHeaderElement { fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { None } fn request_layout( - &mut self, - _id: Option<&GlobalElementId>, - _inspector_id: Option<&InspectorElementId>, - window: &mut Window, - cx: &mut App, + &mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>, + window: &mut Window, cx: &mut App, ) -> (LayoutId, ()) { + let total_h = HEADER_HEIGHT + TAB_BAR_HEIGHT; let style = Style { size: Size { width: Length::Definite(relative(1.0)), - height: Length::Definite(DefiniteLength::Absolute( - AbsoluteLength::Pixels(px(HEADER_HEIGHT)), - )), + height: Length::Definite(DefiniteLength::Absolute(AbsoluteLength::Pixels(px(total_h)))), }, flex_shrink: 0.0, ..Style::default() @@ -52,22 +51,23 @@ impl Element for ProjectBoxHeaderElement { } fn prepaint( - &mut self, _id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, + &mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>, _bounds: Bounds, _rl: &mut (), _window: &mut Window, _cx: &mut App, ) {} fn paint( - &mut self, _id: Option<&GlobalElementId>, _inspector_id: Option<&InspectorElementId>, + &mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>, bounds: Bounds, _rl: &mut (), _pp: &mut (), window: &mut Window, cx: &mut App, ) { let o = bounds.origin; 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 { - bounds, + bounds: Bounds { origin: o, size: size(w, hh) }, corner_radii: Corners { top_left: px(6.0), top_right: px(6.0), bottom_left: px(0.0), bottom_right: px(0.0) }, background: theme::MANTLE.into(), @@ -76,21 +76,21 @@ impl Element for ProjectBoxHeaderElement { border_style: BorderStyle::default(), }); - // 2. Bottom border — 1px SURFACE0. + // Header bottom border 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, )); - // 3. Accent stripe — 3×20px, vertically centred, 12px from left. + // Accent stripe let stripe_x = o.x + px(12.0); 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) .corner_radii(px(2.0)), ); - // 4. Status dot — 8×8px circle. + // Status dot let dot_color = match self.status { AgentStatus::Running => if self.blink_visible.as_ref() .map(|b| b.load(Ordering::Relaxed)).unwrap_or(true) @@ -99,32 +99,67 @@ impl Element for ProjectBoxHeaderElement { AgentStatus::Done => theme::BLUE, AgentStatus::Error => theme::RED, }; - let dot_x = stripe_x + px(11.0); // 3px stripe + 8px gap - let dot_y = o.y + (h - px(8.0)) * 0.5; + let dot_x = stripe_x + px(11.0); 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)), ); - // 5. Name text — 13px TEXT, left-anchored after the dot. + // Name text let ts = window.text_system().clone(); - let lh = h; if !self.name.contains('\n') { let run = TextRun { len: self.name.len(), font: font(".SystemUIFont"), color: theme::TEXT.into(), background_color: None, underline: None, strikethrough: 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') { let run = TextRun { len: self.cwd.len(), font: font(".SystemUIFont"), color: theme::OVERLAY0.into(), background_color: None, underline: None, strikethrough: 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 _ = 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); } } }