use core::any::Any;
use core::fmt;
use fmt::Debug;
use alloc::{rc::Rc, vec::Vec};
use nami::watcher::BoxWatcherGuard;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum LayoutDirection {
#[default]
LeftToRight,
RightToLeft,
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct AutomaticLayoutDirection(pub nami::Computed<LayoutDirection>);
impl LayoutDirection {
#[must_use]
pub const fn is_right_to_left(self) -> bool {
matches!(self, Self::RightToLeft)
}
}
#[must_use]
pub fn layout_direction(environment: &crate::Environment) -> nami::Computed<LayoutDirection> {
if let Some(direction) = environment.get::<LayoutDirection>() {
return nami::Computed::constant(*direction);
}
if let Some(direction) = environment.get::<nami::Binding<LayoutDirection>>() {
return direction.clone().into();
}
if let Some(direction) = environment.get::<nami::Computed<LayoutDirection>>() {
return direction.clone();
}
environment.get::<AutomaticLayoutDirection>().map_or_else(
|| nami::Computed::constant(LayoutDirection::default()),
|direction| direction.0.clone(),
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub enum StretchAxis {
#[default]
None,
Horizontal,
Vertical,
Both,
MainAxis,
CrossAxis,
}
impl StretchAxis {
#[must_use]
pub const fn stretches_horizontal(&self) -> bool {
matches!(self, Self::Horizontal | Self::Both)
}
#[must_use]
pub const fn stretches_vertical(&self) -> bool {
matches!(self, Self::Vertical | Self::Both)
}
#[must_use]
pub const fn stretches_any(&self) -> bool {
!matches!(self, Self::None)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
pub struct LayoutPriority(i32);
impl LayoutPriority {
#[must_use]
pub const fn new(priority: i32) -> Self {
Self(priority)
}
#[must_use]
pub const fn get(self) -> i32 {
self.0
}
}
impl crate::components::metadata::MetadataKey for LayoutPriority {}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AlignmentKeyId {
low: u64,
high: u64,
}
impl AlignmentKeyId {
#[must_use]
pub const fn new(low: u64, high: u64) -> Self {
Self { low, high }
}
#[must_use]
pub const fn low(self) -> u64 {
self.low
}
#[must_use]
pub const fn high(self) -> u64 {
self.high
}
#[must_use]
pub const fn from_name(name: &str) -> Self {
let hash = fnv1a_128(name.as_bytes());
let bytes = hash.to_le_bytes();
Self {
low: u64::from_le_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]),
high: u64::from_le_bytes([
bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
bytes[15],
]),
}
}
}
const fn fnv1a_128(bytes: &[u8]) -> u128 {
const FNV_OFFSET: u128 = 0x6c62_272e_07bb_0142_62b8_2175_6295_c58d;
const FNV_PRIME: u128 = 0x0000_0000_0100_0000_0000_0000_0000_013b;
let mut hash = FNV_OFFSET;
let mut i = 0;
while i < bytes.len() {
hash ^= bytes[i] as u128;
hash = hash.wrapping_mul(FNV_PRIME);
i += 1;
}
hash
}
#[derive(Clone, Copy)]
pub struct HorizontalAlignment {
stable_id: AlignmentKeyId,
default_value: fn(&ViewDimensions) -> f32,
}
impl HorizontalAlignment {
#[allow(non_upper_case_globals)]
pub const Leading: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.leading"),
default_value: leading_alignment_default,
};
#[allow(non_upper_case_globals)]
pub const Center: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.center"),
default_value: center_horizontal_alignment_default,
};
#[allow(non_upper_case_globals)]
pub const Trailing: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.horizontal.trailing"),
default_value: trailing_alignment_default,
};
#[must_use]
pub const fn stable_id(self) -> AlignmentKeyId {
self.stable_id
}
#[must_use]
pub(crate) fn default_value(self, dimensions: &ViewDimensions) -> f32 {
(self.default_value)(dimensions)
}
}
impl Default for HorizontalAlignment {
fn default() -> Self {
Self::Center
}
}
impl PartialEq for HorizontalAlignment {
fn eq(&self, other: &Self) -> bool {
self.stable_id == other.stable_id
}
}
impl Eq for HorizontalAlignment {}
impl Debug for HorizontalAlignment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HorizontalAlignment")
.field("stable_id", &self.stable_id)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy)]
pub struct VerticalAlignment {
stable_id: AlignmentKeyId,
default_value: fn(&ViewDimensions) -> f32,
}
impl VerticalAlignment {
#[allow(non_upper_case_globals)]
pub const Top: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.top"),
default_value: top_alignment_default,
};
#[allow(non_upper_case_globals)]
pub const Center: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.center"),
default_value: center_vertical_alignment_default,
};
#[allow(non_upper_case_globals)]
pub const Bottom: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.bottom"),
default_value: bottom_alignment_default,
};
#[allow(non_upper_case_globals)]
pub const FirstBaseline: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.first_baseline"),
default_value: first_baseline_alignment_default,
};
#[allow(non_upper_case_globals)]
pub const LastBaseline: Self = Self {
stable_id: AlignmentKeyId::from_name("waterui.layout.vertical.last_baseline"),
default_value: last_baseline_alignment_default,
};
#[must_use]
pub const fn stable_id(self) -> AlignmentKeyId {
self.stable_id
}
#[must_use]
pub(crate) fn default_value(self, dimensions: &ViewDimensions) -> f32 {
(self.default_value)(dimensions)
}
}
impl Default for VerticalAlignment {
fn default() -> Self {
Self::Center
}
}
impl PartialEq for VerticalAlignment {
fn eq(&self, other: &Self) -> bool {
self.stable_id == other.stable_id
}
}
impl Eq for VerticalAlignment {}
impl Debug for VerticalAlignment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("VerticalAlignment")
.field("stable_id", &self.stable_id)
.finish_non_exhaustive()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Alignment {
horizontal: HorizontalAlignment,
vertical: VerticalAlignment,
}
impl Alignment {
#[allow(non_upper_case_globals)]
pub const Top: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Top);
#[allow(non_upper_case_globals)]
pub const TopLeading: Self = Self::new(HorizontalAlignment::Leading, VerticalAlignment::Top);
#[allow(non_upper_case_globals)]
pub const TopTrailing: Self = Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Top);
#[allow(non_upper_case_globals)]
pub const Center: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Center);
#[allow(non_upper_case_globals)]
pub const Leading: Self = Self::new(HorizontalAlignment::Leading, VerticalAlignment::Center);
#[allow(non_upper_case_globals)]
pub const Trailing: Self = Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Center);
#[allow(non_upper_case_globals)]
pub const Bottom: Self = Self::new(HorizontalAlignment::Center, VerticalAlignment::Bottom);
#[allow(non_upper_case_globals)]
pub const BottomLeading: Self =
Self::new(HorizontalAlignment::Leading, VerticalAlignment::Bottom);
#[allow(non_upper_case_globals)]
pub const BottomTrailing: Self =
Self::new(HorizontalAlignment::Trailing, VerticalAlignment::Bottom);
#[must_use]
pub const fn new(horizontal: HorizontalAlignment, vertical: VerticalAlignment) -> Self {
Self {
horizontal,
vertical,
}
}
#[must_use]
pub const fn horizontal(&self) -> HorizontalAlignment {
self.horizontal
}
#[must_use]
pub const fn vertical(&self) -> VerticalAlignment {
self.vertical
}
}
impl Default for Alignment {
fn default() -> Self {
Self::Center
}
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct ViewDimensions {
pub size: Size,
explicit_horizontal_guides: Vec<(HorizontalAlignment, f32)>,
explicit_vertical_guides: Vec<(VerticalAlignment, f32)>,
}
impl ViewDimensions {
#[must_use]
pub const fn new(size: Size) -> Self {
Self {
size,
explicit_horizontal_guides: Vec::new(),
explicit_vertical_guides: Vec::new(),
}
}
#[must_use]
pub fn horizontal(&self, alignment: HorizontalAlignment) -> f32 {
self.explicit_horizontal(alignment)
.unwrap_or_else(|| alignment.default_value(self))
}
#[must_use]
pub fn vertical(&self, alignment: VerticalAlignment) -> f32 {
self.explicit_vertical(alignment)
.unwrap_or_else(|| alignment.default_value(self))
}
#[must_use]
pub fn explicit_horizontal(&self, alignment: HorizontalAlignment) -> Option<f32> {
self.explicit_horizontal_guides
.iter()
.rev()
.find_map(|(guide, value)| (*guide == alignment).then_some(*value))
}
#[must_use]
pub fn explicit_vertical(&self, alignment: VerticalAlignment) -> Option<f32> {
self.explicit_vertical_guides
.iter()
.rev()
.find_map(|(guide, value)| (*guide == alignment).then_some(*value))
}
pub fn explicit_horizontal_guides(
&self,
) -> impl Iterator<Item = (HorizontalAlignment, f32)> + '_ {
self.explicit_horizontal_guides.iter().copied()
}
pub fn explicit_vertical_guides(&self) -> impl Iterator<Item = (VerticalAlignment, f32)> + '_ {
self.explicit_vertical_guides.iter().copied()
}
pub fn set_horizontal(&mut self, alignment: HorizontalAlignment, value: f32) {
self.explicit_horizontal_guides.push((alignment, value));
}
pub fn set_vertical(&mut self, alignment: VerticalAlignment, value: f32) {
self.explicit_vertical_guides.push((alignment, value));
}
#[must_use]
pub fn with_horizontal(mut self, alignment: HorizontalAlignment, value: f32) -> Self {
self.set_horizontal(alignment, value);
self
}
#[must_use]
pub fn with_vertical(mut self, alignment: VerticalAlignment, value: f32) -> Self {
self.set_vertical(alignment, value);
self
}
}
#[derive(Clone, Copy)]
pub struct PlacedSubview<'a> {
pub view: &'a dyn SubView,
pub frame: Rect,
}
impl Debug for PlacedSubview<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PlacedSubview")
.field("frame", &self.frame)
.finish_non_exhaustive()
}
}
impl<'a> PlacedSubview<'a> {
#[must_use]
pub const fn new(view: &'a dyn SubView, frame: Rect) -> Self {
Self { view, frame }
}
#[must_use]
pub fn dimensions(&self) -> ViewDimensions {
self.view.measure(ProposalSize::new(
Some(self.frame.width()),
Some(self.frame.height()),
))
}
#[must_use]
pub fn horizontal(&self, alignment: HorizontalAlignment) -> f32 {
self.frame.x() + self.dimensions().horizontal(alignment)
}
#[must_use]
pub fn vertical(&self, alignment: VerticalAlignment) -> f32 {
self.frame.y() + self.dimensions().vertical(alignment)
}
#[must_use]
pub fn explicit_horizontal(&self, alignment: HorizontalAlignment) -> Option<f32> {
self.dimensions()
.explicit_horizontal(alignment)
.map(|value| self.frame.x() + value)
}
#[must_use]
pub fn explicit_vertical(&self, alignment: VerticalAlignment) -> Option<f32> {
self.dimensions()
.explicit_vertical(alignment)
.map(|value| self.frame.y() + value)
}
}
const fn leading_alignment_default(dimensions: &ViewDimensions) -> f32 {
let _ = dimensions;
0.0
}
const fn center_horizontal_alignment_default(dimensions: &ViewDimensions) -> f32 {
dimensions.size.width * 0.5
}
const fn trailing_alignment_default(dimensions: &ViewDimensions) -> f32 {
dimensions.size.width
}
const fn top_alignment_default(dimensions: &ViewDimensions) -> f32 {
let _ = dimensions;
0.0
}
const fn center_vertical_alignment_default(dimensions: &ViewDimensions) -> f32 {
dimensions.size.height * 0.5
}
const fn bottom_alignment_default(dimensions: &ViewDimensions) -> f32 {
dimensions.size.height
}
const fn first_baseline_alignment_default(dimensions: &ViewDimensions) -> f32 {
dimensions.size.height
}
const fn last_baseline_alignment_default(dimensions: &ViewDimensions) -> f32 {
dimensions.size.height
}
pub trait SubView {
#[must_use]
fn measure(&self, proposal: ProposalSize) -> ViewDimensions;
fn stretch_axis(&self) -> StretchAxis;
fn priority(&self) -> i32;
}
pub struct MemoizedSubView<'a> {
inner: &'a dyn SubView,
cache: core::cell::RefCell<[Option<(ProposalSize, ViewDimensions)>; MEMOIZED_PROPOSALS]>,
}
pub const MEMOIZED_PROPOSALS: usize = 4;
impl<'a> MemoizedSubView<'a> {
#[must_use]
pub fn new(inner: &'a dyn SubView) -> Self {
Self {
inner,
cache: core::cell::RefCell::new([const { None }; MEMOIZED_PROPOSALS]),
}
}
}
impl Debug for MemoizedSubView<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MemoizedSubView").finish_non_exhaustive()
}
}
const fn proposal_axis_bits(axis: Option<f32>) -> Option<u32> {
match axis {
Some(value) => Some(value.to_bits()),
None => None,
}
}
fn same_proposal(left: ProposalSize, right: ProposalSize) -> bool {
proposal_axis_bits(left.width) == proposal_axis_bits(right.width)
&& proposal_axis_bits(left.height) == proposal_axis_bits(right.height)
}
impl SubView for MemoizedSubView<'_> {
fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
if let Some((_, dimensions)) = self
.cache
.borrow()
.iter()
.flatten()
.find(|(cached, _)| same_proposal(*cached, proposal))
{
return dimensions.clone();
}
let dimensions = self.inner.measure(proposal);
if let Some(slot) = self
.cache
.borrow_mut()
.iter_mut()
.find(|slot| slot.is_none())
{
*slot = Some((proposal, dimensions.clone()));
}
dimensions
}
fn stretch_axis(&self) -> StretchAxis {
self.inner.stretch_axis()
}
fn priority(&self) -> i32 {
self.inner.priority()
}
}
pub fn with_memoized_children<R>(
children: &[&dyn SubView],
pass: impl FnOnce(&[&dyn SubView]) -> R,
) -> R {
let memoized: Vec<MemoizedSubView<'_>> =
children.iter().copied().map(MemoizedSubView::new).collect();
let refs: Vec<&dyn SubView> = memoized.iter().map(|child| child as &dyn SubView).collect();
pass(&refs)
}
#[doc(hidden)]
pub type LayoutInvalidationCallback = Rc<dyn Fn() + 'static>;
pub trait Layout: Debug + Any {
fn size_that_fits(&self, proposal: ProposalSize, children: &[&dyn SubView]) -> Size;
fn place(&self, bounds: Rect, children: &[&dyn SubView]) -> Vec<Rect>;
fn explicit_horizontal(
&self,
_alignment: HorizontalAlignment,
_bounds: Rect,
_children: &[PlacedSubview<'_>],
) -> Option<f32> {
None
}
fn explicit_vertical(
&self,
_alignment: VerticalAlignment,
_bounds: Rect,
_children: &[PlacedSubview<'_>],
) -> Option<f32> {
None
}
fn explicit_horizontal_alignments(&self) -> Vec<HorizontalAlignment> {
Vec::new()
}
fn explicit_vertical_alignments(&self) -> Vec<VerticalAlignment> {
Vec::new()
}
fn stretch_axis(&self, children: &[StretchAxis]) -> StretchAxis {
let _ = children;
StretchAxis::None
}
#[doc(hidden)]
fn watch_invalidation(&self, _invalidate: LayoutInvalidationCallback) -> Vec<BoxWatcherGuard> {
Vec::new()
}
}
#[must_use]
pub fn measure_layout(
layout: &dyn Layout,
proposal: ProposalSize,
children: &[&dyn SubView],
) -> ViewDimensions {
with_memoized_children(children, |children| {
measure_layout_memoized(layout, proposal, children)
})
}
fn measure_layout_memoized(
layout: &dyn Layout,
proposal: ProposalSize,
children: &[&dyn SubView],
) -> ViewDimensions {
let size = layout.size_that_fits(proposal, children);
let bounds = Rect::from_size(size);
let child_rects = layout.place(bounds, children);
let placed_subviews: Vec<PlacedSubview<'_>> = children
.iter()
.zip(child_rects.iter().copied())
.map(|(view, frame)| PlacedSubview::new(*view, frame))
.collect();
let mut dimensions = ViewDimensions::new(size);
let mut horizontal_keys = layout.explicit_horizontal_alignments();
let mut vertical_keys = layout.explicit_vertical_alignments();
for child in &placed_subviews {
let child_dimensions = child.dimensions();
for (alignment, _) in child_dimensions.explicit_horizontal_guides() {
if !horizontal_keys.contains(&alignment) {
horizontal_keys.push(alignment);
}
}
for (alignment, _) in child_dimensions.explicit_vertical_guides() {
if !vertical_keys.contains(&alignment) {
vertical_keys.push(alignment);
}
}
}
for alignment in horizontal_keys {
if let Some(value) = layout.explicit_horizontal(alignment, bounds, &placed_subviews) {
dimensions.set_horizontal(alignment, value);
}
}
for alignment in vertical_keys {
if let Some(value) = layout.explicit_vertical(alignment, bounds, &placed_subviews) {
dimensions.set_vertical(alignment, value);
}
}
dimensions
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Rect {
origin: Point,
size: Size,
}
impl Rect {
#[must_use]
pub const fn new(origin: Point, size: Size) -> Self {
Self { origin, size }
}
#[must_use]
pub const fn from_size(size: Size) -> Self {
Self {
origin: Point::zero(),
size,
}
}
#[must_use]
pub const fn origin(&self) -> Point {
self.origin
}
#[must_use]
pub const fn size(&self) -> &Size {
&self.size
}
#[must_use]
pub const fn x(&self) -> f32 {
self.origin.x
}
#[must_use]
pub const fn y(&self) -> f32 {
self.origin.y
}
#[must_use]
pub const fn width(&self) -> f32 {
self.size.width
}
#[must_use]
pub const fn height(&self) -> f32 {
self.size.height
}
#[must_use]
pub const fn min_x(&self) -> f32 {
self.origin.x
}
#[must_use]
pub const fn min_y(&self) -> f32 {
self.origin.y
}
#[must_use]
pub const fn max_x(&self) -> f32 {
self.origin.x + self.size.width
}
#[must_use]
pub const fn max_y(&self) -> f32 {
self.origin.y + self.size.height
}
#[must_use]
pub const fn mid_x(&self) -> f32 {
self.origin.x + self.size.width / 2.0
}
#[must_use]
pub const fn mid_y(&self) -> f32 {
self.origin.y + self.size.height / 2.0
}
#[must_use]
pub const fn center(&self) -> Point {
Point::new(self.mid_x(), self.mid_y())
}
#[must_use]
pub fn inset(&self, top: f32, bottom: f32, leading: f32, trailing: f32) -> Self {
Self::new(
Point::new(self.origin.x + leading, self.origin.y + top),
Size::new(
(self.size.width - leading - trailing).max(0.0),
(self.size.height - top - bottom).max(0.0),
),
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Default)]
pub struct Size {
pub width: f32,
pub height: f32,
}
impl Size {
#[must_use]
pub const fn new(width: f32, height: f32) -> Self {
Self { width, height }
}
#[must_use]
pub const fn zero() -> Self {
Self {
width: 0.0,
height: 0.0,
}
}
#[must_use]
pub const fn is_zero(&self) -> bool {
self.width == 0.0 && self.height == 0.0
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct Point {
pub x: f32,
pub y: f32,
}
impl Point {
#[must_use]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
#[must_use]
pub const fn zero() -> Self {
Self { x: 0.0, y: 0.0 }
}
}
impl From<(f32, f32)> for Point {
fn from((x, y): (f32, f32)) -> Self {
Self { x, y }
}
}
impl From<[f32; 2]> for Point {
fn from([x, y]: [f32; 2]) -> Self {
Self { x, y }
}
}
impl From<(f32, f32)> for Size {
fn from((width, height): (f32, f32)) -> Self {
Self { width, height }
}
}
impl From<[f32; 2]> for Size {
fn from([width, height]: [f32; 2]) -> Self {
Self { width, height }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct Vec2 {
pub dx: f32,
pub dy: f32,
}
impl Vec2 {
#[must_use]
pub const fn new(dx: f32, dy: f32) -> Self {
Self { dx, dy }
}
pub const ZERO: Self = Self { dx: 0.0, dy: 0.0 };
}
impl From<(f32, f32)> for Vec2 {
fn from((dx, dy): (f32, f32)) -> Self {
Self { dx, dy }
}
}
impl From<[f32; 2]> for Vec2 {
fn from([dx, dy]: [f32; 2]) -> Self {
Self { dx, dy }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct UnitPoint {
pub x: f32,
pub y: f32,
}
impl UnitPoint {
pub const TOP_LEADING: Self = Self { x: 0.0, y: 0.0 };
pub const TOP: Self = Self { x: 0.5, y: 0.0 };
pub const TOP_TRAILING: Self = Self { x: 1.0, y: 0.0 };
pub const LEADING: Self = Self { x: 0.0, y: 0.5 };
pub const CENTER: Self = Self { x: 0.5, y: 0.5 };
pub const TRAILING: Self = Self { x: 1.0, y: 0.5 };
pub const BOTTOM_LEADING: Self = Self { x: 0.0, y: 1.0 };
pub const BOTTOM: Self = Self { x: 0.5, y: 1.0 };
pub const BOTTOM_TRAILING: Self = Self { x: 1.0, y: 1.0 };
#[must_use]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
}
impl From<(f32, f32)> for UnitPoint {
fn from((x, y): (f32, f32)) -> Self {
Self { x, y }
}
}
impl From<[f32; 2]> for UnitPoint {
fn from([x, y]: [f32; 2]) -> Self {
Self { x, y }
}
}
impl From<Alignment> for UnitPoint {
fn from(alignment: Alignment) -> Self {
let horizontal = alignment.horizontal();
let vertical = alignment.vertical();
if horizontal == HorizontalAlignment::Leading && vertical == VerticalAlignment::Top {
Self::TOP_LEADING
} else if horizontal == HorizontalAlignment::Trailing && vertical == VerticalAlignment::Top
{
Self::TOP_TRAILING
} else if horizontal == HorizontalAlignment::Leading
&& vertical == VerticalAlignment::Bottom
{
Self::BOTTOM_LEADING
} else if horizontal == HorizontalAlignment::Trailing
&& vertical == VerticalAlignment::Bottom
{
Self::BOTTOM_TRAILING
} else if horizontal == HorizontalAlignment::Leading {
Self::LEADING
} else if horizontal == HorizontalAlignment::Trailing {
Self::TRAILING
} else if vertical == VerticalAlignment::Top {
Self::TOP
} else if vertical == VerticalAlignment::Bottom {
Self::BOTTOM
} else {
Self::CENTER
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct Affine2 {
pub a: f32,
pub b: f32,
pub c: f32,
pub d: f32,
pub e: f32,
pub f: f32,
}
impl Affine2 {
pub const IDENTITY: Self = Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 0.0,
f: 0.0,
};
#[must_use]
pub const fn new(
scale_x: f32,
shear_y: f32,
shear_x: f32,
scale_y: f32,
translate_x: f32,
translate_y: f32,
) -> Self {
Self {
a: scale_x,
b: shear_y,
c: shear_x,
d: scale_y,
e: translate_x,
f: translate_y,
}
}
#[must_use]
pub const fn translate(tx: f32, ty: f32) -> Self {
Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: tx,
f: ty,
}
}
#[must_use]
pub const fn scale(sx: f32, sy: f32) -> Self {
Self {
a: sx,
b: 0.0,
c: 0.0,
d: sy,
e: 0.0,
f: 0.0,
}
}
#[must_use]
pub fn rotate(radians: f32) -> Self {
let (s, c) = radians.sin_cos();
Self {
a: c,
b: s,
c: -s,
d: c,
e: 0.0,
f: 0.0,
}
}
}
impl From<[f32; 6]> for Affine2 {
fn from(coefficients: [f32; 6]) -> Self {
Self {
a: coefficients[0],
b: coefficients[1],
c: coefficients[2],
d: coefficients[3],
e: coefficients[4],
f: coefficients[5],
}
}
}
impl From<Affine2> for [f32; 6] {
fn from(t: Affine2) -> Self {
[t.a, t.b, t.c, t.d, t.e, t.f]
}
}
macro_rules! impl_layout_signal_constant {
($($ty:ty),+ $(,)?) => {
$(
impl nami::Signal for $ty {
type Output = Self;
type Guard = ();
fn get(&self) -> Self::Output {
*self
}
fn watch(
&self,
_watcher: impl Fn(nami::watcher::Context<Self::Output>) + 'static,
) {
}
}
)+
};
}
impl_layout_signal_constant!(
LayoutDirection,
Point,
Size,
Rect,
Vec2,
UnitPoint,
Affine2,
HorizontalAlignment,
VerticalAlignment,
Alignment
);
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct ProposalSize {
pub width: Option<f32>,
pub height: Option<f32>,
}
impl ProposalSize {
#[must_use]
pub fn new(width: impl Into<Option<f32>>, height: impl Into<Option<f32>>) -> Self {
Self {
width: width.into(),
height: height.into(),
}
}
pub const UNSPECIFIED: Self = Self {
width: None,
height: None,
};
pub const ZERO: Self = Self {
width: Some(0.0),
height: Some(0.0),
};
pub const INFINITY: Self = Self {
width: Some(f32::INFINITY),
height: Some(f32::INFINITY),
};
#[must_use]
pub fn width_or(&self, default: f32) -> f32 {
self.width.unwrap_or(default)
}
#[must_use]
pub fn height_or(&self, default: f32) -> f32 {
self.height.unwrap_or(default)
}
#[must_use]
pub const fn with_width(self, width: Option<f32>) -> Self {
Self {
width,
height: self.height,
}
}
#[must_use]
pub const fn with_height(self, height: Option<f32>) -> Self {
Self {
width: self.width,
height,
}
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn test_rect_geometry() {
let rect = Rect::new(Point::new(10.0, 20.0), Size::new(100.0, 50.0));
assert_eq!(rect.min_x(), 10.0);
assert_eq!(rect.min_y(), 20.0);
assert_eq!(rect.max_x(), 110.0);
assert_eq!(rect.max_y(), 70.0);
assert_eq!(rect.mid_x(), 60.0);
assert_eq!(rect.mid_y(), 45.0);
assert_eq!(rect.width(), 100.0);
assert_eq!(rect.height(), 50.0);
}
#[test]
fn test_rect_inset() {
let rect = Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0));
let inset = rect.inset(10.0, 10.0, 20.0, 20.0);
assert_eq!(inset.x(), 20.0);
assert_eq!(inset.y(), 10.0);
assert_eq!(inset.width(), 60.0);
assert_eq!(inset.height(), 80.0);
}
#[test]
fn test_proposal_size() {
let proposal = ProposalSize::new(Some(100.0), None);
assert_eq!(proposal.width_or(0.0), 100.0);
assert_eq!(proposal.height_or(50.0), 50.0);
let with_height = proposal.with_height(Some(200.0));
assert_eq!(with_height.width, Some(100.0));
assert_eq!(with_height.height, Some(200.0));
}
struct CountingSubView {
measures: core::cell::Cell<usize>,
}
impl SubView for CountingSubView {
fn measure(&self, proposal: ProposalSize) -> ViewDimensions {
self.measures.set(self.measures.get() + 1);
ViewDimensions::new(Size::new(proposal.width_or(10.0), proposal.height_or(20.0)))
}
fn stretch_axis(&self) -> StretchAxis {
StretchAxis::None
}
fn priority(&self) -> i32 {
0
}
}
#[test]
fn memoized_subview_measures_once_per_distinct_proposal() {
let inner = CountingSubView {
measures: core::cell::Cell::new(0),
};
let memo = MemoizedSubView::new(&inner);
let ideal = ProposalSize::UNSPECIFIED;
let constrained = ProposalSize::new(Some(80.0), None);
for _ in 0..5 {
assert_eq!(memo.measure(ideal).size, Size::new(10.0, 20.0));
assert_eq!(memo.measure(constrained).size, Size::new(80.0, 20.0));
}
assert_eq!(
inner.measures.get(),
2,
"ten probes over two distinct proposals must reach the child twice"
);
}
#[test]
fn memoized_subview_forwards_priority_and_stretch() {
let inner = CountingSubView {
measures: core::cell::Cell::new(0),
};
let memo = MemoizedSubView::new(&inner);
assert_eq!(memo.priority(), inner.priority());
assert_eq!(memo.stretch_axis(), inner.stretch_axis());
}
}