Rerender list elements when they notify during events

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

gpui/src/elements/list.rs | 35 ++++++++++++++++++++++++++++++++---
gpui/src/presenter.rs     |  7 +++++++
2 files changed, 39 insertions(+), 3 deletions(-)

Detailed changes

gpui/src/elements/list.rs 🔗

@@ -12,6 +12,7 @@ use std::{cell::RefCell, collections::VecDeque, ops::Range, rc::Rc};
 
 pub struct List {
     state: ListState,
+    invalidated_elements: Vec<ElementRc>,
 }
 
 #[derive(Clone)]
@@ -79,7 +80,10 @@ struct Height(f32);
 
 impl List {
     pub fn new(state: ListState) -> Self {
-        Self { state }
+        Self {
+            state,
+            invalidated_elements: Default::default(),
+        }
     }
 }
 
@@ -258,10 +262,35 @@ impl Element for List {
         let mut handled = false;
 
         let mut state = self.state.0.borrow_mut();
-        for (mut element, _) in state.visible_elements(bounds, scroll_top) {
-            handled = element.dispatch_event(event, cx) || handled;
+        let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item);
+        let mut cursor = state.items.cursor::<Count, ()>();
+        let mut new_items = cursor.slice(&Count(scroll_top.item_ix), Bias::Right, &());
+        while let Some(item) = cursor.item() {
+            if item_origin.y() > bounds.max_y() {
+                break;
+            }
+
+            if let ListItem::Rendered(element) = item {
+                let prev_notify_count = cx.notify_count();
+                let mut element = element.clone();
+                handled = element.dispatch_event(event, cx) || handled;
+                item_origin.set_y(item_origin.y() + element.size().y());
+                if cx.notify_count() > prev_notify_count {
+                    new_items.push(ListItem::Unrendered, &());
+                    self.invalidated_elements.push(element);
+                } else {
+                    new_items.push(item.clone(), &());
+                }
+                cursor.next(&());
+            } else {
+                unreachable!();
+            }
         }
 
+        new_items.push_tree(cursor.suffix(&()), &());
+        drop(cursor);
+        state.items = new_items;
+
         match event {
             Event::ScrollWheel {
                 position,

gpui/src/presenter.rs 🔗

@@ -195,6 +195,7 @@ impl Presenter {
             text_layout_cache: &self.text_layout_cache,
             view_stack: Default::default(),
             invalidated_views: Default::default(),
+            notify_count: 0,
             app: cx,
         }
     }
@@ -300,6 +301,7 @@ pub struct EventContext<'a> {
     pub font_cache: &'a FontCache,
     pub text_layout_cache: &'a TextLayoutCache,
     pub app: &'a mut MutableAppContext,
+    pub notify_count: usize,
     view_stack: Vec<usize>,
     invalidated_views: HashSet<usize>,
 }
@@ -325,10 +327,15 @@ impl<'a> EventContext<'a> {
     }
 
     pub fn notify(&mut self) {
+        self.notify_count += 1;
         if let Some(view_id) = self.view_stack.last() {
             self.invalidated_views.insert(*view_id);
         }
     }
+
+    pub fn notify_count(&self) -> usize {
+        self.notify_count
+    }
 }
 
 impl<'a> Deref for EventContext<'a> {