chore: reduce component overhead (#3)

Reviewed-on: #3
This commit was merged in pull request #3.
This commit is contained in:
2026-11-09 10:09:56 +00:00
parent 6815585528
commit 1c091db699
44 changed files with 1376 additions and 1241 deletions
-1
View File
@@ -20,7 +20,6 @@ default = ["full"]
full = ["dock", "resize_handle", "resizable", "shell", "window_controls"]
dock = [
"resize_handle",
"resizable",
"dep:gpui-native-core",
"dep:gpui-native-data",
"dep:gpui-native-icons",
+40 -22
View File
@@ -192,10 +192,8 @@ impl DockState {
}
/// Registers a panel descriptor, replacing any earlier one with its id.
pub fn register_panel(&mut self, panel: PanelDescriptor, cx: &mut Context<Self>) {
if self.panels.insert(panel.id.clone(), panel).is_none() {
cx.notify();
}
pub fn register_panel(&mut self, panel: PanelDescriptor) {
self.panels.insert(panel.id.clone(), panel);
}
/// Opens a panel into the active pane and activates its tab.
@@ -330,6 +328,17 @@ impl DockState {
};
if changed {
// The kept tab may have no right view, show the first remaining one.
self.right_view = self
.locate(id)
.and_then(|(pane_key, _)| self.pane(pane_key))
.and_then(|pane| {
pane.active.as_deref().and_then(|active| {
pane.tabs.iter().find(|tab| tab.panel.as_ref() == active)
})
})
.and_then(|tab| tab.right_view.clone())
.or_else(|| first_right_view(&self.root));
cx.notify();
}
}
@@ -354,10 +363,14 @@ impl DockState {
}
self.detach_pane(pane_key);
if self.active_pane == pane_key {
self.active_pane = self.first_pane_key().unwrap_or(0);
}
// The closed pane's views are gone; show the first remaining one.
self.right_view = first_right_view(&self.root);
cx.notify();
}
@@ -526,6 +539,19 @@ impl DockState {
}
}
fn pane(&self, key: u64) -> Option<&Pane> {
Self::pane_in(&self.root, key)
}
fn pane_in(node: &Node, key: u64) -> Option<&Pane> {
match node {
Node::Pane(pane) => (pane.key == key).then_some(pane),
Node::Split(split) => {
Self::pane_in(&split.a, key).or_else(|| Self::pane_in(&split.b, key))
}
}
}
fn pane_mut(&mut self, key: u64) -> Option<&mut Pane> {
Self::pane_mut_in(&mut self.root, key)
}
@@ -969,15 +995,10 @@ fn render_pane(
.map(|tab| tab.view.clone());
// The cache keeps an un-notified panel from re-rendering on dock frames.
let content_id = SharedString::from(format!("{dock_id}-content-{}", pane.key));
let content = div()
.id(SharedString::from(format!(
"{dock_id}-content-{}",
pane.key
)))
.debug_selector({
let id = SharedString::from(format!("{dock_id}-content-{}", pane.key));
move || id.to_string()
})
.id(content_id.clone())
.debug_selector(move || content_id.to_string())
.flex_1()
.min_h_0()
.overflow_hidden()
@@ -999,6 +1020,7 @@ fn render_pane(
let pane = pane.clone();
let titlebar = *titlebar;
let padding_x = metrics.dense_padding_x;
move |window, cx| {
let selected = pane
.active
@@ -1016,7 +1038,7 @@ fn render_pane(
move |_, _| {
tabs.iter()
.map(|tab| {
let mut entry = Tab::new(tab.title.clone());
let mut entry = Tab::new(tab.panel.clone(), tab.title.clone());
if let Some(role) = tab.icon {
entry = entry.icon(role);
}
@@ -1065,21 +1087,17 @@ fn render_pane(
}
});
let bar_id = SharedString::from(format!("{dock_id}-pane-{}-bar", pane.key));
h_flex()
.id(SharedString::from(format!(
"{dock_id}-pane-{}-bar",
pane.key
)))
.debug_selector({
let id = SharedString::from(format!("{dock_id}-pane-{}-bar", pane.key));
move || id.to_string()
})
.id(bar_id.clone())
.debug_selector(move || bar_id.to_string())
.flex_none()
.w_full()
.h(titlebar.height)
.items_center()
.pl(if titlebar.traffic_light_pad {
px(TRAFFIC_LIGHT_PAD)
TRAFFIC_LIGHT_PAD
} else {
padding_x
})
+14 -6
View File
@@ -10,11 +10,11 @@ use gpui::{
SharedString, StatefulInteractiveElement as _, Styled as _, Window, canvas, deferred, div,
fill, point, px,
};
use gpui_native_theme::{ActiveTheme as _, Size};
use gpui_native_theme::{ActiveTheme as _, RESIZE_KEYBOARD_STEP, Size};
use gpui_native_utils::{focus_handle, focus_ring};
/// How far one keyboard arrow step moves the seam.
const KEYBOARD_STEP: f32 = 32.0;
const KEYBOARD_STEP: Pixels = RESIZE_KEYBOARD_STEP;
type ResizeHandler = Rc<dyn Fn(Pixels, &mut Window, &mut App)>;
type ResetHandler = Rc<dyn Fn(&mut Window, &mut App)>;
@@ -180,10 +180,10 @@ impl RenderOnce for ResizeHandle {
let on_resize = self.on_resize.clone();
move |event: &KeyDownEvent, window, cx| {
let delta = match (axis, event.keystroke.key.as_str()) {
(Axis::Horizontal, "left") => Some(px(-KEYBOARD_STEP)),
(Axis::Horizontal, "right") => Some(px(KEYBOARD_STEP)),
(Axis::Vertical, "up") => Some(px(-KEYBOARD_STEP)),
(Axis::Vertical, "down") => Some(px(KEYBOARD_STEP)),
(Axis::Horizontal, "left") => Some(-KEYBOARD_STEP),
(Axis::Horizontal, "right") => Some(KEYBOARD_STEP),
(Axis::Vertical, "up") => Some(-KEYBOARD_STEP),
(Axis::Vertical, "down") => Some(KEYBOARD_STEP),
_ => None,
};
if let Some(delta) = delta {
@@ -255,6 +255,14 @@ impl RenderOnce for ResizeHandle {
return;
}
if event.pressed_button != Some(MouseButton::Left) {
drag.update(cx, |drag, cx| {
drag.dragging = false;
cx.notify();
});
return;
}
let delta = match axis {
Axis::Horizontal => event.position.x - last.x,
Axis::Vertical => event.position.y - last.y,
+20 -40
View File
@@ -4,8 +4,7 @@
use gpui::prelude::FluentBuilder as _;
use gpui::{
AnyElement, App, AppContext as _, Context, ElementId, Entity, InteractiveElement as _,
IntoElement, ParentElement as _, Pixels, RenderOnce, SharedString, Styled as _, Window, div,
px,
IntoElement, ParentElement as _, Pixels, RenderOnce, Styled as _, Window, div, px,
};
use gpui_native_core::button::{Button, ButtonVariant};
use gpui_native_core::common::{Sizable as _, h_flex, v_flex};
@@ -108,20 +107,6 @@ impl Side {
Side::Right => "right",
}
}
fn toggle_icon(self) -> IconRole {
match self {
Side::Left => IconRole::PanelLeft,
Side::Right => IconRole::PanelRight,
}
}
fn toggle_label(self) -> SharedString {
match self {
Side::Left => SharedString::from("Hide left sidebar"),
Side::Right => SharedString::from("Hide right sidebar"),
}
}
}
/// The application shell.
@@ -204,6 +189,7 @@ impl RenderOnce for Shell {
let width = state.read(cx).left_sidebar_width();
layout
.child(sidebar_region(
(id.clone(), "left"),
width,
left.take()
.map(|sidebar| sidebar.into_any_element())
@@ -242,6 +228,7 @@ impl RenderOnce for Shell {
}
}))
.child(sidebar_region(
(id.clone(), "right"),
width,
right.take().unwrap_or_else(|| div().into_any_element()),
Side::Right,
@@ -262,6 +249,7 @@ impl RenderOnce for Shell {
/// One sidebar: a header strip with the hide toggle over the app's content.
#[allow(clippy::too_many_arguments)]
fn sidebar_region(
id: impl Into<ElementId>,
width: Pixels,
content: AnyElement,
side: Side,
@@ -272,6 +260,7 @@ fn sidebar_region(
window: &mut Window,
cx: &mut App,
) -> AnyElement {
let id = id.into();
let theme = cx.theme();
let background = theme.colors.background;
let strip_background = match (side, material) {
@@ -280,7 +269,7 @@ fn sidebar_region(
};
let strip = h_flex()
.id(SharedString::from(format!("sidebar-strip-{}", side.name())))
.id((id.clone(), "strip"))
.debug_selector(move || format!("sidebar-strip-{}", side.name()))
.flex_none()
.h(strip_height)
@@ -288,40 +277,31 @@ fn sidebar_region(
.items_center()
.map(|strip| match side {
Side::Left => strip.when(cfg!(target_os = "macos"), |strip| {
strip.pl(px(TRAFFIC_LIGHT_PAD))
strip.pl(TRAFFIC_LIGHT_PAD)
}),
Side::Right => strip,
})
.px(padding_x)
.child(titlebar_strip(
div()
.id(SharedString::from(format!(
"sidebar-strip-drag-{}",
side.name()
)))
.flex_1()
.self_stretch(),
div().id((id.clone(), "strip-drag")).flex_1().self_stretch(),
window,
cx,
))
.map(|strip| match side {
Side::Left => strip.child(
Button::new(SharedString::from(format!(
"sidebar-toggle-{}",
side.name()
)))
.icon(side.toggle_icon())
.variant(ButtonVariant::Ghost)
.small()
.accessibility_label(side.toggle_label())
.on_click({
let state = state.clone();
move |_, _, cx| {
state.update(cx, |state, cx| state.set_left_sidebar_visible(false, cx))
}
}),
Button::new((id, "sidebar-toggle"))
.icon(IconRole::PanelLeft)
.variant(ButtonVariant::Ghost)
.small()
.accessibility_label("Hide left sidebar")
.on_click({
let state = state.clone();
move |_, _, cx| {
state.update(cx, |state, cx| state.set_left_sidebar_visible(false, cx))
}
}),
),
Side::Right => strip.child(WindowControls::new("sidebar-window-controls")),
Side::Right => strip.child(WindowControls::new((id, "window-controls"))),
});
v_flex()
+4 -15
View File
@@ -23,13 +23,9 @@ use gpui::Decorations;
use gpui::{SharedString, px, rgba};
/// macOS traffic-lights zone: the pad a leading titlebar strip reserves.
pub const TRAFFIC_LIGHT_PAD: f32 = 78.0;
/// The width of a Windows caption button.
pub use gpui_native_theme::TRAFFIC_LIGHT_PAD;
#[cfg(target_os = "windows")]
const CAPTION_WIDTH: f32 = 36.0;
/// Windows paints its caption close hover red in every theme.
#[cfg(target_os = "windows")]
const WINDOWS_CLOSE_HOVER: (f32, f32, f32) = (232.0 / 255.0, 17.0 / 255.0, 32.0 / 255.0);
use gpui_native_theme::{WINDOWS_CAPTION_WIDTH as CAPTION_WIDTH, WINDOWS_CLOSE_HOVER_HEX};
/// The window controls for the trailing end of a titlebar strip.
#[derive(IntoElement)]
@@ -150,7 +146,7 @@ impl RenderOnce for WindowControls {
}
/// Double-click on a titlebar strip maximizes where the platform does not do it.
pub fn maximize_on_double_click() -> impl Fn(&ClickEvent, &mut Window, &mut App) + 'static {
fn maximize_on_double_click() -> impl Fn(&ClickEvent, &mut Window, &mut App) + 'static {
move |event, window, _cx| {
if event.click_count() != 2 {
return;
@@ -210,16 +206,9 @@ fn caption_button(
) -> AnyElement {
let theme = cx.theme();
let glyph_size = theme.sizes.small.caption_size;
let (close_r, close_g, close_b) = WINDOWS_CLOSE_HOVER;
let close_hover = rgba((
(close_r * 255.0) as u8,
(close_g * 255.0) as u8,
(close_b * 255.0) as u8,
0xff,
));
let hover = if close {
close_hover
rgba(WINDOWS_CLOSE_HOVER_HEX)
} else {
theme.colors.muted_hover
};
+6 -10
View File
@@ -7,12 +7,8 @@ use gpui::{
IntoElement, MouseButton, ParentElement as _, Pixels, Point, ResizeEdge, Size, Styled as _,
Tiling, Window, canvas, div, point, px, size,
};
use gpui_native_theme::ActiveTheme as _;
use gpui_native_theme::{ActiveTheme as _, BORDER_WIDTH, WINDOW_RESIZE_INSET};
/// The width of the band inside the window's edge that resizes it.
const RESIZE_INSET: f32 = 4.0;
/// The hairline around the content.
const BORDER_WIDTH: f32 = 1.0;
/// How much of the shadow's strength survives losing window focus.
const INACTIVE_SHADOW_OPACITY: f32 = 0.7;
@@ -28,7 +24,7 @@ pub fn frame(child: impl IntoElement, window: &mut Window, cx: &App) -> Div {
tiling
};
let inset = px(RESIZE_INSET);
let inset = WINDOW_RESIZE_INSET;
let border = cx.theme().colors.border;
let radius = cx.theme().radius.overlay;
@@ -75,10 +71,10 @@ pub fn frame(child: impl IntoElement, window: &mut Window, cx: &App) -> Div {
.size_full()
.overflow_hidden()
.border_color(border)
.when(!tiling.top, |surface| surface.border_t(px(BORDER_WIDTH)))
.when(!tiling.bottom, |surface| surface.border_b(px(BORDER_WIDTH)))
.when(!tiling.left, |surface| surface.border_l(px(BORDER_WIDTH)))
.when(!tiling.right, |surface| surface.border_r(px(BORDER_WIDTH)))
.when(!tiling.top, |surface| surface.border_t(BORDER_WIDTH))
.when(!tiling.bottom, |surface| surface.border_b(BORDER_WIDTH))
.when(!tiling.left, |surface| surface.border_l(BORDER_WIDTH))
.when(!tiling.right, |surface| surface.border_r(BORDER_WIDTH))
.when(!(tiling.top || tiling.left), |surface| {
surface.rounded_tl(radius)
})
+8 -8
View File
@@ -112,9 +112,9 @@ fn drag(visual: &mut VisualTestContext, from: gpui::Point<Pixels>, to: gpui::Poi
fn register(dock: &Entity<DockState>, tags: &[&'static str], cx: &mut TestAppContext) {
cx.update(|cx| {
dock.update(cx, |dock, cx| {
dock.update(cx, |dock, _cx| {
for tag in tags {
dock.register_panel(descriptor(tag), cx);
dock.register_panel(descriptor(tag));
}
});
});
@@ -492,9 +492,9 @@ fn activating_a_panel_with_a_right_view_opens_the_sidebar() {
let mut cx = TestAppContext::single();
let (dock, opened, mut visual) = sidebar_window(&mut cx);
cx.update(|cx| {
dock.update(cx, |dock, cx| {
dock.register_panel(descriptor_with_right_sidebar("overview"), cx);
dock.register_panel(descriptor("inbox"), cx);
dock.update(cx, |dock, _cx| {
dock.register_panel(descriptor_with_right_sidebar("overview"));
dock.register_panel(descriptor("inbox"));
});
});
@@ -519,9 +519,9 @@ fn closing_the_displayed_tab_falls_back_to_another_right_view() {
let mut cx = TestAppContext::single();
let (dock, opened, mut visual) = sidebar_window(&mut cx);
cx.update(|cx| {
dock.update(cx, |dock, cx| {
dock.register_panel(descriptor_with_right_sidebar("overview"), cx);
dock.register_panel(descriptor_with_right_sidebar("explore"), cx);
dock.update(cx, |dock, _cx| {
dock.register_panel(descriptor_with_right_sidebar("overview"));
dock.register_panel(descriptor_with_right_sidebar("explore"));
});
});
open(&mut visual, &dock, "overview");
+22
View File
@@ -151,6 +151,28 @@ fn moves_without_a_drag_report_nothing() {
assert!(deltas.borrow().is_empty());
}
/// The button may be released outside the window, where no MouseUp is
/// delivered — the drag must end when a move reports no pressed button.
#[test]
fn releasing_outside_the_window_ends_the_drag() {
let (_cx, deltas, _resets, mut visual) = handle_window(Axis::Horizontal, false);
let start = handle_center(&mut visual);
visual.simulate_mouse_down(start, MouseButton::Left, Modifiers::default());
visual.simulate_event(MouseMoveEvent {
position: point(start.x + px(30.), start.y),
pressed_button: None,
modifiers: Modifiers::default(),
});
visual.simulate_event(MouseMoveEvent {
position: point(start.x + px(60.), start.y),
pressed_button: None,
modifiers: Modifiers::default(),
});
assert!(deltas.borrow().is_empty(), "the drag outlived its button");
}
#[test]
fn arrow_keys_move_the_seam_when_focused() {
let (_cx, deltas, _resets, mut visual) = handle_window(Axis::Horizontal, false);
+6 -14
View File
@@ -74,9 +74,7 @@ fn the_pill_bar_pads_for_traffic_lights_when_it_leads_the_window() {
let mut cx = TestAppContext::single();
let (dock, mut visual) = dock_window(false, false, &mut cx);
cx.update(|cx| {
dock.update(cx, |dock, cx| {
dock.register_panel(descriptor("overview"), cx)
});
dock.update(cx, |dock, _cx| dock.register_panel(descriptor("overview")));
});
open(&mut visual, &dock, "overview");
@@ -87,9 +85,7 @@ fn the_pill_bar_pads_for_traffic_lights_when_it_leads_the_window() {
let (dock, mut visual) = dock_window(false, true, &mut cx);
let _ = &dock;
cx.update(|cx| {
dock.update(cx, |dock, cx| {
dock.register_panel(descriptor("overview"), cx)
});
dock.update(cx, |dock, _cx| dock.register_panel(descriptor("overview")));
});
open(&mut visual, &dock, "overview");
@@ -101,11 +97,11 @@ fn the_pill_bar_pads_for_traffic_lights_when_it_leads_the_window() {
// The bare bar keeps the standard leading padding; the padded bar clears
// the traffic lights.
assert!(
bare_inset < px(TRAFFIC_LIGHT_PAD),
bare_inset < TRAFFIC_LIGHT_PAD,
"the bare bar must not reserve the traffic-light pad (bare {bare_inset:?})"
);
assert!(
padded_inset >= px(TRAFFIC_LIGHT_PAD) - px(1.),
padded_inset >= TRAFFIC_LIGHT_PAD - px(1.),
"the pad must clear the traffic lights (bare {bare_inset:?}, padded {padded_inset:?})"
);
}
@@ -115,9 +111,7 @@ fn window_controls_render_only_when_the_flag_is_set() {
let mut cx = TestAppContext::single();
let (dock, mut visual) = dock_window(false, false, &mut cx);
cx.update(|cx| {
dock.update(cx, |dock, cx| {
dock.register_panel(descriptor("overview"), cx)
});
dock.update(cx, |dock, _cx| dock.register_panel(descriptor("overview")));
});
open(&mut visual, &dock, "overview");
@@ -138,9 +132,7 @@ fn the_flag_hosts_the_platform_controls_in_the_bar() {
let mut cx = TestAppContext::single();
let (dock, mut visual) = dock_window(true, false, &mut cx);
cx.update(|cx| {
dock.update(cx, |dock, cx| {
dock.register_panel(descriptor("overview"), cx)
});
dock.update(cx, |dock, _cx| dock.register_panel(descriptor("overview")));
});
open(&mut visual, &dock, "overview");