From 477740360dbcbe8881cbce51eb621db6c6fb9836 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 31 Aug 2021 21:57:56 -0700 Subject: [PATCH] Start work on partial rendering for List element Co-Authored-By: Nathan Sobo Co-Authored-By: Antonio Scandurra --- gpui/src/elements/list.rs | 599 ++++++++++++++++++++++-------------- gpui/src/presenter.rs | 29 +- gpui/src/sum_tree.rs | 11 +- gpui/src/sum_tree/cursor.rs | 38 +++ zed/src/channel.rs | 6 + zed/src/chat_panel.rs | 45 +-- 6 files changed, 468 insertions(+), 260 deletions(-) diff --git a/gpui/src/elements/list.rs b/gpui/src/elements/list.rs index d0163468b1fd875f715c0367d58fafbe3ebb7e42..2bb1e7060eeb835eb2f623c21296c69167e60f82 100644 --- a/gpui/src/elements/list.rs +++ b/gpui/src/elements/list.rs @@ -6,7 +6,7 @@ use crate::{ json::json, sum_tree::{self, Bias, SumTree}, DebugContext, Element, ElementBox, ElementRc, Event, EventContext, LayoutContext, PaintContext, - RenderContext, SizeConstraint, View, + SizeConstraint, }; use std::{cell::RefCell, ops::Range, rc::Rc}; @@ -17,7 +17,7 @@ pub struct List { #[derive(Clone)] pub struct ListState(Rc>); -#[derive(Eq, PartialEq)] +#[derive(Clone, Copy, Eq, PartialEq)] pub enum Orientation { Top, Bottom, @@ -25,87 +25,56 @@ pub enum Orientation { struct StateInner { last_layout_width: Option, - elements: Vec>, - heights: SumTree, - scroll_top: Option<(usize, f32)>, + render_item: Box ElementBox>, + rendered_range: Range, + items: SumTree, + scroll_top: Option, orientation: Orientation, + overdraw: usize, scroll_handler: Option, &mut EventContext)>>, } -#[derive(Clone, Debug)] -enum ElementHeight { - Pending, - Ready(f32), +#[derive(Clone, Copy, Debug, Default, PartialEq)] +pub struct ScrollTop { + item_ix: usize, + offset_in_item: f32, +} + +#[derive(Clone)] +enum ListItem { + Unrendered, + Rendered(ElementRc), + Removed(f32), } #[derive(Clone, Debug, Default, PartialEq)] -struct ElementHeightSummary { +struct ListItemSummary { count: usize, - pending_count: usize, + rendered_count: usize, + unrendered_count: usize, height: f32, } -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] struct Count(usize); -#[derive(Clone, Debug, Default)] -struct PendingCount(usize); +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct RenderedCount(usize); + +#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct UnrenderedCount(usize); #[derive(Clone, Debug, Default)] struct Height(f32); impl List { - pub fn new(state: ListState, cx: &RenderContext, build_items: F) -> Self - where - F: Fn(Range) -> I, - I: IntoIterator, - V: View, - { - { - let state = &mut *state.0.borrow_mut(); - if cx.refreshing { - let elements = (build_items)(0..state.elements.len()); - state.last_layout_width = None; - state.elements.clear(); - state - .elements - .extend(elements.into_iter().map(|e| Some(e.into()))); - state.heights = SumTree::new(); - state.heights.extend( - (0..state.elements.len()).map(|_| ElementHeight::Pending), - &(), - ); - } else { - let mut cursor = state.heights.cursor::(); - cursor.seek(&PendingCount(1), sum_tree::Bias::Left, &()); - - while cursor.item().is_some() { - let start_ix = cursor.sum_start().0; - while cursor.item().map_or(false, |h| h.is_pending()) { - cursor.next(&()); - } - let end_ix = cursor.sum_start().0; - if end_ix > start_ix { - state.elements.splice( - start_ix..end_ix, - (build_items)(start_ix..end_ix) - .into_iter() - .map(|e| Some(e.into())), - ); - } - - cursor.seek(&PendingCount(cursor.seek_start().0 + 1), Bias::Left, &()); - } - } - } - + pub fn new(state: ListState) -> Self { Self { state } } } impl Element for List { - type LayoutState = Vec; - + type LayoutState = ScrollTop; type PaintState = (); fn layout( @@ -114,79 +83,162 @@ impl Element for List { cx: &mut LayoutContext, ) -> (Vector2F, Self::LayoutState) { let state = &mut *self.state.0.borrow_mut(); + let size = constraint.max; let mut item_constraint = constraint; item_constraint.min.set_y(0.); item_constraint.max.set_y(f32::INFINITY); - if state.last_layout_width == Some(constraint.max.x()) { - let mut old_heights = state.heights.cursor::(); - let mut new_heights = old_heights.slice(&PendingCount(1), sum_tree::Bias::Left, &()); - - while let Some(height) = old_heights.item() { - if height.is_pending() { - let element = &mut state.elements[old_heights.sum_start().count]; - let element_size = element.as_mut().unwrap().layout(item_constraint, cx); - new_heights.push(ElementHeight::Ready(element_size.y()), &()); - old_heights.next(&()); - } else { - new_heights.push_tree( - old_heights.slice( - &PendingCount(old_heights.sum_start().pending_count + 1), - Bias::Left, - &(), - ), - &(), - ); + if state.last_layout_width != Some(constraint.max.x()) { + state.rendered_range = 0..0; + state.items = SumTree::from_iter( + (0..state.items.summary().count).map(|_| ListItem::Unrendered), + &(), + ) + } + + let overdraw = state.overdraw; + let old_rendered_range = state.rendered_range.clone(); + let old_items = state.items.clone(); + let orientation = state.orientation; + let stored_scroll_top = state.scroll_top; + let mut new_items = SumTree::new(); + + let mut render_item = |ix, old_item: &ListItem| { + let element = if let ListItem::Rendered(element) = old_item { + element.clone() + } else { + let mut element = (state.render_item)(ix, cx); + element.layout(item_constraint, cx); + element.into() + }; + element + }; + + // Determine the scroll top. When parked at the end of a bottom-oriented + // list, this requires rendering items starting from the end of the list + // until the visible region is full. In other cases, the stored scroll + // can be used. + let scroll_top; + let trailing_items; + if let (Orientation::Bottom, None) = (orientation, stored_scroll_top) { + let mut rendered_height = 0.; + let mut cursor = old_items.cursor::(); + + let mut visible_items = Vec::new(); + cursor.seek(&Count(old_items.summary().count), Bias::Left, &()); + while let Some(item) = cursor.item() { + if rendered_height >= size.y() { + break; } + + let element = render_item(cursor.seek_start().0, item); + rendered_height += element.size().y(); + visible_items.push(ListItem::Rendered(element)); + cursor.prev(&()); } - drop(old_heights); - state.heights = new_heights; + scroll_top = ScrollTop { + item_ix: cursor.seek_start().0, + offset_in_item: rendered_height - size.y(), + }; + visible_items.reverse(); + trailing_items = Some(visible_items); } else { - state.heights = SumTree::new(); - for element in &mut state.elements { - let element = element.as_mut().unwrap(); - let size = element.layout(item_constraint, cx); - state.heights.push(ElementHeight::Ready(size.y()), &()); + scroll_top = stored_scroll_top.unwrap_or_default(); + trailing_items = None; + } + + let new_rendered_range_start = scroll_top.item_ix.saturating_sub(overdraw); + let mut cursor = old_items.cursor::(); + + // Discard any rendered elements before the overdraw window. + if old_rendered_range.start < new_rendered_range_start { + new_items.push_tree( + cursor.slice(&Count(old_rendered_range.start), Bias::Right, &()), + &(), + ); + let remove_to = old_rendered_range.end.min(new_rendered_range_start); + while cursor.seek_start().0 < remove_to { + new_items.push(cursor.item().unwrap().remove(), &()); + cursor.next(&()); } - state.last_layout_width = Some(constraint.max.x()); } - let size = constraint.max; - let visible_elements = state.elements[state.visible_range(size.y())] - .iter() - .map(|e| e.clone().unwrap()) - .collect(); - (size, visible_elements) - } + new_items.push_tree( + cursor.slice(&Count(new_rendered_range_start), Bias::Right, &()), + &(), + ); - fn paint( - &mut self, - bounds: RectF, - visible_elements: &mut Self::LayoutState, - cx: &mut PaintContext, - ) { - cx.scene.push_layer(Some(bounds)); - let state = &mut *self.state.0.borrow_mut(); - let visible_range = state.visible_range(bounds.height()); + // Ensure that all items in the overdraw window before the visible range are rendered. + while cursor.seek_start().0 < scroll_top.item_ix { + new_items.push( + ListItem::Rendered(render_item(cursor.seek_start().0, cursor.item().unwrap())), + &(), + ); + cursor.next(&()); + } - let mut item_top = { - let mut cursor = state.heights.cursor::(); - cursor.seek(&Count(visible_range.start), Bias::Right, &()); - cursor.sum_start().0 - }; - if state.orientation == Orientation::Bottom - && bounds.height() > state.heights.summary().height - { - item_top += bounds.height() - state.heights.summary().height; + // The remaining items may have already been rendered, when parked at the + // end of a bottom-oriented list. If so, append them. + let new_rendered_range_end; + if let Some(trailing_items) = trailing_items { + new_rendered_range_end = new_rendered_range_start + trailing_items.len(); + new_items.extend(trailing_items, &()); + } else { + // Ensure that enough items are rendered to fill the visible range. + let mut rendered_top = -scroll_top.offset_in_item; + while let Some(item) = cursor.item() { + if rendered_top >= size.y() { + break; + } + + let element = render_item(cursor.seek_start().0, item); + rendered_top += element.size().y(); + new_items.push(ListItem::Rendered(element), &()); + cursor.next(&()); + } + + // Ensure that all items in the overdraw window after the visible range + // are rendered. + new_rendered_range_end = + (cursor.seek_start().0 + overdraw).min(old_items.summary().count); + while cursor.seek_start().0 < new_rendered_range_end { + new_items.push( + ListItem::Rendered(render_item(cursor.seek_start().0, cursor.item().unwrap())), + &(), + ); + cursor.next(&()); + } + + // Preserve the remainder of the items, but discard any rendered items after + // the overdraw window. + if cursor.seek_start().0 < old_rendered_range.start { + new_items.push_tree( + cursor.slice(&Count(old_rendered_range.start), Bias::Right, &()), + &(), + ); + } + while cursor.seek_start().0 < old_rendered_range.end { + new_items.push(cursor.item().unwrap().remove(), &()); + cursor.next(&()); + } + new_items.push_tree(cursor.suffix(&()), &()); } - let scroll_top = state.scroll_top(bounds.height()); - for element in visible_elements { - let origin = bounds.origin() + vec2f(0., item_top - scroll_top); + drop(cursor); + state.items = new_items; + state.rendered_range = new_rendered_range_start..new_rendered_range_end; + (constraint.max, scroll_top) + } + + fn paint(&mut self, bounds: RectF, scroll_top: &mut ScrollTop, cx: &mut PaintContext) { + cx.scene.push_layer(Some(bounds)); + + let state = &mut *self.state.0.borrow_mut(); + for (mut element, origin) in state.visible_elements(bounds, scroll_top) { element.paint(origin, cx); - item_top += element.size().y(); } + cx.scene.pop_layer(); } @@ -194,14 +246,14 @@ impl Element for List { &mut self, event: &Event, bounds: RectF, - visible_elements: &mut Self::LayoutState, + scroll_top: &mut ScrollTop, _: &mut (), cx: &mut EventContext, ) -> bool { let mut handled = false; let mut state = self.state.0.borrow_mut(); - for element in visible_elements { + for (mut element, _) in state.visible_elements(bounds, scroll_top) { handled = element.dispatch_event(event, cx) || handled; } @@ -212,7 +264,7 @@ impl Element for List { precise, } => { if bounds.contains_point(*position) { - if state.scroll(*position, *delta, *precise, bounds.height(), cx) { + if state.scroll(scroll_top, bounds.height(), *delta, *precise, cx) { handled = true; } } @@ -226,61 +278,91 @@ impl Element for List { fn debug( &self, bounds: RectF, - visible_elements: &Self::LayoutState, + scroll_top: &Self::LayoutState, _: &(), cx: &DebugContext, ) -> serde_json::Value { let state = self.state.0.borrow_mut(); - let visible_range = state.visible_range(bounds.height()); - let visible_elements = visible_elements - .iter() - .map(|e| e.debug(cx)) + let visible_elements = state + .visible_elements(bounds, scroll_top) + .map(|e| e.0.debug(cx)) .collect::>(); + let visible_range = scroll_top.item_ix..(scroll_top.item_ix + visible_elements.len()); json!({ "visible_range": visible_range, "visible_elements": visible_elements, - "scroll_top": state.scroll_top, + "scroll_top": state.scroll_top.map(|top| (top.item_ix, top.offset_in_item)), }) } } impl ListState { - pub fn new(element_count: usize, orientation: Orientation) -> Self { - let mut heights = SumTree::new(); - heights.extend((0..element_count).map(|_| ElementHeight::Pending), &()); + pub fn new( + element_count: usize, + orientation: Orientation, + min_overdraw: usize, + render_item: F, + ) -> Self + where + F: 'static + FnMut(usize, &mut LayoutContext) -> ElementBox, + { + let mut items = SumTree::new(); + items.extend((0..element_count).map(|_| ListItem::Unrendered), &()); Self(Rc::new(RefCell::new(StateInner { last_layout_width: None, - elements: (0..element_count).map(|_| None).collect(), - heights, + render_item: Box::new(render_item), + rendered_range: 0..0, + items, scroll_top: None, orientation, + overdraw: min_overdraw, scroll_handler: None, }))) } + pub fn reset(&self, element_count: usize) { + let state = &mut *self.0.borrow_mut(); + state.scroll_top = None; + state.items = SumTree::new(); + state + .items + .extend((0..element_count).map(|_| ListItem::Unrendered), &()); + } + pub fn splice(&self, old_range: Range, count: usize) { let state = &mut *self.0.borrow_mut(); - if let Some((ix, offset)) = state.scroll_top.as_mut() { - if old_range.contains(ix) { - *ix = old_range.start; - *offset = 0.; - } else if old_range.end <= *ix { - *ix = *ix - (old_range.end - old_range.start) + count; + if let Some(ScrollTop { + item_ix, + offset_in_item, + }) = state.scroll_top.as_mut() + { + if old_range.contains(item_ix) { + *item_ix = old_range.start; + *offset_in_item = 0.; + } else if old_range.end <= *item_ix { + *item_ix = *item_ix - (old_range.end - old_range.start) + count; } } - let mut old_heights = state.heights.cursor::(); + let new_end = old_range.start + count; + if old_range.start < state.rendered_range.start { + state.rendered_range.start = + new_end + state.rendered_range.start.saturating_sub(old_range.end); + } + if old_range.start < state.rendered_range.end { + state.rendered_range.end = + new_end + state.rendered_range.end.saturating_sub(old_range.end); + } + + let mut old_heights = state.items.cursor::(); let mut new_heights = old_heights.slice(&Count(old_range.start), Bias::Right, &()); old_heights.seek_forward(&Count(old_range.end), Bias::Right, &()); - let old_elements = state.elements.splice(old_range, (0..count).map(|_| None)); - drop(old_elements); - - new_heights.extend((0..count).map(|_| ElementHeight::Pending), &()); + new_heights.extend((0..count).map(|_| ListItem::Unrendered), &()); new_heights.push_tree(old_heights.suffix(&()), &()); drop(old_heights); - state.heights = new_heights; + state.items = new_heights; } pub fn set_scroll_handler( @@ -292,55 +374,76 @@ impl ListState { } impl StateInner { - fn visible_range(&self, height: f32) -> Range { - let mut cursor = self.heights.cursor::(); - cursor.seek(&Height(self.scroll_top(height)), Bias::Right, &()); - let start_ix = cursor.sum_start().0; - cursor.seek(&Height(self.scroll_top(height) + height), Bias::Left, &()); - let end_ix = cursor.sum_start().0; - start_ix..self.elements.len().min(end_ix + 1) + fn visible_range(&self, height: f32, scroll_top: &ScrollTop) -> Range { + let mut cursor = self.items.cursor::(); + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + let start_y = cursor.sum_start().0 + scroll_top.offset_in_item; + let mut cursor = cursor.swap_dimensions(); + cursor.seek_forward(&Height(start_y + height), Bias::Right, &()); + scroll_top.item_ix..cursor.sum_start().0 + } + + fn visible_elements<'a>( + &'a self, + bounds: RectF, + scroll_top: &ScrollTop, + ) -> impl Iterator + 'a { + let mut item_origin = bounds.origin() - vec2f(0., scroll_top.offset_in_item); + let mut cursor = self.items.cursor::(); + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + std::iter::from_fn(move || { + while let Some(item) = cursor.item() { + if item_origin.y() > bounds.max_y() { + break; + } + + if let ListItem::Rendered(element) = item { + let result = (element.clone(), item_origin); + item_origin.set_y(item_origin.y() + element.size().y()); + cursor.next(&()); + return Some(result); + } + + cursor.next(&()); + } + + None + }) } fn scroll( &mut self, - _: Vector2F, + scroll_top: &ScrollTop, + height: f32, mut delta: Vector2F, precise: bool, - height: f32, cx: &mut EventContext, ) -> bool { if !precise { delta *= 20.; } - let delta_y; - let seek_bias; - match self.orientation { - Orientation::Top => { - delta_y = delta.y(); - seek_bias = Bias::Right; - } - Orientation::Bottom => { - delta_y = -delta.y(); - seek_bias = Bias::Left; - } - }; + let scroll_max = (self.items.summary().height - height).max(0.); + let new_scroll_top = (self.scroll_top(height) + delta.y()) + .max(0.) + .min(scroll_max); - let scroll_max = (self.heights.summary().height - height).max(0.); - let new_scroll_top = (self.scroll_top(height) + delta_y).max(0.).min(scroll_max); if self.orientation == Orientation::Bottom && new_scroll_top == scroll_max { self.scroll_top = None; } else { - let mut cursor = self.heights.cursor::(); - cursor.seek(&Height(new_scroll_top), seek_bias, &()); - let ix = cursor.sum_start().0; - let offset = new_scroll_top - cursor.seek_start().0; - self.scroll_top = Some((ix, offset)); + let mut cursor = self.items.cursor::(); + cursor.seek(&Height(new_scroll_top), Bias::Right, &()); + let item_ix = cursor.sum_start().0; + let offset_in_item = new_scroll_top - cursor.seek_start().0; + self.scroll_top = Some(ScrollTop { + item_ix, + offset_in_item, + }); } if self.scroll_handler.is_some() { - let range = self.visible_range(height); - self.scroll_handler.as_mut().unwrap()(range, cx); + let visible_range = self.visible_range(height, scroll_top); + self.scroll_handler.as_mut().unwrap()(visible_range, cx); } cx.notify(); @@ -348,11 +451,15 @@ impl StateInner { } fn scroll_top(&self, height: f32) -> f32 { - let scroll_max = (self.heights.summary().height - height).max(0.); - if let Some((ix, offset)) = self.scroll_top { - let mut cursor = self.heights.cursor::(); - cursor.seek(&Count(ix), Bias::Right, &()); - (cursor.sum_start().0 + offset).min(scroll_max) + let scroll_max = (self.items.summary().height - height).max(0.); + if let Some(ScrollTop { + item_ix, + offset_in_item, + }) = self.scroll_top + { + let mut cursor = self.items.cursor::(); + cursor.seek(&Count(item_ix), Bias::Right, &()); + (cursor.sum_start().0 + offset_in_item).min(scroll_max) } else { match self.orientation { Orientation::Top => 0., @@ -362,78 +469,85 @@ impl StateInner { } } -impl ElementHeight { - fn is_pending(&self) -> bool { - matches!(self, ElementHeight::Pending) +impl ListItem { + fn remove(&self) -> Self { + match self { + ListItem::Unrendered => ListItem::Unrendered, + ListItem::Rendered(element) => ListItem::Removed(element.size().y()), + ListItem::Removed(height) => ListItem::Removed(*height), + } } } -impl sum_tree::Item for ElementHeight { - type Summary = ElementHeightSummary; +impl sum_tree::Item for ListItem { + type Summary = ListItemSummary; fn summary(&self) -> Self::Summary { match self { - ElementHeight::Pending => ElementHeightSummary { + ListItem::Unrendered => ListItemSummary { count: 1, - pending_count: 1, + rendered_count: 0, + unrendered_count: 1, height: 0., }, - ElementHeight::Ready(height) => ElementHeightSummary { + ListItem::Rendered(element) => ListItemSummary { count: 1, - pending_count: 0, + rendered_count: 1, + unrendered_count: 0, + height: element.size().y(), + }, + ListItem::Removed(height) => ListItemSummary { + count: 1, + rendered_count: 0, + unrendered_count: 1, height: *height, }, } } } -impl sum_tree::Summary for ElementHeightSummary { +impl sum_tree::Summary for ListItemSummary { type Context = (); fn add_summary(&mut self, summary: &Self, _: &()) { self.count += summary.count; - self.pending_count += summary.pending_count; + self.rendered_count += summary.rendered_count; + self.unrendered_count += summary.unrendered_count; self.height += summary.height; } } -impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for ElementHeightSummary { - fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) { +impl<'a> sum_tree::Dimension<'a, ListItemSummary> for ListItemSummary { + fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) { sum_tree::Summary::add_summary(self, summary, &()); } } -impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Count { - fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) { +impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Count { + fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) { self.0 += summary.count; } } -impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Count { - fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for PendingCount { - fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) { - self.0 += summary.pending_count; +impl<'a> sum_tree::Dimension<'a, ListItemSummary> for RenderedCount { + fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) { + self.0 += summary.rendered_count; } } -impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for PendingCount { - fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering { - self.0.cmp(&other.0) +impl<'a> sum_tree::Dimension<'a, ListItemSummary> for UnrenderedCount { + fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) { + self.0 += summary.unrendered_count; } } -impl<'a> sum_tree::Dimension<'a, ElementHeightSummary> for Height { - fn add_summary(&mut self, summary: &'a ElementHeightSummary, _: &()) { +impl<'a> sum_tree::Dimension<'a, ListItemSummary> for Height { + fn add_summary(&mut self, summary: &'a ListItemSummary, _: &()) { self.0 += summary.height; } } -impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Height { +impl<'a> sum_tree::SeekDimension<'a, ListItemSummary> for Height { fn cmp(&self, other: &Self, _: &()) -> std::cmp::Ordering { self.0.partial_cmp(&other.0).unwrap() } @@ -442,78 +556,89 @@ impl<'a> sum_tree::SeekDimension<'a, ElementHeightSummary> for Height { #[cfg(test)] mod tests { use super::*; - use crate::{elements::*, geometry::vector::vec2f, Entity}; + use crate::{elements::*, geometry::vector::vec2f, Entity, RenderContext, View}; #[crate::test(self)] fn test_layout(cx: &mut crate::MutableAppContext) { let mut presenter = cx.build_presenter(0, 0.); - let mut elements = vec![20., 30., 100.]; - let state = ListState::new(elements.len(), Orientation::Top); + let elements = Rc::new(RefCell::new(vec![20., 30., 100.])); + let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000, { + let elements = elements.clone(); + move |ix, _| item(elements.borrow()[ix]) + }); - let mut list = List::new( - state.clone(), - &cx.build_render_context::(0, 0, 0., false), - |range| elements[range].iter().copied().map(item), - ) - .boxed(); + let mut list = List::new(state.clone()).boxed(); let size = list.layout( SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)), &mut presenter.build_layout_context(cx), ); assert_eq!(size, vec2f(100., 40.)); assert_eq!( - state.0.borrow().heights.summary(), - ElementHeightSummary { + state.0.borrow().items.summary(), + ListItemSummary { count: 3, - pending_count: 0, + rendered_count: 3, + unrendered_count: 0, height: 150. } ); state.0.borrow_mut().scroll( - Default::default(), + &ScrollTop { + item_ix: 0, + offset_in_item: 0., + }, + 40., vec2f(0., 54.), true, - size.y(), &mut presenter.build_event_context(cx), ); - assert_eq!(state.0.borrow().scroll_top, Some((2, 4.))); + assert_eq!( + state.0.borrow().scroll_top, + Some(ScrollTop { + item_ix: 2, + offset_in_item: 4. + }) + ); assert_eq!(state.0.borrow().scroll_top(size.y()), 54.); - elements.splice(1..2, vec![40., 50.]); - elements.push(60.); + elements.borrow_mut().splice(1..2, vec![40., 50.]); + elements.borrow_mut().push(60.); state.splice(1..2, 2); state.splice(4..4, 1); assert_eq!( - state.0.borrow().heights.summary(), - ElementHeightSummary { + state.0.borrow().items.summary(), + ListItemSummary { count: 5, - pending_count: 3, + rendered_count: 2, + unrendered_count: 3, height: 120. } ); - let mut list = List::new( - state.clone(), - &cx.build_render_context::(0, 0, 0., false), - |range| elements[range].iter().copied().map(item), - ) - .boxed(); + let mut list = List::new(state.clone()).boxed(); let size = list.layout( SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)), &mut presenter.build_layout_context(cx), ); assert_eq!(size, vec2f(100., 40.)); assert_eq!( - state.0.borrow().heights.summary(), - ElementHeightSummary { + state.0.borrow().items.summary(), + ListItemSummary { count: 5, - pending_count: 0, + rendered_count: 5, + unrendered_count: 0, height: 270. } ); - assert_eq!(state.0.borrow().scroll_top, Some((3, 4.))); + assert_eq!( + state.0.borrow().scroll_top, + Some(ScrollTop { + item_ix: 3, + offset_in_item: 4. + }) + ); assert_eq!(state.0.borrow().scroll_top(size.y()), 114.); } diff --git a/gpui/src/presenter.rs b/gpui/src/presenter.rs index 0e8b63a5474eca08922906d2205968d0a2be450c..eddddea3424e947dd90701ddfe5b87e1400e0682 100644 --- a/gpui/src/presenter.rs +++ b/gpui/src/presenter.rs @@ -5,7 +5,8 @@ use crate::{ json::{self, ToJson}, platform::Event, text_layout::TextLayoutCache, - Action, AnyAction, AssetCache, ElementBox, FontSystem, Scene, + Action, AnyAction, AssetCache, ElementBox, Entity, FontSystem, ModelHandle, ReadModel, + ReadView, Scene, View, ViewHandle, }; use pathfinder_geometry::vector::{vec2f, Vector2F}; use serde_json::json; @@ -233,6 +234,32 @@ impl<'a> LayoutContext<'a> { } } +impl<'a> Deref for LayoutContext<'a> { + type Target = MutableAppContext; + + fn deref(&self) -> &Self::Target { + self.app + } +} + +impl<'a> DerefMut for LayoutContext<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.app + } +} + +impl<'a> ReadView for LayoutContext<'a> { + fn read_view(&self, handle: &ViewHandle) -> &T { + self.app.read_view(handle) + } +} + +impl<'a> ReadModel for LayoutContext<'a> { + fn read_model(&self, handle: &ModelHandle) -> &T { + self.app.read_model(handle) + } +} + pub struct PaintContext<'a> { rendered_views: &'a mut HashMap, pub scene: &'a mut Scene, diff --git a/gpui/src/sum_tree.rs b/gpui/src/sum_tree.rs index eeb53cc1881f769123c2e9b965a5617bfda38a6b..c250845b3e9f07d0c15f2a7810316c22fdf64e0f 100644 --- a/gpui/src/sum_tree.rs +++ b/gpui/src/sum_tree.rs @@ -10,7 +10,7 @@ const TREE_BASE: usize = 2; #[cfg(not(test))] const TREE_BASE: usize = 6; -pub trait Item: Clone + fmt::Debug { +pub trait Item: Clone { type Summary: Summary; fn summary(&self) -> Self::Summary; @@ -87,6 +87,15 @@ impl SumTree { tree } + pub fn from_iter>( + iter: I, + cx: &::Context, + ) -> Self { + let mut tree = Self::new(); + tree.extend(iter, cx); + tree + } + #[allow(unused)] pub fn items(&self, cx: &::Context) -> Vec { let mut items = Vec::new(); diff --git a/gpui/src/sum_tree/cursor.rs b/gpui/src/sum_tree/cursor.rs index 990fe3090051a3280ecaa2ec28c928fcb6c257d4..c2fda87cdf455cdee21e5fa821d1bf84727b9775 100644 --- a/gpui/src/sum_tree/cursor.rs +++ b/gpui/src/sum_tree/cursor.rs @@ -10,6 +10,22 @@ struct StackEntry<'a, T: Item, S, U> { sum_dimension: U, } +impl<'a, T, S, U> StackEntry<'a, T, S, U> +where + T: Item, + S: SeekDimension<'a, T::Summary>, + U: SeekDimension<'a, T::Summary>, +{ + fn swap_dimensions(self) -> StackEntry<'a, T, U, S> { + StackEntry { + tree: self.tree, + index: self.index, + seek_dimension: self.sum_dimension, + sum_dimension: self.seek_dimension, + } + } +} + #[derive(Clone)] pub struct Cursor<'a, T: Item, S, U> { tree: &'a SumTree, @@ -602,6 +618,28 @@ where } } +impl<'a, T, S, U> Cursor<'a, T, S, U> +where + T: Item, + S: SeekDimension<'a, T::Summary>, + U: SeekDimension<'a, T::Summary>, +{ + pub fn swap_dimensions(self) -> Cursor<'a, T, U, S> { + Cursor { + tree: self.tree, + stack: self + .stack + .into_iter() + .map(StackEntry::swap_dimensions) + .collect(), + seek_dimension: self.sum_dimension, + sum_dimension: self.seek_dimension, + did_seek: self.did_seek, + at_end: self.at_end, + } + } +} + pub struct FilterCursor<'a, F: Fn(&T::Summary) -> bool, T: Item, U> { cursor: Cursor<'a, T, (), U>, filter_node: F, diff --git a/zed/src/channel.rs b/zed/src/channel.rs index 858bbb5ce163a0869d0c9ec7e1d7e9eac84cb737..387ea508cba370b8c9ff95c7274218a38b48b097 100644 --- a/zed/src/channel.rs +++ b/zed/src/channel.rs @@ -313,6 +313,12 @@ impl Channel { &self.messages } + pub fn message(&self, ix: usize) -> &ChannelMessage { + let mut cursor = self.messages.cursor::(); + cursor.seek(&Count(ix), Bias::Right, &()); + cursor.item().unwrap() + } + pub fn messages_in_range(&self, range: Range) -> impl Iterator { let mut cursor = self.messages.cursor::(); cursor.seek(&Count(range.start), Bias::Right, &()); diff --git a/zed/src/chat_panel.rs b/zed/src/chat_panel.rs index b81192bd595e8ab43cb8016aa7128f7a097bb4bd..a1376fbc008a7210b50ad37c71b539aacc7d5830 100644 --- a/zed/src/chat_panel.rs +++ b/zed/src/chat_panel.rs @@ -70,10 +70,25 @@ impl ChatPanel { } }) }); + + let mut message_list = ListState::new(0, Orientation::Bottom, 100, { + let this = cx.handle().downgrade(); + move |ix, cx| { + let this = this.upgrade(cx).unwrap().read(cx); + let message = this.active_channel.as_ref().unwrap().0.read(cx).message(ix); + this.render_message(message) + } + }); + message_list.set_scroll_handler(|visible_range, cx| { + if visible_range.start < 5 { + cx.dispatch_action(LoadMoreMessages); + } + }); + let mut this = Self { channel_list, active_channel: Default::default(), - message_list: ListState::new(0, Orientation::Bottom), + message_list, input_editor, channel_select, settings, @@ -120,14 +135,8 @@ impl ChatPanel { fn set_active_channel(&mut self, channel: ModelHandle, cx: &mut ViewContext) { if self.active_channel.as_ref().map(|e| &e.0) != Some(&channel) { + self.message_list.reset(channel.read(cx).message_count()); let subscription = cx.subscribe(&channel, Self::channel_did_change); - self.message_list = - ListState::new(channel.read(cx).message_count(), Orientation::Bottom); - self.message_list.set_scroll_handler(|visible_range, cx| { - if visible_range.start < 5 { - cx.dispatch_action(LoadMoreMessages); - } - }); self.active_channel = Some((channel, subscription)); } } @@ -149,16 +158,9 @@ impl ChatPanel { cx.notify(); } - fn render_active_channel_messages(&self, cx: &mut RenderContext) -> ElementBox { - let messages = if let Some((channel, _)) = self.active_channel.as_ref() { - let channel = channel.read(cx); - let now = OffsetDateTime::now_utc(); - List::new(self.message_list.clone(), cx, |range| { - channel - .messages_in_range(range) - .map(|message| self.render_message(message, now)) - }) - .boxed() + fn render_active_channel_messages(&self) -> ElementBox { + let messages = if self.active_channel.is_some() { + List::new(self.message_list.clone()).boxed() } else { Empty::new().boxed() }; @@ -166,7 +168,8 @@ impl ChatPanel { Expanded::new(1., messages).boxed() } - fn render_message(&self, message: &ChannelMessage, now: OffsetDateTime) -> ElementBox { + fn render_message(&self, message: &ChannelMessage) -> ElementBox { + let now = OffsetDateTime::now_utc(); let settings = self.settings.borrow(); let theme = &settings.theme.chat_panel.message; Flex::column() @@ -271,7 +274,7 @@ impl View for ChatPanel { "ChatPanel" } - fn render(&mut self, cx: &mut RenderContext) -> ElementBox { + fn render(&mut self, _: &mut RenderContext) -> ElementBox { let theme = &self.settings.borrow().theme; Container::new( Flex::column() @@ -280,7 +283,7 @@ impl View for ChatPanel { .with_style(&theme.chat_panel.channel_select.container) .boxed(), ) - .with_child(self.render_active_channel_messages(cx)) + .with_child(self.render_active_channel_messages()) .with_child(self.render_input_box()) .boxed(), )