Drop scroll events if there's been a reset

co-authored-by: Nathan <nathan@zed.dev>
co-authored-by: Conrad <conrad@zed.dev>
This commit is contained in:
Mikayla 2024-01-17 13:00:29 -08:00
parent 80852c3e18
commit 6db18e8972
No known key found for this signature in database
4 changed files with 112 additions and 114 deletions

View File

@ -643,7 +643,8 @@ impl<'a> VisualTestContext {
/// Simulate an event from the platform, e.g. a SrollWheelEvent /// Simulate an event from the platform, e.g. a SrollWheelEvent
/// Make sure you've called [VisualTestContext::draw] first! /// Make sure you've called [VisualTestContext::draw] first!
pub fn simulate_event<E: InputEvent>(&mut self, event: E) { pub fn simulate_event<E: InputEvent>(&mut self, event: E) {
self.update(|cx| cx.dispatch_event(event.to_platform_input())); self.test_window(self.window)
.simulate_input(event.to_platform_input());
self.background_executor.run_until_parked(); self.background_executor.run_until_parked();
} }

View File

@ -28,9 +28,9 @@ struct StateInner {
render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>, render_item: Box<dyn FnMut(usize, &mut WindowContext) -> AnyElement>,
items: SumTree<ListItem>, items: SumTree<ListItem>,
logical_scroll_top: Option<ListOffset>, logical_scroll_top: Option<ListOffset>,
pending_scroll_delta: Pixels,
alignment: ListAlignment, alignment: ListAlignment,
overdraw: Pixels, overdraw: Pixels,
reset: bool,
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>, scroll_handler: Option<Box<dyn FnMut(&ListScrollEvent, &mut WindowContext)>>,
} }
@ -93,12 +93,17 @@ impl ListState {
alignment: orientation, alignment: orientation,
overdraw, overdraw,
scroll_handler: None, scroll_handler: None,
pending_scroll_delta: px(0.), reset: false,
}))) })))
} }
/// Reset this instantiation of the list state.
///
/// Note that this will cause scroll events to be dropped until the next paint.
pub fn reset(&self, element_count: usize) { pub fn reset(&self, element_count: usize) {
let state = &mut *self.0.borrow_mut(); let state = &mut *self.0.borrow_mut();
state.reset = true;
state.logical_scroll_top = None; state.logical_scroll_top = None;
state.items = SumTree::new(); state.items = SumTree::new();
state state
@ -154,11 +159,13 @@ impl ListState {
scroll_top.item_ix = item_count; scroll_top.item_ix = item_count;
scroll_top.offset_in_item = px(0.); scroll_top.offset_in_item = px(0.);
} }
state.logical_scroll_top = Some(scroll_top); state.logical_scroll_top = Some(scroll_top);
} }
pub fn scroll_to_reveal_item(&self, ix: usize) { pub fn scroll_to_reveal_item(&self, ix: usize) {
let state = &mut *self.0.borrow_mut(); let state = &mut *self.0.borrow_mut();
let mut scroll_top = state.logical_scroll_top(); let mut scroll_top = state.logical_scroll_top();
let height = state let height = state
.last_layout_bounds .last_layout_bounds
@ -189,9 +196,9 @@ impl ListState {
/// Get the bounds for the given item in window coordinates. /// Get the bounds for the given item in window coordinates.
pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> { pub fn bounds_for_item(&self, ix: usize) -> Option<Bounds<Pixels>> {
let state = &*self.0.borrow(); let state = &*self.0.borrow();
let bounds = state.last_layout_bounds.unwrap_or_default(); let bounds = state.last_layout_bounds.unwrap_or_default();
let scroll_top = state.logical_scroll_top(); let scroll_top = state.logical_scroll_top();
if ix < scroll_top.item_ix { if ix < scroll_top.item_ix {
return None; return None;
} }
@ -232,7 +239,11 @@ impl StateInner {
delta: Point<Pixels>, delta: Point<Pixels>,
cx: &mut WindowContext, cx: &mut WindowContext,
) { ) {
// self.pending_scroll_delta += delta.y; // Drop scroll events after a reset, since we can't calculate
// the new logical scroll top without the item heights
if self.reset {
return;
}
let scroll_max = (self.items.summary().height - height).max(px(0.)); let scroll_max = (self.items.summary().height - height).max(px(0.));
let new_scroll_top = (self.scroll_top(scroll_top) - delta.y) let new_scroll_top = (self.scroll_top(scroll_top) - delta.y)
@ -329,6 +340,8 @@ impl Element for List {
) { ) {
let state = &mut *self.state.0.borrow_mut(); let state = &mut *self.state.0.borrow_mut();
state.reset = false;
// If the width of the list has changed, invalidate all cached item heights // If the width of the list has changed, invalidate all cached item heights
if state.last_layout_bounds.map_or(true, |last_bounds| { if state.last_layout_bounds.map_or(true, |last_bounds| {
last_bounds.size.width != bounds.size.width last_bounds.size.width != bounds.size.width
@ -352,117 +365,104 @@ impl Element for List {
let mut cursor = old_items.cursor::<Count>(); let mut cursor = old_items.cursor::<Count>();
loop { // Render items after the scroll top, including those in the trailing overdraw
// Render items after the scroll top, including those in the trailing overdraw cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); for (ix, item) in cursor.by_ref().enumerate() {
for (ix, item) in cursor.by_ref().enumerate() { let visible_height = rendered_height - scroll_top.offset_in_item;
let visible_height = rendered_height - scroll_top.offset_in_item; if visible_height >= bounds.size.height + state.overdraw {
if visible_height >= bounds.size.height + state.overdraw { break;
break;
}
// Use the previously cached height if available
let mut height = if let ListItem::Rendered { height } = item {
Some(*height)
} else {
None
};
// If we're within the visible area or the height wasn't cached, render and measure the item's element
if visible_height < bounds.size.height || height.is_none() {
let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
let element_size = element.measure(available_item_space, cx);
height = Some(element_size.height);
if visible_height < bounds.size.height {
item_elements.push_back(element);
}
}
let height = height.unwrap();
rendered_height += height;
measured_items.push_back(ListItem::Rendered { height });
} }
// Prepare to start walking upward from the item at the scroll top. // Use the previously cached height if available
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); let mut height = if let ListItem::Rendered { height } = item {
Some(*height)
} else {
None
};
// If the rendered items do not fill the visible region, then adjust // If we're within the visible area or the height wasn't cached, render and measure the item's element
// the scroll top upward. if visible_height < bounds.size.height || height.is_none() {
if rendered_height - scroll_top.offset_in_item < bounds.size.height { let mut element = (state.render_item)(scroll_top.item_ix + ix, cx);
while rendered_height < bounds.size.height { let element_size = element.measure(available_item_space, cx);
cursor.prev(&()); height = Some(element_size.height);
if cursor.item().is_some() { if visible_height < bounds.size.height {
let mut element = (state.render_item)(cursor.start().0, cx); item_elements.push_back(element);
let element_size = element.measure(available_item_space, cx);
rendered_height += element_size.height;
measured_items.push_front(ListItem::Rendered {
height: element_size.height,
});
item_elements.push_front(element)
} else {
break;
}
}
scroll_top = ListOffset {
item_ix: cursor.start().0,
offset_in_item: rendered_height - bounds.size.height,
};
match state.alignment {
ListAlignment::Top => {
scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
state.logical_scroll_top = Some(scroll_top);
}
ListAlignment::Bottom => {
scroll_top = ListOffset {
item_ix: cursor.start().0,
offset_in_item: rendered_height - bounds.size.height,
};
state.logical_scroll_top = None;
}
};
}
// Measure items in the leading overdraw
let mut leading_overdraw = scroll_top.offset_in_item;
while leading_overdraw < state.overdraw {
cursor.prev(&());
if let Some(item) = cursor.item() {
let height = if let ListItem::Rendered { height } = item {
*height
} else {
let mut element = (state.render_item)(cursor.start().0, cx);
element.measure(available_item_space, cx).height
};
leading_overdraw += height;
measured_items.push_front(ListItem::Rendered { height });
} else {
break;
} }
} }
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); let height = height.unwrap();
let mut cursor = old_items.cursor::<Count>(); rendered_height += height;
let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); measured_items.push_back(ListItem::Rendered { height });
new_items.extend(measured_items, &());
cursor.seek(&Count(measured_range.end), Bias::Right, &());
new_items.append(cursor.suffix(&()), &());
state.items = new_items;
state.last_layout_bounds = Some(bounds);
// if !state.pending_scroll_delta.is_zero() {
// // Do scroll manipulation
// state.pending_scroll_delta = px(0.);
// } else {
break;
// }
} }
// Prepare to start walking upward from the item at the scroll top.
cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &());
// If the rendered items do not fill the visible region, then adjust
// the scroll top upward.
if rendered_height - scroll_top.offset_in_item < bounds.size.height {
while rendered_height < bounds.size.height {
cursor.prev(&());
if cursor.item().is_some() {
let mut element = (state.render_item)(cursor.start().0, cx);
let element_size = element.measure(available_item_space, cx);
rendered_height += element_size.height;
measured_items.push_front(ListItem::Rendered {
height: element_size.height,
});
item_elements.push_front(element)
} else {
break;
}
}
scroll_top = ListOffset {
item_ix: cursor.start().0,
offset_in_item: rendered_height - bounds.size.height,
};
match state.alignment {
ListAlignment::Top => {
scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.));
state.logical_scroll_top = Some(scroll_top);
}
ListAlignment::Bottom => {
scroll_top = ListOffset {
item_ix: cursor.start().0,
offset_in_item: rendered_height - bounds.size.height,
};
state.logical_scroll_top = None;
}
};
}
// Measure items in the leading overdraw
let mut leading_overdraw = scroll_top.offset_in_item;
while leading_overdraw < state.overdraw {
cursor.prev(&());
if let Some(item) = cursor.item() {
let height = if let ListItem::Rendered { height } = item {
*height
} else {
let mut element = (state.render_item)(cursor.start().0, cx);
element.measure(available_item_space, cx).height
};
leading_overdraw += height;
measured_items.push_front(ListItem::Rendered { height });
} else {
break;
}
}
let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len());
let mut cursor = old_items.cursor::<Count>();
let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &());
new_items.extend(measured_items, &());
cursor.seek(&Count(measured_range.end), Bias::Right, &());
new_items.append(cursor.suffix(&()), &());
// Paint the visible items // Paint the visible items
cx.with_content_mask(Some(ContentMask { bounds }), |cx| { cx.with_content_mask(Some(ContentMask { bounds }), |cx| {
let mut item_origin = bounds.origin; let mut item_origin = bounds.origin;
@ -474,12 +474,13 @@ impl Element for List {
} }
}); });
state.items = new_items;
state.last_layout_bounds = Some(bounds);
let list_state = self.state.clone(); let list_state = self.state.clone();
let height = bounds.size.height; let height = bounds.size.height;
dbg!("scroll is being bound");
cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| {
dbg!("scroll dispatched!");
if phase == DispatchPhase::Bubble if phase == DispatchPhase::Bubble
&& bounds.contains(&event.position) && bounds.contains(&event.position)
&& cx.was_top_layer(&event.position, cx.stacking_order()) && cx.was_top_layer(&event.position, cx.stacking_order())
@ -624,7 +625,5 @@ mod test {
// Scroll position should stay at the top of the list // Scroll position should stay at the top of the list
assert_eq!(state.logical_scroll_top().item_ix, 0); assert_eq!(state.logical_scroll_top().item_ix, 0);
assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); assert_eq!(state.logical_scroll_top().offset_in_item, px(0.));
panic!("We should not get here yet!")
} }
} }

View File

@ -209,7 +209,6 @@ mod tests {
); );
assert!(!matcher.has_pending_keystrokes()); assert!(!matcher.has_pending_keystrokes());
eprintln!("PROBLEM AREA");
// If a is prefixed, C will not be dispatched because there // If a is prefixed, C will not be dispatched because there
// was a pending binding for it // was a pending binding for it
assert_eq!( assert_eq!(

View File

@ -1717,7 +1717,6 @@ impl<'a> WindowContext<'a> {
.mouse_listeners .mouse_listeners
.remove(&event.type_id()) .remove(&event.type_id())
{ {
dbg!(handlers.len());
// Because handlers may add other handlers, we sort every time. // Because handlers may add other handlers, we sort every time.
handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));