diff --git a/crates/gpui/src/app.rs b/crates/gpui/src/app.rs index 053242691a..b680024ab1 100644 --- a/crates/gpui/src/app.rs +++ b/crates/gpui/src/app.rs @@ -45,7 +45,7 @@ use crate::{ executor::{self, Task}, keymap_matcher::{self, Binding, KeymapContext, KeymapMatcher, Keystroke, MatchResult}, platform::{ - self, Appearance, FontSystem, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, + self, FontSystem, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton, PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions, }, util::post_inc, @@ -872,60 +872,6 @@ impl AppContext { self.active_labeled_tasks.values().cloned() } - pub fn render_view(&mut self, params: RenderParams) -> Result> { - todo!() - // let window_id = params.window_id; - // let view_id = params.view_id; - // let mut view = self - // .views - // .remove(&(window_id, view_id)) - // .ok_or_else(|| anyhow!("view not found"))?; - // let element = view.render(params, self); - // self.views.insert((window_id, view_id), view); - // Ok(element) - } - - pub fn render_views( - &mut self, - window_id: usize, - titlebar_height: f32, - appearance: Appearance, - ) -> HashMap> { - todo!() - // self.start_frame(); - // #[allow(clippy::needless_collect)] - // let view_ids = self - // .views - // .keys() - // .filter_map(|(win_id, view_id)| { - // if *win_id == window_id { - // Some(*view_id) - // } else { - // None - // } - // }) - // .collect::>(); - - // view_ids - // .into_iter() - // .map(|view_id| { - // ( - // view_id, - // self.render_view(RenderParams { - // window_id, - // view_id, - // titlebar_height, - // hovered_region_ids: Default::default(), - // clicked_region_ids: None, - // refreshing: false, - // appearance, - // }) - // .unwrap(), - // ) - // }) - // .collect() - } - pub(crate) fn start_frame(&mut self) { self.frame_count += 1; } @@ -2488,19 +2434,8 @@ impl UpdateView for AppContext { where T: View, { - self.update_window(handle.window_id, |cx| { - cx.update_any_view(handle.view_id, |view, cx| { - let mut cx = ViewContext::mutable(cx, handle.view_id); - update( - view.as_any_mut() - .downcast_mut() - .expect("downcast is type safe"), - &mut cx, - ) - }) - .unwrap() // TODO: Are these unwraps safe? - }) - .unwrap() + self.update_window(handle.window_id, |cx| cx.update_view(handle, update)) + .unwrap() // TODO: Is this unwrap safe? } } @@ -3941,16 +3876,6 @@ impl<'a, T> DerefMut for Reference<'a, T> { } } -pub struct RenderParams { - pub window_id: usize, - pub view_id: usize, - pub titlebar_height: f32, - pub hovered_region_ids: HashSet, - pub clicked_region_ids: Option<(HashSet, MouseButton)>, - pub refreshing: bool, - pub appearance: Appearance, -} - #[derive(Debug, Clone, Default)] pub struct MouseState { pub(crate) hovered: bool, diff --git a/crates/gpui/src/app/window.rs b/crates/gpui/src/app/window.rs index 78c8b9e02b..4ae0e9326a 100644 --- a/crates/gpui/src/app/window.rs +++ b/crates/gpui/src/app/window.rs @@ -14,11 +14,11 @@ use crate::{ text_layout::TextLayoutCache, util::post_inc, AnyView, AnyViewHandle, AnyWeakViewHandle, AppContext, Drawable, Entity, ModelContext, - ModelHandle, MouseRegion, MouseRegionId, ParentId, ReadView, RenderParams, SceneBuilder, - UpdateModel, UpdateView, UpgradeViewHandle, View, ViewContext, ViewHandle, WeakViewHandle, + ModelHandle, MouseRegion, MouseRegionId, ParentId, ReadView, SceneBuilder, UpdateModel, + UpdateView, UpgradeViewHandle, View, ViewContext, ViewHandle, WeakViewHandle, WindowInvalidation, }; -use anyhow::bail; +use anyhow::{anyhow, bail, Result}; use collections::{HashMap, HashSet}; use pathfinder_geometry::vector::{vec2f, Vector2F}; use postage::oneshot; @@ -74,7 +74,7 @@ impl Window { invalidation: None, is_fullscreen: false, platform_window, - rendered_views: cx.render_views(window_id, titlebar_height, appearance), + rendered_views: Default::default(), cursor_regions: Default::default(), mouse_regions: Default::default(), text_layout_cache: TextLayoutCache::new(cx.font_system.clone()), @@ -91,6 +91,9 @@ impl Window { let root_view = window_context .build_and_insert_view(ParentId::Root, |cx| Some(build_view(cx))) .unwrap(); + if let Some(mut invalidation) = window_context.window.invalidation.take() { + window_context.invalidate(&mut invalidation, appearance); + } window.focused_view_id = Some(root_view.id()); window.root_view = Some(root_view.into_any()); window @@ -150,7 +153,16 @@ impl UpdateView for WindowContext<'_, '_> { where T: View, { - self.app_context.update_view(handle, update) + self.update_any_view(handle.view_id, |view, cx| { + let mut cx = ViewContext::mutable(cx, handle.view_id); + update( + view.as_any_mut() + .downcast_mut() + .expect("downcast is type safe"), + &mut cx, + ) + }) + .unwrap() // TODO: Is this unwrap safe? } } @@ -678,7 +690,6 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { self.window.rendered_views.remove(view_id); } for view_id in &invalidation.updated { - let window_id = self.window_id; let titlebar_height = self.window.titlebar_height; let hovered_region_ids = self.window.hovered_region_ids.clone(); let clicked_region_ids = self @@ -688,7 +699,6 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { let element = self .render_view(RenderParams { - window_id, view_id: *view_id, titlebar_height, hovered_region_ids, @@ -701,39 +711,16 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { } } - pub fn refresh(&mut self, invalidation: &mut WindowInvalidation, appearance: Appearance) { - self.invalidate(invalidation, appearance); - - let view_ids = self - .window - .rendered_views - .keys() - .copied() - .collect::>(); - - for view_id in view_ids { - if !invalidation.updated.contains(&view_id) { - let window_id = self.window_id; - let titlebar_height = self.window.titlebar_height; - let hovered_region_ids = self.window.hovered_region_ids.clone(); - let clicked_region_ids = self - .window - .clicked_button - .map(|button| (self.window.clicked_region_ids.clone(), button)); - let element = self - .render_view(RenderParams { - window_id, - view_id, - titlebar_height, - hovered_region_ids, - clicked_region_ids, - refreshing: true, - appearance, - }) - .unwrap(); - self.window.rendered_views.insert(view_id, element); - } - } + pub fn render_view(&mut self, params: RenderParams) -> Result> { + let window_id = self.window_id; + let view_id = params.view_id; + let mut view = self + .views + .remove(&(window_id, view_id)) + .ok_or_else(|| anyhow!("view not found"))?; + let element = view.render(self, view_id); + self.views.insert((window_id, view_id), view); + Ok(element) } pub fn build_scene(&mut self) -> Scene { @@ -854,32 +841,6 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { self.window.platform_window.prompt(level, msg, answers) } - fn add_view(&mut self, parent: &AnyViewHandle, build_view: F) -> ViewHandle - where - T: View, - F: FnOnce(&mut ViewContext) -> T, - { - if parent.window_id == self.window_id { - self.build_and_insert_view(ParentId::View(parent.view_id), |cx| Some(build_view(cx))) - .unwrap() - } else { - self.app_context.add_view(parent, build_view) - } - } - - fn add_option_view( - &mut self, - parent_handle: impl Into, - build_view: F, - ) -> Option> - where - T: View, - F: FnOnce(&mut ViewContext) -> Option, - { - let parent_handle = parent_handle.into(); - self.build_and_insert_view(ParentId::View(parent_handle.view_id), build_view) - } - pub fn replace_root_view(&mut self, build_root_view: F) -> ViewHandle where V: View, @@ -923,6 +884,15 @@ impl<'a: 'b, 'b> WindowContext<'a, 'b> { } } +pub struct RenderParams { + pub view_id: usize, + pub titlebar_height: f32, + pub hovered_region_ids: HashSet, + pub clicked_region_ids: Option<(HashSet, MouseButton)>, + pub refreshing: bool, + pub appearance: Appearance, +} + #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub enum Axis { #[default] diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e294f03f36..d808959fcd 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -648,267 +648,265 @@ mod tests { use super::*; use crate::{elements::Empty, geometry::vector::vec2f, Entity}; use rand::prelude::*; + use std::env; #[crate::test(self)] fn test_layout(cx: &mut crate::AppContext) { - todo!() - // let (_, view) = cx.add_window(Default::default(), |_| TestView); - // let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)); + cx.add_window(Default::default(), |cx| { + let mut view = TestView; + let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.)); + let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)])); + let state = ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, { + let elements = elements.clone(); + move |_, ix, _| { + let (id, height) = elements.borrow()[ix]; + TestElement::new(id, height).boxed() + } + }); - // let elements = Rc::new(RefCell::new(vec![(0, 20.), (1, 30.), (2, 100.)])); + let mut list = List::new(state.clone()); + let (size, _) = list.layout(constraint, &mut view, cx); + assert_eq!(size, vec2f(100., 40.)); + assert_eq!( + state.0.borrow().items.summary().clone(), + ListItemSummary { + count: 3, + rendered_count: 3, + unrendered_count: 0, + height: 150. + } + ); - // let state = view.update(cx, |_, cx| { - // ListState::new(elements.borrow().len(), Orientation::Top, 1000.0, cx, { - // let elements = elements.clone(); - // move |_, ix, _| { - // let (id, height) = elements.borrow()[ix]; - // TestElement::new(id, height).boxed() - // } - // }) - // }); + state.0.borrow_mut().scroll( + &ListOffset { + item_ix: 0, + offset_in_item: 0., + }, + 40., + vec2f(0., -54.), + true, + &mut view, + cx, + ); - // let mut list = List::new(state.clone()); - // let (size, _) = list.layout( - // constraint, - // &mut presenter.build_layout_context(vec2f(100., 40.), false, cx), - // ); - // assert_eq!(size, vec2f(100., 40.)); - // assert_eq!( - // state.0.borrow().items.summary().clone(), - // ListItemSummary { - // count: 3, - // rendered_count: 3, - // unrendered_count: 0, - // height: 150. - // } - // ); + let (_, logical_scroll_top) = list.layout(constraint, &mut view, cx); + assert_eq!( + logical_scroll_top, + ListOffset { + item_ix: 2, + offset_in_item: 4. + } + ); + assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.); - // state.0.borrow_mut().scroll( - // &ListOffset { - // item_ix: 0, - // offset_in_item: 0., - // }, - // 40., - // vec2f(0., -54.), - // true, - // &mut presenter.build_event_context(&mut Default::default(), cx), - // ); - // let (_, logical_scroll_top) = list.layout( - // constraint, - // &mut presenter.build_layout_context(vec2f(100., 40.), false, cx), - // ); - // assert_eq!( - // logical_scroll_top, - // ListOffset { - // item_ix: 2, - // offset_in_item: 4. - // } - // ); - // assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 54.); + elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]); + elements.borrow_mut().push((5, 60.)); + state.splice(1..2, 2); + state.splice(4..4, 1); + assert_eq!( + state.0.borrow().items.summary().clone(), + ListItemSummary { + count: 5, + rendered_count: 2, + unrendered_count: 3, + height: 120. + } + ); - // elements.borrow_mut().splice(1..2, vec![(3, 40.), (4, 50.)]); - // elements.borrow_mut().push((5, 60.)); - // state.splice(1..2, 2); - // state.splice(4..4, 1); - // assert_eq!( - // state.0.borrow().items.summary().clone(), - // ListItemSummary { - // count: 5, - // rendered_count: 2, - // unrendered_count: 3, - // height: 120. - // } - // ); + let (size, logical_scroll_top) = list.layout(constraint, &mut view, cx); + assert_eq!(size, vec2f(100., 40.)); + assert_eq!( + state.0.borrow().items.summary().clone(), + ListItemSummary { + count: 5, + rendered_count: 5, + unrendered_count: 0, + height: 270. + } + ); + assert_eq!( + logical_scroll_top, + ListOffset { + item_ix: 3, + offset_in_item: 4. + } + ); + assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.); - // let (size, logical_scroll_top) = list.layout( - // constraint, - // &mut presenter.build_layout_context(vec2f(100., 40.), false, cx), - // ); - // assert_eq!(size, vec2f(100., 40.)); - // assert_eq!( - // state.0.borrow().items.summary().clone(), - // ListItemSummary { - // count: 5, - // rendered_count: 5, - // unrendered_count: 0, - // height: 270. - // } - // ); - // assert_eq!( - // logical_scroll_top, - // ListOffset { - // item_ix: 3, - // offset_in_item: 4. - // } - // ); - // assert_eq!(state.0.borrow().scroll_top(&logical_scroll_top), 114.); + view + }); } - #[crate::test(self, iterations = 10, seed = 0)] + #[crate::test(self, iterations = 10)] fn test_random(cx: &mut crate::AppContext, mut rng: StdRng) { - todo!() - // let operations = env::var("OPERATIONS") - // .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - // .unwrap_or(10); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); - // let (window_id, view) = cx.add_window(Default::default(), |_| TestView); - // let mut next_id = 0; - // let elements = Rc::new(RefCell::new( - // (0..rng.gen_range(0..=20)) - // .map(|_| { - // let id = next_id; - // next_id += 1; - // (id, rng.gen_range(0..=200) as f32 / 2.0) - // }) - // .collect::>(), - // )); - // let orientation = *[Orientation::Top, Orientation::Bottom] - // .choose(&mut rng) - // .unwrap(); - // let overdraw = rng.gen_range(1..=100) as f32; + cx.add_window(Default::default(), |cx| { + let mut view = TestView; - // let state = view.update(cx, |_, cx| { - // ListState::new(elements.borrow().len(), orientation, overdraw, cx, { - // let elements = elements.clone(); - // move |_, ix, _| { - // let (id, height) = elements.borrow()[ix]; - // TestElement::new(id, height).boxed() - // } - // }) - // }); + let mut next_id = 0; + let elements = Rc::new(RefCell::new( + (0..rng.gen_range(0..=20)) + .map(|_| { + let id = next_id; + next_id += 1; + (id, rng.gen_range(0..=200) as f32 / 2.0) + }) + .collect::>(), + )); + let orientation = *[Orientation::Top, Orientation::Bottom] + .choose(&mut rng) + .unwrap(); + let overdraw = rng.gen_range(1..=100) as f32; - // let mut width = rng.gen_range(0..=2000) as f32 / 2.; - // let mut height = rng.gen_range(0..=2000) as f32 / 2.; - // log::info!("orientation: {:?}", orientation); - // log::info!("overdraw: {}", overdraw); - // log::info!("elements: {:?}", elements.borrow()); - // log::info!("size: ({:?}, {:?})", width, height); - // log::info!("=================="); + let state = ListState::new(elements.borrow().len(), orientation, overdraw, { + let elements = elements.clone(); + move |_, ix, _| { + let (id, height) = elements.borrow()[ix]; + TestElement::new(id, height).boxed() + } + }); - // let mut last_logical_scroll_top = None; - // for _ in 0..operations { - // match rng.gen_range(0..=100) { - // 0..=29 if last_logical_scroll_top.is_some() => { - // let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw)); - // log::info!( - // "Scrolling by {:?}, previous scroll top: {:?}", - // delta, - // last_logical_scroll_top.unwrap() - // ); - // state.0.borrow_mut().scroll( - // last_logical_scroll_top.as_ref().unwrap(), - // height, - // delta, - // true, - // &mut presenter.build_event_context(&mut Default::default(), cx), - // ); - // } - // 30..=34 => { - // width = rng.gen_range(0..=2000) as f32 / 2.; - // log::info!("changing width: {:?}", width); - // } - // 35..=54 => { - // height = rng.gen_range(0..=1000) as f32 / 2.; - // log::info!("changing height: {:?}", height); - // } - // _ => { - // let mut elements = elements.borrow_mut(); - // let end_ix = rng.gen_range(0..=elements.len()); - // let start_ix = rng.gen_range(0..=end_ix); - // let new_elements = (0..rng.gen_range(0..10)) - // .map(|_| { - // let id = next_id; - // next_id += 1; - // (id, rng.gen_range(0..=200) as f32 / 2.) - // }) - // .collect::>(); - // log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements); - // state.splice(start_ix..end_ix, new_elements.len()); - // elements.splice(start_ix..end_ix, new_elements); - // for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() { - // if let ListItem::Rendered(element) = item { - // let (expected_id, _) = elements[ix]; - // element.with_metadata(|metadata: Option<&usize>| { - // assert_eq!(*metadata.unwrap(), expected_id); - // }); - // } - // } - // } - // } + let mut width = rng.gen_range(0..=2000) as f32 / 2.; + let mut height = rng.gen_range(0..=2000) as f32 / 2.; + log::info!("orientation: {:?}", orientation); + log::info!("overdraw: {}", overdraw); + log::info!("elements: {:?}", elements.borrow()); + log::info!("size: ({:?}, {:?})", width, height); + log::info!("=================="); - // let mut list = List::new(state.clone()); - // let window_size = vec2f(width, height); - // let (size, logical_scroll_top) = list.layout( - // SizeConstraint::new(vec2f(0., 0.), window_size), - // &mut presenter.build_layout_context(window_size, false, cx), - // ); - // assert_eq!(size, window_size); - // last_logical_scroll_top = Some(logical_scroll_top); + let mut last_logical_scroll_top = None; + for _ in 0..operations { + match rng.gen_range(0..=100) { + 0..=29 if last_logical_scroll_top.is_some() => { + let delta = vec2f(0., rng.gen_range(-overdraw..=overdraw)); + log::info!( + "Scrolling by {:?}, previous scroll top: {:?}", + delta, + last_logical_scroll_top.unwrap() + ); + state.0.borrow_mut().scroll( + last_logical_scroll_top.as_ref().unwrap(), + height, + delta, + true, + &mut view, + cx, + ); + } + 30..=34 => { + width = rng.gen_range(0..=2000) as f32 / 2.; + log::info!("changing width: {:?}", width); + } + 35..=54 => { + height = rng.gen_range(0..=1000) as f32 / 2.; + log::info!("changing height: {:?}", height); + } + _ => { + let mut elements = elements.borrow_mut(); + let end_ix = rng.gen_range(0..=elements.len()); + let start_ix = rng.gen_range(0..=end_ix); + let new_elements = (0..rng.gen_range(0..10)) + .map(|_| { + let id = next_id; + next_id += 1; + (id, rng.gen_range(0..=200) as f32 / 2.) + }) + .collect::>(); + log::info!("splice({:?}, {:?})", start_ix..end_ix, new_elements); + state.splice(start_ix..end_ix, new_elements.len()); + elements.splice(start_ix..end_ix, new_elements); + for (ix, item) in state.0.borrow().items.cursor::<()>().enumerate() { + if let ListItem::Rendered(element) = item { + let (expected_id, _) = elements[ix]; + element.borrow().with_metadata(|metadata: Option<&usize>| { + assert_eq!(*metadata.unwrap(), expected_id); + }); + } + } + } + } - // let state = state.0.borrow(); - // log::info!("items {:?}", state.items.items(&())); + let mut list = List::new(state.clone()); + let window_size = vec2f(width, height); + let (size, logical_scroll_top) = list.layout( + SizeConstraint::new(vec2f(0., 0.), window_size), + &mut view, + cx, + ); + assert_eq!(size, window_size); + last_logical_scroll_top = Some(logical_scroll_top); - // let scroll_top = state.scroll_top(&logical_scroll_top); - // let rendered_top = (scroll_top - overdraw).max(0.); - // let rendered_bottom = scroll_top + height + overdraw; - // let mut item_top = 0.; + let state = state.0.borrow(); + log::info!("items {:?}", state.items.items(&())); - // log::info!( - // "rendered top {:?}, rendered bottom {:?}, scroll top {:?}", - // rendered_top, - // rendered_bottom, - // scroll_top, - // ); + let scroll_top = state.scroll_top(&logical_scroll_top); + let rendered_top = (scroll_top - overdraw).max(0.); + let rendered_bottom = scroll_top + height + overdraw; + let mut item_top = 0.; - // let mut first_rendered_element_top = None; - // let mut last_rendered_element_bottom = None; - // assert_eq!(state.items.summary().count, elements.borrow().len()); - // for (ix, item) in state.items.cursor::<()>().enumerate() { - // match item { - // ListItem::Unrendered => { - // let item_bottom = item_top; - // assert!(item_bottom <= rendered_top || item_top >= rendered_bottom); - // item_top = item_bottom; - // } - // ListItem::Removed(height) => { - // let (id, expected_height) = elements.borrow()[ix]; - // assert_eq!( - // *height, expected_height, - // "element {} height didn't match", - // id - // ); - // let item_bottom = item_top + height; - // assert!(item_bottom <= rendered_top || item_top >= rendered_bottom); - // item_top = item_bottom; - // } - // ListItem::Rendered(element) => { - // let (expected_id, expected_height) = elements.borrow()[ix]; - // element.with_metadata(|metadata: Option<&usize>| { - // assert_eq!(*metadata.unwrap(), expected_id); - // }); - // assert_eq!(element.size().y(), expected_height); - // let item_bottom = item_top + element.size().y(); - // first_rendered_element_top.get_or_insert(item_top); - // last_rendered_element_bottom = Some(item_bottom); - // assert!(item_bottom > rendered_top || item_top < rendered_bottom); - // item_top = item_bottom; - // } - // } - // } + log::info!( + "rendered top {:?}, rendered bottom {:?}, scroll top {:?}", + rendered_top, + rendered_bottom, + scroll_top, + ); - // match orientation { - // Orientation::Top => { - // if let Some(first_rendered_element_top) = first_rendered_element_top { - // assert!(first_rendered_element_top <= scroll_top); - // } - // } - // Orientation::Bottom => { - // if let Some(last_rendered_element_bottom) = last_rendered_element_bottom { - // assert!(last_rendered_element_bottom >= scroll_top + height); - // } - // } - // } - // } + let mut first_rendered_element_top = None; + let mut last_rendered_element_bottom = None; + assert_eq!(state.items.summary().count, elements.borrow().len()); + for (ix, item) in state.items.cursor::<()>().enumerate() { + match item { + ListItem::Unrendered => { + let item_bottom = item_top; + assert!(item_bottom <= rendered_top || item_top >= rendered_bottom); + item_top = item_bottom; + } + ListItem::Removed(height) => { + let (id, expected_height) = elements.borrow()[ix]; + assert_eq!( + *height, expected_height, + "element {} height didn't match", + id + ); + let item_bottom = item_top + height; + assert!(item_bottom <= rendered_top || item_top >= rendered_bottom); + item_top = item_bottom; + } + ListItem::Rendered(element) => { + let (expected_id, expected_height) = elements.borrow()[ix]; + let element = element.borrow(); + element.with_metadata(|metadata: Option<&usize>| { + assert_eq!(*metadata.unwrap(), expected_id); + }); + assert_eq!(element.size().y(), expected_height); + let item_bottom = item_top + element.size().y(); + first_rendered_element_top.get_or_insert(item_top); + last_rendered_element_bottom = Some(item_bottom); + assert!(item_bottom > rendered_top || item_top < rendered_bottom); + item_top = item_bottom; + } + } + } + + match orientation { + Orientation::Top => { + if let Some(first_rendered_element_top) = first_rendered_element_top { + assert!(first_rendered_element_top <= scroll_top); + } + } + Orientation::Bottom => { + if let Some(last_rendered_element_bottom) = last_rendered_element_bottom { + assert!(last_rendered_element_bottom >= scroll_top + height); + } + } + } + } + + view + }); } struct TestView;