diff --git a/gpui/src/elements/list.rs b/gpui/src/elements/list.rs index c506ef484a2932992de6ca9521d5d34c913d0ac2..1adf10abcf340e2ad279bfaf4a520b67f2587c05 100644 --- a/gpui/src/elements/list.rs +++ b/gpui/src/elements/list.rs @@ -1,7 +1,11 @@ use crate::{ - geometry::{rect::RectF, vector::Vector2F}, + geometry::{ + rect::RectF, + vector::{vec2f, Vector2F}, + }, + json::json, sum_tree::{self, Bias, SumTree}, - Element, + DebugContext, Element, Event, EventContext, LayoutContext, PaintContext, SizeConstraint, }; use parking_lot::Mutex; use std::{ops::Range, sync::Arc}; @@ -19,6 +23,7 @@ struct StateInner { last_layout_width: f32, elements: Vec, heights: SumTree, + scroll_top: f32, } #[derive(Clone, Debug)] @@ -47,6 +52,25 @@ impl List { pub fn new(state: ListState) -> Self { Self { state } } + + fn scroll( + &self, + _: Vector2F, + delta: Vector2F, + precise: bool, + scroll_max: f32, + cx: &mut EventContext, + ) -> bool { + if !precise { + todo!("still need to handle non-precise scroll events from a mouse wheel"); + } + + let mut state = self.state.0.lock(); + state.scroll_top = (state.scroll_top - delta.y()).max(0.0).min(scroll_max); + cx.notify(); + + true + } } impl Element for List { @@ -56,8 +80,8 @@ impl Element for List { fn layout( &mut self, - constraint: crate::SizeConstraint, - cx: &mut crate::LayoutContext, + constraint: SizeConstraint, + cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let state = &mut *self.state.0.lock(); let mut item_constraint = constraint; @@ -100,34 +124,69 @@ impl Element for List { (constraint.max, ()) } - fn paint( - &mut self, - bounds: RectF, - layout: &mut Self::LayoutState, - cx: &mut crate::PaintContext, - ) -> Self::PaintState { - todo!() + fn paint(&mut self, bounds: RectF, _: &mut (), cx: &mut PaintContext) { + let state = &mut *self.state.0.lock(); + let visible_range = state.visible_range(bounds.height()); + + let mut item_top = { + let mut cursor = state.heights.cursor::(); + cursor.seek(&Count(visible_range.start), Bias::Right, &()); + cursor.sum_start().0 + }; + for element in &mut state.elements[visible_range] { + let origin = bounds.origin() + vec2f(0., item_top) - state.scroll_top; + element.paint(origin, cx); + item_top += element.size().y(); + } } fn dispatch_event( &mut self, - event: &crate::Event, + event: &Event, bounds: RectF, - layout: &mut Self::LayoutState, - paint: &mut Self::PaintState, - cx: &mut crate::EventContext, + _: &mut (), + _: &mut (), + cx: &mut EventContext, ) -> bool { - todo!() + let mut handled = false; + + let mut state = self.state.0.lock(); + let visible_range = state.visible_range(bounds.height()); + for item in &mut state.elements[visible_range] { + handled = item.dispatch_event(event, cx) || handled; + } + + match event { + Event::ScrollWheel { + position, + delta, + precise, + } => { + if bounds.contains_point(*position) { + let scroll_max = state.scroll_max(bounds.height()); + if self.scroll(*position, *delta, *precise, scroll_max, cx) { + handled = true; + } + } + } + _ => {} + } + + handled } - fn debug( - &self, - bounds: RectF, - layout: &Self::LayoutState, - paint: &Self::PaintState, - cx: &crate::DebugContext, - ) -> serde_json::Value { - todo!() + fn debug(&self, bounds: RectF, _: &(), _: &(), cx: &DebugContext) -> serde_json::Value { + let state = self.state.0.lock(); + let visible_range = state.visible_range(bounds.height()); + let visible_elements = state.elements[visible_range.clone()] + .iter() + .map(|e| e.debug(cx)) + .collect::>(); + json!({ + "visible_range": visible_range, + "visible_elements": visible_elements, + "scroll_top": state.scroll_top, + }) } } @@ -139,6 +198,7 @@ impl ListState { last_layout_width: 0., elements, heights, + scroll_top: 0., }))) } @@ -170,6 +230,21 @@ impl ListState { } } +impl StateInner { + fn visible_range(&self, height: f32) -> Range { + let mut cursor = self.heights.cursor::(); + cursor.seek(&Height(self.scroll_top), Bias::Right, &()); + let start_ix = cursor.sum_start().0; + cursor.seek(&Height(self.scroll_top + height), Bias::Left, &()); + let end_ix = cursor.sum_start().0; + start_ix..end_ix + 1 + } + + fn scroll_max(&self, height: f32) -> f32 { + self.heights.summary().height - height + } +} + impl ElementHeight { fn is_pending(&self) -> bool { matches!(self, ElementHeight::Pending) @@ -241,6 +316,12 @@ impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Height { } } +impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Height { + fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering { + self.0.partial_cmp(&other.0).unwrap() + } +} + #[cfg(test)] mod tests { use super::*;