Restore logical offset preservation behaviour

Conrad Irwin created

Change summary

crates/collab_ui2/src/collab_panel.rs | 325 ++++++++++++++--------------
crates/gpui2/src/elements/div.rs      |  31 ++
2 files changed, 186 insertions(+), 170 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -303,7 +303,6 @@ pub struct CollabPanel {
     project: Model<Project>,
     match_candidates: Vec<StringMatchCandidate>,
     scroll_handle: ScrollHandle,
-    // list_state: ListState<Self>,
     subscriptions: Vec<Subscription>,
     collapsed_sections: Vec<Section>,
     collapsed_channels: Vec<ChannelId>,
@@ -590,7 +589,6 @@ impl CollabPanel {
                 client: workspace.app_state().client.clone(),
                 //                 context_menu_on_selected: true,
                 drag_target_channel: ChannelDragTarget::None,
-                //                 list_state,
             };
 
             this.update_entries(false, cx);
@@ -706,9 +704,9 @@ impl CollabPanel {
         let query = self.filter_editor.read(cx).text(cx);
         let executor = cx.background_executor().clone();
 
-        // let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
-        let _old_entries = mem::take(&mut self.entries);
-        //         let mut scroll_to_top = false;
+        let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
+        let old_entries = mem::take(&mut self.entries);
+        let scroll_to_top = false;
 
         //         if let Some(room) = ActiveCall::global(cx).read(cx).room() {
         //             self.entries.push(ListEntry::Header(Section::ActiveCall));
@@ -1075,71 +1073,62 @@ impl CollabPanel {
             self.entries.push(ListEntry::ContactPlaceholder);
         }
 
-        // if select_same_item {
-        //     if let Some(prev_selected_entry) = prev_selected_entry {
-        //         self.selection.take();
-        //         for (ix, entry) in self.entries.iter().enumerate() {
-        //             if *entry == prev_selected_entry {
-        //                 self.selection = Some(ix);
-        //                 break;
-        //             }
-        //         }
-        //     }
-        // } else {
-        //     self.selection = self.selection.and_then(|prev_selection| {
-        //         if self.entries.is_empty() {
-        //             None
-        //         } else {
-        //             Some(prev_selection.min(self.entries.len() - 1))
-        //         }
-        //     });
-        // }
+        if select_same_item {
+            if let Some(prev_selected_entry) = prev_selected_entry {
+                self.selection.take();
+                for (ix, entry) in self.entries.iter().enumerate() {
+                    if *entry == prev_selected_entry {
+                        self.selection = Some(ix);
+                        self.scroll_handle.scroll_to_item(ix);
+                        break;
+                    }
+                }
+            }
+        } else {
+            self.selection = self.selection.and_then(|prev_selection| {
+                if self.entries.is_empty() {
+                    None
+                } else {
+                    let ix = prev_selection.min(self.entries.len() - 1);
+                    self.scroll_handle.scroll_to_item(ix);
+                    Some(ix)
+                }
+            });
+        }
 
-        // let old_scroll_top = self.list_state.logical_scroll_top();
-
-        // self.list_state.reset(self.entries.len());
-
-        // if scroll_to_top {
-        //     self.list_state.scroll_to(ListOffset::default());
-        // } else {
-        //     // Attempt to maintain the same scroll position.
-        //     if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
-        //         let new_scroll_top = self
-        //             .entries
-        //             .iter()
-        //             .position(|entry| entry == old_top_entry)
-        //             .map(|item_ix| ListOffset {
-        //                 item_ix,
-        //                 offset_in_item: old_scroll_top.offset_in_item,
-        //             })
-        //             .or_else(|| {
-        //                 let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
-        //                 let item_ix = self
-        //                     .entries
-        //                     .iter()
-        //                     .position(|entry| entry == entry_after_old_top)?;
-        //                 Some(ListOffset {
-        //                     item_ix,
-        //                     offset_in_item: 0.,
-        //                 })
-        //             })
-        //             .or_else(|| {
-        //                 let entry_before_old_top =
-        //                     old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
-        //                 let item_ix = self
-        //                     .entries
-        //                     .iter()
-        //                     .position(|entry| entry == entry_before_old_top)?;
-        //                 Some(ListOffset {
-        //                     item_ix,
-        //                     offset_in_item: 0.,
-        //                 })
-        //             });
+        if scroll_to_top {
+            self.scroll_handle.scroll_to_item(0)
+        } else {
+            let (old_index, old_offset) = self.scroll_handle.logical_scroll_top();
+            // Attempt to maintain the same scroll position.
+            if let Some(old_top_entry) = old_entries.get(old_index) {
+                let (new_index, new_offset) = self
+                    .entries
+                    .iter()
+                    .position(|entry| entry == old_top_entry)
+                    .map(|item_ix| (item_ix, old_offset))
+                    .or_else(|| {
+                        let entry_after_old_top = old_entries.get(old_index + 1)?;
+                        let item_ix = self
+                            .entries
+                            .iter()
+                            .position(|entry| entry == entry_after_old_top)?;
+                        Some((item_ix, px(0.)))
+                    })
+                    .or_else(|| {
+                        let entry_before_old_top = old_entries.get(old_index.saturating_sub(1))?;
+                        let item_ix = self
+                            .entries
+                            .iter()
+                            .position(|entry| entry == entry_before_old_top)?;
+                        Some((item_ix, px(0.)))
+                    })
+                    .unwrap_or_else(|| (old_index, old_offset));
 
-        //         self.list_state
-        //             .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
-        //     }
-        // }
+                self.scroll_handle
+                    .set_logical_scroll_top(new_index, new_offset);
+            }
+        }
 
         cx.notify();
     }
@@ -3430,106 +3419,106 @@ impl FocusableView for CollabPanel {
     }
 }
 
-// impl PartialEq for ListEntry {
-//     fn eq(&self, other: &Self) -> bool {
-//         match self {
-//             ListEntry::Header(section_1) => {
-//                 if let ListEntry::Header(section_2) = other {
-//                     return section_1 == section_2;
-//                 }
-//             }
-//             ListEntry::CallParticipant { user: user_1, .. } => {
-//                 if let ListEntry::CallParticipant { user: user_2, .. } = other {
-//                     return user_1.id == user_2.id;
-//                 }
-//             }
-//             ListEntry::ParticipantProject {
-//                 project_id: project_id_1,
-//                 ..
-//             } => {
-//                 if let ListEntry::ParticipantProject {
-//                     project_id: project_id_2,
-//                     ..
-//                 } = other
-//                 {
-//                     return project_id_1 == project_id_2;
-//                 }
-//             }
-//             ListEntry::ParticipantScreen {
-//                 peer_id: peer_id_1, ..
-//             } => {
-//                 if let ListEntry::ParticipantScreen {
-//                     peer_id: peer_id_2, ..
-//                 } = other
-//                 {
-//                     return peer_id_1 == peer_id_2;
-//                 }
-//             }
-//             ListEntry::Channel {
-//                 channel: channel_1, ..
-//             } => {
-//                 if let ListEntry::Channel {
-//                     channel: channel_2, ..
-//                 } = other
-//                 {
-//                     return channel_1.id == channel_2.id;
-//                 }
-//             }
-//             ListEntry::ChannelNotes { channel_id } => {
-//                 if let ListEntry::ChannelNotes {
-//                     channel_id: other_id,
-//                 } = other
-//                 {
-//                     return channel_id == other_id;
-//                 }
-//             }
-//             ListEntry::ChannelChat { channel_id } => {
-//                 if let ListEntry::ChannelChat {
-//                     channel_id: other_id,
-//                 } = other
-//                 {
-//                     return channel_id == other_id;
-//                 }
-//             }
-//             ListEntry::ChannelInvite(channel_1) => {
-//                 if let ListEntry::ChannelInvite(channel_2) = other {
-//                     return channel_1.id == channel_2.id;
-//                 }
-//             }
-//             ListEntry::IncomingRequest(user_1) => {
-//                 if let ListEntry::IncomingRequest(user_2) = other {
-//                     return user_1.id == user_2.id;
-//                 }
-//             }
-//             ListEntry::OutgoingRequest(user_1) => {
-//                 if let ListEntry::OutgoingRequest(user_2) = other {
-//                     return user_1.id == user_2.id;
-//                 }
-//             }
-//             ListEntry::Contact {
-//                 contact: contact_1, ..
-//             } => {
-//                 if let ListEntry::Contact {
-//                     contact: contact_2, ..
-//                 } = other
-//                 {
-//                     return contact_1.user.id == contact_2.user.id;
-//                 }
-//             }
-//             ListEntry::ChannelEditor { depth } => {
-//                 if let ListEntry::ChannelEditor { depth: other_depth } = other {
-//                     return depth == other_depth;
-//                 }
-//             }
-//             ListEntry::ContactPlaceholder => {
-//                 if let ListEntry::ContactPlaceholder = other {
-//                     return true;
-//                 }
-//             }
-//         }
-//         false
-//     }
-// }
+impl PartialEq for ListEntry {
+    fn eq(&self, other: &Self) -> bool {
+        match self {
+            ListEntry::Header(section_1) => {
+                if let ListEntry::Header(section_2) = other {
+                    return section_1 == section_2;
+                }
+            }
+            // ListEntry::CallParticipant { user: user_1, .. } => {
+            //     if let ListEntry::CallParticipant { user: user_2, .. } = other {
+            //         return user_1.id == user_2.id;
+            //     }
+            // }
+            // ListEntry::ParticipantProject {
+            //     project_id: project_id_1,
+            //     ..
+            // } => {
+            //     if let ListEntry::ParticipantProject {
+            //         project_id: project_id_2,
+            //         ..
+            //     } = other
+            //     {
+            //         return project_id_1 == project_id_2;
+            //     }
+            // }
+            // ListEntry::ParticipantScreen {
+            //     peer_id: peer_id_1, ..
+            // } => {
+            //     if let ListEntry::ParticipantScreen {
+            //         peer_id: peer_id_2, ..
+            //     } = other
+            //     {
+            //         return peer_id_1 == peer_id_2;
+            //     }
+            // }
+            ListEntry::Channel {
+                channel: channel_1, ..
+            } => {
+                if let ListEntry::Channel {
+                    channel: channel_2, ..
+                } = other
+                {
+                    return channel_1.id == channel_2.id;
+                }
+            }
+            // ListEntry::ChannelNotes { channel_id } => {
+            //     if let ListEntry::ChannelNotes {
+            //         channel_id: other_id,
+            //     } = other
+            //     {
+            //         return channel_id == other_id;
+            //     }
+            // }
+            // ListEntry::ChannelChat { channel_id } => {
+            //     if let ListEntry::ChannelChat {
+            //         channel_id: other_id,
+            //     } = other
+            //     {
+            //         return channel_id == other_id;
+            //     }
+            // }
+            // ListEntry::ChannelInvite(channel_1) => {
+            //     if let ListEntry::ChannelInvite(channel_2) = other {
+            //         return channel_1.id == channel_2.id;
+            //     }
+            // }
+            ListEntry::IncomingRequest(user_1) => {
+                if let ListEntry::IncomingRequest(user_2) = other {
+                    return user_1.id == user_2.id;
+                }
+            }
+            ListEntry::OutgoingRequest(user_1) => {
+                if let ListEntry::OutgoingRequest(user_2) = other {
+                    return user_1.id == user_2.id;
+                }
+            }
+            ListEntry::Contact {
+                contact: contact_1, ..
+            } => {
+                if let ListEntry::Contact {
+                    contact: contact_2, ..
+                } = other
+                {
+                    return contact_1.user.id == contact_2.user.id;
+                }
+            }
+            ListEntry::ChannelEditor { depth } => {
+                if let ListEntry::ChannelEditor { depth: other_depth } = other {
+                    return depth == other_depth;
+                }
+            }
+            ListEntry::ContactPlaceholder => {
+                if let ListEntry::ContactPlaceholder = other {
+                    return true;
+                }
+            }
+        }
+        false
+    }
+}
 
 // fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Element<CollabPanel> {
 //     Svg::new(svg_path)

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

@@ -636,12 +636,20 @@ impl Element for Div {
             let mut state = scroll_handle.0.borrow_mut();
             state.child_bounds = Vec::with_capacity(element_state.child_layout_ids.len());
             state.bounds = bounds;
+            let requested = state.requested_scroll_top.take();
 
-            for child_layout_id in &element_state.child_layout_ids {
+            for (ix, child_layout_id) in element_state.child_layout_ids.iter().enumerate() {
                 let child_bounds = cx.layout_bounds(*child_layout_id);
                 child_min = child_min.min(&child_bounds.origin);
                 child_max = child_max.max(&child_bounds.lower_right());
-                state.child_bounds.push(child_bounds)
+                state.child_bounds.push(child_bounds);
+
+                if let Some(requested) = requested.as_ref() {
+                    if requested.0 == ix {
+                        *state.offset.borrow_mut() =
+                            bounds.origin - (child_bounds.origin - point(px(0.), requested.1));
+                    }
+                }
             }
             (child_max - child_min).into()
         } else {
@@ -1460,6 +1468,7 @@ struct ScrollHandleState {
     offset: Rc<RefCell<Point<Pixels>>>,
     bounds: Bounds<Pixels>,
     child_bounds: Vec<Bounds<Pixels>>,
+    requested_scroll_top: Option<(usize, Pixels)>,
 }
 
 #[derive(Clone)]
@@ -1513,4 +1522,22 @@ impl ScrollHandle {
             state.offset.borrow_mut().y = state.bounds.bottom() - bounds.bottom();
         }
     }
+
+    pub fn logical_scroll_top(&self) -> (usize, Pixels) {
+        let ix = self.top_item();
+        let state = self.0.borrow();
+
+        if let Some(child_bounds) = state.child_bounds.get(ix) {
+            (
+                ix,
+                child_bounds.top() + state.offset.borrow().y - state.bounds.top(),
+            )
+        } else {
+            (ix, px(0.))
+        }
+    }
+
+    pub fn set_logical_scroll_top(&self, ix: usize, px: Pixels) {
+        self.0.borrow_mut().requested_scroll_top = Some((ix, px));
+    }
 }