use std::any::{Any, TypeId};
use std::fmt::{self, Debug};
use std::rc::Rc;
use std::time::Duration;
use inset_animation::Curve;
use inset_embedder::{Clip, Matrix4, Offset, Rect, Size, clamp_double};
use inset_foundation::{App, Handle, HandleId, Listenable, Listener};
use inset_painting::{Axis, AxisDirection, axis_direction_to_axis, transform_rect};
use crate::box_::{BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
use crate::debug::debug_check_has_bounded_axis;
use crate::layer::{ClipRectLayer, LayerHandle};
use crate::object::{
AnyRenderObject, ContainerRenderObjectData, ContainerRenderObjectMixin, RenderHandle,
RenderObject, RenderObjectData, debug_checking_intrinsics, resolve, translate,
};
use crate::painting_context::PaintingContext;
use crate::pipeline_owner::PipelineOwner;
use crate::sliver::{
AnyRenderSliver, GrowthDirection, SliverConstraints, SliverGeometry, SliverHitTestResult,
SliverLogicalContainerParentData, SliverPhysicalContainerParentData,
apply_growth_direction_to_axis_direction, apply_growth_direction_to_scroll_direction,
};
use crate::viewport_offset::AnyViewportOffset;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CacheExtentStyle {
Pixel,
Viewport,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ScrollCacheExtent {
Pixels(f64),
Viewport(f64),
}
impl ScrollCacheExtent {
fn calculate_cache_offset(self, main_axis_extent: f64) -> f64 {
match self {
ScrollCacheExtent::Pixels(pixels) => pixels,
ScrollCacheExtent::Viewport(value) => value * main_axis_extent,
}
}
pub fn style(self) -> CacheExtentStyle {
match self {
ScrollCacheExtent::Pixels(_) => CacheExtentStyle::Pixel,
ScrollCacheExtent::Viewport(_) => CacheExtentStyle::Viewport,
}
}
pub fn value(self) -> f64 {
match self {
ScrollCacheExtent::Pixels(pixels) => pixels,
ScrollCacheExtent::Viewport(value) => value,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub enum SliverPaintOrder {
#[default]
FirstIsTop,
LastIsTop,
}
pub trait RenderAbstractViewport: RenderObject {
fn get_offset_to_reveal(
self: RenderHandle<Self>,
app: &App,
target: AnyRenderObject,
alignment: f64,
rect: Option<Rect>,
axis: Option<Axis>,
) -> RevealedOffset;
fn as_abstract_viewport(self: RenderHandle<Self>) -> AnyRenderAbstractViewport {
AnyRenderAbstractViewport {
id: self.id(),
vtable: const { &RenderAbstractViewportVTable::of::<Self>() },
}
}
}
struct RenderAbstractViewportVTable {
get_offset_to_reveal: GetOffsetToRevealFn,
as_object: fn(&App, HandleId) -> AnyRenderObject,
}
type GetOffsetToRevealFn =
fn(&App, HandleId, AnyRenderObject, f64, Option<Rect>, Option<Axis>) -> RevealedOffset;
impl RenderAbstractViewportVTable {
const fn of<T: RenderAbstractViewport>() -> RenderAbstractViewportVTable {
RenderAbstractViewportVTable {
get_offset_to_reveal: |app, id, target, alignment, rect, axis| {
T::get_offset_to_reveal(resolve(id), app, target, alignment, rect, axis)
},
as_object: |app, id| resolve::<T>(id).as_render_object(app),
}
}
}
#[derive(Clone, Copy)]
pub struct AnyRenderAbstractViewport {
id: HandleId,
vtable: &'static RenderAbstractViewportVTable,
}
impl AnyRenderAbstractViewport {
pub const DEFAULT_CACHE_EXTENT: f64 = 250.0;
pub fn maybe_of(
app: &App,
object: Option<AnyRenderObject>,
) -> Option<AnyRenderAbstractViewport> {
let mut object = object;
while let Some(current) = object {
if let Some(viewport) = current.interface::<AnyRenderAbstractViewport>() {
return Some(viewport);
}
object = current.parent(app);
}
None
}
pub fn of(app: &App, object: Option<AnyRenderObject>) -> AnyRenderAbstractViewport {
AnyRenderAbstractViewport::maybe_of(app, object).unwrap_or_else(|| {
panic!(
"RenderAbstractViewport::of was called with a render object that was not a \
descendant of a RenderAbstractViewport: {object:?}"
)
})
}
pub fn as_object(self, app: &App) -> AnyRenderObject {
(self.vtable.as_object)(app, self.id)
}
pub fn get_offset_to_reveal(
self,
app: &App,
target: AnyRenderObject,
alignment: f64,
rect: Option<Rect>,
axis: Option<Axis>,
) -> RevealedOffset {
(self.vtable.get_offset_to_reveal)(app, self.id, target, alignment, rect, axis)
}
}
impl PartialEq for AnyRenderAbstractViewport {
fn eq(&self, other: &AnyRenderAbstractViewport) -> bool {
self.id == other.id
}
}
impl Eq for AnyRenderAbstractViewport {}
impl Debug for AnyRenderAbstractViewport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AnyRenderAbstractViewport({:?})", self.id)
}
}
#[derive(Clone, Copy, Debug)]
pub struct RevealedOffset {
pub offset: f64,
pub rect: Rect,
}
impl RevealedOffset {
pub const fn new(offset: f64, rect: Rect) -> RevealedOffset {
RevealedOffset { offset, rect }
}
pub fn clamp_offset(
leading_edge_offset: RevealedOffset,
trailing_edge_offset: RevealedOffset,
current_offset: f64,
) -> Option<RevealedOffset> {
let inverted = leading_edge_offset.offset < trailing_edge_offset.offset;
let (smaller, larger) = if inverted {
(leading_edge_offset, trailing_edge_offset)
} else {
(trailing_edge_offset, leading_edge_offset)
};
if current_offset > larger.offset {
Some(larger)
} else if current_offset < smaller.offset {
Some(smaller)
} else {
None
}
}
}
pub type SliverAdvance<T> =
dyn Fn(RenderHandle<T>, &App, AnyRenderSliver) -> Option<AnyRenderSliver>;
pub struct RenderViewportBaseData {
axis_direction: AxisDirection,
cross_axis_direction: AxisDirection,
offset: AnyViewportOffset,
scroll_cache_extent: ScrollCacheExtent,
calculated_cache_extent: Option<f64>,
paint_order: SliverPaintOrder,
clip_behavior: Clip,
clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
}
impl RenderViewportBaseData {
pub fn new(
cross_axis_direction: AxisDirection,
offset: AnyViewportOffset,
) -> RenderViewportBaseData {
RenderViewportBaseData {
axis_direction: AxisDirection::Down,
cross_axis_direction,
offset,
scroll_cache_extent: ScrollCacheExtent::Pixels(
AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT,
),
calculated_cache_extent: None,
paint_order: SliverPaintOrder::FirstIsTop,
clip_behavior: Clip::HardEdge,
clip_rect_layer: LayerHandle::new(),
}
}
}
#[macro_export]
macro_rules! render_viewport_base_accessors {
() => {
fn viewport_data(
self: $crate::RenderHandle<Self>,
app: &::inset_foundation::App,
) -> &$crate::RenderViewportBaseData {
&self.get(app).viewport
}
fn viewport_data_mut(
self: $crate::RenderHandle<Self>,
app: &mut ::inset_foundation::App,
) -> &mut $crate::RenderViewportBaseData {
&mut self.get_mut(app).viewport
}
};
}
pub trait RenderViewportBase:
RenderBox + RenderAbstractViewport + ContainerRenderObjectMixin<ChildType = AnyRenderSliver>
{
fn viewport_data(self: RenderHandle<Self>, app: &App) -> &RenderViewportBaseData;
fn viewport_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderViewportBaseData;
fn axis_direction(self: RenderHandle<Self>, app: &App) -> AxisDirection {
self.viewport_data(app).axis_direction
}
fn set_axis_direction(self: RenderHandle<Self>, app: &mut App, value: AxisDirection) {
if value == self.axis_direction(app) {
return;
}
self.viewport_data_mut(app).axis_direction = value;
self.mark_needs_layout(app);
}
fn cross_axis_direction(self: RenderHandle<Self>, app: &App) -> AxisDirection {
self.viewport_data(app).cross_axis_direction
}
fn set_cross_axis_direction(self: RenderHandle<Self>, app: &mut App, value: AxisDirection) {
if value == self.cross_axis_direction(app) {
return;
}
self.viewport_data_mut(app).cross_axis_direction = value;
self.mark_needs_layout(app);
}
fn axis(self: RenderHandle<Self>, app: &App) -> Axis {
axis_direction_to_axis(self.axis_direction(app))
}
fn offset(self: RenderHandle<Self>, app: &App) -> AnyViewportOffset {
self.viewport_data(app).offset
}
fn set_offset(self: RenderHandle<Self>, app: &mut App, value: AnyViewportOffset) {
let old = self.offset(app);
if value == old {
return;
}
if self.attached(app) {
old.remove_listener(app, &self.mark_needs_layout_listener());
}
self.viewport_data_mut(app).offset = value;
if self.attached(app) {
value.add_listener(app, self.mark_needs_layout_listener());
}
self.mark_needs_layout(app);
}
fn mark_needs_layout_listener(self: RenderHandle<Self>) -> Listener {
Listener::handle_method(self.handle(), viewport_offset_changed::<Self>)
}
fn scroll_cache_extent(self: RenderHandle<Self>, app: &App) -> ScrollCacheExtent {
self.viewport_data(app).scroll_cache_extent
}
fn set_scroll_cache_extent(
self: RenderHandle<Self>,
app: &mut App,
value: Option<ScrollCacheExtent>,
) {
let effective_value = value.unwrap_or(ScrollCacheExtent::Pixels(
AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT,
));
if effective_value == self.scroll_cache_extent(app) {
return;
}
self.viewport_data_mut(app).scroll_cache_extent = effective_value;
self.mark_needs_layout(app);
}
#[deprecated(note = "Use scroll_cache_extent instead.")]
fn cache_extent(self: RenderHandle<Self>, app: &App) -> f64 {
self.scroll_cache_extent(app).value()
}
#[deprecated(note = "Use set_scroll_cache_extent instead.")]
fn set_cache_extent(self: RenderHandle<Self>, app: &mut App, value: Option<f64>) {
let current = self.scroll_cache_extent(app);
if value == Some(current.value()) {
return;
}
let replacement = match value {
None => ScrollCacheExtent::Pixels(AnyRenderAbstractViewport::DEFAULT_CACHE_EXTENT),
Some(value) => match current {
ScrollCacheExtent::Pixels(_) => ScrollCacheExtent::Pixels(value),
ScrollCacheExtent::Viewport(_) => ScrollCacheExtent::Viewport(value),
},
};
self.viewport_data_mut(app).scroll_cache_extent = replacement;
self.mark_needs_layout(app);
}
#[deprecated(note = "Use scroll_cache_extent instead.")]
fn cache_extent_style(self: RenderHandle<Self>, app: &App) -> CacheExtentStyle {
self.scroll_cache_extent(app).style()
}
#[deprecated(note = "Use set_scroll_cache_extent instead.")]
fn set_cache_extent_style(self: RenderHandle<Self>, app: &mut App, value: CacheExtentStyle) {
let current = self.scroll_cache_extent(app);
if value == current.style() {
return;
}
self.viewport_data_mut(app).scroll_cache_extent = match value {
CacheExtentStyle::Pixel => ScrollCacheExtent::Pixels(current.value()),
CacheExtentStyle::Viewport => ScrollCacheExtent::Viewport(current.value()),
};
self.mark_needs_layout(app);
}
fn paint_order(self: RenderHandle<Self>, app: &App) -> SliverPaintOrder {
self.viewport_data(app).paint_order
}
fn set_paint_order(self: RenderHandle<Self>, app: &mut App, value: SliverPaintOrder) {
if value == self.paint_order(app) {
return;
}
self.viewport_data_mut(app).paint_order = value;
self.mark_needs_paint(app);
}
fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
self.viewport_data(app).clip_behavior
}
fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
if value == self.clip_behavior(app) {
return;
}
self.viewport_data_mut(app).clip_behavior = value;
self.mark_needs_paint(app);
}
fn did_attach(
self: RenderHandle<Self>,
app: &mut App,
owner: inset_foundation::Handle<PipelineOwner>,
) {
ContainerRenderObjectMixin::did_attach(self, app, owner);
self.offset(app)
.add_listener(app, self.mark_needs_layout_listener());
}
fn did_detach(self: RenderHandle<Self>, app: &mut App) {
self.offset(app)
.remove_listener(app, &self.mark_needs_layout_listener());
ContainerRenderObjectMixin::did_detach(self, app);
}
fn debug_throw_if_not_checking_intrinsics(self: RenderHandle<Self>) -> bool {
let _ = self;
debug_assert!(
debug_checking_intrinsics(),
"{} does not support returning intrinsic dimensions.\n\
Calculating the intrinsic dimensions would require instantiating every child of \
the viewport, which defeats the point of viewports being lazy.\n\
If you are merely trying to shrink-wrap the viewport in the main axis direction, \
consider a RenderShrinkWrappingViewport render object (ShrinkWrappingViewport \
widget), which achieves that effect without implementing the intrinsic dimension \
API.",
std::any::type_name::<Self>()
);
true
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
let _ = (app, height);
debug_assert!(self.debug_throw_if_not_checking_intrinsics());
0.0
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
let _ = (app, height);
debug_assert!(self.debug_throw_if_not_checking_intrinsics());
0.0
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
let _ = (app, width);
debug_assert!(self.debug_throw_if_not_checking_intrinsics());
0.0
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
let _ = (app, width);
debug_assert!(self.debug_throw_if_not_checking_intrinsics());
0.0
}
#[allow(clippy::too_many_arguments)]
fn layout_child_sequence(
self: RenderHandle<Self>,
app: &mut App,
child: Option<AnyRenderSliver>,
scroll_offset: f64,
overlap: f64,
layout_offset: f64,
remaining_paint_extent: f64,
main_axis_extent: f64,
cross_axis_extent: f64,
growth_direction: GrowthDirection,
advance: &SliverAdvance<Self>,
remaining_cache_extent: f64,
cache_origin: f64,
) -> f64 {
debug_assert!(scroll_offset.is_finite());
debug_assert!(scroll_offset >= 0.0);
let initial_layout_offset = layout_offset;
let adjusted_user_scroll_direction = apply_growth_direction_to_scroll_direction(
self.offset(app).user_scroll_direction(app),
growth_direction,
);
let axis_direction = self.axis_direction(app);
let cross_axis_direction = self.cross_axis_direction(app);
let mut max_paint_offset = layout_offset + overlap;
let mut preceding_scroll_extent = 0.0;
let mut scroll_offset = scroll_offset;
let mut layout_offset = layout_offset;
let mut remaining_cache_extent = remaining_cache_extent;
let mut cache_origin = cache_origin;
let mut child = child;
while let Some(current) = child {
let sliver_scroll_offset = scroll_offset.max(0.0);
let corrected_cache_origin = cache_origin.max(-sliver_scroll_offset);
let cache_extent_correction = cache_origin - corrected_cache_origin;
debug_assert!(sliver_scroll_offset >= corrected_cache_origin.abs());
debug_assert!(corrected_cache_origin <= 0.0);
debug_assert!(sliver_scroll_offset >= 0.0);
debug_assert!(cache_extent_correction <= 0.0);
current.layout(
app,
SliverConstraints::new(
axis_direction,
growth_direction,
adjusted_user_scroll_direction,
sliver_scroll_offset,
preceding_scroll_extent,
max_paint_offset - layout_offset,
(remaining_paint_extent - layout_offset + initial_layout_offset).max(0.0),
cross_axis_extent,
cross_axis_direction,
main_axis_extent,
(remaining_cache_extent + cache_extent_correction).max(0.0),
corrected_cache_origin,
),
true,
);
let child_layout_geometry = current.geometry(app);
debug_assert!(child_layout_geometry.debug_assert_is_valid());
if let Some(correction) = child_layout_geometry.scroll_offset_correction {
return correction;
}
let effective_layout_offset = layout_offset + child_layout_geometry.paint_origin;
if child_layout_geometry.visible || scroll_offset > 0.0 {
self.update_child_layout_offset(
app,
current,
effective_layout_offset,
growth_direction,
);
} else {
self.update_child_layout_offset(
app,
current,
-scroll_offset + initial_layout_offset,
growth_direction,
);
}
max_paint_offset = (effective_layout_offset + child_layout_geometry.paint_extent)
.max(max_paint_offset);
scroll_offset -= child_layout_geometry.scroll_extent;
preceding_scroll_extent += child_layout_geometry.scroll_extent;
layout_offset += child_layout_geometry.layout_extent;
if child_layout_geometry.cache_extent != 0.0 {
remaining_cache_extent -=
child_layout_geometry.cache_extent - cache_extent_correction;
cache_origin =
(corrected_cache_origin + child_layout_geometry.cache_extent).min(0.0);
}
self.update_out_of_band_data(app, growth_direction, child_layout_geometry);
child = advance(self, app, current);
}
0.0
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
if self.first_child(app).is_none() {
return;
}
let clip_behavior = self.clip_behavior(app);
if self.has_visual_overflow(app) && clip_behavior != Clip::None {
let bounds = Offset::ZERO & self.size(app);
let old = self.viewport_data(app).clip_rect_layer.layer();
let layer = context.push_clip_rect(
app,
self.as_object().needs_compositing(app),
offset,
bounds,
|app, context, offset| self.paint_contents(app, context, offset),
clip_behavior,
old,
);
LayerHandle::set_layer(
app,
|app| &mut self.viewport_data_mut(app).clip_rect_layer,
layer,
);
} else {
LayerHandle::set_layer(
app,
|app| &mut self.viewport_data_mut(app).clip_rect_layer,
None,
);
self.paint_contents(app, context, offset);
}
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
LayerHandle::set_layer(
app,
|app| &mut self.viewport_data_mut(app).clip_rect_layer,
None,
);
crate::object::RenderObjectBase::dispose(self, app);
}
fn paint_contents(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
for child in self.children_in_paint_order(app) {
if child.geometry(app).visible {
let child_offset = offset + self.paint_offset_of(app, child);
context.paint_child(app, child.as_object(), child_offset);
}
}
}
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut BoxHitTestResult<'_>,
position: Offset,
) -> bool {
let (main_axis_position, cross_axis_position) = match self.axis(app) {
Axis::Vertical => (position.dy(), position.dx()),
Axis::Horizontal => (position.dx(), position.dy()),
};
for child in self.children_in_hit_test_order(app) {
if !child.geometry(app).visible {
continue;
}
let mut transform = Matrix4::IDENTITY;
RenderObject::apply_paint_transform(self, app, child.as_object(), &mut transform);
let child_main_axis_position =
self.compute_child_main_axis_position(app, child, main_axis_position);
let is_hit =
result.add_with_out_of_band_position(None, Some(transform), None, |result| {
child.hit_test(
app,
&mut SliverHitTestResult::wrap(result),
child_main_axis_position,
cross_axis_position,
)
});
if is_hit {
return true;
}
}
false
}
fn get_offset_to_reveal(
self: RenderHandle<Self>,
app: &App,
target: AnyRenderObject,
alignment: f64,
rect: Option<Rect>,
_axis: Option<Axis>,
) -> RevealedOffset {
let axis = self.axis(app);
let this = self.as_object();
let mut leading_scroll_offset = 0.0;
let mut child = target;
let mut pivot = None;
let mut only_slivers = target.as_sliver().is_some();
let mut rect = rect;
while child.parent(app) != Some(this) {
let parent = child
.parent(app)
.expect("target is a descendant of this viewport");
if let Some(box_child) = child.as_box() {
pivot = Some(box_child);
}
match parent.as_sliver() {
Some(sliver) => {
leading_scroll_offset += sliver
.child_scroll_offset(app, child)
.expect("a sliver parent reports its child's scroll offset");
}
None => {
only_slivers = false;
leading_scroll_offset = 0.0;
}
}
child = parent;
}
let rect_local;
let pivot_extent;
let growth_direction;
if let Some(pivot) = pivot {
debug_assert!(pivot.as_object().parent(app).is_some());
debug_assert_ne!(pivot.as_object().parent(app), Some(this));
debug_assert_ne!(pivot.as_object(), this);
let pivot_parent = pivot
.as_object()
.parent(app)
.and_then(AnyRenderObject::as_sliver)
.expect("only sliver parents are supported between a pivot and the viewport");
growth_direction = pivot_parent.constraints(app).growth_direction;
pivot_extent = match axis {
Axis::Horizontal => pivot.size(app).width(),
Axis::Vertical => pivot.size(app).height(),
};
let rect = *rect.get_or_insert_with(|| target.paint_bounds(app));
rect_local =
transform_rect(&target.get_transform_to(app, Some(pivot.as_object())), rect);
} else if only_slivers {
let target_sliver = target.as_sliver().expect("only_slivers");
growth_direction = target_sliver.constraints(app).growth_direction;
pivot_extent = target_sliver.geometry(app).scroll_extent;
rect_local = *rect.get_or_insert_with(|| {
let constraints = target_sliver.constraints(app);
let geometry = target_sliver.geometry(app);
match axis {
Axis::Horizontal => Rect::from_ltwh(
0.0,
0.0,
geometry.scroll_extent,
constraints.cross_axis_extent,
),
Axis::Vertical => Rect::from_ltwh(
0.0,
0.0,
constraints.cross_axis_extent,
geometry.scroll_extent,
),
}
});
} else {
let rect = rect.expect("a non-sliver target must be given a rect");
return RevealedOffset::new(self.offset(app).pixels(app), rect);
}
debug_assert_eq!(child.parent(app), Some(this));
let sliver = child
.as_sliver()
.expect("a viewport's children are slivers");
leading_scroll_offset += match apply_growth_direction_to_axis_direction(
self.axis_direction(app),
growth_direction,
) {
AxisDirection::Up => pivot_extent - rect_local.bottom,
AxisDirection::Left => pivot_extent - rect_local.right,
AxisDirection::Right => rect_local.left,
AxisDirection::Down => rect_local.top,
};
let is_pinned = sliver.geometry(app).max_scroll_obstruction_extent > 0.0
&& leading_scroll_offset >= 0.0;
leading_scroll_offset = self.scroll_offset_of(app, sliver, leading_scroll_offset);
let transform = target.get_transform_to(app, Some(this));
let mut target_rect = transform_rect(&transform, rect.expect("set above"));
let extent_of_pinned_slivers = self.max_scroll_obstruction_extent_before(app, sliver);
match sliver.constraints(app).growth_direction {
GrowthDirection::Forward => {
if is_pinned && alignment <= 0.0 {
return RevealedOffset::new(f64::INFINITY, target_rect);
}
leading_scroll_offset -= extent_of_pinned_slivers;
}
GrowthDirection::Reverse => {
if is_pinned && alignment >= 1.0 {
return RevealedOffset::new(f64::NEG_INFINITY, target_rect);
}
leading_scroll_offset -= match axis {
Axis::Vertical => target_rect.height(),
Axis::Horizontal => target_rect.width(),
};
}
}
let size = self.size(app);
let main_axis_extent_difference = match axis {
Axis::Horizontal => size.width() - extent_of_pinned_slivers - rect_local.width(),
Axis::Vertical => size.height() - extent_of_pinned_slivers - rect_local.height(),
};
let target_offset = leading_scroll_offset - main_axis_extent_difference * alignment;
let offset_difference = self.offset(app).pixels(app) - target_offset;
target_rect = match self.axis_direction(app) {
AxisDirection::Up => target_rect.translate(0.0, -offset_difference),
AxisDirection::Down => target_rect.translate(0.0, offset_difference),
AxisDirection::Left => target_rect.translate(-offset_difference, 0.0),
AxisDirection::Right => target_rect.translate(offset_difference, 0.0),
};
RevealedOffset::new(target_offset, target_rect)
}
fn compute_absolute_paint_offset(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
layout_offset: f64,
growth_direction: GrowthDirection,
) -> Offset {
debug_assert!(self.has_size(app));
let paint_extent = child.geometry(app).paint_extent;
let size = self.size(app);
match apply_growth_direction_to_axis_direction(self.axis_direction(app), growth_direction) {
AxisDirection::Up => Offset::new(0.0, size.height() - layout_offset - paint_extent),
AxisDirection::Left => Offset::new(size.width() - layout_offset - paint_extent, 0.0),
AxisDirection::Right => Offset::new(layout_offset, 0.0),
AxisDirection::Down => Offset::new(0.0, layout_offset),
}
}
fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool;
fn update_out_of_band_data(
self: RenderHandle<Self>,
app: &mut App,
growth_direction: GrowthDirection,
child_layout_geometry: SliverGeometry,
);
fn update_child_layout_offset(
self: RenderHandle<Self>,
app: &mut App,
child: AnyRenderSliver,
layout_offset: f64,
growth_direction: GrowthDirection,
);
fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset;
fn scroll_offset_of(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
scroll_offset_within_child: f64,
) -> f64;
fn max_scroll_obstruction_extent_before(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
) -> f64;
fn compute_child_main_axis_position(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
parent_main_axis_position: f64,
) -> f64;
fn index_of_first_child(self: RenderHandle<Self>, app: &App) -> i32;
fn label_for_child(self: RenderHandle<Self>, index: i32) -> String;
fn children_in_paint_order(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
match self.paint_order(app) {
SliverPaintOrder::FirstIsTop => self.children_last_to_first(app),
SliverPaintOrder::LastIsTop => self.children_as_list(app),
}
}
fn children_in_hit_test_order(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
match self.paint_order(app) {
SliverPaintOrder::FirstIsTop => self.children_as_list(app),
SliverPaintOrder::LastIsTop => self.children_last_to_first(app),
}
}
fn children_last_to_first(self: RenderHandle<Self>, app: &App) -> Vec<AnyRenderSliver> {
let mut children = Vec::new();
let mut child = self.last_child(app);
while let Some(current) = child {
children.push(current);
child = self.child_before(app, current);
}
children
}
fn show_on_screen(
self: RenderHandle<Self>,
app: &mut App,
descendant: Option<AnyRenderObject>,
rect: Option<Rect>,
duration: Duration,
curve: Rc<dyn Curve>,
) {
let offset = self.offset(app);
if !offset.allow_implicit_scrolling(app) {
return crate::object::RenderObjectBase::show_on_screen(
self, app, descendant, rect, duration, curve,
);
}
let new_rect = show_in_viewport(
app,
descendant,
rect,
self.as_abstract_viewport(),
offset,
duration,
Rc::clone(&curve),
);
crate::object::RenderObjectBase::show_on_screen(self, app, None, new_rect, duration, curve);
}
}
fn viewport_offset_changed<T: RenderViewportBase>(
this: inset_foundation::Handle<T>,
app: &mut App,
) {
RenderHandle::from_handle(this).mark_needs_layout(app);
}
pub fn show_in_viewport(
app: &mut App,
descendant: Option<AnyRenderObject>,
rect: Option<Rect>,
viewport: AnyRenderAbstractViewport,
offset: AnyViewportOffset,
duration: Duration,
curve: Rc<dyn Curve>,
) -> Option<Rect> {
let Some(descendant) = descendant else {
return rect;
};
let leading_edge_offset = viewport.get_offset_to_reveal(app, descendant, 0.0, rect, None);
let trailing_edge_offset = viewport.get_offset_to_reveal(app, descendant, 1.0, rect, None);
let current_offset = offset.pixels(app);
let target_offset =
RevealedOffset::clamp_offset(leading_edge_offset, trailing_edge_offset, current_offset);
let Some(target_offset) = target_offset else {
let viewport_object = viewport.as_object(app);
debug_assert!(viewport_object.parent(app).is_some());
let transform = descendant.get_transform_to(app, viewport_object.parent(app));
return Some(transform_rect(
&transform,
rect.unwrap_or_else(|| descendant.paint_bounds(app)),
));
};
offset.move_to(app, target_offset.offset, Some(duration), Some(curve), None);
Some(target_offset.rect)
}
pub struct RenderViewport {
render_object: RenderObjectData,
render_box: RenderBoxData,
container: ContainerRenderObjectData<AnyRenderSliver>,
viewport: RenderViewportBaseData,
anchor: f64,
center: Option<AnyRenderSliver>,
min_scroll_extent: f64,
max_scroll_extent: f64,
has_visual_overflow: bool,
}
impl RenderViewport {
const MAX_LAYOUT_CYCLES_PER_CHILD: usize = 10;
pub fn new(
app: &mut App,
cross_axis_direction: AxisDirection,
offset: AnyViewportOffset,
children: Option<Vec<AnyRenderSliver>>,
center: Option<AnyRenderSliver>,
) -> RenderHandle<RenderViewport> {
let this = RenderHandle::new_box(
app,
RenderViewport {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
container: ContainerRenderObjectData::new(),
viewport: RenderViewportBaseData::new(cross_axis_direction, offset),
anchor: 0.0,
center,
min_scroll_extent: 0.0,
max_scroll_extent: 0.0,
has_visual_overflow: false,
},
);
this.add_all(app, children);
if center.is_none()
&& let Some(first_child) = this.first_child(app)
{
this.get_mut(app).center = Some(first_child);
}
this
}
pub fn anchor(self: RenderHandle<Self>, app: &App) -> f64 {
self.get(app).anchor
}
pub fn set_anchor(self: RenderHandle<Self>, app: &mut App, value: f64) {
debug_assert!((0.0..=1.0).contains(&value));
if value == self.get(app).anchor {
return;
}
self.get_mut(app).anchor = value;
self.mark_needs_layout(app);
}
pub fn center(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderSliver> {
self.get(app).center
}
pub fn set_center(self: RenderHandle<Self>, app: &mut App, value: Option<AnyRenderSliver>) {
if value == self.get(app).center {
return;
}
self.get_mut(app).center = value;
self.mark_needs_layout(app);
}
fn attempt_layout(
self: RenderHandle<Self>,
app: &mut App,
main_axis_extent: f64,
cross_axis_extent: f64,
corrected_offset: f64,
) -> f64 {
debug_assert!(!main_axis_extent.is_nan());
debug_assert!(main_axis_extent >= 0.0);
debug_assert!(main_axis_extent.is_finite());
debug_assert!(cross_axis_extent.is_finite());
debug_assert!(cross_axis_extent >= 0.0);
debug_assert!(corrected_offset.is_finite());
let this = self.get_mut(app);
this.min_scroll_extent = 0.0;
this.max_scroll_extent = 0.0;
this.has_visual_overflow = false;
let anchor = self.get(app).anchor;
let center_offset = main_axis_extent * anchor - corrected_offset;
let reverse_direction_remaining_paint_extent =
clamp_double(center_offset, 0.0, main_axis_extent);
let forward_direction_remaining_paint_extent =
clamp_double(main_axis_extent - center_offset, 0.0, main_axis_extent);
let calculated_cache_extent = self
.scroll_cache_extent(app)
.calculate_cache_offset(main_axis_extent);
self.viewport_data_mut(app).calculated_cache_extent = Some(calculated_cache_extent);
let full_cache_extent = main_axis_extent + 2.0 * calculated_cache_extent;
let center_cache_offset = center_offset + calculated_cache_extent;
let reverse_direction_remaining_cache_extent =
clamp_double(center_cache_offset, 0.0, full_cache_extent);
let forward_direction_remaining_cache_extent = clamp_double(
full_cache_extent - center_cache_offset,
0.0,
full_cache_extent,
);
let center = self.center(app).expect("checked by perform_layout");
let leading_negative_child = self.child_before(app, center);
if let Some(leading_negative_child) = leading_negative_child {
let result = self.layout_child_sequence(
app,
Some(leading_negative_child),
main_axis_extent.max(center_offset) - main_axis_extent,
0.0,
forward_direction_remaining_paint_extent,
reverse_direction_remaining_paint_extent,
main_axis_extent,
cross_axis_extent,
GrowthDirection::Reverse,
&|this, app, child| this.child_before(app, child),
reverse_direction_remaining_cache_extent,
clamp_double(
main_axis_extent - center_offset,
-calculated_cache_extent,
0.0,
),
);
if result != 0.0 {
return -result;
}
}
self.layout_child_sequence(
app,
Some(center),
(-center_offset).max(0.0),
if leading_negative_child.is_none() {
(-center_offset).min(0.0)
} else {
0.0
},
if center_offset >= main_axis_extent {
center_offset
} else {
reverse_direction_remaining_paint_extent
},
forward_direction_remaining_paint_extent,
main_axis_extent,
cross_axis_extent,
GrowthDirection::Forward,
&|this, app, child| this.child_after(app, child),
forward_direction_remaining_cache_extent,
clamp_double(center_offset, -calculated_cache_extent, 0.0),
)
}
}
impl ContainerRenderObjectMixin for RenderViewport {
type ChildType = AnyRenderSliver;
type ParentDataType = SliverPhysicalContainerParentData;
fn container_data(
self: RenderHandle<Self>,
app: &App,
) -> &ContainerRenderObjectData<AnyRenderSliver> {
&self.get(app).container
}
fn container_data_mut(
self: RenderHandle<Self>,
app: &mut App,
) -> &mut ContainerRenderObjectData<AnyRenderSliver> {
&mut self.get_mut(app).container
}
}
impl RenderViewportBase for RenderViewport {
crate::render_viewport_base_accessors!();
fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
self.get(app).has_visual_overflow
}
fn update_out_of_band_data(
self: RenderHandle<Self>,
app: &mut App,
growth_direction: GrowthDirection,
child_layout_geometry: SliverGeometry,
) {
let this = self.get_mut(app);
match growth_direction {
GrowthDirection::Forward => {
this.max_scroll_extent += child_layout_geometry.scroll_extent
}
GrowthDirection::Reverse => {
this.min_scroll_extent -= child_layout_geometry.scroll_extent
}
}
if child_layout_geometry.has_visual_overflow {
this.has_visual_overflow = true;
}
}
fn update_child_layout_offset(
self: RenderHandle<Self>,
app: &mut App,
child: AnyRenderSliver,
layout_offset: f64,
growth_direction: GrowthDirection,
) {
let paint_offset =
self.compute_absolute_paint_offset(app, child, layout_offset, growth_direction);
child
.as_object()
.parent_data_of_mut::<SliverPhysicalContainerParentData>(app)
.set_paint_offset(paint_offset);
}
fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset {
child
.as_object()
.parent_data_of::<SliverPhysicalContainerParentData>(app)
.paint_offset()
}
fn scroll_offset_of(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
scroll_offset_within_child: f64,
) -> f64 {
debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
match child.constraints(app).growth_direction {
GrowthDirection::Forward => {
let mut scroll_offset_to_child = 0.0;
let mut current = self.center(app);
while current != Some(child) {
let sliver = current.expect("the center precedes a forward child");
scroll_offset_to_child += sliver.geometry(app).scroll_extent;
current = self.child_after(app, sliver);
}
scroll_offset_to_child + scroll_offset_within_child
}
GrowthDirection::Reverse => {
let mut scroll_offset_to_child = 0.0;
let center = self.center(app).expect("a laid-out viewport has a center");
let mut current = self.child_before(app, center);
while current != Some(child) {
let sliver = current.expect("a reverse child precedes the center");
scroll_offset_to_child -= sliver.geometry(app).scroll_extent;
current = self.child_before(app, sliver);
}
scroll_offset_to_child - scroll_offset_within_child
}
}
}
fn max_scroll_obstruction_extent_before(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
) -> f64 {
debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
let mut pinned_extent = 0.0;
match child.constraints(app).growth_direction {
GrowthDirection::Forward => {
let mut current = self.center(app);
while current != Some(child) {
let sliver = current.expect("the center precedes a forward child");
pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
current = self.child_after(app, sliver);
}
}
GrowthDirection::Reverse => {
let center = self.center(app).expect("a laid-out viewport has a center");
let mut current = self.child_before(app, center);
while current != Some(child) {
let sliver = current.expect("a reverse child precedes the center");
pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
current = self.child_before(app, sliver);
}
}
}
pinned_extent
}
fn compute_child_main_axis_position(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
parent_main_axis_position: f64,
) -> f64 {
let paint_offset = child
.as_object()
.parent_data_of::<SliverPhysicalContainerParentData>(app)
.paint_offset();
let constraints = child.constraints(app);
let paint_extent = child.geometry(app).paint_extent;
match apply_growth_direction_to_axis_direction(
constraints.axis_direction,
constraints.growth_direction,
) {
AxisDirection::Down => parent_main_axis_position - paint_offset.dy(),
AxisDirection::Right => parent_main_axis_position - paint_offset.dx(),
AxisDirection::Up => paint_extent - (parent_main_axis_position - paint_offset.dy()),
AxisDirection::Left => paint_extent - (parent_main_axis_position - paint_offset.dx()),
}
}
fn index_of_first_child(self: RenderHandle<Self>, app: &App) -> i32 {
let center = self.center(app).expect("a laid-out viewport has a center");
debug_assert_eq!(center.as_object().parent(app), Some(self.as_object()));
debug_assert!(self.first_child(app).is_some());
let mut count = 0;
let mut child = Some(center);
while child != self.first_child(app) {
count -= 1;
child = self.child_before(app, child.expect("the first child terminates the walk"));
}
count
}
fn label_for_child(self: RenderHandle<Self>, index: i32) -> String {
if index == 0 {
"center child".to_owned()
} else {
format!("child {index}")
}
}
}
impl RenderAbstractViewport for RenderViewport {
fn get_offset_to_reveal(
self: RenderHandle<Self>,
app: &App,
target: AnyRenderObject,
alignment: f64,
rect: Option<Rect>,
axis: Option<Axis>,
) -> RevealedOffset {
RenderViewportBase::get_offset_to_reveal(self, app, target, alignment, rect, axis)
}
}
impl RenderObject for RenderViewport {
crate::render_object_accessors!();
fn visit_children(
self: RenderHandle<Self>,
app: &App,
visitor: &mut dyn FnMut(AnyRenderObject),
) {
ContainerRenderObjectMixin::visit_children(self, app, visitor)
}
fn did_attach(
self: RenderHandle<Self>,
app: &mut App,
owner: inset_foundation::Handle<PipelineOwner>,
) {
RenderViewportBase::did_attach(self, app, owner)
}
fn did_detach(self: RenderHandle<Self>, app: &mut App) {
RenderViewportBase::did_detach(self, app)
}
fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::redepth_children(self, app)
}
fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
let _ = self;
true
}
fn interface(self: RenderHandle<Self>, id: TypeId) -> Option<Box<dyn Any>> {
(id == TypeId::of::<AnyRenderAbstractViewport>())
.then(|| Box::new(self.as_abstract_viewport()) as Box<dyn Any>)
}
fn sized_by_parent(self: RenderHandle<Self>, _app: &App) -> bool {
let _ = self;
true
}
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let offset = self.offset(app);
let size = self.size(app);
match self.axis(app) {
Axis::Vertical => offset.apply_viewport_dimension(app, size.height()),
Axis::Horizontal => offset.apply_viewport_dimension(app, size.width()),
};
let Some(center) = self.center(app) else {
debug_assert!(self.first_child(app).is_none());
let this = self.get_mut(app);
this.min_scroll_extent = 0.0;
this.max_scroll_extent = 0.0;
this.has_visual_overflow = false;
offset.apply_content_dimensions(app, 0.0, 0.0);
return;
};
debug_assert_eq!(center.as_object().parent(app), Some(self.as_object()));
let (main_axis_extent, cross_axis_extent) = match self.axis(app) {
Axis::Vertical => (size.height(), size.width()),
Axis::Horizontal => (size.width(), size.height()),
};
let center_offset_adjustment = center.center_offset_adjustment(app);
let max_layout_cycles = RenderViewport::MAX_LAYOUT_CYCLES_PER_CHILD * self.child_count(app);
let anchor = self.get(app).anchor;
let mut count = 0;
loop {
let correction = self.attempt_layout(
app,
main_axis_extent,
cross_axis_extent,
offset.pixels(app) + center_offset_adjustment,
);
if correction != 0.0 {
offset.correct_by(app, correction);
} else {
let min_scroll_extent = self.get(app).min_scroll_extent;
let max_scroll_extent = self.get(app).max_scroll_extent;
if offset.apply_content_dimensions(
app,
(min_scroll_extent + main_axis_extent * anchor).min(0.0),
(max_scroll_extent - main_axis_extent * (1.0 - anchor)).max(0.0),
) {
break;
}
}
count += 1;
if count >= max_layout_cycles {
break;
}
}
debug_assert!(
count < max_layout_cycles,
"A RenderViewport exceeded its maximum number of layout cycles.\n\
RenderViewport render objects, during layout, can retry if either their slivers or \
their ViewportOffset decide that the offset should be corrected to take into \
account information collected during that layout.\n\
In the case of this RenderViewport object, however, this happened {count} times and \
still there was no consensus on the scroll offset. This usually indicates a bug."
);
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
RenderViewportBase::paint(self, app, context, offset)
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
RenderViewportBase::dispose(self, app)
}
fn show_on_screen(
self: RenderHandle<Self>,
app: &mut App,
descendant: Option<AnyRenderObject>,
rect: Option<Rect>,
duration: Duration,
curve: Rc<dyn Curve>,
) {
RenderViewportBase::show_on_screen(self, app, descendant, rect, duration, curve)
}
}
impl RenderBox for RenderViewport {
crate::render_box_accessors!();
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
if !child.parent_data_is::<SliverPhysicalContainerParentData>(app) {
child.set_parent_data(app, SliverPhysicalContainerParentData::new());
}
}
fn apply_paint_transform(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderObject,
transform: &mut Matrix4,
) {
child
.parent_data_of::<SliverPhysicalContainerParentData>(app)
.apply_paint_transform(transform);
}
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut BoxHitTestResult<'_>,
position: Offset,
) -> bool {
RenderViewportBase::hit_test_children(self, app, result, position)
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderViewportBase::compute_min_intrinsic_width(self, app, height)
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderViewportBase::compute_max_intrinsic_width(self, app, height)
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderViewportBase::compute_min_intrinsic_height(self, app, width)
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderViewportBase::compute_max_intrinsic_height(self, app, width)
}
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
debug_assert!(debug_check_has_bounded_axis(self.axis(app), constraints));
constraints.biggest()
}
}
pub struct RenderShrinkWrappingViewport {
render_object: RenderObjectData,
render_box: RenderBoxData,
container: ContainerRenderObjectData<AnyRenderSliver>,
viewport: RenderViewportBaseData,
max_scroll_extent: f64,
shrink_wrap_extent: f64,
has_visual_overflow: bool,
}
impl RenderShrinkWrappingViewport {
pub fn new(
app: &mut App,
cross_axis_direction: AxisDirection,
offset: AnyViewportOffset,
children: Option<Vec<AnyRenderSliver>>,
) -> RenderHandle<RenderShrinkWrappingViewport> {
let this = RenderHandle::new_box(
app,
RenderShrinkWrappingViewport {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
container: ContainerRenderObjectData::new(),
viewport: RenderViewportBaseData::new(cross_axis_direction, offset),
max_scroll_extent: 0.0,
shrink_wrap_extent: 0.0,
has_visual_overflow: false,
},
);
this.add_all(app, children);
this
}
fn debug_check_has_bounded_cross_axis(self: RenderHandle<Self>, app: &App) -> bool {
let constraints = self.constraints(app);
match self.axis(app) {
Axis::Vertical => debug_assert!(
constraints.has_bounded_width(),
"Vertical viewport was given unbounded width.\n\
Viewports expand in the cross axis to fill their container and constrain their \
children to match their extent in the cross axis. In this case, a vertical \
shrinkwrapping viewport was given an unlimited amount of horizontal space in \
which to expand."
),
Axis::Horizontal => debug_assert!(
constraints.has_bounded_height(),
"Horizontal viewport was given unbounded height.\n\
Viewports expand in the cross axis to fill their container and constrain their \
children to match their extent in the cross axis. In this case, a horizontal \
shrinkwrapping viewport was given an unlimited amount of vertical space in \
which to expand."
),
}
true
}
fn attempt_layout(
self: RenderHandle<Self>,
app: &mut App,
main_axis_extent: f64,
cross_axis_extent: f64,
corrected_offset: f64,
) -> f64 {
debug_assert!(!main_axis_extent.is_nan());
debug_assert!(main_axis_extent >= 0.0);
debug_assert!(cross_axis_extent.is_finite());
debug_assert!(cross_axis_extent >= 0.0);
debug_assert!(corrected_offset.is_finite());
let this = self.get_mut(app);
this.max_scroll_extent = 0.0;
this.shrink_wrap_extent = 0.0;
this.has_visual_overflow = corrected_offset < 0.0;
let calculated_cache_extent = if main_axis_extent.is_finite() {
self.scroll_cache_extent(app)
.calculate_cache_offset(main_axis_extent)
} else {
0.0
};
self.viewport_data_mut(app).calculated_cache_extent = Some(calculated_cache_extent);
let first_child = self.first_child(app);
self.layout_child_sequence(
app,
first_child,
corrected_offset.max(0.0),
corrected_offset.min(0.0),
(-corrected_offset).max(0.0),
main_axis_extent + corrected_offset.min(0.0),
main_axis_extent,
cross_axis_extent,
GrowthDirection::Forward,
&|this, app, child| this.child_after(app, child),
main_axis_extent + 2.0 * calculated_cache_extent,
-calculated_cache_extent,
)
}
}
impl ContainerRenderObjectMixin for RenderShrinkWrappingViewport {
type ChildType = AnyRenderSliver;
type ParentDataType = SliverLogicalContainerParentData;
fn container_data(
self: RenderHandle<Self>,
app: &App,
) -> &ContainerRenderObjectData<AnyRenderSliver> {
&self.get(app).container
}
fn container_data_mut(
self: RenderHandle<Self>,
app: &mut App,
) -> &mut ContainerRenderObjectData<AnyRenderSliver> {
&mut self.get_mut(app).container
}
}
impl RenderViewportBase for RenderShrinkWrappingViewport {
crate::render_viewport_base_accessors!();
fn debug_throw_if_not_checking_intrinsics(self: RenderHandle<Self>) -> bool {
let _ = self;
debug_assert!(
debug_checking_intrinsics(),
"{} does not support returning intrinsic dimensions.\n\
Calculating the intrinsic dimensions would require instantiating every child of \
the viewport, which defeats the point of viewports being lazy.\n\
If you are merely trying to shrink-wrap the viewport in the main axis direction, \
you should be able to achieve that effect by just giving the viewport loose \
constraints, without needing to measure its intrinsic dimensions.",
std::any::type_name::<Self>()
);
true
}
fn has_visual_overflow(self: RenderHandle<Self>, app: &App) -> bool {
self.get(app).has_visual_overflow
}
fn update_out_of_band_data(
self: RenderHandle<Self>,
app: &mut App,
growth_direction: GrowthDirection,
child_layout_geometry: SliverGeometry,
) {
debug_assert_eq!(growth_direction, GrowthDirection::Forward);
let this = self.get_mut(app);
this.max_scroll_extent += child_layout_geometry.scroll_extent;
if child_layout_geometry.has_visual_overflow {
this.has_visual_overflow = true;
}
this.shrink_wrap_extent += child_layout_geometry.max_paint_extent;
}
fn update_child_layout_offset(
self: RenderHandle<Self>,
app: &mut App,
child: AnyRenderSliver,
layout_offset: f64,
growth_direction: GrowthDirection,
) {
debug_assert_eq!(growth_direction, GrowthDirection::Forward);
child
.as_object()
.parent_data_of_mut::<SliverLogicalContainerParentData>(app)
.set_layout_offset(Some(layout_offset));
}
fn paint_offset_of(self: RenderHandle<Self>, app: &App, child: AnyRenderSliver) -> Offset {
let layout_offset = child
.as_object()
.parent_data_of::<SliverLogicalContainerParentData>(app)
.layout_offset()
.expect("a laid-out child has a layout offset");
self.compute_absolute_paint_offset(app, child, layout_offset, GrowthDirection::Forward)
}
fn scroll_offset_of(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
scroll_offset_within_child: f64,
) -> f64 {
debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
debug_assert_eq!(
child.constraints(app).growth_direction,
GrowthDirection::Forward
);
let mut scroll_offset_to_child = 0.0;
let mut current = self.first_child(app);
while current != Some(child) {
let sliver = current.expect("child is in the child list");
scroll_offset_to_child += sliver.geometry(app).scroll_extent;
current = self.child_after(app, sliver);
}
scroll_offset_to_child + scroll_offset_within_child
}
fn max_scroll_obstruction_extent_before(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
) -> f64 {
debug_assert_eq!(child.as_object().parent(app), Some(self.as_object()));
debug_assert_eq!(
child.constraints(app).growth_direction,
GrowthDirection::Forward
);
let mut pinned_extent = 0.0;
let mut current = self.first_child(app);
while current != Some(child) {
let sliver = current.expect("child is in the child list");
pinned_extent += sliver.geometry(app).max_scroll_obstruction_extent;
current = self.child_after(app, sliver);
}
pinned_extent
}
fn compute_child_main_axis_position(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderSliver,
parent_main_axis_position: f64,
) -> f64 {
debug_assert!(self.has_size(app));
let layout_offset = child
.as_object()
.parent_data_of::<SliverLogicalContainerParentData>(app)
.layout_offset()
.expect("a laid-out child has a layout offset");
let constraints = child.constraints(app);
let size = self.size(app);
match apply_growth_direction_to_axis_direction(
constraints.axis_direction,
constraints.growth_direction,
) {
AxisDirection::Down | AxisDirection::Right => parent_main_axis_position - layout_offset,
AxisDirection::Up => size.height() - parent_main_axis_position - layout_offset,
AxisDirection::Left => size.width() - parent_main_axis_position - layout_offset,
}
}
fn index_of_first_child(self: RenderHandle<Self>, _app: &App) -> i32 {
let _ = self;
0
}
fn label_for_child(self: RenderHandle<Self>, index: i32) -> String {
let _ = self;
format!("child {index}")
}
}
impl RenderAbstractViewport for RenderShrinkWrappingViewport {
fn get_offset_to_reveal(
self: RenderHandle<Self>,
app: &App,
target: AnyRenderObject,
alignment: f64,
rect: Option<Rect>,
axis: Option<Axis>,
) -> RevealedOffset {
RenderViewportBase::get_offset_to_reveal(self, app, target, alignment, rect, axis)
}
}
impl RenderObject for RenderShrinkWrappingViewport {
crate::render_object_accessors!();
fn visit_children(
self: RenderHandle<Self>,
app: &App,
visitor: &mut dyn FnMut(AnyRenderObject),
) {
ContainerRenderObjectMixin::visit_children(self, app, visitor)
}
fn did_attach(
self: RenderHandle<Self>,
app: &mut App,
owner: inset_foundation::Handle<PipelineOwner>,
) {
RenderViewportBase::did_attach(self, app, owner)
}
fn did_detach(self: RenderHandle<Self>, app: &mut App) {
RenderViewportBase::did_detach(self, app)
}
fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::redepth_children(self, app)
}
fn is_repaint_boundary(self: RenderHandle<Self>, _app: &App) -> bool {
let _ = self;
true
}
fn interface(self: RenderHandle<Self>, id: TypeId) -> Option<Box<dyn Any>> {
(id == TypeId::of::<AnyRenderAbstractViewport>())
.then(|| Box::new(self.as_abstract_viewport()) as Box<dyn Any>)
}
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let constraints = self.constraints(app);
let offset = self.offset(app);
if self.first_child(app).is_none() {
debug_assert!(self.debug_check_has_bounded_cross_axis(app));
let size = match self.axis(app) {
Axis::Vertical => Size::new(constraints.max_width, constraints.min_height),
Axis::Horizontal => Size::new(constraints.min_width, constraints.max_height),
};
self.set_size(app, size);
offset.apply_viewport_dimension(app, 0.0);
let this = self.get_mut(app);
this.max_scroll_extent = 0.0;
this.shrink_wrap_extent = 0.0;
this.has_visual_overflow = false;
offset.apply_content_dimensions(app, 0.0, 0.0);
return;
}
debug_assert!(self.debug_check_has_bounded_cross_axis(app));
let (main_axis_extent, cross_axis_extent) = match self.axis(app) {
Axis::Vertical => (constraints.max_height, constraints.max_width),
Axis::Horizontal => (constraints.max_width, constraints.max_height),
};
let effective_extent;
loop {
let corrected_offset = offset.pixels(app);
let correction =
self.attempt_layout(app, main_axis_extent, cross_axis_extent, corrected_offset);
if correction != 0.0 {
offset.correct_by(app, correction);
continue;
}
let shrink_wrap_extent = self.get(app).shrink_wrap_extent;
let extent = match self.axis(app) {
Axis::Vertical => constraints.constrain_height(shrink_wrap_extent),
Axis::Horizontal => constraints.constrain_width(shrink_wrap_extent),
};
let did_accept_viewport_dimension = offset.apply_viewport_dimension(app, extent);
let max_scroll_extent = self.get(app).max_scroll_extent;
let did_accept_content_dimension =
offset.apply_content_dimensions(app, 0.0, (max_scroll_extent - extent).max(0.0));
if did_accept_viewport_dimension && did_accept_content_dimension {
effective_extent = extent;
break;
}
}
let size = match self.axis(app) {
Axis::Vertical => constraints.constrain_dimensions(cross_axis_extent, effective_extent),
Axis::Horizontal => {
constraints.constrain_dimensions(effective_extent, cross_axis_extent)
}
};
self.set_size(app, size);
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
RenderViewportBase::paint(self, app, context, offset)
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
RenderViewportBase::dispose(self, app)
}
fn show_on_screen(
self: RenderHandle<Self>,
app: &mut App,
descendant: Option<AnyRenderObject>,
rect: Option<Rect>,
duration: Duration,
curve: Rc<dyn Curve>,
) {
RenderViewportBase::show_on_screen(self, app, descendant, rect, duration, curve)
}
}
impl RenderBox for RenderShrinkWrappingViewport {
crate::render_box_accessors!();
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
if !child.parent_data_is::<SliverLogicalContainerParentData>(app) {
child.set_parent_data(app, SliverLogicalContainerParentData::new());
}
}
fn apply_paint_transform(
self: RenderHandle<Self>,
app: &App,
child: AnyRenderObject,
transform: &mut Matrix4,
) {
let offset = self.paint_offset_of(
app,
child
.as_sliver()
.expect("a viewport's children are slivers"),
);
translate(transform, offset.dx(), offset.dy());
}
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut BoxHitTestResult<'_>,
position: Offset,
) -> bool {
RenderViewportBase::hit_test_children(self, app, result, position)
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderViewportBase::compute_min_intrinsic_width(self, app, height)
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderViewportBase::compute_max_intrinsic_width(self, app, height)
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderViewportBase::compute_min_intrinsic_height(self, app, width)
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderViewportBase::compute_max_intrinsic_height(self, app, width)
}
}
#[cfg(test)]
mod tests {
use inset_foundation::AppCell;
use std::cell::Cell;
use std::rc::Rc;
use inset_foundation::Handle;
use inset_gestures::HitTestResult;
use super::*;
use crate::box_::BoxConstraints;
use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
use crate::proxy_box::RenderConstrainedBox;
use crate::sliver::{RenderSliver, RenderSliverToBoxAdapter};
use crate::viewport_offset::{FixedViewportOffset, ViewportOffset};
struct TestSliver {
render_object: RenderObjectData,
render_sliver: crate::sliver::RenderSliverData,
extent: f64,
paint_offsets: Rc<Cell<Vec<Offset>>>,
}
impl RenderObject for TestSliver {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let constraints = self.constraints(app);
let extent = self.get(app).extent;
let painted = self.calculate_paint_offset(app, constraints, 0.0, extent);
let cached = self.calculate_cache_offset(app, constraints, 0.0, extent);
self.set_geometry(
app,
SliverGeometry::builder()
.scroll_extent(extent)
.paint_extent(painted)
.cache_extent(cached)
.max_paint_extent(extent)
.build(),
);
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
_context: &mut PaintingContext,
offset: Offset,
) {
let recorded = self.get(app).paint_offsets.clone();
let mut offsets = recorded.take();
offsets.push(offset);
recorded.set(offsets);
}
}
impl RenderSliver for TestSliver {
crate::render_sliver_accessors!();
fn hit_test_self(
self: RenderHandle<Self>,
_app: &App,
_main_axis_position: f64,
_cross_axis_position: f64,
) -> bool {
let _ = self;
true
}
}
fn test_sliver(app: &mut App, extent: f64) -> (AnyRenderSliver, Rc<Cell<Vec<Offset>>>) {
let paint_offsets = Rc::new(Cell::new(Vec::new()));
let sliver = RenderHandle::new_sliver(
app,
TestSliver {
render_object: RenderObjectData::new(),
render_sliver: crate::sliver::RenderSliverData::new(),
extent,
paint_offsets: paint_offsets.clone(),
},
)
.as_sliver();
(sliver, paint_offsets)
}
fn first_frame(
app: &mut App,
root: crate::box_::AnyRenderBox,
constraints: BoxConstraints,
) -> Handle<PipelineOwner> {
let owner = PipelineOwner::new(app, None, None);
owner.set_root_node(app, Some(root.as_object()));
root.layout(app, constraints, false);
let paint_root = OffsetLayer::new(app, Offset::ZERO);
paint_root.as_layer().attach(app, root.as_object().id());
root.as_object()
.schedule_initial_paint(app, paint_root.as_container_layer());
owner.flush_compositing_bits(app);
owner.flush_paint(app);
owner
}
fn vertical_viewport(
app: &mut App,
offset: Handle<FixedViewportOffset>,
children: Vec<AnyRenderSliver>,
) -> RenderHandle<RenderViewport> {
RenderViewport::new(
app,
AxisDirection::Right,
offset.as_viewport_offset(),
Some(children),
None,
)
}
#[test]
#[should_panic(expected = "does not support returning intrinsic dimensions")]
fn a_viewport_has_no_intrinsic_dimensions() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![]);
viewport.as_box().get_min_intrinsic_width(&mut app, 0.0);
}
#[test]
fn a_viewport_reports_zero_intrinsics_while_checking_them() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![]);
crate::object::set_debug_checking_intrinsics(true);
let width = viewport.as_box().get_min_intrinsic_width(&mut app, 0.0);
let height = viewport.as_box().get_max_intrinsic_height(&mut app, 0.0);
crate::object::set_debug_checking_intrinsics(false);
assert_eq!((width, height), (0.0, 0.0));
}
#[test]
fn a_viewport_dry_layout_is_the_biggest_size() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (sliver, _paints) = test_sliver(&mut app, 100.0);
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![sliver]);
let constraints = BoxConstraints::new().max_width(200.0).max_height(400.0);
let dry = viewport.as_box().get_dry_layout(&mut app, constraints);
first_frame(&mut app, viewport.as_box(), constraints);
assert_eq!(dry, Size::new(200.0, 400.0));
assert_eq!(dry, viewport.size(&app));
}
#[test]
fn a_viewport_lays_its_slivers_out_in_order() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (first, first_paints) = test_sliver(&mut app, 100.0);
let (second, second_paints) = test_sliver(&mut app, 300.0);
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
assert_eq!(viewport.size(&app), Size::new(200.0, 400.0));
assert_eq!(viewport.center(&app), Some(first));
assert_eq!(first.geometry(&app).paint_extent, 100.0);
assert_eq!(second.geometry(&app).paint_extent, 300.0);
assert_eq!(viewport.paint_offset_of(&app, first), Offset::ZERO);
assert_eq!(
viewport.paint_offset_of(&app, second),
Offset::new(0.0, 100.0)
);
assert_eq!(first_paints.take(), vec![Offset::ZERO]);
assert_eq!(second_paints.take(), vec![Offset::new(0.0, 100.0)]);
}
#[test]
fn a_scrolled_viewport_moves_its_slivers() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (first, _) = test_sliver(&mut app, 100.0);
let (second, _) = test_sliver(&mut app, 300.0);
let offset = FixedViewportOffset::new(&mut app, 60.0);
let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
assert_eq!(first.constraints(&app).scroll_offset, 60.0);
assert_eq!(first.geometry(&app).paint_extent, 40.0);
assert_eq!(viewport.paint_offset_of(&app, first), Offset::ZERO);
assert_eq!(
viewport.paint_offset_of(&app, second),
Offset::new(0.0, 40.0)
);
assert_eq!(viewport.scroll_offset_of(&app, second, 0.0), 100.0);
}
#[test]
fn a_viewport_hit_tests_the_sliver_under_the_position() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (first, _) = test_sliver(&mut app, 100.0);
let (second, _) = test_sliver(&mut app, 300.0);
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
let mut result = HitTestResult::new();
let hit = viewport.hit_test(
&mut app,
&mut BoxHitTestResult::wrap(&mut result),
Offset::new(20.0, 150.0),
);
assert!(hit);
assert_eq!(result.path().len(), 2);
assert_eq!(
viewport.compute_child_main_axis_position(&app, second, 150.0),
50.0
);
}
#[test]
fn the_cache_extent_widens_the_laid_out_region() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (sliver, _) = test_sliver(&mut app, 1000.0);
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![sliver]);
viewport.set_scroll_cache_extent(&mut app, Some(ScrollCacheExtent::Pixels(100.0)));
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
assert_eq!(sliver.constraints(&app).remaining_cache_extent, 500.0);
assert_eq!(sliver.geometry(&app).cache_extent, 500.0);
assert_eq!(sliver.geometry(&app).paint_extent, 400.0);
viewport.set_scroll_cache_extent(&mut app, Some(ScrollCacheExtent::Viewport(0.5)));
viewport.as_box().layout(
&mut app,
BoxConstraints::tight(Size::new(200.0, 400.0)),
false,
);
assert_eq!(sliver.constraints(&app).remaining_cache_extent, 600.0);
}
#[test]
fn a_shrink_wrapping_viewport_sizes_itself_to_its_slivers() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (first, _) = test_sliver(&mut app, 60.0);
let (second, _) = test_sliver(&mut app, 90.0);
let offset = FixedViewportOffset::zero(&mut app);
let viewport = RenderShrinkWrappingViewport::new(
&mut app,
AxisDirection::Right,
offset.as_viewport_offset(),
Some(vec![first, second]),
);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::new()
.max_width(200.0)
.min_width(200.0)
.max_height(400.0),
);
assert_eq!(viewport.size(&app), Size::new(200.0, 150.0));
assert_eq!(
viewport.paint_offset_of(&app, second),
Offset::new(0.0, 60.0)
);
assert_eq!(viewport.index_of_first_child(&app), 0);
assert_eq!(viewport.label_for_child(1), "child 1");
}
#[test]
fn a_viewport_hosts_a_box_through_the_adapter() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let child =
RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(80.0)), None);
let adapter = RenderSliverToBoxAdapter::new(&mut app, Some(child.as_box()));
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![adapter.as_sliver()]);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
assert_eq!(child.size(&app), Size::new(200.0, 80.0));
assert_eq!(adapter.geometry(&app).scroll_extent, 80.0);
assert_eq!(
child.as_box().local_to_global(&app, Offset::ZERO, None),
Offset::ZERO
);
}
#[test]
fn maybe_of_finds_the_enclosing_viewport() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let child =
RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(80.0)), None);
let adapter = RenderSliverToBoxAdapter::new(&mut app, Some(child.as_box()));
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![adapter.as_sliver()]);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
let found = AnyRenderAbstractViewport::maybe_of(&app, Some(child.as_object()));
assert_eq!(found, Some(viewport.as_abstract_viewport()));
assert!(AnyRenderAbstractViewport::maybe_of(&app, None).is_none());
}
#[test]
fn get_offset_to_reveal_reports_the_leading_edge_offset() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let leading =
RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(100.0)), None);
let first = RenderSliverToBoxAdapter::new(&mut app, Some(leading.as_box()));
let target =
RenderConstrainedBox::new(&mut app, BoxConstraints::tight_for(None, Some(50.0)), None);
let second = RenderSliverToBoxAdapter::new(&mut app, Some(target.as_box()));
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(
&mut app,
offset,
vec![first.as_sliver(), second.as_sliver()],
);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
let revealed = RenderAbstractViewport::get_offset_to_reveal(
viewport,
&app,
target.as_object(),
0.0,
None,
None,
);
assert_eq!(revealed.offset, 100.0);
assert_eq!(revealed.rect.top, 0.0);
assert_eq!(revealed.rect.bottom, 50.0);
}
#[test]
fn clamp_offset_picks_the_nearer_edge() {
let leading = RevealedOffset::new(100.0, Rect::ZERO);
let trailing = RevealedOffset::new(20.0, Rect::ZERO);
assert_eq!(
RevealedOffset::clamp_offset(leading, trailing, 200.0).map(|offset| offset.offset),
Some(100.0)
);
assert_eq!(
RevealedOffset::clamp_offset(leading, trailing, 0.0).map(|offset| offset.offset),
Some(20.0)
);
assert!(RevealedOffset::clamp_offset(leading, trailing, 50.0).is_none());
}
#[test]
fn the_paint_order_reverses_the_hit_test_order() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (first, _) = test_sliver(&mut app, 100.0);
let (second, _) = test_sliver(&mut app, 300.0);
let offset = FixedViewportOffset::zero(&mut app);
let viewport = vertical_viewport(&mut app, offset, vec![first, second]);
first_frame(
&mut app,
viewport.as_box(),
BoxConstraints::tight(Size::new(200.0, 400.0)),
);
assert_eq!(viewport.children_in_paint_order(&app), vec![second, first]);
assert_eq!(
viewport.children_in_hit_test_order(&app),
vec![first, second]
);
viewport.set_paint_order(&mut app, SliverPaintOrder::LastIsTop);
assert_eq!(viewport.children_in_paint_order(&app), vec![first, second]);
assert_eq!(
viewport.children_in_hit_test_order(&app),
vec![second, first]
);
}
}