use super::{ChildInfo, Layout, LayoutContext};
use crate::compat::{Any, Box, HashMap, Vec};
use crate::core::{ObjectId, Rect, Size};
use crate::widget::Widget;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AbsolutePosition {
pub x: i32,
pub y: i32,
pub width: Option<u32>,
pub height: Option<u32>,
pub anchor: Anchor,
pub margins: (i32, i32, i32, i32),
pub baseline: i32,
pub fill: bool,
}
impl AbsolutePosition {
pub fn new(x: i32, y: i32) -> Self {
Self {
x,
y,
width: None,
height: None,
anchor: Anchor::TopLeft,
margins: (0, 0, 0, 0),
baseline: 0,
fill: false,
}
}
pub fn fill() -> Self {
Self { fill: true, ..Self::new(0, 0) }
}
pub fn center_in() -> Self {
Self::new(0, 0).with_anchor_only(Anchor::Center)
}
pub fn with_margins(mut self, left: i32, top: i32, right: i32, bottom: i32) -> Self {
self.margins = (left, top, right, bottom);
self
}
pub fn with_baseline(mut self, baseline: i32) -> Self {
self.baseline = baseline;
self
}
pub fn lerp(from: Self, to: Self, progress: f32) -> Self {
let t = if progress.is_finite() { progress.clamp(0.0, 1.0) } else { 0.0 };
let blend = |a: i32, b: i32| -> i32 { a + ((b - a) as f32 * t).round() as i32 };
let blend_size = |a: Option<u32>, b: Option<u32>| -> Option<u32> {
match (a, b) {
(Some(a), Some(b)) => Some(blend(a as i32, b as i32).max(0) as u32),
_ => a.or(b),
}
};
Self {
x: blend(from.x, to.x),
y: blend(from.y, to.y),
width: blend_size(from.width, to.width),
height: blend_size(from.height, to.height),
anchor: if t < 0.5 { from.anchor } else { to.anchor },
margins: (
blend(from.margins.0, to.margins.0),
blend(from.margins.1, to.margins.1),
blend(from.margins.2, to.margins.2),
blend(from.margins.3, to.margins.3),
),
baseline: blend(from.baseline, to.baseline),
fill: if t < 0.5 { from.fill } else { to.fill },
}
}
pub fn with_size(mut self, width: u32, height: u32) -> Self {
self.width = Some(width);
self.height = Some(height);
self
}
pub fn with_anchor(mut self, anchor: Anchor, offset_x: i32, offset_y: i32) -> Self {
self.anchor = anchor;
self.x = offset_x;
self.y = offset_y;
self
}
pub fn with_anchor_only(mut self, anchor: Anchor) -> Self {
self.anchor = anchor;
self
}
pub fn to_rect(&self, _parent_size: Size, child_size: Size) -> Rect {
let width = self.width.unwrap_or(child_size.width);
let height = self.height.unwrap_or(child_size.height);
let (x, y) = match self.anchor {
Anchor::TopLeft => (self.x, self.y),
Anchor::TopCenter => {
let x = self.x - (width as i32) / 2;
(x, self.y)
}
Anchor::TopRight => {
let x = self.x - width as i32;
(x, self.y)
}
Anchor::CenterLeft => {
let y = self.y - (height as i32) / 2;
(self.x, y)
}
Anchor::Center => {
let x = self.x - (width as i32) / 2;
let y = self.y - (height as i32) / 2;
(x, y)
}
Anchor::CenterRight => {
let x = self.x - width as i32;
let y = self.y - (height as i32) / 2;
(x, y)
}
Anchor::BottomLeft => {
let y = self.y - height as i32;
(self.x, y)
}
Anchor::BottomCenter => {
let x = self.x - (width as i32) / 2;
let y = self.y - height as i32;
(x, y)
}
Anchor::BottomRight => {
let x = self.x - width as i32;
let y = self.y - height as i32;
(x, y)
}
Anchor::BaselineLeft => (self.x, self.y - self.baseline),
Anchor::BaselineCenter => (self.x - (width as i32) / 2, self.y - self.baseline),
Anchor::BaselineRight => (self.x - width as i32, self.y - self.baseline),
};
Rect::new(x, y, width, height)
}
pub fn resolve(&self, parent_size: Size, child_size: Size) -> Rect {
if self.fill || self.margins != (0, 0, 0, 0) {
self.to_rect_in_parent(parent_size, child_size)
} else {
self.to_rect(parent_size, child_size)
}
}
pub fn to_rect_in_parent(&self, parent_size: Size, child_size: Size) -> Rect {
let (ml, mt, mr, mb) = self.margins;
let pw = (parent_size.width as i32 - ml - mr).max(0);
let ph = (parent_size.height as i32 - mt - mb).max(0);
if self.fill {
return Rect::new(ml, mt, pw as u32, ph as u32);
}
let width = self.width.unwrap_or(child_size.width);
let height = self.height.unwrap_or(child_size.height);
let x = match self.anchor.horizontal() {
HorizontalAnchor::Start => self.x,
HorizontalAnchor::Center => (pw - width as i32) / 2 + self.x,
HorizontalAnchor::End => pw - width as i32 - self.x,
};
let y = match self.anchor.vertical() {
VerticalAnchor::Start => self.y,
VerticalAnchor::Center => (ph - height as i32) / 2 + self.y,
VerticalAnchor::End => ph - height as i32 - self.y,
VerticalAnchor::Baseline => self.y - self.baseline,
};
Rect::new(ml + x, mt + y, width, height)
}
}
impl Default for AbsolutePosition {
fn default() -> Self {
Self::new(0, 0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum TransitionEasing {
#[default]
Linear,
Smooth,
}
impl TransitionEasing {
pub fn apply(self, progress: f32) -> f32 {
let t = progress.clamp(0.0, 1.0);
match self {
TransitionEasing::Linear => t,
TransitionEasing::Smooth => t * t * (3.0 - 2.0 * t),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct AbsoluteTransition {
from: AbsolutePosition,
to: AbsolutePosition,
duration_ms: u32,
elapsed_ms: u32,
easing: TransitionEasing,
}
impl AbsoluteTransition {
pub fn new(from: AbsolutePosition, to: AbsolutePosition, duration_ms: u32) -> Self {
Self { from, to, duration_ms, elapsed_ms: 0, easing: TransitionEasing::default() }
}
pub fn with_easing(mut self, easing: TransitionEasing) -> Self {
self.easing = easing;
self
}
pub fn advance(&mut self, delta_ms: u32) -> bool {
if !self.is_animating() {
return false;
}
self.elapsed_ms = self.elapsed_ms.saturating_add(delta_ms).min(self.duration_ms);
self.is_animating()
}
pub fn is_animating(&self) -> bool {
self.elapsed_ms < self.duration_ms
}
pub fn progress(&self) -> f32 {
if self.duration_ms == 0 {
return 1.0;
}
self.elapsed_ms as f32 / self.duration_ms as f32
}
pub fn sample(&self) -> AbsolutePosition {
AbsolutePosition::lerp(self.from, self.to, self.easing.apply(self.progress()))
}
pub fn finish(&mut self) -> AbsolutePosition {
self.elapsed_ms = self.duration_ms;
self.to
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Anchor {
#[default]
TopLeft,
TopCenter,
TopRight,
CenterLeft,
Center,
CenterRight,
BottomLeft,
BottomCenter,
BottomRight,
BaselineLeft,
BaselineCenter,
BaselineRight,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HorizontalAnchor {
Start,
Center,
End,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VerticalAnchor {
Start,
Baseline,
Center,
End,
}
impl Anchor {
pub fn horizontal(self) -> HorizontalAnchor {
match self {
Anchor::TopLeft | Anchor::CenterLeft | Anchor::BottomLeft | Anchor::BaselineLeft => {
HorizontalAnchor::Start
}
Anchor::TopCenter | Anchor::Center | Anchor::BottomCenter | Anchor::BaselineCenter => {
HorizontalAnchor::Center
}
Anchor::TopRight
| Anchor::CenterRight
| Anchor::BottomRight
| Anchor::BaselineRight => HorizontalAnchor::End,
}
}
pub fn vertical(self) -> VerticalAnchor {
match self {
Anchor::TopLeft | Anchor::TopCenter | Anchor::TopRight => VerticalAnchor::Start,
Anchor::CenterLeft | Anchor::Center | Anchor::CenterRight => VerticalAnchor::Center,
Anchor::BottomLeft | Anchor::BottomCenter | Anchor::BottomRight => VerticalAnchor::End,
Anchor::BaselineLeft | Anchor::BaselineCenter | Anchor::BaselineRight => {
VerticalAnchor::Baseline
}
}
}
}
pub use Anchor as AbsoluteAnchor;
#[derive(Debug, Clone, Copy)]
pub struct Constraint {
pub min_width: Option<u32>,
pub max_width: Option<u32>,
pub min_height: Option<u32>,
pub max_height: Option<u32>,
pub aspect_ratio: Option<f32>,
}
impl Constraint {
pub fn new() -> Self {
Self {
min_width: None,
max_width: None,
min_height: None,
max_height: None,
aspect_ratio: None,
}
}
pub fn with_min_width(mut self, width: u32) -> Self {
self.min_width = Some(width);
self
}
pub fn with_max_width(mut self, width: u32) -> Self {
self.max_width = Some(width);
self
}
pub fn with_min_height(mut self, height: u32) -> Self {
self.min_height = Some(height);
self
}
pub fn with_max_height(mut self, height: u32) -> Self {
self.max_height = Some(height);
self
}
pub fn with_aspect_ratio(mut self, ratio: f32) -> Self {
self.aspect_ratio = Some(ratio);
self
}
pub fn apply(&self, size: Size) -> Size {
let mut width = size.width;
let mut height = size.height;
if let Some(min) = self.min_width {
width = width.max(min);
}
if let Some(max) = self.max_width {
width = width.min(max);
}
if let Some(min) = self.min_height {
height = height.max(min);
}
if let Some(max) = self.max_height {
height = height.min(max);
}
if let Some(ratio) = self.aspect_ratio {
if !(ratio.is_finite() && ratio > 0.0) {
return Size::new(width, height);
}
let height_f = height.max(1) as f32;
let current_ratio = width as f32 / height_f;
if current_ratio > ratio {
width = (height_f * ratio).round() as u32;
} else {
height = (width as f32 / ratio).round() as u32;
}
}
Size::new(width, height)
}
}
crate::impl_default_via_new!(Constraint);
pub struct AbsoluteLayout {
children: Vec<AbsoluteEntry>,
members: HashMap<ObjectId, ()>,
}
struct AbsoluteEntry {
widget_id: ObjectId,
size_hint: Option<Size>,
position: AbsolutePosition,
constraint: Option<Constraint>,
}
impl AbsoluteLayout {
pub fn new() -> Self {
Self { children: Vec::new(), members: HashMap::new() }
}
pub fn add_child(&mut self, child: Box<dyn Widget>, position: AbsolutePosition) {
let widget_id = child.id();
let size_hint = child.size_hint();
self.members.insert(widget_id, ());
self.children.push(AbsoluteEntry {
widget_id,
size_hint: Some(size_hint),
position,
constraint: None,
});
}
pub fn add_child_with_constraint(
&mut self,
child: Box<dyn Widget>,
position: AbsolutePosition,
constraint: Constraint,
) {
let widget_id = child.id();
let size_hint = child.size_hint();
self.members.insert(widget_id, ());
self.children.push(AbsoluteEntry {
widget_id,
size_hint: Some(size_hint),
position,
constraint: Some(constraint),
});
}
pub fn remove_child(&mut self, index: usize) -> Option<Box<dyn Widget>> {
if index < self.children.len() {
let removed = self.children.remove(index);
if !self.children.iter().any(|entry| entry.widget_id == removed.widget_id) {
self.members.remove(&removed.widget_id);
}
}
None
}
pub fn clear_children(&mut self) {
self.children.clear();
self.members.clear();
}
pub fn child_count(&self) -> usize {
self.children.len()
}
pub fn layout(&self, parent_rect: Rect) -> Vec<Rect> {
let parent_size = parent_rect.size();
let mut positions = Vec::new();
for entry in &self.children {
let child_size = entry.size_hint.unwrap_or(Size::new(0, 0));
let constrained_size = entry
.constraint
.as_ref()
.map(|constraint| constraint.apply(child_size))
.unwrap_or(child_size);
let rect = entry.position.resolve(parent_size, constrained_size);
positions.push(rect);
}
positions
}
pub fn calculate_positions(
&self,
container: &Rect,
positions: &[AbsolutePosition],
sizes: &[Size],
) -> Vec<Rect> {
let mut rects = Vec::new();
let parent_size = container.size();
for (position, size) in positions.iter().zip(sizes.iter()) {
let rect = position.resolve(parent_size, *size);
rects.push(rect);
}
rects
}
pub fn set_position(&mut self, index: usize, position: AbsolutePosition) -> bool {
if let Some(entry) = self.children.get_mut(index) {
entry.position = position;
true
} else {
false
}
}
pub fn set_constraint(&mut self, index: usize, constraint: Constraint) -> bool {
if let Some(entry) = self.children.get_mut(index) {
entry.constraint = Some(constraint);
true
} else {
false
}
}
pub fn set_constraint_optional(
&mut self,
index: usize,
constraint: Option<Constraint>,
) -> bool {
if let Some(entry) = self.children.get_mut(index) {
entry.constraint = constraint;
true
} else {
false
}
}
pub fn remove_constraint(&mut self, index: usize) -> bool {
self.set_constraint_optional(index, None)
}
pub fn get_position(&self, index: usize) -> Option<&AbsolutePosition> {
self.children.get(index).map(|entry| &entry.position)
}
pub fn get_constraint(&self, index: usize) -> Option<&Constraint> {
self.children.get(index).and_then(|entry| entry.constraint.as_ref())
}
}
crate::impl_default_via_new!(AbsoluteLayout);
impl Layout for AbsoluteLayout {
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
fn add_widget(&mut self, widget_id: ObjectId, _stretch: u32) {
if self.members.insert(widget_id, ()).is_none() {
self.children.push(AbsoluteEntry {
widget_id,
size_hint: None,
position: AbsolutePosition::new(0, 0),
constraint: None,
});
}
}
fn remove_widget(&mut self, widget_id: ObjectId) {
self.children.retain(|entry| entry.widget_id != widget_id);
self.members.remove(&widget_id);
}
fn update(&self, rect: Rect, widgets: &mut dyn FnMut(ObjectId, Rect)) {
let positions = self.layout(rect);
for (i, child_rect) in positions.iter().enumerate() {
if let Some(entry) = self.children.get(i) {
widgets(entry.widget_id, *child_rect);
}
}
}
fn update_with_context(
&self,
rect: Rect,
context: &LayoutContext,
widgets: &mut dyn FnMut(ObjectId, Rect),
) {
let _ = context;
self.update(rect, widgets);
}
fn arrange(&self, rect: Rect, children: &[ChildInfo], widgets: &mut dyn FnMut(ObjectId, Rect)) {
let _ = children;
self.update(rect, widgets);
}
fn arrange_with_context(
&self,
rect: Rect,
children: &[ChildInfo],
context: &LayoutContext,
widgets: &mut dyn FnMut(ObjectId, Rect),
) {
let _ = context;
self.arrange(rect, children, widgets);
}
fn child_ids(&self) -> Vec<ObjectId> {
let mut seen: HashMap<ObjectId, ()> = HashMap::new();
let mut ids: Vec<ObjectId> = Vec::with_capacity(self.children.len());
for entry in &self.children {
if seen.insert(entry.widget_id, ()).is_none() {
ids.push(entry.widget_id);
}
}
ids
}
fn has_child(&self, id: ObjectId) -> bool {
self.members.contains_key(&id)
}
fn widget_count(&self) -> usize {
self.members.len()
}
fn index_of(&self, id: ObjectId) -> Option<usize> {
let mut seen: HashMap<ObjectId, ()> = HashMap::new();
for entry in &self.children {
if seen.insert(entry.widget_id, ()).is_none() && entry.widget_id == id {
return Some(seen.len() - 1);
}
}
None
}
fn child_rect(&self, id: ObjectId, rect: Rect) -> Option<Rect> {
if !self.has_child(id) {
return None;
}
let entry = self.children.iter().find(|entry| entry.widget_id == id)?;
let child_size = entry.size_hint.unwrap_or(Size::new(0, 0));
let constrained_size = entry
.constraint
.as_ref()
.map(|constraint| constraint.apply(child_size))
.unwrap_or(child_size);
Some(entry.position.resolve(rect.size(), constrained_size))
}
fn clear(&mut self) {
self.clear_children();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::compat::HashMap;
struct TestWidget {
id: ObjectId,
size: Size,
}
impl TestWidget {
fn new(id: ObjectId, width: u32, height: u32) -> Self {
Self { id, size: Size::new(width, height) }
}
}
impl crate::event::EventHandler for TestWidget {
fn handle_event(&mut self, _event: &crate::event::Event) {}
}
impl Widget for TestWidget {
fn id(&self) -> ObjectId {
self.id
}
fn size_hint(&self) -> Size {
self.size
}
}
#[test]
fn remove_keeps_identity_and_geometry_aligned() {
let mut layout = AbsoluteLayout::new();
layout.add_widget(1, 0);
layout.set_position(0, AbsolutePosition::new(10, 10));
layout.add_child(Box::new(TestWidget::new(2, 20, 20)), AbsolutePosition::new(50, 50));
assert_eq!(layout.child_count(), 2);
layout.remove_widget(1);
assert_eq!(layout.child_count(), 1, "removing an id-only child must drop its slot");
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 100, 100), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.len(), 1, "the deleted sibling is gone from the update");
assert_eq!(
rects.get(&2),
Some(&Rect::new(50, 50, 20, 20)),
"the survivor keeps its own geometry, not the deleted sibling's"
);
}
#[test]
fn removing_any_position_keeps_survivors_aligned() {
let mut layout = AbsoluteLayout::new();
for (id, x) in [(1u64, 10i32), (2, 20), (3, 30)] {
layout.add_widget(id, 0);
let index = layout.child_count() - 1;
layout.set_position(index, AbsolutePosition::new(x, 0));
}
layout.remove_widget(2);
assert_eq!(layout.child_ids(), vec![1, 3]);
let mut rects = HashMap::new();
layout.update(Rect::new(0, 0, 100, 100), &mut |id, rect| {
rects.insert(id, rect);
});
assert_eq!(rects.get(&1).map(|r| r.x), Some(10));
assert_eq!(rects.get(&3).map(|r| r.x), Some(30));
}
#[test]
fn duplicate_register_does_not_add_a_second_entry() {
let mut layout = AbsoluteLayout::new();
layout.add_widget(1, 0);
layout.add_widget(1, 0);
assert_eq!(layout.child_count(), 1);
assert_eq!(layout.child_ids(), vec![1]);
}
#[test]
fn dyn_clear_removes_absolute_children() {
let mut layout: Box<dyn Layout> = Box::new(AbsoluteLayout::new());
layout.add_widget(1, 0);
layout.add_widget(2, 0);
assert!(layout.has_child(1));
layout.clear();
assert!(!layout.has_child(1));
assert!(!layout.has_child(2));
assert!(layout.child_ids().is_empty());
layout.clear();
assert!(layout.child_ids().is_empty());
}
#[test]
fn test_absolute_position() {
let pos =
AbsolutePosition::new(100, 100).with_size(50, 50).with_anchor_only(Anchor::Center);
let rect = pos.to_rect(Size::new(200, 200), Size::new(50, 50));
assert_eq!(rect.x, 75);
assert_eq!(rect.y, 75);
assert_eq!(rect.width, 50);
assert_eq!(rect.height, 50);
}
#[test]
fn test_constraint() {
let constraint = Constraint::new()
.with_min_width(50)
.with_max_width(100)
.with_min_height(50)
.with_max_height(100);
let size = constraint.apply(Size::new(200, 200));
assert_eq!(size.width, 100);
assert_eq!(size.height, 100);
let size = constraint.apply(Size::new(30, 30));
assert_eq!(size.width, 50);
assert_eq!(size.height, 50);
}
#[test]
fn test_aspect_ratio() {
let constraint = Constraint::new().with_aspect_ratio(2.0);
let size = constraint.apply(Size::new(200, 100));
assert_eq!(size.width, 200);
assert_eq!(size.height, 100);
let size = constraint.apply(Size::new(200, 200));
assert_eq!(size.width, 200);
assert_eq!(size.height, 100);
}
#[test]
fn anchored_in_parent_pins_to_the_parents_edges() {
let parent = Size::new(400, 300);
let child = Size::new(60, 40);
let corner = AbsolutePosition::new(8, 8)
.with_anchor_only(Anchor::BottomRight)
.to_rect_in_parent(parent, child);
assert_eq!(corner, Rect::new(400 - 60 - 8, 300 - 40 - 8, 60, 40));
let centred = AbsolutePosition::new(0, 0)
.with_anchor_only(Anchor::Center)
.to_rect_in_parent(parent, child);
assert_eq!(centred, Rect::new(170, 130, 60, 40));
let top_left = AbsolutePosition::new(5, 6)
.with_anchor_only(Anchor::TopLeft)
.to_rect_in_parent(parent, child);
assert_eq!(top_left, Rect::new(5, 6, 60, 40));
let position = AbsolutePosition::new(400, 300).with_anchor_only(Anchor::BottomRight);
assert_eq!(
position.to_rect(parent, child),
Rect::new(400 - 60, 300 - 40, 60, 40),
"the old spelling places the child's own corner at the given point"
);
assert_eq!(
position.to_rect_in_parent(parent, child),
Rect::new(400 - 60 - 400, 300 - 40 - 300, 60, 40),
"the new spelling treats the point as an inset from the parent's edge"
);
}
#[test]
fn fill_margins_and_baseline_express_declarative_anchors() {
let parent = Size::new(200, 100);
let filled = AbsolutePosition::fill()
.with_margins(4, 6, 8, 10)
.to_rect_in_parent(parent, Size::new(10, 10));
assert_eq!(filled, Rect::new(4, 6, 200 - 4 - 8, 100 - 6 - 10));
let centred = AbsolutePosition::center_in().to_rect_in_parent(parent, Size::new(20, 40));
assert_eq!(centred, Rect::new(90, 30, 20, 40));
let baseline = AbsolutePosition::new(10, 50)
.with_anchor_only(Anchor::BaselineLeft)
.with_baseline(12)
.to_rect_in_parent(parent, Size::new(30, 20));
assert_eq!(baseline, Rect::new(10, 38, 30, 20));
let animated = AbsolutePosition::lerp(
AbsolutePosition::new(0, 0),
AbsolutePosition::new(100, 40).with_size(20, 20),
0.5,
);
assert_eq!((animated.x, animated.y), (50, 20));
assert_eq!(
(animated.width, animated.height),
(Some(20), Some(20)),
"an unset size keeps the defined endpoint rather than interpolating from zero"
);
}
#[test]
fn the_layout_applies_fill_and_margins() {
let mut layout = AbsoluteLayout::new();
layout.add_widget(1, 0);
layout.set_position(0, AbsolutePosition::fill().with_margins(4, 6, 8, 10));
let rects = layout.layout(Rect::new(0, 0, 200, 100));
assert_eq!(rects[0], Rect::new(4, 6, 188, 84));
layout.set_position(0, AbsolutePosition::new(10, 20));
let rects = layout.layout(Rect::new(0, 0, 200, 100));
assert_eq!(rects[0], Rect::new(10, 20, 0, 0));
}
#[test]
fn a_transition_animates_an_anchored_child_over_frames() {
let from = AbsolutePosition::new(0, 0);
let to = AbsolutePosition::new(100, 40);
let mut transition =
AbsoluteTransition::new(from, to, 200).with_easing(TransitionEasing::Smooth);
assert!(transition.is_animating());
assert_eq!(transition.sample().x, 0, "the first frame starts at the origin");
assert!(transition.advance(100));
assert_eq!(transition.progress(), 0.5);
assert_eq!(transition.sample().x, 50);
assert!(!transition.advance(100), "the last frame ends the transition");
assert_eq!(transition.sample().x, 100);
let mut instant = AbsoluteTransition::new(from, to, 0);
assert!(!instant.advance(16));
assert_eq!(instant.sample().x, 100);
assert_eq!(instant.finish().x, 100);
}
#[test]
fn every_anchor_decomposes_onto_its_axes() {
for (anchor, horizontal, vertical) in [
(Anchor::TopLeft, HorizontalAnchor::Start, VerticalAnchor::Start),
(Anchor::TopCenter, HorizontalAnchor::Center, VerticalAnchor::Start),
(Anchor::TopRight, HorizontalAnchor::End, VerticalAnchor::Start),
(Anchor::CenterLeft, HorizontalAnchor::Start, VerticalAnchor::Center),
(Anchor::Center, HorizontalAnchor::Center, VerticalAnchor::Center),
(Anchor::CenterRight, HorizontalAnchor::End, VerticalAnchor::Center),
(Anchor::BottomLeft, HorizontalAnchor::Start, VerticalAnchor::End),
(Anchor::BottomCenter, HorizontalAnchor::Center, VerticalAnchor::End),
(Anchor::BottomRight, HorizontalAnchor::End, VerticalAnchor::End),
] {
assert_eq!(anchor.horizontal(), horizontal, "{anchor:?}");
assert_eq!(anchor.vertical(), vertical, "{anchor:?}");
}
}
#[test]
fn absolute_layout_is_send() {
fn assert_send<T: Send>() {}
assert_send::<AbsoluteLayout>();
}
}