diff --git a/crates/gpui/src/elements/uniform_list.rs b/crates/gpui/src/elements/uniform_list.rs index ba002a67f3c614e614dd591d795f839e7f1ea73d..72843ea6330aaa24d9e1d6bf34d024cdeb54ad4a 100644 --- a/crates/gpui/src/elements/uniform_list.rs +++ b/crates/gpui/src/elements/uniform_list.rs @@ -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 { diff --git a/crates/miniprofiler_ui/src/miniprofiler_ui.rs b/crates/miniprofiler_ui/src/miniprofiler_ui.rs index 5fb80b6307ba3b93b3a9c5def7b8da620fdd738c..93ccfc559c6eedc5e1be1c3ca68355aeba878a76 100644 --- a/crates/miniprofiler_ui/src/miniprofiler_ui.rs +++ b/crates/miniprofiler_ui/src/miniprofiler_ui.rs @@ -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>, _refresh: Option>, } @@ -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, - item: TimingBar, - cx: &App, - ) -> impl IntoElement { + fn render_timing(value_range: Range, 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, ) -> 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::>(), + ); + + 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) }) } }