use std::any::{Any, TypeId};
use std::fmt::{self, Debug};
use inset_embedder::{Clip, Offset, Rect, Size, TextBaseline, TextDirection, lerp_double};
use inset_foundation::{App, Handle};
use inset_painting::{Alignment, AlignmentGeometry};
use crate::box_::{
AnyRenderBox, BaselineOffset, BoxConstraints, BoxHitTestResult, BoxParentData,
ContainerBoxParentData, RenderBox, RenderBoxContainerDefaultsMixin, RenderBoxData,
};
use crate::layer::{ClipRectLayer, LayerHandle};
use crate::layout_helper::ChildLayoutHelper;
use crate::object::{
AnyRenderObject, ContainerParentData, ContainerParentDataMixin, ContainerRenderObjectData,
ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject, RenderObjectData,
};
use crate::painting_context::PaintingContext;
use crate::pipeline_owner::PipelineOwner;
#[derive(Clone, Copy, PartialEq)]
pub struct RelativeRect {
pub left: f64,
pub top: f64,
pub right: f64,
pub bottom: f64,
}
impl RelativeRect {
pub const fn from_ltrb(left: f64, top: f64, right: f64, bottom: f64) -> RelativeRect {
RelativeRect {
left,
top,
right,
bottom,
}
}
pub fn from_size(rect: Rect, container: Size) -> RelativeRect {
RelativeRect {
left: rect.left,
top: rect.top,
right: container.width() - rect.right,
bottom: container.height() - rect.bottom,
}
}
pub fn from_rect(rect: Rect, container: Rect) -> RelativeRect {
RelativeRect {
left: rect.left - container.left,
top: rect.top - container.top,
right: container.right - rect.right,
bottom: container.bottom - rect.bottom,
}
}
pub fn from_directional(
text_direction: TextDirection,
start: f64,
top: f64,
end: f64,
bottom: f64,
) -> RelativeRect {
let (left, right) = match text_direction {
TextDirection::Rtl => (end, start),
TextDirection::Ltr => (start, end),
};
RelativeRect::from_ltrb(left, top, right, bottom)
}
pub const FILL: RelativeRect = RelativeRect::from_ltrb(0.0, 0.0, 0.0, 0.0);
pub fn has_insets(&self) -> bool {
self.left > 0.0 || self.top > 0.0 || self.right > 0.0 || self.bottom > 0.0
}
pub fn shift(&self, offset: Offset) -> RelativeRect {
RelativeRect::from_ltrb(
self.left + offset.dx(),
self.top + offset.dy(),
self.right - offset.dx(),
self.bottom - offset.dy(),
)
}
pub fn inflate(&self, delta: f64) -> RelativeRect {
RelativeRect::from_ltrb(
self.left - delta,
self.top - delta,
self.right - delta,
self.bottom - delta,
)
}
pub fn deflate(&self, delta: f64) -> RelativeRect {
self.inflate(-delta)
}
pub fn intersect(&self, other: RelativeRect) -> RelativeRect {
RelativeRect::from_ltrb(
self.left.max(other.left),
self.top.max(other.top),
self.right.max(other.right),
self.bottom.max(other.bottom),
)
}
pub fn to_rect(&self, container: Rect) -> Rect {
Rect::from_ltrb(
self.left,
self.top,
container.width() - self.right,
container.height() - self.bottom,
)
}
pub fn to_size(&self, container: Size) -> Size {
Size::new(
container.width() - self.left - self.right,
container.height() - self.top - self.bottom,
)
}
pub fn lerp(a: Option<RelativeRect>, b: Option<RelativeRect>, t: f64) -> Option<RelativeRect> {
match (a, b) {
(None, None) => None,
(None, Some(b)) => Some(RelativeRect::from_ltrb(
b.left * t,
b.top * t,
b.right * t,
b.bottom * t,
)),
(Some(a), None) => {
let k = 1.0 - t;
Some(RelativeRect::from_ltrb(
a.left * k,
a.top * k,
a.right * k,
a.bottom * k,
))
}
(Some(a), Some(b)) => Some(RelativeRect::from_ltrb(
lerp_double(Some(a.left), Some(b.left), t).unwrap(),
lerp_double(Some(a.top), Some(b.top), t).unwrap(),
lerp_double(Some(a.right), Some(b.right), t).unwrap(),
lerp_double(Some(a.bottom), Some(b.bottom), t).unwrap(),
)),
}
}
}
impl Debug for RelativeRect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"RelativeRect::from_ltrb({:.1}, {:.1}, {:.1}, {:.1})",
self.left, self.top, self.right, self.bottom
)
}
}
#[derive(Debug)]
pub struct StackParentData {
box_parent_data: BoxParentData,
container_parent_data: ContainerParentData<AnyRenderBox>,
pub top: Option<f64>,
pub right: Option<f64>,
pub bottom: Option<f64>,
pub left: Option<f64>,
pub width: Option<f64>,
pub height: Option<f64>,
}
impl StackParentData {
pub const fn new() -> StackParentData {
StackParentData {
box_parent_data: BoxParentData::new(),
container_parent_data: ContainerParentData::new(),
top: None,
right: None,
bottom: None,
left: None,
width: None,
height: None,
}
}
pub fn rect(&self) -> RelativeRect {
RelativeRect::from_ltrb(
self.left.expect("left is set"),
self.top.expect("top is set"),
self.right.expect("right is set"),
self.bottom.expect("bottom is set"),
)
}
pub fn set_rect(&mut self, value: RelativeRect) {
self.top = Some(value.top);
self.right = Some(value.right);
self.bottom = Some(value.bottom);
self.left = Some(value.left);
}
pub fn is_positioned(&self) -> bool {
self.top.is_some()
|| self.right.is_some()
|| self.bottom.is_some()
|| self.left.is_some()
|| self.width.is_some()
|| self.height.is_some()
}
pub fn positioned_child_constraints(&self, stack_size: Size) -> BoxConstraints {
debug_assert!(self.is_positioned());
let width = match (self.left, self.right) {
(Some(left), Some(right)) => Some(stack_size.width() - right - left),
_ => self.width,
};
let height = match (self.top, self.bottom) {
(Some(top), Some(bottom)) => Some(stack_size.height() - bottom - top),
_ => self.height,
};
debug_assert!(height.is_none_or(|height| !height.is_nan()));
debug_assert!(width.is_none_or(|width| !width.is_nan()));
BoxConstraints::tight_for(
width.map(|width| width.max(0.0)),
height.map(|height| height.max(0.0)),
)
}
}
impl Default for StackParentData {
fn default() -> StackParentData {
StackParentData::new()
}
}
impl ParentData for StackParentData {
fn detach(&mut self) {
ContainerParentDataMixin::detach(self);
}
fn provide(&self, id: TypeId) -> Option<&dyn Any> {
if id == TypeId::of::<StackParentData>() {
return Some(self);
}
self.box_parent_data.provide(id)
}
fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
if id == TypeId::of::<StackParentData>() {
return Some(self);
}
self.box_parent_data.provide_mut(id)
}
}
impl ContainerParentDataMixin for StackParentData {
type ChildType = AnyRenderBox;
fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
&self.container_parent_data
}
fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
&mut self.container_parent_data
}
}
impl ContainerBoxParentData for StackParentData {
fn box_parent_data(&self) -> &BoxParentData {
&self.box_parent_data
}
fn box_parent_data_mut(&mut self) -> &mut BoxParentData {
&mut self.box_parent_data
}
}
impl fmt::Display for StackParentData {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut values = Vec::new();
for (name, value) in [
("top", self.top),
("right", self.right),
("bottom", self.bottom),
("left", self.left),
("width", self.width),
("height", self.height),
] {
if let Some(value) = value {
values.push(format!("{name}={value}"));
}
}
if values.is_empty() {
values.push("not positioned".to_string());
}
values.push(self.box_parent_data.to_string());
f.write_str(&values.join("; "))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StackFit {
Loose,
Expand,
Passthrough,
}
pub struct RenderStackData {
has_visual_overflow: bool,
alignment: AlignmentGeometry,
text_direction: Option<TextDirection>,
fit: StackFit,
clip_behavior: Clip,
resolved_alignment_cache: Option<Alignment>,
clip_rect_layer: LayerHandle<Handle<ClipRectLayer>>,
}
impl RenderStackData {
pub fn new() -> RenderStackData {
RenderStackData {
has_visual_overflow: false,
alignment: AlignmentGeometry::TOP_START,
text_direction: None,
fit: StackFit::Loose,
clip_behavior: Clip::HardEdge,
resolved_alignment_cache: None,
clip_rect_layer: LayerHandle::new(),
}
}
}
impl Default for RenderStackData {
fn default() -> RenderStackData {
RenderStackData::new()
}
}
pub fn get_intrinsic_dimension(
app: &mut App,
first_child: Option<AnyRenderBox>,
mut main_child_size_getter: impl FnMut(&mut App, AnyRenderBox) -> f64,
) -> f64 {
let mut extent: f64 = 0.0;
let mut child = first_child;
while let Some(current) = child {
let child_parent_data = current.as_object().parent_data_of::<StackParentData>(app);
let (is_positioned, next_sibling) = (
child_parent_data.is_positioned(),
child_parent_data.next_sibling(),
);
if !is_positioned {
extent = extent.max(main_child_size_getter(app, current));
}
child = next_sibling;
}
extent
}
pub fn layout_positioned_child(
app: &mut App,
child: AnyRenderBox,
size: Size,
alignment: Alignment,
) -> bool {
let child_constraints = {
let child_parent_data = child.as_object().parent_data_of::<StackParentData>(app);
debug_assert!(child_parent_data.is_positioned());
child_parent_data.positioned_child_constraints(size)
};
child.layout(app, child_constraints, true);
let child_size = child.size(app);
let child_parent_data = child.as_object().parent_data_of::<StackParentData>(app);
let x = match (child_parent_data.left, child_parent_data.right) {
(Some(left), _) => left,
(None, Some(right)) => size.width() - right - child_size.width(),
(None, None) => alignment.along_offset(size - child_size).dx(),
};
let y = match (child_parent_data.top, child_parent_data.bottom) {
(Some(top), _) => top,
(None, Some(bottom)) => size.height() - bottom - child_size.height(),
(None, None) => alignment.along_offset(size - child_size).dy(),
};
child
.parent_data_of_mut::<StackParentData>(app)
.set_offset(Offset::new(x, y));
x < 0.0
|| x + child_size.width() > size.width()
|| y < 0.0
|| y + child_size.height() > size.height()
}
fn baseline_for_child(
app: &mut App,
child: AnyRenderBox,
stack_size: Size,
non_positioned_child_constraints: BoxConstraints,
alignment: Alignment,
baseline: TextBaseline,
) -> Option<f64> {
let child_parent_data = child.as_object().parent_data_of::<StackParentData>(app);
let child_constraints = if child_parent_data.is_positioned() {
child_parent_data.positioned_child_constraints(stack_size)
} else {
non_positioned_child_constraints
};
let (top, bottom) = (child_parent_data.top, child_parent_data.bottom);
let baseline_offset = child.get_dry_baseline(app, child_constraints, baseline)?;
let y = match (top, bottom) {
(Some(top), _) => top,
(None, Some(bottom)) => {
stack_size.height() - bottom - child.get_dry_layout(app, child_constraints).height()
}
(None, None) => alignment
.along_offset(stack_size - child.get_dry_layout(app, child_constraints))
.dy(),
};
Some(baseline_offset + y)
}
pub trait RenderStackBase:
RenderBoxContainerDefaultsMixin<ParentDataType = StackParentData>
{
fn stack_data(self: RenderHandle<Self>, app: &App) -> &RenderStackData;
fn stack_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderStackData;
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
if !child.parent_data_is::<StackParentData>(app) {
child.set_parent_data(app, StackParentData::new());
}
}
fn resolved_alignment(self: RenderHandle<Self>, app: &mut App) -> Alignment {
if let Some(cached) = self.stack_data(app).resolved_alignment_cache {
return cached;
}
let resolved = self.alignment(app).resolve(self.text_direction(app));
self.stack_data_mut(app).resolved_alignment_cache = Some(resolved);
resolved
}
fn mark_need_resolution(self: RenderHandle<Self>, app: &mut App) {
self.stack_data_mut(app).resolved_alignment_cache = None;
self.mark_needs_layout(app);
}
fn alignment(self: RenderHandle<Self>, app: &App) -> AlignmentGeometry {
self.stack_data(app).alignment
}
fn set_alignment(self: RenderHandle<Self>, app: &mut App, value: AlignmentGeometry) {
if self.stack_data(app).alignment == value {
return;
}
self.stack_data_mut(app).alignment = value;
self.mark_need_resolution(app);
}
fn text_direction(self: RenderHandle<Self>, app: &App) -> Option<TextDirection> {
self.stack_data(app).text_direction
}
fn set_text_direction(self: RenderHandle<Self>, app: &mut App, value: Option<TextDirection>) {
if self.stack_data(app).text_direction == value {
return;
}
self.stack_data_mut(app).text_direction = value;
self.mark_need_resolution(app);
}
fn fit(self: RenderHandle<Self>, app: &App) -> StackFit {
self.stack_data(app).fit
}
fn set_fit(self: RenderHandle<Self>, app: &mut App, value: StackFit) {
if self.stack_data(app).fit != value {
self.stack_data_mut(app).fit = value;
self.mark_needs_layout(app);
}
}
fn clip_behavior(self: RenderHandle<Self>, app: &App) -> Clip {
self.stack_data(app).clip_behavior
}
fn set_clip_behavior(self: RenderHandle<Self>, app: &mut App, value: Clip) {
if value != self.stack_data(app).clip_behavior {
self.stack_data_mut(app).clip_behavior = value;
self.mark_needs_paint(app);
}
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
let first_child = self.first_child(app);
get_intrinsic_dimension(app, first_child, |app, child| {
child.get_min_intrinsic_width(app, height)
})
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
let first_child = self.first_child(app);
get_intrinsic_dimension(app, first_child, |app, child| {
child.get_max_intrinsic_width(app, height)
})
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
let first_child = self.first_child(app);
get_intrinsic_dimension(app, first_child, |app, child| {
child.get_min_intrinsic_height(app, width)
})
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
let first_child = self.first_child(app);
get_intrinsic_dimension(app, first_child, |app, child| {
child.get_max_intrinsic_height(app, width)
})
}
fn compute_distance_to_actual_baseline(
self: RenderHandle<Self>,
app: &mut App,
baseline: TextBaseline,
) -> Option<f64> {
self.default_compute_distance_to_highest_actual_baseline(app, baseline)
}
fn compute_dry_baseline(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
baseline: TextBaseline,
) -> Option<f64> {
let non_positioned_child_constraints = match self.fit(app) {
StackFit::Loose => constraints.loosen(),
StackFit::Expand => BoxConstraints::tight(constraints.biggest()),
StackFit::Passthrough => constraints,
};
let alignment = self.resolved_alignment(app);
let size = self.as_box().get_dry_layout(app, constraints);
let mut baseline_offset = BaselineOffset::NO_BASELINE;
let mut child = self.first_child(app);
while let Some(current) = child {
baseline_offset = baseline_offset.min_of(BaselineOffset(baseline_for_child(
app,
current,
size,
non_positioned_child_constraints,
alignment,
baseline,
)));
child = self.child_after(app, current);
}
baseline_offset.offset()
}
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
self.compute_size(app, constraints, ChildLayoutHelper::dry_layout_child)
}
fn compute_size(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
mut layout_child: impl FnMut(&mut App, AnyRenderBox, BoxConstraints) -> Size,
) -> Size {
let mut has_non_positioned_children = false;
if self.child_count(app) == 0 {
return if constraints.biggest().is_finite() {
constraints.biggest()
} else {
constraints.smallest()
};
}
let mut width = constraints.min_width;
let mut height = constraints.min_height;
let non_positioned_constraints = match self.fit(app) {
StackFit::Loose => constraints.loosen(),
StackFit::Expand => BoxConstraints::tight(constraints.biggest()),
StackFit::Passthrough => constraints,
};
let mut child = self.first_child(app);
while let Some(current) = child {
let is_positioned = current
.as_object()
.parent_data_of::<StackParentData>(app)
.is_positioned();
if !is_positioned {
has_non_positioned_children = true;
let child_size = layout_child(app, current, non_positioned_constraints);
width = width.max(child_size.width());
height = height.max(child_size.height());
}
child = self.child_after(app, current);
}
let size = if has_non_positioned_children {
let size = Size::new(width, height);
debug_assert_eq!(size.width(), constraints.constrain_width(width));
debug_assert_eq!(size.height(), constraints.constrain_height(height));
size
} else {
constraints.biggest()
};
debug_assert!(
size.is_finite(),
"A Stack requires bounded constraints from its parent. This error commonly occurs \
when a Stack is placed inside a widget like Column, ListView, or other widgets that \
do not constrain their children. To fix this, wrap the Stack in a widget that \
provides finite height and width constraints, such as a SizedBox or ConstrainedBox. \
Use Expanded only if the parent is a Flex widget like Row or Column."
);
size
}
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let constraints = self.constraints(app);
self.stack_data_mut(app).has_visual_overflow = false;
let size = self.compute_size(app, constraints, ChildLayoutHelper::layout_child);
self.set_size(app, size);
let resolved_alignment = self.resolved_alignment(app);
let mut child = self.first_child(app);
while let Some(current) = child {
let is_positioned = current
.as_object()
.parent_data_of::<StackParentData>(app)
.is_positioned();
if !is_positioned {
let offset = resolved_alignment.along_offset(size - current.size(app));
current
.parent_data_of_mut::<StackParentData>(app)
.set_offset(offset);
} else {
let overflowed = layout_positioned_child(app, current, size, resolved_alignment);
self.stack_data_mut(app).has_visual_overflow |= overflowed;
}
child = self.child_after(app, current);
}
}
fn paint_stack(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
self.default_paint(app, context, offset);
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
let clip_behavior = self.clip_behavior(app);
if clip_behavior != Clip::None && self.stack_data(app).has_visual_overflow {
let size = self.size(app);
let old = self.stack_data(app).clip_rect_layer.layer();
let layer = context.push_clip_rect(
app,
self.as_object().needs_compositing(app),
offset,
Offset::ZERO & size,
|app, context, offset| self.paint_stack(app, context, offset),
clip_behavior,
old,
);
LayerHandle::set_layer(
app,
|app| &mut self.stack_data_mut(app).clip_rect_layer,
layer,
);
} else {
LayerHandle::set_layer(
app,
|app| &mut self.stack_data_mut(app).clip_rect_layer,
None,
);
self.paint_stack(app, context, offset);
}
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
LayerHandle::set_layer(
app,
|app| &mut self.stack_data_mut(app).clip_rect_layer,
None,
);
crate::object::RenderObjectBase::dispose(self, app);
}
}
pub struct RenderStack {
render_object: RenderObjectData,
render_box: RenderBoxData,
container: ContainerRenderObjectData<AnyRenderBox>,
stack: RenderStackData,
}
impl RenderStack {
pub fn new(app: &mut App) -> RenderHandle<RenderStack> {
RenderHandle::new_box(
app,
RenderStack {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
container: ContainerRenderObjectData::new(),
stack: RenderStackData::new(),
},
)
}
}
impl ContainerRenderObjectMixin for RenderStack {
type ChildType = AnyRenderBox;
type ParentDataType = StackParentData;
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
}
}
impl RenderBoxContainerDefaultsMixin for RenderStack {}
impl RenderStackBase for RenderStack {
fn stack_data(self: RenderHandle<Self>, app: &App) -> &RenderStackData {
&self.get(app).stack
}
fn stack_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderStackData {
&mut self.get_mut(app).stack
}
}
impl RenderObject for RenderStack {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
RenderStackBase::perform_layout(self, app)
}
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: Handle<PipelineOwner>) {
ContainerRenderObjectMixin::did_attach(self, app, owner)
}
fn did_detach(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::did_detach(self, app)
}
fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::redepth_children(self, app)
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
RenderStackBase::paint(self, app, context, offset)
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
RenderStackBase::dispose(self, app)
}
}
impl RenderBox for RenderStack {
crate::render_box_accessors!();
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
RenderStackBase::setup_parent_data(self, app, child)
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderStackBase::compute_min_intrinsic_width(self, app, height)
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderStackBase::compute_max_intrinsic_width(self, app, height)
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderStackBase::compute_min_intrinsic_height(self, app, width)
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderStackBase::compute_max_intrinsic_height(self, app, width)
}
fn compute_distance_to_actual_baseline(
self: RenderHandle<Self>,
app: &mut App,
baseline: TextBaseline,
) -> Option<f64> {
RenderStackBase::compute_distance_to_actual_baseline(self, app, baseline)
}
fn compute_dry_baseline(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
baseline: TextBaseline,
) -> Option<f64> {
RenderStackBase::compute_dry_baseline(self, app, constraints, baseline)
}
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
RenderStackBase::compute_dry_layout(self, app, constraints)
}
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut BoxHitTestResult<'_>,
position: Offset,
) -> bool {
self.default_hit_test_children(app, result, position)
}
}
pub struct RenderIndexedStack {
render_object: RenderObjectData,
render_box: RenderBoxData,
container: ContainerRenderObjectData<AnyRenderBox>,
stack: RenderStackData,
index: Option<i32>,
}
impl RenderIndexedStack {
pub fn new(app: &mut App) -> RenderHandle<RenderIndexedStack> {
RenderHandle::new_box(
app,
RenderIndexedStack {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
container: ContainerRenderObjectData::new(),
stack: RenderStackData::new(),
index: Some(0),
},
)
}
pub fn index(self: RenderHandle<Self>, app: &App) -> Option<i32> {
self.get(app).index
}
pub fn set_index(self: RenderHandle<Self>, app: &mut App, value: Option<i32>) {
if self.get(app).index != value {
self.get_mut(app).index = value;
self.mark_needs_layout(app);
}
}
fn child_at_index(self: RenderHandle<Self>, app: &App) -> Option<AnyRenderBox> {
let index = self.index(app)?;
let mut child = self.first_child(app);
let mut i = 0;
while i < index
&& let Some(current) = child
{
child = self.child_after(app, current);
i += 1;
}
debug_assert!(self.first_child(app).is_none() || child.is_some());
child
}
}
impl ContainerRenderObjectMixin for RenderIndexedStack {
type ChildType = AnyRenderBox;
type ParentDataType = StackParentData;
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
}
}
impl RenderBoxContainerDefaultsMixin for RenderIndexedStack {}
impl RenderStackBase for RenderIndexedStack {
fn stack_data(self: RenderHandle<Self>, app: &App) -> &RenderStackData {
&self.get(app).stack
}
fn stack_data_mut(self: RenderHandle<Self>, app: &mut App) -> &mut RenderStackData {
&mut self.get_mut(app).stack
}
fn paint_stack(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
let Some(displayed_child) = self.child_at_index(app) else {
return;
};
let child_offset = displayed_child
.as_object()
.parent_data_of::<StackParentData>(app)
.offset();
context.paint_child(app, displayed_child.as_object(), child_offset + offset);
}
}
impl RenderObject for RenderIndexedStack {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
RenderStackBase::perform_layout(self, app)
}
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: Handle<PipelineOwner>) {
ContainerRenderObjectMixin::did_attach(self, app, owner)
}
fn did_detach(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::did_detach(self, app)
}
fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
ContainerRenderObjectMixin::redepth_children(self, app)
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
context: &mut PaintingContext,
offset: Offset,
) {
RenderStackBase::paint(self, app, context, offset)
}
fn dispose(self: RenderHandle<Self>, app: &mut App) {
RenderStackBase::dispose(self, app)
}
}
impl RenderBox for RenderIndexedStack {
crate::render_box_accessors!();
fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
RenderStackBase::setup_parent_data(self, app, child)
}
fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderStackBase::compute_min_intrinsic_width(self, app, height)
}
fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, height: f64) -> f64 {
RenderStackBase::compute_max_intrinsic_width(self, app, height)
}
fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderStackBase::compute_min_intrinsic_height(self, app, width)
}
fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
RenderStackBase::compute_max_intrinsic_height(self, app, width)
}
fn compute_distance_to_actual_baseline(
self: RenderHandle<Self>,
app: &mut App,
baseline: TextBaseline,
) -> Option<f64> {
let displayed_child = self.child_at_index(app)?;
let child_offset = displayed_child
.as_object()
.parent_data_of::<StackParentData>(app)
.offset();
let offset = BaselineOffset(displayed_child.get_distance_to_actual_baseline(app, baseline))
+ child_offset.dy();
offset.offset()
}
fn compute_dry_baseline(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
baseline: TextBaseline,
) -> Option<f64> {
let displayed_child = self.child_at_index(app)?;
let non_positioned_child_constraints = match self.fit(app) {
StackFit::Loose => constraints.loosen(),
StackFit::Expand => BoxConstraints::tight(constraints.biggest()),
StackFit::Passthrough => constraints,
};
let alignment = self.resolved_alignment(app);
let size = self.as_box().get_dry_layout(app, constraints);
baseline_for_child(
app,
displayed_child,
size,
non_positioned_child_constraints,
alignment,
baseline,
)
}
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
RenderStackBase::compute_dry_layout(self, app, constraints)
}
fn hit_test_children(
self: RenderHandle<Self>,
app: &mut App,
result: &mut BoxHitTestResult<'_>,
position: Offset,
) -> bool {
let Some(displayed_child) = self.child_at_index(app) else {
return false;
};
let child_offset = displayed_child
.as_object()
.parent_data_of::<StackParentData>(app)
.offset();
result.add_with_paint_offset(Some(child_offset), position, |result, transformed| {
debug_assert_eq!(transformed, position - child_offset);
displayed_child.hit_test(app, result, transformed)
})
}
}
#[cfg(test)]
mod tests {
use inset_foundation::AppCell;
use std::cell::Cell;
use std::rc::Rc;
use super::*;
use crate::flex::{MainAxisSize, RenderFlex};
use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer, PictureLayer};
use crate::pipeline_owner::PipelineOwner;
use crate::proxy_box::{RenderConstrainedBox, RenderRepaintBoundary};
use crate::shifted_box::RenderPadding;
use inset_embedder::valo::Op;
use inset_painting::EdgeInsetsGeometry;
struct SizedTestBox {
render_object: RenderObjectData,
render_box: RenderBoxData,
preferred_size: Size,
paints: Rc<Cell<u32>>,
}
impl RenderObject for SizedTestBox {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let preferred_size = self.get(app).preferred_size;
let size = self.constraints(app).constrain(preferred_size);
self.set_size(app, size);
}
fn paint(
self: RenderHandle<Self>,
app: &mut App,
_context: &mut PaintingContext,
_offset: Offset,
) {
let paints = self.get(app).paints.clone();
paints.set(paints.get() + 1);
}
}
impl RenderBox for SizedTestBox {
crate::render_box_accessors!();
}
struct BaselineTestBox {
render_object: RenderObjectData,
render_box: RenderBoxData,
preferred_size: Size,
baseline: f64,
}
impl RenderObject for BaselineTestBox {
crate::render_object_accessors!();
fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
let preferred_size = self.get(app).preferred_size;
let size = self.constraints(app).constrain(preferred_size);
self.set_size(app, size);
}
}
impl RenderBox for BaselineTestBox {
crate::render_box_accessors!();
fn compute_dry_layout(
self: RenderHandle<Self>,
app: &mut App,
constraints: BoxConstraints,
) -> Size {
constraints.constrain(self.get(app).preferred_size)
}
fn compute_distance_to_actual_baseline(
self: RenderHandle<Self>,
app: &mut App,
_baseline: TextBaseline,
) -> Option<f64> {
Some(self.get(app).baseline)
}
fn compute_dry_baseline(
self: RenderHandle<Self>,
app: &mut App,
_constraints: BoxConstraints,
_baseline: TextBaseline,
) -> Option<f64> {
Some(self.get(app).baseline)
}
}
fn baseline_box(app: &mut App, size: Size, baseline: f64) -> AnyRenderBox {
RenderHandle::new_box(
app,
BaselineTestBox {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
preferred_size: size,
baseline,
},
)
.as_box()
}
fn dry_and_actual_baseline(
app: &mut App,
stack: AnyRenderBox,
constraints: BoxConstraints,
) -> (Option<f64>, Option<f64>) {
let dry = stack.get_dry_baseline(app, constraints, TextBaseline::Alphabetic);
stack.layout(app, constraints, true);
let actual = stack.get_distance_to_baseline(app, TextBaseline::Alphabetic, true);
(dry, actual)
}
fn sized_box(app: &mut App, size: Size) -> (AnyRenderBox, Rc<Cell<u32>>) {
let paints = Rc::new(Cell::new(0));
let box_ = RenderHandle::new_box(
app,
SizedTestBox {
render_object: RenderObjectData::new(),
render_box: RenderBoxData::new(),
preferred_size: size,
paints: paints.clone(),
},
)
.as_box();
(box_, paints)
}
fn schedule_root_paint(app: &mut App, node: AnyRenderObject) {
let root = OffsetLayer::new(app, Offset::ZERO);
root.as_layer().attach(app, node.id());
node.schedule_initial_paint(app, root.as_container_layer());
}
fn first_frame(
app: &mut App,
root: 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);
schedule_root_paint(app, root.as_object());
owner.flush_compositing_bits(app);
owner.flush_paint(app);
owner
}
fn picture_has_clip(app: &App, boundary: AnyRenderObject) -> bool {
let layer = boundary.debug_layer(app).expect("painted");
layer
.depth_first_iterate_children(app)
.into_iter()
.filter_map(|child| {
app.handle::<PictureLayer>(child.id())
.and_then(|picture| picture.picture(app).map(|p| p.ops().to_vec()))
})
.flatten()
.any(|op| matches!(op, Op::ClipPath { .. }))
}
#[test]
fn stack_intrinsics_are_the_largest_non_positioned_child() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let first =
RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(30.0, 40.0)), None);
let second =
RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(50.0, 20.0)), None);
let positioned = RenderConstrainedBox::new(
&mut app,
BoxConstraints::tight(Size::new(200.0, 200.0)),
None,
);
let stack = RenderStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.add_all(
&mut app,
Some(vec![first.as_box(), second.as_box(), positioned.as_box()]),
);
positioned
.as_box()
.parent_data_of_mut::<StackParentData>(&mut app)
.left = Some(0.0);
let box_ = stack.as_box();
assert_eq!(box_.get_min_intrinsic_width(&mut app, 0.0), 50.0);
assert_eq!(box_.get_max_intrinsic_width(&mut app, 0.0), 50.0);
assert_eq!(box_.get_min_intrinsic_height(&mut app, 0.0), 40.0);
assert_eq!(box_.get_max_intrinsic_height(&mut app, 0.0), 40.0);
}
#[test]
fn dry_layout_matches_layout_for_a_padding_in_a_flex_in_a_stack() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let leaf =
RenderConstrainedBox::new(&mut app, BoxConstraints::tight(Size::new(40.0, 20.0)), None);
let padding = RenderPadding::new(
&mut app,
EdgeInsetsGeometry::all(5.0),
None,
Some(leaf.as_box()),
);
let row = RenderFlex::new(&mut app);
row.set_text_direction(&mut app, Some(TextDirection::Ltr));
row.set_main_axis_size(&mut app, MainAxisSize::Min);
row.add_all(&mut app, Some(vec![padding.as_box()]));
let stack = RenderStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.add_all(&mut app, Some(vec![row.as_box()]));
let constraints = BoxConstraints::new().max_width(300.0).max_height(300.0);
let dry = stack.as_box().get_dry_layout(&mut app, constraints);
stack.layout(&mut app, constraints, false);
assert_eq!(dry, Size::new(50.0, 30.0));
assert_eq!(dry, stack.size(&app));
assert_eq!(row.size(&app), Size::new(50.0, 30.0));
}
#[test]
fn positioned_child_is_laid_out_by_its_rect() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (child, _) = sized_box(&mut app, Size::new(10.0, 10.0));
let stack = RenderStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.add(&mut app, child);
child
.parent_data_of_mut::<StackParentData>(&mut app)
.set_rect(RelativeRect::from_ltrb(10.0, 20.0, 30.0, 40.0));
stack.layout(
&mut app,
BoxConstraints::tight(Size::new(100.0, 100.0)),
false,
);
assert_eq!(stack.size(&app), Size::new(100.0, 100.0));
assert_eq!(child.size(&app), Size::new(60.0, 40.0));
assert_eq!(child.box_parent_data(&app).offset, Offset::new(10.0, 20.0));
}
#[test]
fn non_positioned_children_are_sized_by_the_fit() {
let cases = [
(
StackFit::Loose,
Size::new(20.0, 20.0),
Size::new(50.0, 50.0),
),
(
StackFit::Expand,
Size::new(100.0, 100.0),
Size::new(100.0, 100.0),
),
(
StackFit::Passthrough,
Size::new(50.0, 50.0),
Size::new(50.0, 50.0),
),
];
for (fit, child_size, stack_size) in cases {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (child, _) = sized_box(&mut app, Size::new(20.0, 20.0));
let stack = RenderStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.set_fit(&mut app, fit);
stack.add(&mut app, child);
stack.layout(
&mut app,
BoxConstraints {
min_width: 50.0,
max_width: 100.0,
min_height: 50.0,
max_height: 100.0,
},
false,
);
assert_eq!(child.size(&app), child_size, "{fit:?}");
assert_eq!(stack.size(&app), stack_size, "{fit:?}");
}
}
#[test]
fn indexed_stack_paints_one_child() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (first, first_paints) = sized_box(&mut app, Size::new(20.0, 20.0));
let (second, second_paints) = sized_box(&mut app, Size::new(30.0, 30.0));
let stack = RenderIndexedStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.add_all(&mut app, Some(vec![first, second]));
stack.set_index(&mut app, Some(1));
let root = RenderRepaintBoundary::new(&mut app, Some(stack.as_box()));
let owner = first_frame(
&mut app,
root.as_box(),
BoxConstraints::tight(Size::new(100.0, 100.0)),
);
assert_eq!(first.size(&app), Size::new(20.0, 20.0));
assert_eq!(second.size(&app), Size::new(30.0, 30.0));
assert_eq!(first_paints.get(), 0);
assert_eq!(second_paints.get(), 1);
stack.set_index(&mut app, Some(0));
owner.flush_layout(&mut app);
owner.flush_compositing_bits(&mut app);
owner.flush_paint(&mut app);
assert_eq!(first_paints.get(), 1);
assert_eq!(second_paints.get(), 1);
}
#[test]
fn overflowing_positioned_child_is_clipped() {
for (clip_behavior, clips) in [(Clip::None, false), (Clip::HardEdge, true)] {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let (child, _) = sized_box(&mut app, Size::new(10.0, 10.0));
let stack = RenderStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.set_clip_behavior(&mut app, clip_behavior);
stack.add(&mut app, child);
child.parent_data_of_mut::<StackParentData>(&mut app).left = Some(-10.0);
let root = RenderRepaintBoundary::new(&mut app, Some(stack.as_box()));
let owner = first_frame(
&mut app,
root.as_box(),
BoxConstraints::tight(Size::new(100.0, 100.0)),
);
let clipped = picture_has_clip(&app, root.as_object());
assert_eq!(clipped, clips, "{clip_behavior:?}");
let _ = owner;
}
}
#[test]
fn a_stack_baseline_is_its_highest_child_baseline() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let short = baseline_box(&mut app, Size::new(40.0, 20.0), 15.0);
let tall = baseline_box(&mut app, Size::new(40.0, 60.0), 50.0);
let pinned = baseline_box(&mut app, Size::new(40.0, 20.0), 15.0);
let stack = RenderStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.set_alignment(&mut app, AlignmentGeometry::from(Alignment::CENTER));
stack.add_all(&mut app, Some(vec![short, tall, pinned]));
pinned
.parent_data_of_mut::<StackParentData>(&mut app)
.bottom = Some(0.0);
let constraints = BoxConstraints::new().max_width(100.0).max_height(100.0);
assert_eq!(
dry_and_actual_baseline(&mut app, stack.as_box(), constraints),
(Some(35.0), Some(35.0))
);
}
#[test]
fn an_indexed_stack_baseline_is_its_displayed_child_baseline() {
let cell = AppCell::new();
let mut app = cell.borrow_mut();
let short = baseline_box(&mut app, Size::new(40.0, 20.0), 15.0);
let tall = baseline_box(&mut app, Size::new(40.0, 60.0), 50.0);
let stack = RenderIndexedStack::new(&mut app);
stack.set_text_direction(&mut app, Some(TextDirection::Ltr));
stack.set_alignment(&mut app, AlignmentGeometry::from(Alignment::CENTER));
stack.add_all(&mut app, Some(vec![short, tall]));
stack.set_index(&mut app, Some(0));
let constraints = BoxConstraints::new().max_width(100.0).max_height(100.0);
assert_eq!(
dry_and_actual_baseline(&mut app, stack.as_box(), constraints),
(Some(35.0), Some(35.0))
);
}
}