diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 85620102e0..0d7e3d3e0c 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2504,7 +2504,7 @@ impl Window { &mut self, key: impl Into, cx: &mut App, - init: impl FnOnce(&mut Self, &mut App) -> S, + init: impl FnOnce(&mut Self, &mut Context) -> S, ) -> Entity { let current_view = self.current_view(); self.with_global_id(key.into(), |global_id, window| { @@ -2537,7 +2537,7 @@ impl Window { pub fn use_state( &mut self, cx: &mut App, - init: impl FnOnce(&mut Self, &mut App) -> S, + init: impl FnOnce(&mut Self, &mut Context) -> S, ) -> Entity { self.use_keyed_state( ElementId::CodeLocation(*core::panic::Location::caller()), diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index 64ac605bb7..5328620ab3 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -4700,7 +4700,7 @@ impl OutlinePanel { Scrollbars::for_settings::() .tracked_scroll_handle(self.scroll_handle.clone()) .with_track_along(ScrollAxes::Horizontal) - .tracked_entity(cx.entity()), + .notify_content(), window, cx, ) diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index a3fedbc531..92d01357ae 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -5347,7 +5347,7 @@ impl Render for ProjectPanel { Scrollbars::for_settings::() .tracked_scroll_handle(self.scroll_handle.clone()) .with_track_along(ScrollAxes::Horizontal) - .tracked_entity(cx.entity()), + .notify_content(), window, cx, ) diff --git a/crates/ui/src/components/scrollbar.rs b/crates/ui/src/components/scrollbar.rs index 5c5659047a..36812f1490 100644 --- a/crates/ui/src/components/scrollbar.rs +++ b/crates/ui/src/components/scrollbar.rs @@ -18,7 +18,7 @@ use std::ops::Range; use crate::scrollbars::{ScrollbarVisibilitySetting, ShowScrollbar}; -const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1); +const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_millis(1500); const SCROLLBAR_PADDING: Pixels = px(4.); pub mod scrollbars { @@ -105,7 +105,10 @@ where let element_id = config.id.take().unwrap_or_else(|| caller_location.into()); window.use_keyed_state(element_id, cx, |window, cx| { - ScrollbarStateWrapper(cx.new(|cx| ScrollbarState::new_from_config(config, window, cx))) + let parent_id = cx.entity_id(); + ScrollbarStateWrapper( + cx.new(|cx| ScrollbarState::new_from_config(config, parent_id, window, cx)), + ) }) } @@ -352,7 +355,7 @@ pub struct Scrollbars< > { id: Option, tracked_setting: PhantomData, - tracked_entity_id: Option, + tracked_entity: Option>, scrollable_handle: Box T>, handle_was_added: bool, visibility: Point, @@ -376,7 +379,7 @@ impl Scrollbars { tracked_setting: PhantomData, handle_was_added: false, scrollable_handle: Box::new(ScrollHandle::new), - tracked_entity_id: None, + tracked_entity: None, visibility: show_along.apply_to(Default::default(), ReservedSpace::Thumb), scrollbar_width: ScrollbarWidth::Normal, } @@ -398,9 +401,15 @@ impl self } - /// Set a parent model which should be notified whenever this Scrollbar gets a scroll event. - pub fn tracked_entity(mut self, entity: Entity) -> Self { - self.tracked_entity_id = Some(entity.entity_id()); + /// Notify the current context whenever this scrollbar gets a scroll event + pub fn notify_content(mut self) -> Self { + self.tracked_entity = Some(None); + self + } + + /// Set a parent model which should be notified whenever this scrollbar gets a scroll event. + pub fn tracked_entity(mut self, entity: &Entity) -> Self { + self.tracked_entity = Some(Some(entity.entity_id())); self } @@ -411,7 +420,7 @@ impl let Self { id, tracked_setting, - tracked_entity_id, + tracked_entity: tracked_entity_id, scrollbar_width, visibility, .. @@ -422,7 +431,7 @@ impl scrollable_handle: Box::new(|| tracked_scroll_handle), id, tracked_setting, - tracked_entity_id, + tracked_entity: tracked_entity_id, visibility, scrollbar_width, } @@ -475,6 +484,11 @@ impl VisibilityState { } } +enum ParentHovered { + Yes(bool), + No(bool), +} + /// This is used to ensure notifies within the state do not notify the parent /// unintentionally. struct ScrollbarStateWrapper( @@ -484,7 +498,7 @@ struct ScrollbarStateWrapper /// A scrollbar state that should be persisted across frames. struct ScrollbarState { thumb_state: ThumbState, - parent_id: Option, + notify_id: Option, manually_added: bool, scroll_handle: T, width: ScrollbarWidth, @@ -492,6 +506,7 @@ struct ScrollbarState, show_state: VisibilityState, + mouse_in_parent: bool, last_prepaint_state: Option, _auto_hide_task: Option>, } @@ -499,6 +514,7 @@ struct ScrollbarState ScrollbarState { fn new_from_config( config: Scrollbars, + parent_id: EntityId, window: &mut Window, cx: &mut Context, ) -> Self { @@ -507,7 +523,7 @@ impl ScrollbarState { let mut state = ScrollbarState { thumb_state: Default::default(), - parent_id: config.tracked_entity_id, + notify_id: config.tracked_entity.map(|id| id.unwrap_or(parent_id)), manually_added: config.handle_was_added, scroll_handle: (config.scrollable_handle)(), width: config.scrollbar_width, @@ -515,6 +531,7 @@ impl ScrollbarState { tracked_setting: PhantomData, show_setting: ShowScrollbar::Always, show_state: VisibilityState::Visible, + mouse_in_parent: true, last_prepaint_state: None, _auto_hide_task: None, }; @@ -545,9 +562,10 @@ impl ScrollbarState { } } - fn show_scrollbars(&mut self, cx: &mut Context) { - self._auto_hide_task.take(); + fn show_scrollbars(&mut self, window: &mut Window, cx: &mut Context) { self.set_visibility(VisibilityState::Visible, cx); + self._auto_hide_task.take(); + self.schedule_auto_hide(window, cx); } fn set_show_scrollbar( @@ -600,7 +618,7 @@ impl ScrollbarState { &self.scroll_handle } - fn set_offset(&mut self, offset: Point, cx: &mut Context) { + fn set_offset(&mut self, offset: Point, window: &mut Window, cx: &mut Context) { if self.scroll_handle.offset() != offset { self.scroll_handle.set_offset(offset); self.notify_parent(cx); @@ -608,19 +626,30 @@ impl ScrollbarState { } // We always want to show scrollbars in cases where the offset is updated. - self.show_scrollbars(cx); + self.show_scrollbars(window, cx); } fn is_dragging(&self) -> bool { self.thumb_state.is_dragging() } - fn set_dragging(&mut self, axis: ScrollbarAxis, drag_offset: Pixels, cx: &mut Context) { - self.set_thumb_state(ThumbState::Dragging(axis, drag_offset), cx); + fn set_dragging( + &mut self, + axis: ScrollbarAxis, + drag_offset: Pixels, + window: &mut Window, + cx: &mut Context, + ) { + self.set_thumb_state(ThumbState::Dragging(axis, drag_offset), window, cx); self.scroll_handle().drag_started(); } - fn update_hovered_thumb(&mut self, position: &Point, cx: &mut Context) { + fn update_hovered_thumb( + &mut self, + position: &Point, + window: &mut Window, + cx: &mut Context, + ) { self.set_thumb_state( if let Some(&ScrollbarLayout { axis, .. }) = self .last_prepaint_state @@ -631,17 +660,33 @@ impl ScrollbarState { } else { ThumbState::Inactive }, + window, cx, ); } - fn set_thumb_state(&mut self, state: ThumbState, cx: &mut Context) { + fn set_thumb_state(&mut self, state: ThumbState, window: &mut Window, cx: &mut Context) { if self.thumb_state != state { + if matches!(state, ThumbState::Inactive) { + self.schedule_auto_hide(window, cx); + } else { + self.show_scrollbars(window, cx); + } self.thumb_state = state; cx.notify(); } } + fn update_parent_hovered(&mut self, position: &Point) -> ParentHovered { + let last_parent_hovered = self.mouse_in_parent; + self.mouse_in_parent = self.parent_hovered(position); + let state_changed = self.mouse_in_parent != last_parent_hovered; + match self.mouse_in_parent { + true => ParentHovered::Yes(state_changed), + false => ParentHovered::No(state_changed), + } + } + fn parent_hovered(&self, position: &Point) -> bool { self.last_prepaint_state .as_ref() @@ -714,7 +759,7 @@ impl ScrollbarState { } fn notify_parent(&self, cx: &mut App) { - if let Some(entity_id) = self.parent_id { + if let Some(entity_id) = self.notify_id { cx.notify(entity_id); } } @@ -1036,7 +1081,7 @@ impl Element for ScrollbarEl .. } in &prepaint_state.thumbs { - const MAXIMUM_OPACITY: f32 = 0.5; + const MAXIMUM_OPACITY: f32 = 0.7; let thumb_state = self.state.read(cx).thumb_state; let thumb_base_color = match thumb_state { ThumbState::Dragging(dragged_axis, _) if dragged_axis == *axis => { @@ -1083,7 +1128,7 @@ impl Element for ScrollbarEl window.on_mouse_event({ let state = self.state.clone(); - move |event: &MouseDownEvent, phase, _, cx| { + move |event: &MouseDownEvent, phase, window, cx| { state.update(cx, |state, cx| { let Some(scrollbar_layout) = (phase.capture() && event.button == MouseButton::Left) @@ -1100,7 +1145,7 @@ impl Element for ScrollbarEl if thumb_bounds.contains(&event.position) { let offset = event.position.along(*axis) - thumb_bounds.origin.along(*axis); - state.set_dragging(*axis, offset, cx); + state.set_dragging(*axis, offset, window, cx); } else { let scroll_handle = state.scroll_handle(); let click_offset = scrollbar_layout.compute_click_offset( @@ -1110,6 +1155,7 @@ impl Element for ScrollbarEl ); state.set_offset( scroll_handle.offset().apply_along(*axis, |_| click_offset), + window, cx, ); }; @@ -1131,9 +1177,10 @@ impl Element for ScrollbarEl state.update(cx, |state, cx| { state.set_offset( current_offset + event.delta.pixel_delta(window.line_height()), + window, cx, ); - state.show_scrollbars(cx); + state.show_scrollbars(window, cx); cx.stop_propagation(); }); } @@ -1160,21 +1207,27 @@ impl Element for ScrollbarEl let new_offset = scroll_handle.offset().apply_along(axis, |_| drag_offset); - state.update(cx, |state, cx| state.set_offset(new_offset, cx)); + state.update(cx, |state, cx| { + state.set_offset(new_offset, window, cx) + }); cx.stop_propagation(); } } _ => state.update(cx, |state, cx| { - if state.parent_hovered(&event.position) { - // todo! this should only be fired on first re-enter, if at all - state.show_scrollbars(cx); - - if event.pressed_button.is_none() { - state.update_hovered_thumb(&event.position, cx); + match state.update_parent_hovered(&event.position) { + ParentHovered::Yes(state_changed) + if event.pressed_button.is_none() => + { + if state_changed { + state.show_scrollbars(window, cx); + } + state.update_hovered_thumb(&event.position, window, cx); cx.stop_propagation(); } - } else { - state.schedule_auto_hide(window, cx); + ParentHovered::No(state_changed) if state_changed => { + state.schedule_auto_hide(window, cx); + } + _ => {} } }), } @@ -1198,7 +1251,7 @@ impl Element for ScrollbarEl return; } - state.update_hovered_thumb(&event.position, cx); + state.update_hovered_thumb(&event.position, window, cx); }); } });