From 3bbaefa9a23fc8f00b7d7ed0e4b9c99591e31294 Mon Sep 17 00:00:00 2001 From: Hibryda Date: Fri, 20 Mar 2026 00:32:11 +0100 Subject: [PATCH] perf(ui-gpui): ProjectBoxFullElement wraps entire card, 2.1% CPU MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Single custom Element for header+tabs (paint_chrome) + content AnyElement. Only 1 div remains (content wrapper). Down from 90% → 2.1% over 11 iterations. --- ui-gpui/src/components/project_box.rs | 55 +-- ui-gpui/src/components/project_box_element.rs | 315 ++++++++++++------ 2 files changed, 224 insertions(+), 146 deletions(-) diff --git a/ui-gpui/src/components/project_box.rs b/ui-gpui/src/components/project_box.rs index 1e96518..c503ed7 100644 --- a/ui-gpui/src/components/project_box.rs +++ b/ui-gpui/src/components/project_box.rs @@ -9,10 +9,9 @@ use gpui::*; use crate::CachedView; -use crate::components::project_box_element::ProjectBoxHeaderElement; +use crate::components::project_box_element::ProjectBoxFullElement; use crate::state::{AgentStatus, Project, ProjectTab}; use crate::theme; -// blink_state used via fully-qualified path in init_subviews + render // ── Accent Colors by Index ────────────────────────────────────────── @@ -55,7 +54,7 @@ fn tab_button(label: &'static str, active: bool, accent: Rgba) -> Div { .text_color(if active { accent } else { theme::SUBTEXT0 }) .cursor_pointer(); if active { tab = tab.border_b_2().border_color(accent); } - tab.child(label) // &'static str → no allocation + tab.child(label) } // ── ProjectBox View ───────────────────────────────────────────────── @@ -68,9 +67,6 @@ pub struct ProjectBox { // Cached strings to avoid allocation on every render id_project: SharedString, id_model: SharedString, - id_resize: SharedString, - id_docs: SharedString, - id_files: SharedString, cached_name: SharedString, cached_cwd: SharedString, } @@ -81,9 +77,6 @@ impl ProjectBox { Self { id_project: SharedString::from(format!("project-{id}")), id_model: SharedString::from(format!("model-{id}")), - id_resize: SharedString::from(format!("resize-{id}")), - id_docs: SharedString::from(format!("docs-{id}")), - id_files: SharedString::from(format!("files-{id}")), cached_name: SharedString::from(project.name.clone()), cached_cwd: SharedString::from(project.cwd.clone()), project, @@ -95,16 +88,12 @@ impl ProjectBox { /// Initialize sub-views. Must be called after the ProjectBox entity is created. pub fn init_subviews(&mut self, cx: &mut Context) { - // Initialize sub-views after entity registration - // SharedBlink: Arc toggled by background timer. // Timer calls cx.notify() on THIS ProjectBox directly — no intermediate entities. - // mark_view_dirty walks: ProjectBox → Workspace (2 levels only). let should_pulse = matches!(self.project.agent.status, AgentStatus::Running) && self.project.accent_index == 0; if should_pulse { let blink = crate::components::blink_state::SharedBlink::new(); - // Get our own entity handle to pass to the timer let self_entity = cx.entity().downgrade(); let visible = blink.visible.clone(); cx.spawn(async move |_: WeakEntity, cx: &mut AsyncApp| { @@ -144,7 +133,7 @@ impl Render for ProjectBox { ProjectTab::Files => 2, }; - // Content area — only part that uses divs (cached children for Model tab) + // Content area — single div with cached children for the Model tab. let mut content = div() .id(self.id_model.clone()) .flex_1() @@ -171,31 +160,17 @@ impl Render for ProjectBox { .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() - .min_w(px(400.0)) - .min_h(px(300.0)) - .flex() - .flex_col() - .bg(theme::BASE) - .rounded(px(8.0)) - .border_1() - .border_color(theme::SURFACE0) - .overflow_hidden() - // Header + Tab Bar: single custom Element (0 divs, ~10 GPU primitives) - .child(ProjectBoxHeaderElement { - id: self.id_project.clone().into(), - name: self.cached_name.clone(), - cwd: self.cached_cwd.clone(), - accent, - status: self.project.agent.status, - blink_visible: self.shared_blink.as_ref().map(|b| b.visible.clone()), - active_tab: tab_idx, - }) - // Content area (cached child views for Model tab) - .child(content) + // Return a single custom Element that owns the entire card. + // Eliminates the outer root div — down from 2 divs to 1 (only content div remains). + ProjectBoxFullElement { + id: self.id_project.clone().into(), + name: self.cached_name.clone(), + cwd: self.cached_cwd.clone(), + accent, + status: self.project.agent.status, + blink_visible: self.shared_blink.as_ref().map(|b| b.visible.clone()), + active_tab: tab_idx, + content: content.into_any_element(), + } } } diff --git a/ui-gpui/src/components/project_box_element.rs b/ui-gpui/src/components/project_box_element.rs index 45b2b35..8e4f937 100644 --- a/ui-gpui/src/components/project_box_element.rs +++ b/ui-gpui/src/components/project_box_element.rs @@ -1,5 +1,7 @@ -//! ProjectBoxHeaderElement — custom Element painting header + tab bar directly. -//! Zero div overhead for these regions. Content area delegates to child AnyView. +//! ProjectBox custom Elements: +//! - `ProjectBoxHeaderElement` — paints header + tab bar directly (zero div overhead) +//! - `ProjectBoxFullElement` — wraps header paint + content AnyElement as a single +//! custom Element, eliminating the outer root div from ProjectBox::render() use gpui::*; use std::sync::atomic::Ordering; @@ -9,9 +11,124 @@ use crate::theme; pub const HEADER_HEIGHT: f32 = 36.0; pub const TAB_BAR_HEIGHT: f32 = 28.0; +pub const CHROME_HEIGHT: f32 = HEADER_HEIGHT + TAB_BAR_HEIGHT; const TAB_LABELS: [&str; 3] = ["Model", "Docs", "Files"]; +// ── Shared paint helper ─────────────────────────────────────────────────────── + +/// Paint header background + tab bar into `bounds`. Used by both Elements. +fn paint_chrome( + bounds: Bounds, + name: &SharedString, + cwd: &SharedString, + accent: Rgba, + status: AgentStatus, + blink_visible: &Option>, + active_tab: usize, + window: &mut Window, + cx: &mut App, +) { + let o = bounds.origin; + let w = bounds.size.width; + let hh = px(HEADER_HEIGHT); + let tbh = px(TAB_BAR_HEIGHT); + + // Header background (rounded top corners) + window.paint_quad(PaintQuad { + 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(), + border_widths: Edges::default(), + border_color: transparent_black(), + border_style: BorderStyle::default(), + }); + // Header bottom border + window.paint_quad(fill( + Bounds { origin: point(o.x, o.y + hh - px(1.0)), size: size(w, px(1.0)) }, + theme::SURFACE0, + )); + + // Accent stripe + let stripe_x = o.x + px(12.0); + window.paint_quad( + fill(Bounds { origin: point(stripe_x, o.y + (hh - px(20.0)) * 0.5), + size: size(px(3.0), px(20.0)) }, accent) + .corner_radii(px(2.0)), + ); + + // Status dot + let dot_color = match status { + AgentStatus::Running => if blink_visible.as_ref() + .map(|b| b.load(Ordering::Relaxed)).unwrap_or(true) + { theme::GREEN } else { theme::SURFACE1 }, + AgentStatus::Idle => theme::OVERLAY0, + AgentStatus::Done => theme::BLUE, + AgentStatus::Error => theme::RED, + }; + let dot_x = stripe_x + px(11.0); + window.paint_quad( + 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)), + ); + + // Name + CWD text + let ts = window.text_system().clone(); + if !name.contains('\n') { + let run = TextRun { len: name.len(), font: font(".SystemUIFont"), + color: theme::TEXT.into(), background_color: None, + underline: None, strikethrough: None }; + let shaped = ts.shape_line(name.clone(), px(13.0), &[run], None); + let _ = shaped.paint(point(dot_x + px(12.0), o.y + (hh - px(13.0)) * 0.5), hh, window, cx); + } + if !cwd.contains('\n') { + let run = TextRun { len: cwd.len(), font: font(".SystemUIFont"), + color: theme::OVERLAY0.into(), background_color: None, + underline: None, strikethrough: None }; + let shaped = ts.shape_line(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 + (hh - px(10.0)) * 0.5), hh, window, cx); + } + + // Tab bar + let tab_y = o.y + hh; + window.paint_quad(fill( + Bounds { origin: point(o.x, tab_y), size: size(w, tbh) }, + theme::MANTLE, + )); + window.paint_quad(fill( + Bounds { origin: point(o.x, tab_y + tbh - px(1.0)), size: size(w, px(1.0)) }, + theme::SURFACE0, + )); + let mut tab_x = o.x + px(8.0); + for (i, label) in TAB_LABELS.iter().enumerate() { + let active = i == active_tab; + let color: Hsla = if active { 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); + if active { + window.paint_quad(fill( + Bounds { origin: point(tab_x, tab_y + tbh - px(3.0)), + size: size(shaped.width, px(2.0)) }, + accent, + )); + } + tab_x = tab_x + shaped.width + px(12.0); + } +} + +// ── ProjectBoxHeaderElement ─────────────────────────────────────────────────── + +/// Standalone header + tab bar element. Used when the outer container is still a div. +/// Kept for compatibility; ProjectBoxFullElement supersedes it. pub struct ProjectBoxHeaderElement { pub id: ElementId, pub name: SharedString, @@ -19,7 +136,7 @@ pub struct ProjectBoxHeaderElement { pub accent: Rgba, pub status: AgentStatus, pub blink_visible: Option>, - pub active_tab: usize, // 0=Model, 1=Docs, 2=Files + pub active_tab: usize, } impl IntoElement for ProjectBoxHeaderElement { @@ -38,11 +155,11 @@ impl Element for ProjectBoxHeaderElement { &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(total_h)))), + height: Length::Definite(DefiniteLength::Absolute(AbsoluteLength::Pixels( + px(CHROME_HEIGHT)))), }, flex_shrink: 0.0, ..Style::default() @@ -60,106 +177,92 @@ impl Element for ProjectBoxHeaderElement { bounds: Bounds, _rl: &mut (), _pp: &mut (), window: &mut Window, cx: &mut App, ) { - let o = bounds.origin; - let w = bounds.size.width; - let hh = px(HEADER_HEIGHT); - let tbh = px(TAB_BAR_HEIGHT); - - // ── Header Background ── - window.paint_quad(PaintQuad { - 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(), - border_widths: Edges::default(), - border_color: transparent_black(), - border_style: BorderStyle::default(), - }); - - // Header bottom border - window.paint_quad(fill( - Bounds { origin: point(o.x, o.y + hh - px(1.0)), size: size(w, px(1.0)) }, - theme::SURFACE0, - )); - - // Accent stripe - let stripe_x = o.x + px(12.0); - window.paint_quad( - 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)), - ); - - // 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) - { theme::GREEN } else { theme::SURFACE1 }, - AgentStatus::Idle => theme::OVERLAY0, - AgentStatus::Done => theme::BLUE, - AgentStatus::Error => theme::RED, - }; - let dot_x = stripe_x + px(11.0); - window.paint_quad( - 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)), - ); - - // Name text - let ts = window.text_system().clone(); - 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 + (hh - px(13.0)) * 0.5), hh, window, cx); - } - - // 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 + (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); - } + paint_chrome(bounds, &self.name, &self.cwd, self.accent, self.status, + &self.blink_visible, self.active_tab, window, cx); + } +} + +// ── ProjectBoxFullElement ───────────────────────────────────────────────────── + +/// Single custom Element for the entire ProjectBox card (header + tabs + content). +/// ProjectBox::render() returns this — no outer root div needed. +pub struct ProjectBoxFullElement { + pub id: ElementId, + pub name: SharedString, + pub cwd: SharedString, + pub accent: Rgba, + pub status: AgentStatus, + pub blink_visible: Option>, + pub active_tab: usize, + /// Content area div (AgentPane + TerminalView or placeholder). Moved out in + /// request_layout and carried as RequestLayoutState through to paint. + pub content: AnyElement, +} + +impl IntoElement for ProjectBoxFullElement { + type Element = Self; + fn into_element(self) -> Self { self } +} + +impl Element for ProjectBoxFullElement { + /// Content AnyElement carried from request_layout → prepaint → paint. + type RequestLayoutState = AnyElement; + type PrepaintState = (); + + fn id(&self) -> Option { Some(self.id.clone()) } + fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { None } + + fn request_layout( + &mut self, + _id: Option<&GlobalElementId>, + _iid: Option<&InspectorElementId>, + window: &mut Window, + cx: &mut App, + ) -> (LayoutId, AnyElement) { + // Move content out of self so we can call request_layout on it (needs &mut self). + let mut content = Empty.into_any_element(); + std::mem::swap(&mut content, &mut self.content); + let child_id = content.request_layout(window, cx); + + // Root style: fill parent, flex-col (chrome occupies fixed top portion via paint). + let style = Style { + size: size(relative(1.0).into(), relative(1.0).into()), + flex_shrink: 0.0, + display: Display::Flex, + flex_direction: FlexDirection::Column, + ..Style::default() + }; + (window.request_layout(style, [child_id], cx), content) + } + + fn prepaint( + &mut self, + _id: Option<&GlobalElementId>, + _iid: Option<&InspectorElementId>, + bounds: Bounds, + content: &mut AnyElement, + window: &mut Window, + cx: &mut App, + ) -> () { + // Place content below the painted chrome (header + tab bar). + let content_origin = point(bounds.origin.x, bounds.origin.y + px(CHROME_HEIGHT)); + content.prepaint_at(content_origin, window, cx); + } + + fn paint( + &mut self, + _id: Option<&GlobalElementId>, + _iid: Option<&InspectorElementId>, + bounds: Bounds, + content: &mut AnyElement, + _pp: &mut (), + window: &mut Window, + cx: &mut App, + ) { + // 1. Paint header + tabs directly. + paint_chrome(bounds, &self.name, &self.cwd, self.accent, self.status, + &self.blink_visible, self.active_tab, window, cx); + // 2. Paint the content child (already prepainted at correct origin). + content.paint(window, cx); } }