scrollbar: Add max_fps to limit drag scroll update rate for reduce CPU usage. (#604)

And set Table to use 60 FPS.
This commit is contained in:
Jason Lee 2025-02-07 10:07:47 +08:00 committed by GitHub
parent abb3de013e
commit 2e3f3dd669
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 54 additions and 12 deletions

View file

@ -1,4 +1,8 @@
use std::{cell::Cell, rc::Rc, time::Instant};
use std::{
cell::Cell,
rc::Rc,
time::{Duration, Instant},
};
use crate::ActiveTheme;
use gpui::{
@ -69,6 +73,8 @@ pub struct ScrollbarState {
drag_pos: Point<Pixels>,
last_scroll_offset: Point<Pixels>,
last_scroll_time: Option<Instant>,
// Last update offset
last_update: Instant,
}
impl Default for ScrollbarState {
@ -80,6 +86,7 @@ impl Default for ScrollbarState {
drag_pos: point(px(0.), px(0.)),
last_scroll_offset: point(px(0.), px(0.)),
last_scroll_time: None,
last_update: Instant::now(),
}
}
}
@ -139,6 +146,12 @@ impl ScrollbarState {
state
}
fn with_last_update(&self, t: Instant) -> Self {
let mut state = *self;
state.last_update = t;
state
}
fn is_scrollbar_visible(&self) -> bool {
if let Some(last_time) = self.last_scroll_time {
let elapsed = Instant::now().duration_since(last_time).as_secs_f32();
@ -198,6 +211,11 @@ pub struct Scrollbar {
scroll_handle: Rc<Box<dyn ScrollHandleOffsetable>>,
scroll_size: gpui::Size<Pixels>,
state: Rc<Cell<ScrollbarState>>,
/// Maximum frames per second for scrolling by drag. Default is 120 FPS.
///
/// This is used to limit the update rate of the scrollbar when it is
/// being dragged for some complex interactions for reducing CPU usage.
max_fps: usize,
}
impl Scrollbar {
@ -215,6 +233,7 @@ impl Scrollbar {
scroll_size,
width: px(12.),
scroll_handle: Rc::new(Box::new(scroll_handle)),
max_fps: 120,
}
}
@ -294,6 +313,16 @@ impl Scrollbar {
self
}
/// Set maximum frames per second for scrolling by drag. Default is 120 FPS.
///
/// If you have very high CPU usage, consider reducing this value to improve performance.
///
/// Available values: 30..120
pub fn max_fps(mut self, max_fps: usize) -> Self {
self.max_fps = max_fps.clamp(30, 120);
self
}
fn style_for_active(cx: &App) -> (Hsla, Hsla, Hsla, Pixels, Pixels) {
(
cx.theme().scrollbar_thumb_hover,
@ -710,19 +739,21 @@ impl Element for Scrollbar {
let scroll_handle = self.scroll_handle.clone();
let state = self.state.clone();
let view_id = self.view_id;
let max_fps_duration = Duration::from_millis((1000 / self.max_fps) as u64);
move |event: &MouseMoveEvent, _, _, cx| {
let mut notify = false;
// Update hovered state for scrollbar
if bounds.contains(&event.position) {
if state.get().hovered_axis != Some(axis) {
state.set(state.get().with_hovered(Some(axis)));
cx.notify(view_id);
notify = true;
}
} else {
if state.get().hovered_axis == Some(axis) {
if state.get().hovered_axis.is_some() {
state.set(state.get().with_hovered(None));
cx.notify(view_id);
notify = true;
}
}
}
@ -731,12 +762,12 @@ impl Element for Scrollbar {
if thumb_bounds.contains(&event.position) {
if state.get().hovered_on_thumb != Some(axis) {
state.set(state.get().with_hovered_on_thumb(Some(axis)));
cx.notify(view_id);
notify = true;
}
} else {
if state.get().hovered_on_thumb == Some(axis) {
state.set(state.get().with_hovered_on_thumb(None));
cx.notify(view_id);
notify = true;
}
}
@ -772,10 +803,18 @@ impl Element for Scrollbar {
if (scroll_handle.offset().y - offset.y).abs() > px(1.)
|| (scroll_handle.offset().x - offset.x).abs() > px(1.)
{
scroll_handle.set_offset(offset);
cx.notify(view_id);
// Limit update rate
if state.get().last_update.elapsed() > max_fps_duration {
scroll_handle.set_offset(offset);
state.set(state.get().with_last_update(Instant::now()));
notify = true;
}
}
}
if notify {
cx.notify(view_id);
}
}
});

View file

@ -866,11 +866,14 @@ where
.left_0()
.right_0()
.bottom_0()
.child(Scrollbar::uniform_scroll(
cx.entity().entity_id(),
state,
self.vertical_scroll_handle.clone(),
)),
.child(
Scrollbar::uniform_scroll(
cx.entity().entity_id(),
state,
self.vertical_scroll_handle.clone(),
)
.max_fps(60),
),
)
}