Add a scroll to top of item function to scroll handle

Anthony created

This enables settings UI to scroll to the top of a selected header
instead of just when the header is visible

Change summary

crates/gpui/src/elements/div.rs       | 57 +++++++++++++++++++++++-----
crates/settings_ui/src/settings_ui.rs |  2 
2 files changed, 47 insertions(+), 12 deletions(-)

Detailed changes

crates/gpui/src/elements/div.rs 🔗

@@ -3034,7 +3034,20 @@ struct ScrollHandleState {
     child_bounds: Vec<Bounds<Pixels>>,
     scroll_to_bottom: bool,
     overflow: Point<Overflow>,
-    active_item: Option<usize>,
+    active_item: Option<ScrollActiveItem>,
+}
+
+#[derive(Default, Debug, Clone, Copy)]
+struct ScrollActiveItem {
+    index: usize,
+    strategy: ScrollStrategy,
+}
+
+#[derive(Default, Debug, Clone, Copy)]
+enum ScrollStrategy {
+    #[default]
+    FirstVisible,
+    Top,
 }
 
 /// A handle to the scrollable aspects of an element.
@@ -3097,26 +3110,48 @@ impl ScrollHandle {
     /// Update [ScrollHandleState]'s active item for scrolling to in prepaint
     pub fn scroll_to_item(&self, ix: usize) {
         let mut state = self.0.borrow_mut();
-        state.active_item = Some(ix);
+        state.active_item = Some(ScrollActiveItem {
+            index: ix,
+            strategy: ScrollStrategy::default(),
+        });
+    }
+
+    /// Update [ScrollHandleState]'s active item for scrolling to in prepaint
+    /// This scrolls the minimal amount to ensure that the child is the first visible element
+    pub fn scroll_to_top_of_item(&self, ix: usize) {
+        let mut state = self.0.borrow_mut();
+        state.active_item = Some(ScrollActiveItem {
+            index: ix,
+            strategy: ScrollStrategy::Top,
+        });
     }
 
-    /// Scrolls the minimal amount to ensure that the child is
-    /// fully visible
+    /// Scrolls the minimal amount to either ensure that the child is
+    /// fully visible or the top element of the view depends on the
+    /// scroll strategy
     fn scroll_to_active_item(&self) {
         let mut state = self.0.borrow_mut();
 
-        let Some(active_item_index) = state.active_item else {
+        let Some(active_item) = state.active_item else {
             return;
         };
-        let active_item = match state.child_bounds.get(active_item_index) {
+
+        let active_item = match state.child_bounds.get(active_item.index) {
             Some(bounds) => {
                 let mut scroll_offset = state.offset.borrow_mut();
 
-                if state.overflow.y == Overflow::Scroll {
-                    if bounds.top() + scroll_offset.y < state.bounds.top() {
+                match active_item.strategy {
+                    ScrollStrategy::FirstVisible => {
+                        if state.overflow.y == Overflow::Scroll {
+                            if bounds.top() + scroll_offset.y < state.bounds.top() {
+                                scroll_offset.y = state.bounds.top() - bounds.top();
+                            } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
+                                scroll_offset.y = state.bounds.bottom() - bounds.bottom();
+                            }
+                        }
+                    }
+                    ScrollStrategy::Top => {
                         scroll_offset.y = state.bounds.top() - bounds.top();
-                    } else if bounds.bottom() + scroll_offset.y > state.bounds.bottom() {
-                        scroll_offset.y = state.bounds.bottom() - bounds.bottom();
                     }
                 }
 
@@ -3129,7 +3164,7 @@ impl ScrollHandle {
                 }
                 None
             }
-            None => Some(active_item_index),
+            None => Some(active_item),
         };
         state.active_item = active_item;
     }

crates/settings_ui/src/settings_ui.rs 🔗

@@ -1095,7 +1095,7 @@ impl SettingsWindow {
                                                         .map(|pair| pair.0)
                                                     {
                                                         this.scroll_handle
-                                                            .scroll_to_item(section_index);
+                                                            .scroll_to_top_of_item(section_index);
                                                     }
                                                 }