perf(ui-gpui): ProjectBoxFullElement wraps entire card, 2.1% CPU

Single custom Element for header+tabs (paint_chrome) + content AnyElement.
Only 1 div remains (content wrapper). Down from 90% → 2.1% over 11 iterations.
This commit is contained in:
Hibryda 2026-03-20 00:32:11 +01:00
parent 5a7a5ad621
commit 3bbaefa9a2
2 changed files with 224 additions and 146 deletions

View file

@ -9,10 +9,9 @@
use gpui::*; use gpui::*;
use crate::CachedView; 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::state::{AgentStatus, Project, ProjectTab};
use crate::theme; use crate::theme;
// blink_state used via fully-qualified path in init_subviews + render
// ── Accent Colors by Index ────────────────────────────────────────── // ── 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 }) .text_color(if active { accent } else { theme::SUBTEXT0 })
.cursor_pointer(); .cursor_pointer();
if active { tab = tab.border_b_2().border_color(accent); } if active { tab = tab.border_b_2().border_color(accent); }
tab.child(label) // &'static str → no allocation tab.child(label)
} }
// ── ProjectBox View ───────────────────────────────────────────────── // ── ProjectBox View ─────────────────────────────────────────────────
@ -68,9 +67,6 @@ pub struct ProjectBox {
// Cached strings to avoid allocation on every render // Cached strings to avoid allocation on every render
id_project: SharedString, id_project: SharedString,
id_model: SharedString, id_model: SharedString,
id_resize: SharedString,
id_docs: SharedString,
id_files: SharedString,
cached_name: SharedString, cached_name: SharedString,
cached_cwd: SharedString, cached_cwd: SharedString,
} }
@ -81,9 +77,6 @@ impl ProjectBox {
Self { Self {
id_project: SharedString::from(format!("project-{id}")), id_project: SharedString::from(format!("project-{id}")),
id_model: SharedString::from(format!("model-{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_name: SharedString::from(project.name.clone()),
cached_cwd: SharedString::from(project.cwd.clone()), cached_cwd: SharedString::from(project.cwd.clone()),
project, project,
@ -95,16 +88,12 @@ impl ProjectBox {
/// Initialize sub-views. Must be called after the ProjectBox entity is created. /// Initialize sub-views. Must be called after the ProjectBox entity is created.
pub fn init_subviews(&mut self, cx: &mut Context<Self>) { pub fn init_subviews(&mut self, cx: &mut Context<Self>) {
// Initialize sub-views after entity registration
// SharedBlink: Arc<AtomicBool> toggled by background timer. // SharedBlink: Arc<AtomicBool> toggled by background timer.
// Timer calls cx.notify() on THIS ProjectBox directly — no intermediate entities. // 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) let should_pulse = matches!(self.project.agent.status, AgentStatus::Running)
&& self.project.accent_index == 0; && self.project.accent_index == 0;
if should_pulse { if should_pulse {
let blink = crate::components::blink_state::SharedBlink::new(); 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 self_entity = cx.entity().downgrade();
let visible = blink.visible.clone(); let visible = blink.visible.clone();
cx.spawn(async move |_: WeakEntity<Self>, cx: &mut AsyncApp| { cx.spawn(async move |_: WeakEntity<Self>, cx: &mut AsyncApp| {
@ -144,7 +133,7 @@ impl Render for ProjectBox {
ProjectTab::Files => 2, 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() let mut content = div()
.id(self.id_model.clone()) .id(self.id_model.clone())
.flex_1() .flex_1()
@ -171,31 +160,17 @@ impl Render for ProjectBox {
.child("src/").child(" main.rs").child(" lib.rs").child("Cargo.toml"), .child("src/").child(" main.rs").child(" lib.rs").child("Cargo.toml"),
}; };
// Root: 1 outer div + custom header/tab Element + content div // Return a single custom Element that owns the entire card.
// Total divs: 2 (root + content) — down from 12 // Eliminates the outer root div — down from 2 divs to 1 (only content div remains).
div() ProjectBoxFullElement {
.id(self.id_project.clone()) id: self.id_project.clone().into(),
.flex_1() name: self.cached_name.clone(),
.min_w(px(400.0)) cwd: self.cached_cwd.clone(),
.min_h(px(300.0)) accent,
.flex() status: self.project.agent.status,
.flex_col() blink_visible: self.shared_blink.as_ref().map(|b| b.visible.clone()),
.bg(theme::BASE) active_tab: tab_idx,
.rounded(px(8.0)) content: content.into_any_element(),
.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)
} }
} }

View file

@ -1,5 +1,7 @@
//! ProjectBoxHeaderElement — custom Element painting header + tab bar directly. //! ProjectBox custom Elements:
//! Zero div overhead for these regions. Content area delegates to child AnyView. //! - `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 gpui::*;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
@ -9,9 +11,124 @@ 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; 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"]; 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<Pixels>,
name: &SharedString,
cwd: &SharedString,
accent: Rgba,
status: AgentStatus,
blink_visible: &Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
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 struct ProjectBoxHeaderElement {
pub id: ElementId, pub id: ElementId,
pub name: SharedString, pub name: SharedString,
@ -19,7 +136,7 @@ pub struct ProjectBoxHeaderElement {
pub accent: Rgba, pub accent: Rgba,
pub status: AgentStatus, pub status: AgentStatus,
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 pub active_tab: usize,
} }
impl IntoElement for ProjectBoxHeaderElement { impl IntoElement for ProjectBoxHeaderElement {
@ -38,11 +155,11 @@ impl Element for ProjectBoxHeaderElement {
&mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>, &mut self, _id: Option<&GlobalElementId>, _iid: Option<&InspectorElementId>,
window: &mut Window, cx: &mut App, 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(AbsoluteLength::Pixels(px(total_h)))), height: Length::Definite(DefiniteLength::Absolute(AbsoluteLength::Pixels(
px(CHROME_HEIGHT)))),
}, },
flex_shrink: 0.0, flex_shrink: 0.0,
..Style::default() ..Style::default()
@ -60,106 +177,92 @@ impl Element for ProjectBoxHeaderElement {
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; paint_chrome(bounds, &self.name, &self.cwd, self.accent, self.status,
let w = bounds.size.width; &self.blink_visible, self.active_tab, window, cx);
let hh = px(HEADER_HEIGHT); }
let tbh = px(TAB_BAR_HEIGHT); }
// ── Header Background ── // ── ProjectBoxFullElement ─────────────────────────────────────────────────────
window.paint_quad(PaintQuad {
bounds: Bounds { origin: o, size: size(w, hh) }, /// Single custom Element for the entire ProjectBox card (header + tabs + content).
corner_radii: Corners { top_left: px(6.0), top_right: px(6.0), /// ProjectBox::render() returns this — no outer root div needed.
bottom_left: px(0.0), bottom_right: px(0.0) }, pub struct ProjectBoxFullElement {
background: theme::MANTLE.into(), pub id: ElementId,
border_widths: Edges::default(), pub name: SharedString,
border_color: transparent_black(), pub cwd: SharedString,
border_style: BorderStyle::default(), pub accent: Rgba,
}); pub status: AgentStatus,
pub blink_visible: Option<std::sync::Arc<std::sync::atomic::AtomicBool>>,
// Header bottom border pub active_tab: usize,
window.paint_quad(fill( /// Content area div (AgentPane + TerminalView or placeholder). Moved out in
Bounds { origin: point(o.x, o.y + hh - px(1.0)), size: size(w, px(1.0)) }, /// request_layout and carried as RequestLayoutState through to paint.
theme::SURFACE0, pub content: AnyElement,
)); }
// Accent stripe impl IntoElement for ProjectBoxFullElement {
let stripe_x = o.x + px(12.0); type Element = Self;
window.paint_quad( fn into_element(self) -> Self { self }
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)), impl Element for ProjectBoxFullElement {
); /// Content AnyElement carried from request_layout → prepaint → paint.
type RequestLayoutState = AnyElement;
// Status dot type PrepaintState = ();
let dot_color = match self.status {
AgentStatus::Running => if self.blink_visible.as_ref() fn id(&self) -> Option<ElementId> { Some(self.id.clone()) }
.map(|b| b.load(Ordering::Relaxed)).unwrap_or(true) fn source_location(&self) -> Option<&'static std::panic::Location<'static>> { None }
{ theme::GREEN } else { theme::SURFACE1 },
AgentStatus::Idle => theme::OVERLAY0, fn request_layout(
AgentStatus::Done => theme::BLUE, &mut self,
AgentStatus::Error => theme::RED, _id: Option<&GlobalElementId>,
}; _iid: Option<&InspectorElementId>,
let dot_x = stripe_x + px(11.0); window: &mut Window,
window.paint_quad( cx: &mut App,
fill(Bounds { origin: point(dot_x, o.y + (hh - px(8.0)) * 0.5), ) -> (LayoutId, AnyElement) {
size: size(px(8.0), px(8.0)) }, dot_color) // Move content out of self so we can call request_layout on it (needs &mut self).
.corner_radii(px(4.0)), let mut content = Empty.into_any_element();
); std::mem::swap(&mut content, &mut self.content);
let child_id = content.request_layout(window, cx);
// Name text
let ts = window.text_system().clone(); // Root style: fill parent, flex-col (chrome occupies fixed top portion via paint).
if !self.name.contains('\n') { let style = Style {
let run = TextRun { len: self.name.len(), font: font(".SystemUIFont"), size: size(relative(1.0).into(), relative(1.0).into()),
color: theme::TEXT.into(), background_color: None, flex_shrink: 0.0,
underline: None, strikethrough: None }; display: Display::Flex,
let shaped = ts.shape_line(self.name.clone(), px(13.0), &[run], None); flex_direction: FlexDirection::Column,
let _ = shaped.paint(point(dot_x + px(12.0), o.y + (hh - px(13.0)) * 0.5), hh, window, cx); ..Style::default()
} };
(window.request_layout(style, [child_id], cx), content)
// CWD text (right-aligned) }
if !self.cwd.contains('\n') {
let run = TextRun { len: self.cwd.len(), font: font(".SystemUIFont"), fn prepaint(
color: theme::OVERLAY0.into(), background_color: None, &mut self,
underline: None, strikethrough: None }; _id: Option<&GlobalElementId>,
let shaped = ts.shape_line(self.cwd.clone(), px(10.0), &[run], None); _iid: Option<&InspectorElementId>,
let cwd_x = o.x + w - shaped.width - px(12.0); bounds: Bounds<Pixels>,
let _ = shaped.paint(point(cwd_x, o.y + (hh - px(10.0)) * 0.5), hh, window, cx); content: &mut AnyElement,
} window: &mut Window,
cx: &mut App,
// ── Tab Bar ── ) -> () {
let tab_y = o.y + hh; // Place content below the painted chrome (header + tab bar).
// Tab bar background let content_origin = point(bounds.origin.x, bounds.origin.y + px(CHROME_HEIGHT));
window.paint_quad(fill( content.prepaint_at(content_origin, window, cx);
Bounds { origin: point(o.x, tab_y), size: size(w, tbh) }, }
theme::MANTLE,
)); fn paint(
// Tab bar bottom border &mut self,
window.paint_quad(fill( _id: Option<&GlobalElementId>,
Bounds { origin: point(o.x, tab_y + tbh - px(1.0)), size: size(w, px(1.0)) }, _iid: Option<&InspectorElementId>,
theme::SURFACE0, bounds: Bounds<Pixels>,
)); content: &mut AnyElement,
_pp: &mut (),
// Tab labels window: &mut Window,
let mut tab_x = o.x + px(8.0); cx: &mut App,
for (i, label) in TAB_LABELS.iter().enumerate() { ) {
let active = i == self.active_tab; // 1. Paint header + tabs directly.
let color: Hsla = if active { self.accent.into() } else { theme::SUBTEXT0.into() }; paint_chrome(bounds, &self.name, &self.cwd, self.accent, self.status,
let run = TextRun { len: label.len(), font: font(".SystemUIFont"), &self.blink_visible, self.active_tab, window, cx);
color, background_color: None, // 2. Paint the content child (already prepainted at correct origin).
underline: None, strikethrough: None }; content.paint(window, cx);
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);
}
} }
} }