Calculate scroll position based on middle item

Anthony created

Change summary

crates/gpui/src/elements/div.rs       | 34 +++++++++++++++++++++++++++++
crates/settings_ui/src/settings_ui.rs |  6 ++++
2 files changed, 39 insertions(+), 1 deletion(-)

Detailed changes

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

@@ -3097,6 +3097,25 @@ impl ScrollHandle {
         }
     }
 
+    /// Get the bottom child that's scrolled into view.
+    pub fn bottom_item(&self) -> usize {
+        let state = self.0.borrow();
+        let bottom = state.bounds.bottom() - state.offset.borrow().y;
+
+        match state.child_bounds.binary_search_by(|bounds| {
+            if bottom < bounds.top() {
+                Ordering::Greater
+            } else if bottom > bounds.bottom() {
+                Ordering::Less
+            } else {
+                Ordering::Equal
+            }
+        }) {
+            Ok(ix) => ix,
+            Err(ix) => ix.min(state.child_bounds.len().saturating_sub(1)),
+        }
+    }
+
     /// Return the bounds into which this child is painted
     pub fn bounds(&self) -> Bounds<Pixels> {
         self.0.borrow().bounds
@@ -3198,6 +3217,21 @@ impl ScrollHandle {
         }
     }
 
+    /// Get the logical scroll bottom, based on a child index and a pixel offset.
+    pub fn logical_scroll_bottom(&self) -> (usize, Pixels) {
+        let ix = self.bottom_item();
+        let state = self.0.borrow();
+
+        if let Some(child_bounds) = state.child_bounds.get(ix) {
+            (
+                ix,
+                child_bounds.bottom() + state.offset.borrow().y - state.bounds.bottom(),
+            )
+        } else {
+            (ix, px(0.))
+        }
+    }
+
     /// Get the count of children for scrollable item.
     pub fn children_count(&self) -> usize {
         self.0.borrow().child_bounds.len()

crates/settings_ui/src/settings_ui.rs 🔗

@@ -944,7 +944,11 @@ impl SettingsWindow {
     }
 
     fn calculate_navbar_entry_from_scroll_position(&mut self) {
-        let scroll_index = self.scroll_handle.logical_scroll_top().0;
+        let top = self.scroll_handle.top_item();
+        let bottom = self.scroll_handle.bottom_item();
+
+        let scroll_index = (top + bottom) / 2;
+        let scroll_index = scroll_index.clamp(top, bottom);
         let mut page_index = self.navbar_entry;
 
         while !self.navbar_entries[page_index].is_root {