use std::rc::Rc;
use inset_embedder::{Matrix4, Offset};
use inset_foundation::{App, Handle, PRECISION_ERROR_TOLERANCE};
use crate::box_::{AnyRenderBox, BoxConstraints};
use crate::object::{
AnyRenderObject, ContainerRenderObjectData, ContainerRenderObjectMixin, RenderHandle,
RenderObject, RenderObjectData,
};
use crate::painting_context::PaintingContext;
use crate::pipeline_owner::PipelineOwner;
use crate::sliver::{
ItemExtentBuilder, RenderSliver, RenderSliverData, RenderSliverHelpers, SliverConstraints,
SliverGeometry, SliverHitTestResult, SliverLayoutDimensions, calculate_cache_offset,
calculate_paint_offset,
};
use crate::sliver_multi_box_adaptor::{
RenderSliverBoxChildManager, RenderSliverMultiBoxAdaptor, RenderSliverMultiBoxAdaptorData,
RenderSliverWithKeepAliveMixin, SliverMultiBoxAdaptorParentData,
};
#[derive(Debug, Default)]
pub struct RenderSliverFixedExtentBoxAdaptorData {
current_layout_dimensions: Option<SliverLayoutDimensions>,
}
impl RenderSliverFixedExtentBoxAdaptorData {
pub const fn new() -> RenderSliverFixedExtentBoxAdaptorData {
RenderSliverFixedExtentBoxAdaptorData {
current_layout_dimensions: None,
}
}
}
#[macro_export]
macro_rules! render_sliver_fixed_extent_accessors {
() => {
fn fixed_extent_data(
self: $crate::RenderHandle<Self>,
app: &::inset_foundation::App,
) -> &$crate::RenderSliverFixedExtentBoxAdaptorData {
&self.get(app).fixed_extent
}
fn fixed_extent_data_mut(
self: $crate::RenderHandle<Self>,
app: &mut ::inset_foundation::App,
) -> &mut $crate::RenderSliverFixedExtentBoxAdaptorData {
&mut self.get_mut(app).fixed_extent
}
};
}
pub trait RenderSliverFixedExtentBoxAdaptor: RenderSliverMultiBoxAdaptor {
fn fixed_extent_data(
self: RenderHandle<Self>,
app: &App,
) -> &RenderSliverFixedExtentBoxAdaptorData;
fn fixed_extent_data_mut(
self: RenderHandle<Self>,
app: &mut App,
) -> &mut RenderSliverFixedExtentBoxAdaptorData;
fn item_extent(self: RenderHandle<Self>, app: &App) -> Option<f64>;
fn item_extent_builder(self: RenderHandle<Self>, app: &App) -> Option<ItemExtentBuilder> {
let _ = app;
let _ = self;
None
}
fn layout_dimensions(self: RenderHandle<Self>, app: &App) -> SliverLayoutDimensions {
self.fixed_extent_data(app)
.current_layout_dimensions
.unwrap_or_else(|| {
let constraints = self.constraints(app);
SliverLayoutDimensions::new(
constraints.scroll_offset,
constraints.preceding_scroll_extent,
constraints.viewport_main_axis_extent,
constraints.cross_axis_extent,
)
})
}
fn index_to_layout_offset(self: RenderHandle<Self>, app: &App, index: i32) -> f64 {
let Some(builder) = self.item_extent_builder(app) else {
let item_extent = self.item_extent(app).expect("one of the two is set");
return item_extent * f64::from(index);
};
let mut offset = 0.0;
let dimensions = self.layout_dimensions(app);
let child_manager = self.child_manager(app);
for i in 0..index {
let child_count = child_manager.estimated_child_count(app);
if child_count.is_some_and(|count| i > count - 1) {
break;
}
let Some(item_extent) = builder(i, dimensions) else {
break;
};
offset += item_extent;
}
offset
}
fn get_min_child_index_for_scroll_offset(
self: RenderHandle<Self>,
app: &App,
scroll_offset: f64,
) -> i32 {
let Some(builder) = self.item_extent_builder(app) else {
let item_extent = self.item_extent(app).expect("one of the two is set");
if item_extent <= 0.0 {
return 0;
}
let actual = scroll_offset / item_extent;
let round = actual.round();
if (actual * item_extent - round * item_extent).abs() < PRECISION_ERROR_TOLERANCE {
return round as i32;
}
return actual.floor() as i32;
};
self.child_index_for_scroll_offset(app, scroll_offset, &builder)
}
fn get_max_child_index_for_scroll_offset(
self: RenderHandle<Self>,
app: &App,
scroll_offset: f64,
) -> i32 {
let Some(builder) = self.item_extent_builder(app) else {
let item_extent = self.item_extent(app).expect("one of the two is set");
if item_extent <= 0.0 {
return 0;
}
let actual = scroll_offset / item_extent - 1.0;
let round = actual.round();
if (actual * item_extent - round * item_extent).abs() < PRECISION_ERROR_TOLERANCE {
return (round as i32).max(0);
}
return (actual.ceil() as i32).max(0);
};
self.child_index_for_scroll_offset(app, scroll_offset, &builder)
}
fn child_index_for_scroll_offset(
self: RenderHandle<Self>,
app: &App,
scroll_offset: f64,
builder: &ItemExtentBuilder,
) -> i32 {
if scroll_offset == 0.0 {
return 0;
}
let dimensions = self.layout_dimensions(app);
let child_manager = self.child_manager(app);
let mut position = 0.0;
let mut index = 0;
while position < scroll_offset {
let child_count = child_manager.estimated_child_count(app);
if child_count.is_some_and(|count| index > count - 1) {
break;
}
let Some(item_extent) = builder(index, dimensions) else {
break;
};
position += item_extent;
index += 1;
}
index - 1
}
fn estimate_max_scroll_offset(
self: RenderHandle<Self>,
app: &App,
constraints: SliverConstraints,
first_index: Option<i32>,
last_index: Option<i32>,
leading_scroll_offset: Option<f64>,
trailing_scroll_offset: Option<f64>,
) -> f64 {
self.child_manager(app).estimate_max_scroll_offset(
app,
constraints,
first_index,
last_index,
leading_scroll_offset,
trailing_scroll_offset,
)
}
fn compute_max_scroll_offset(
self: RenderHandle<Self>,
app: &mut App,
_constraints: SliverConstraints,
) -> f64 {
let child_manager = self.child_manager(app);
let Some(builder) = self.item_extent_builder(app) else {
let item_extent = self.item_extent(app).expect("one of the two is set");
return f64::from(child_manager.child_count(app)) * item_extent;
};
let dimensions = self.layout_dimensions(app);
let mut offset = 0.0;
for i in 0..child_manager.child_count(app) {
let Some(item_extent) = builder(i, dimensions) else {
break;
};
offset += item_extent;
}
offset
}
fn child_constraints(self: RenderHandle<Self>, app: &App, index: i32) -> BoxConstraints {
let extent = match self.item_extent_builder(app) {
None => self.item_extent(app).expect("one of the two is set"),
Some(builder) => builder(index, self.layout_dimensions(app))
.expect("the builder must report an extent for a reified child"),
};
self.constraints(app)
.as_box_constraints(extent, extent, None)
}
fn paint_extent_of(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> f64 {
match self.item_extent_builder(app) {
None => self.item_extent(app).expect("one of the two is set"),
Some(builder) => builder(self.index_of(app, child), self.layout_dimensions(app))
.expect("the builder must report an extent for a reified child"),
}
}
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
debug_assert!(
self.item_extent(app).is_some() != self.item_extent_builder(app).is_some(),
"exactly one of item_extent and item_extent_builder must be set"
);
debug_assert!(
self.item_extent_builder(app).is_some()
|| self
.item_extent(app)
.is_some_and(|extent| extent.is_finite() && extent >= 0.0)
);
let constraints = self.constraints(app);
let child_manager = self.child_manager(app);
child_manager.did_start_layout(app);
child_manager.set_did_underflow(app, false);
let scroll_offset = constraints.scroll_offset + constraints.cache_origin;
debug_assert!(scroll_offset >= 0.0);
let remaining_extent = constraints.remaining_cache_extent;
debug_assert!(remaining_extent >= 0.0);
let target_end_scroll_offset = scroll_offset + remaining_extent;
self.fixed_extent_data_mut(app).current_layout_dimensions =
Some(SliverLayoutDimensions::new(
constraints.scroll_offset,
constraints.preceding_scroll_extent,
constraints.viewport_main_axis_extent,
constraints.cross_axis_extent,
));
let first_index = self.get_min_child_index_for_scroll_offset(app, scroll_offset);
let target_last_index = target_end_scroll_offset
.is_finite()
.then(|| self.get_max_child_index_for_scroll_offset(app, target_end_scroll_offset));
if self.first_child(app).is_some() {
let leading_garbage = self.calculate_leading_garbage(app, first_index);
let trailing_garbage = match target_last_index {
Some(last_index) => self.calculate_trailing_garbage(app, last_index),
None => 0,
};
self.collect_garbage(app, leading_garbage, trailing_garbage);
} else {
self.collect_garbage(app, 0, 0);
}
if self.first_child(app).is_none() {
let layout_offset = self.index_to_layout_offset(app, first_index);
if !self.add_initial_child(app, first_index, layout_offset) {
let max = if first_index <= 0 {
0.0
} else {
self.compute_max_scroll_offset(app, constraints)
};
self.set_geometry(
app,
SliverGeometry::builder()
.scroll_extent(max)
.max_paint_extent(max)
.build(),
);
child_manager.did_finish_layout(app);
return;
}
}
let mut trailing_child_with_layout: Option<AnyRenderBox> = None;
let first_child = self.first_child(app).expect("added above");
let mut index = self.index_of(app, first_child) - 1;
while index >= first_index {
let child_constraints = self.child_constraints(app, index);
let Some(child) = self.insert_and_layout_leading_child(app, child_constraints, false)
else {
let correction = self.index_to_layout_offset(app, index);
self.set_geometry(
app,
SliverGeometry::builder()
.scroll_offset_correction(correction)
.build(),
);
return;
};
let layout_offset = self.index_to_layout_offset(app, index);
let child_parent_data = child
.as_object()
.parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app);
child_parent_data.set_layout_offset(Some(layout_offset));
debug_assert_eq!(child_parent_data.index, Some(index));
trailing_child_with_layout.get_or_insert(child);
index -= 1;
}
if trailing_child_with_layout.is_none() {
let first_child = self.first_child(app).expect("the child list is not empty");
let child_constraints = self.child_constraints(app, self.index_of(app, first_child));
first_child.layout(app, child_constraints, false);
let layout_offset = self.index_to_layout_offset(app, first_index);
first_child
.as_object()
.parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
.set_layout_offset(Some(layout_offset));
trailing_child_with_layout = Some(first_child);
}
let mut estimated_max_scroll_offset = f64::INFINITY;
let mut index = self.index_of(app, trailing_child_with_layout.expect("set above")) + 1;
while target_last_index.is_none_or(|last_index| index <= last_index) {
let after = trailing_child_with_layout.expect("set above");
let mut child = self.child_after(app, after);
if child.is_none_or(|current| self.index_of(app, current) != index) {
let child_constraints = self.child_constraints(app, index);
child = self.insert_and_layout_child(app, child_constraints, Some(after), false);
if child.is_none() {
estimated_max_scroll_offset = self.index_to_layout_offset(app, index);
break;
}
} else {
let child_constraints = self.child_constraints(app, index);
child
.expect("checked")
.layout(app, child_constraints, false);
}
let child = child.expect("set above");
trailing_child_with_layout = Some(child);
debug_assert_eq!(
child
.as_object()
.parent_data_of::<SliverMultiBoxAdaptorParentData>(app)
.index,
Some(index)
);
let layout_offset = self.index_to_layout_offset(app, index);
child
.as_object()
.parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(app)
.set_layout_offset(Some(layout_offset));
index += 1;
}
let last_child = self.last_child(app).expect("the child list is not empty");
let last_index = self.index_of(app, last_child);
let leading_scroll_offset = self.index_to_layout_offset(app, first_index);
let trailing_scroll_offset = self.index_to_layout_offset(app, last_index + 1);
debug_assert!(
first_index == 0
|| RenderSliver::child_scroll_offset(
self,
app,
self.first_child(app).expect("not empty").as_object()
)
.expect("laid out")
- scroll_offset
<= PRECISION_ERROR_TOLERANCE
);
debug_assert!(self.debug_assert_child_list_is_non_empty_and_contiguous(app));
debug_assert_eq!(
self.index_of(app, self.first_child(app).expect("not empty")),
first_index
);
debug_assert!(target_last_index.is_none_or(|target| last_index <= target));
estimated_max_scroll_offset =
estimated_max_scroll_offset.min(self.estimate_max_scroll_offset(
app,
constraints,
Some(first_index),
Some(last_index),
Some(leading_scroll_offset),
Some(trailing_scroll_offset),
));
let paint_extent =
calculate_paint_offset(constraints, leading_scroll_offset, trailing_scroll_offset);
let cache_extent =
calculate_cache_offset(constraints, leading_scroll_offset, trailing_scroll_offset);
let target_end_scroll_offset_for_paint =
constraints.scroll_offset + constraints.remaining_paint_extent;
let target_last_index_for_paint =
target_end_scroll_offset_for_paint.is_finite().then(|| {
self.get_max_child_index_for_scroll_offset(app, target_end_scroll_offset_for_paint)
});
self.set_geometry(
app,
SliverGeometry::builder()
.scroll_extent(estimated_max_scroll_offset)
.paint_extent(paint_extent)
.cache_extent(cache_extent)
.max_paint_extent(estimated_max_scroll_offset)
.has_visual_overflow(
target_last_index_for_paint.is_some_and(|target| last_index >= target)
|| constraints.scroll_offset > 0.0,
)
.build(),
);
if estimated_max_scroll_offset == trailing_scroll_offset {
child_manager.set_did_underflow(app, true);
}
child_manager.did_finish_layout(app);
}
}
macro_rules! fixed_extent_box_adaptor_leaf {
($leaf:ty) => {
impl ContainerRenderObjectMixin for $leaf {
type ChildType = AnyRenderBox;
type ParentDataType = SliverMultiBoxAdaptorParentData;
fn container_data(
self: RenderHandle<Self>,
app: &App,
) -> &ContainerRenderObjectData<AnyRenderBox> {
&self.get(app).container
}
fn container_data_mut(
self: RenderHandle<Self>,
app: &mut App,
) -> &mut ContainerRenderObjectData<AnyRenderBox> {
&mut self.get_mut(app).container
}
fn insert(
self: RenderHandle<Self>,
app: &mut App,
child: AnyRenderBox,
after: Option<AnyRenderBox>,
) {
RenderSliverMultiBoxAdaptor::insert(self, app, child, after)
}
fn move_child(
self: RenderHandle<Self>,
app: &mut App,
child: AnyRenderBox,
after: Option<AnyRenderBox>,
) {
RenderSliverMultiBoxAdaptor::move_child(self, app, child, after)
}
fn remove(self: RenderHandle<Self>, app: &mut App, child: AnyRenderBox) {
RenderSliverMultiBoxAdaptor::remove(self, app, child)
}
fn remove_all(self: RenderHandle<Self>, app: &mut App) {
RenderSliverMultiBoxAdaptor::remove_all(self, app)
}
}
impl RenderSliverHelpers for $leaf {}
impl RenderSliverWithKeepAliveMixin for $leaf {}
impl RenderSliverMultiBoxAdaptor for $leaf {
crate::render_sliver_multi_box_adaptor_accessors!();
fn paint_extent_of(self: RenderHandle<Self>, app: &App, child: AnyRenderBox) -> f64 {
RenderSliverFixedExtentBoxAdaptor::paint_extent_of(self, app, child)
}
}
impl RenderObject for $leaf {
crate::render_object_accessors!();
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
RenderSliverMultiBoxAdaptor::setup_parent_data(self, app, child);
RenderSliverWithKeepAliveMixin::setup_parent_data(self, app, child);
}
fn visit_children(
self: RenderHandle<Self>,
app: &App,
visitor: &mut dyn FnMut(AnyRenderObject),
) {
RenderSliverMultiBoxAdaptor::visit_children(self, app, visitor)
}
fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
RenderSliverMultiBoxAdaptor::did_attach(self, app, owner)
}
fn did_detach(self: RenderHandle<Self>, app: &mut App) {
RenderSliverMultiBoxAdaptor::did_detach(self, app)
}
fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
RenderSliverMultiBoxAdaptor::redepth_children(self, app)
}
fn apply_paint_transform(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderObject,
transform: &mut Matrix4,
) {
RenderSliverMultiBoxAdaptor::apply_paint_transform(self, app, child, transform)
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
RenderSliverMultiBoxAdaptor::paint(self, app, context, offset)
}
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
RenderSliverFixedExtentBoxAdaptor::perform_layout(self, app)
}
}
impl RenderSliver for $leaf {
crate::render_sliver_accessors!();
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut SliverHitTestResult<'_>,
main_axis_position: f64,
cross_axis_position: f64,
) -> bool {
RenderSliverMultiBoxAdaptor::hit_test_children(
self,
app,
result,
main_axis_position,
cross_axis_position,
)
}
fn child_main_axis_position(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderObject,
) -> f64 {
RenderSliverMultiBoxAdaptor::child_main_axis_position(self, app, child)
}
fn child_scroll_offset(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderObject,
) -> Option<f64> {
RenderSliverMultiBoxAdaptor::child_scroll_offset(self, app, child)
}
}
};
}
pub struct RenderSliverFixedExtentList {
render_object: RenderObjectData,
render_sliver: RenderSliverData,
container: ContainerRenderObjectData<AnyRenderBox>,
adaptor: RenderSliverMultiBoxAdaptorData,
fixed_extent: RenderSliverFixedExtentBoxAdaptorData,
item_extent: f64,
}
impl RenderSliverFixedExtentList {
pub fn new(
app: &mut App,
child_manager: Rc<dyn RenderSliverBoxChildManager>,
item_extent: f64,
) -> RenderHandle<RenderSliverFixedExtentList> {
RenderHandle::new_sliver(
app,
RenderSliverFixedExtentList {
render_object: RenderObjectData::new(),
render_sliver: RenderSliverData::new(),
container: ContainerRenderObjectData::new(),
adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
fixed_extent: RenderSliverFixedExtentBoxAdaptorData::new(),
item_extent,
},
)
}
pub fn set_item_extent(self: RenderHandle<Self>, app: &mut App, value: f64) {
if self.get(app).item_extent == value {
return;
}
self.get_mut(app).item_extent = value;
self.mark_needs_layout(app);
}
}
impl RenderSliverFixedExtentBoxAdaptor for RenderSliverFixedExtentList {
crate::render_sliver_fixed_extent_accessors!();
fn item_extent(self: RenderHandle<Self>, app: &App) -> Option<f64> {
Some(self.get(app).item_extent)
}
}
fixed_extent_box_adaptor_leaf!(RenderSliverFixedExtentList);
pub struct RenderSliverVariedExtentList {
render_object: RenderObjectData,
render_sliver: RenderSliverData,
container: ContainerRenderObjectData<AnyRenderBox>,
adaptor: RenderSliverMultiBoxAdaptorData,
fixed_extent: RenderSliverFixedExtentBoxAdaptorData,
item_extent_builder: ItemExtentBuilder,
}
impl RenderSliverVariedExtentList {
pub fn new(
app: &mut App,
child_manager: Rc<dyn RenderSliverBoxChildManager>,
item_extent_builder: ItemExtentBuilder,
) -> RenderHandle<RenderSliverVariedExtentList> {
RenderHandle::new_sliver(
app,
RenderSliverVariedExtentList {
render_object: RenderObjectData::new(),
render_sliver: RenderSliverData::new(),
container: ContainerRenderObjectData::new(),
adaptor: RenderSliverMultiBoxAdaptorData::new(child_manager),
fixed_extent: RenderSliverFixedExtentBoxAdaptorData::new(),
item_extent_builder,
},
)
}
pub fn set_item_extent_builder(
self: RenderHandle<Self>,
app: &mut App,
value: ItemExtentBuilder,
) {
if Rc::ptr_eq(&self.get(app).item_extent_builder, &value) {
return;
}
self.get_mut(app).item_extent_builder = value;
self.mark_needs_layout(app);
}
}
impl RenderSliverFixedExtentBoxAdaptor for RenderSliverVariedExtentList {
crate::render_sliver_fixed_extent_accessors!();
fn item_extent(self: RenderHandle<Self>, _app: &App) -> Option<f64> {
let _ = self;
None
}
fn item_extent_builder(self: RenderHandle<Self>, app: &App) -> Option<ItemExtentBuilder> {
Some(Rc::clone(&self.get(app).item_extent_builder))
}
}
fixed_extent_box_adaptor_leaf!(RenderSliverVariedExtentList);
#[cfg(test)]
mod tests {
use inset_embedder::Size;
use inset_foundation::AppCell;
use inset_gestures::HitTestResult;
use super::*;
use crate::sliver_multi_box_adaptor::test_support::TestChildManager;
use crate::sliver_multi_box_adaptor::viewport_test_support::ScrollHarness;
const VIEWPORT: Size = Size::new(100.0, 200.0);
fn fixed_list(
app: &mut App,
extents: Vec<f64>,
item_extent: f64,
) -> (
RenderHandle<RenderSliverFixedExtentList>,
Rc<TestChildManager<RenderSliverFixedExtentList>>,
ScrollHarness,
) {
let manager = TestChildManager::new(extents);
let list = RenderSliverFixedExtentList::new(app, manager.clone(), item_extent);
manager.attach(list);
let harness = ScrollHarness::new(app, list.as_sliver(), VIEWPORT);
(list, manager, harness)
}
#[test]
fn a_fixed_extent_list_reifies_only_the_visible_children() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
assert_eq!(
list.index_of(&app, list.first_child(&app).expect("built")),
0
);
assert_eq!(list.index_to_layout_offset(&app, 3), 150.0);
let geometry = list.geometry(&app);
assert_eq!(geometry.scroll_extent, 2000.0);
assert_eq!(geometry.paint_extent, 200.0);
assert!(geometry.has_visual_overflow);
assert!(list.child_count(&app) < 40);
}
#[test]
fn scrolling_collects_and_creates_children() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (list, _, harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
assert_eq!(
list.index_of(&app, list.first_child(&app).expect("built")),
0
);
harness.scroll_to(&mut app, 600.0);
let first = list.first_child(&app).expect("children were created");
assert_eq!(list.index_of(&app, first), 7);
assert_eq!(
RenderSliver::child_scroll_offset(list, &app, first.as_object()),
Some(350.0)
);
assert_eq!(
RenderSliver::child_main_axis_position(list, &app, first.as_object()),
-250.0
);
}
#[test]
fn a_fixed_extent_list_hit_tests_its_children() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
let mut result = HitTestResult::new();
let hit = list.hit_test(
&mut app,
&mut SliverHitTestResult::wrap(&mut result),
75.0,
20.0,
);
assert!(hit);
assert_eq!(result.path().len(), 2);
}
#[test]
fn a_kept_alive_child_survives_leaving_the_viewport() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (list, manager, harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
let first = list.first_child(&app).expect("children were created");
first
.as_object()
.parent_data_of_mut::<SliverMultiBoxAdaptorParentData>(&mut app)
.set_keep_alive(true);
harness.scroll_to(&mut app, 1000.0);
assert_eq!(list.keep_alive_children(&app), vec![first]);
assert!(
first
.as_object()
.parent_data_of::<SliverMultiBoxAdaptorParentData>(&app)
.kept_alive()
);
assert!(!list.paints_child(&app, first));
let created_before = manager.created.borrow().len();
harness.scroll_to(&mut app, 0.0);
assert_eq!(list.first_child(&app), Some(first));
assert!(list.keep_alive_children(&app).is_empty());
assert!(list.paints_child(&app, first));
assert!(
!manager.created.borrow()[created_before..].contains(&0),
"the kept-alive child was rebuilt"
);
}
#[test]
fn a_short_list_reports_underflow() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (list, manager, _harness) = fixed_list(&mut app, vec![50.0; 2], 50.0);
assert_eq!(list.geometry(&app).scroll_extent, 100.0);
assert_eq!(list.geometry(&app).paint_extent, 100.0);
assert!(manager.did_underflow.get());
}
#[test]
fn a_varied_extent_list_uses_its_builder() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let manager = TestChildManager::<RenderSliverVariedExtentList>::new(vec![40.0; 10]);
let builder: ItemExtentBuilder =
Rc::new(|index, _| Some(if index % 2 == 0 { 40.0 } else { 20.0 }));
let list = RenderSliverVariedExtentList::new(&mut app, manager.clone(), builder);
manager.attach(list);
let _harness = ScrollHarness::new(&mut app, list.as_sliver(), VIEWPORT);
assert_eq!(list.index_to_layout_offset(&app, 4), 120.0);
assert_eq!(list.item_extent(&app), None);
assert_eq!(list.geometry(&app).paint_extent, 200.0);
assert_eq!(list.geometry(&app).scroll_extent, 400.0);
}
#[test]
fn the_child_index_range_brackets_the_scroll_offset() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (list, _, _harness) = fixed_list(&mut app, vec![50.0; 40], 50.0);
let constraints = list.constraints(&app);
assert_eq!(list.get_min_child_index_for_scroll_offset(&app, 120.0), 2);
assert_eq!(list.get_max_child_index_for_scroll_offset(&app, 120.0), 2);
assert_eq!(list.get_max_child_index_for_scroll_offset(&app, 0.0), 0);
assert_eq!(
list.compute_max_scroll_offset(&mut app, constraints),
2000.0
);
}
}