miniprofiler_ui: Improve MiniProfiler to use uniform list (#43457)

Release Notes:

- N/A

---

- Apply uniform_list for timing list for performance.
- Add paddings for window.
- Add space to `ms`, before: `100ms` after `100 ms`.

## Before 

<img width="1392" height="860" alt="image"
src="https://github.com/user-attachments/assets/9706a96f-7093-4d4f-832f-306948a9b17b"
/>

## After 

<img width="1392" height="864" alt="image"
src="https://github.com/user-attachments/assets/38df1b71-15e7-4101-b0c9-ecdcdb7752d7"
/>
This commit is contained in:
Jason Lee
2025-11-25 16:08:49 +01:00
committed by GitHub
parent 1cbb49864c
commit f58de21068
2 changed files with 84 additions and 67 deletions
+6 -1
View File
@@ -11,7 +11,7 @@ use crate::{
StyleRefinement, Styled, Window, point, size,
};
use smallvec::SmallVec;
use std::{cell::RefCell, cmp, ops::Range, rc::Rc};
use std::{cell::RefCell, cmp, ops::Range, rc::Rc, usize};
use super::ListHorizontalSizingBehavior;
@@ -235,6 +235,11 @@ impl UniformListScrollHandle {
false
}
}
/// Scroll to the bottom of the list.
pub fn scroll_to_bottom(&self) {
self.scroll_to_item(usize::MAX, ScrollStrategy::Bottom);
}
}
impl Styled for UniformList {
+78 -66
View File
@@ -1,21 +1,22 @@
use std::{
ops::Range,
path::PathBuf,
rc::Rc,
time::{Duration, Instant},
};
use gpui::{
App, AppContext, ClipboardItem, Context, Entity, Hsla, InteractiveElement, IntoElement,
ParentElement, Render, ScrollHandle, SerializedTaskTiming, SharedString,
StatefulInteractiveElement, Styled, Task, TaskTiming, TitlebarOptions, WindowBounds,
WindowHandle, WindowOptions, div, prelude::FluentBuilder, px, relative, size,
App, AppContext, ClipboardItem, Context, Div, Entity, Hsla, InteractiveElement,
ParentElement as _, Render, SerializedTaskTiming, SharedString, StatefulInteractiveElement,
Styled, Task, TaskTiming, TitlebarOptions, UniformListScrollHandle, WindowBounds, WindowHandle,
WindowOptions, div, prelude::FluentBuilder, px, relative, size, uniform_list,
};
use util::ResultExt;
use workspace::{
Workspace,
ui::{
ActiveTheme, Button, ButtonCommon, ButtonStyle, Checkbox, Clickable, ToggleState, Tooltip,
WithScrollbar, h_flex, v_flex,
ActiveTheme, Button, ButtonCommon, ButtonStyle, Checkbox, Clickable, Divider,
ScrollableHandle as _, ToggleState, Tooltip, WithScrollbar, h_flex, v_flex,
},
};
use zed_actions::OpenPerformanceProfiler;
@@ -95,7 +96,7 @@ pub struct ProfilerWindow {
data: DataMode,
include_self_timings: ToggleState,
autoscroll: bool,
scroll_handle: ScrollHandle,
scroll_handle: UniformListScrollHandle,
workspace: Option<WindowHandle<Workspace>>,
_refresh: Option<Task<()>>,
}
@@ -111,7 +112,7 @@ impl ProfilerWindow {
data: DataMode::Realtime(None),
include_self_timings: ToggleState::Unselected,
autoscroll: true,
scroll_handle: ScrollHandle::new(),
scroll_handle: UniformListScrollHandle::default(),
workspace: workspace_handle,
_refresh: Some(Self::begin_listen(cx)),
});
@@ -128,16 +129,7 @@ impl ProfilerWindow {
.get_current_thread_timings();
this.update(cx, |this: &mut ProfilerWindow, cx| {
let scroll_offset = this.scroll_handle.offset();
let max_offset = this.scroll_handle.max_offset();
this.autoscroll = -scroll_offset.y >= (max_offset.height - px(5.0));
this.data = DataMode::Realtime(Some(data));
if this.autoscroll {
this.scroll_handle.scroll_to_bottom();
}
cx.notify();
})
.ok();
@@ -157,12 +149,7 @@ impl ProfilerWindow {
}
}
fn render_timing(
&self,
value_range: Range<Instant>,
item: TimingBar,
cx: &App,
) -> impl IntoElement {
fn render_timing(value_range: Range<Instant>, item: TimingBar, cx: &App) -> Div {
let time_ms = item.end.duration_since(item.start).as_secs_f32() * 1000f32;
let remap = value_range
@@ -227,10 +214,10 @@ impl ProfilerWindow {
)
.child(
div()
.min_w(px(60.0))
.min_w(px(70.))
.flex_shrink_0()
.text_right()
.child(format!("{:.1}ms", time_ms)),
.child(format!("{:.1} ms", time_ms)),
)
}
}
@@ -241,15 +228,23 @@ impl Render for ProfilerWindow {
window: &mut gpui::Window,
cx: &mut gpui::Context<Self>,
) -> impl gpui::IntoElement {
let scroll_offset = self.scroll_handle.offset();
let max_offset = self.scroll_handle.max_offset();
self.autoscroll = -scroll_offset.y >= (max_offset.height - px(24.));
if self.autoscroll {
self.scroll_handle.scroll_to_bottom();
}
v_flex()
.id("profiler")
.w_full()
.h_full()
.gap_2()
.bg(cx.theme().colors().surface_background)
.text_color(cx.theme().colors().text)
.child(
h_flex()
.py_2()
.px_4()
.w_full()
.justify_between()
.child(
@@ -346,53 +341,70 @@ impl Render for ProfilerWindow {
let min = e[0].start;
let max = e[e.len() - 1].end.unwrap_or_else(|| Instant::now());
div.child(
let timings = Rc::new(
e.into_iter()
.filter(|timing| {
timing
.end
.unwrap_or_else(|| Instant::now())
.duration_since(timing.start)
.as_millis()
>= 1
})
.filter(|timing| {
if self.include_self_timings.selected() {
true
} else {
!timing.location.file().ends_with("miniprofiler_ui.rs")
}
})
.cloned()
.collect::<Vec<_>>(),
);
div.child(Divider::horizontal()).child(
v_flex()
.id("timings.bars")
.overflow_scroll()
.w_full()
.h_full()
.gap_2()
.track_scroll(&self.scroll_handle)
.on_scroll_wheel(cx.listener(|this, _, _, _cx| {
let scroll_offset = this.scroll_handle.offset();
let max_offset = this.scroll_handle.max_offset();
this.autoscroll = -scroll_offset.y >= (max_offset.height - px(5.0));
}))
.children(
e.iter()
.filter(|timing| {
timing
.end
.unwrap_or_else(|| Instant::now())
.duration_since(timing.start)
.as_millis()
>= 1
})
.filter(|timing| {
if self.include_self_timings.selected() {
true
} else {
!timing.location.file().ends_with("miniprofiler_ui.rs")
.child(
uniform_list("list", timings.len(), {
let timings = timings.clone();
move |visible_range, _, cx| {
let mut items = vec![];
for i in visible_range {
let timing = &timings[i];
let value_range =
max.checked_sub(Duration::from_secs(10)).unwrap_or(min)
..max;
items.push(Self::render_timing(
value_range,
TimingBar {
location: timing.location,
start: timing.start,
end: timing.end.unwrap_or_else(|| Instant::now()),
color: cx
.theme()
.accents()
.color_for_index(i as u32),
},
cx,
));
}
})
.enumerate()
.map(|(i, timing)| {
self.render_timing(
max.checked_sub(Duration::from_secs(10)).unwrap_or(min)
..max,
TimingBar {
location: timing.location,
start: timing.start,
end: timing.end.unwrap_or_else(|| Instant::now()),
color: cx.theme().accents().color_for_index(i as u32),
},
cx,
)
}),
),
items
}
})
.p_4()
.on_scroll_wheel(cx.listener(|this, _, _, cx| {
this.autoscroll = false;
cx.notify();
}))
.track_scroll(self.scroll_handle.clone())
.size_full(),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx),
)
.vertical_scrollbar_for(self.scroll_handle.clone(), window, cx)
})
}
}