iris: separate layout allocation from lengths

This commit is contained in:
iris committed 2026-09-12 19:35:18 -04:00
1 parent 2b9d8c49a0
commit 1f15125992
17 files changed
+395 -168

No files matched your search

+189 -45
View File
@@ -3,10 +3,13 @@ use crate::{UiNum, util::impl_op};
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Size {
pub x: Len,
pub y: Len,
pub x: LayoutLen,
pub y: LayoutLen,
}
/// A length resolved from physical pixels, density-independent pixels, and a
/// fraction of a reference length. Unlike [`LayoutLen`], it carries no claim
/// on space left over by a layout container.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Len {
/// Physical pixels -- a raw device pixel, unaffected by the display's
@@ -17,6 +20,15 @@ pub struct Len {
pub abs: f32,
pub dp: f32,
pub rel: f32,
}
/// A widget length plus its proportional claim on the space left after fixed
/// and relative lengths have been allocated.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LayoutLen {
pub abs: f32,
pub dp: f32,
pub rel: f32,
pub rest: f32,
}
@@ -26,8 +38,25 @@ impl<N: UiNum> From<N> for Len {
}
}
impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
fn from((x, y): (Nx, Ny)) -> Self {
impl<N: UiNum> From<N> for LayoutLen {
fn from(value: N) -> Self {
Self::abs(value.to_f32())
}
}
impl From<Len> for LayoutLen {
fn from(value: Len) -> Self {
Self {
abs: value.abs,
dp: value.dp,
rel: value.rel,
rest: 0.0,
}
}
}
impl<X: Into<LayoutLen>, Y: Into<LayoutLen>> From<(X, Y)> for Size {
fn from((x, y): (X, Y)) -> Self {
Self {
x: x.into(),
y: y.into(),
@@ -35,41 +64,47 @@ impl<Nx: UiNum, Ny: UiNum> From<(Nx, Ny)> for Size {
}
}
impl From<LayoutLen> for Size {
fn from(value: LayoutLen) -> Self {
Self { x: value, y: value }
}
}
impl From<Len> for Size {
fn from(value: Len) -> Self {
Self { x: value, y: value }
Self::from(LayoutLen::from(value))
}
}
impl Size {
pub const ZERO: Self = Self {
x: Len::ZERO,
y: Len::ZERO,
x: LayoutLen::ZERO,
y: LayoutLen::ZERO,
};
pub const REST: Self = Self {
x: Len::REST,
y: Len::REST,
x: LayoutLen::REST,
y: LayoutLen::REST,
};
pub fn abs(v: Vec2) -> Self {
Self {
x: Len::abs(v.x),
y: Len::abs(v.y),
x: LayoutLen::abs(v.x),
y: LayoutLen::abs(v.y),
}
}
pub fn rel(v: Vec2) -> Self {
Self {
x: Len::rel(v.x),
y: Len::rel(v.y),
x: LayoutLen::rel(v.x),
y: LayoutLen::rel(v.y),
}
}
pub fn rest(v: Vec2) -> Self {
Self {
x: Len::rest(v.x),
y: Len::rest(v.y),
x: LayoutLen::rest(v.x),
y: LayoutLen::rest(v.y),
}
}
@@ -80,7 +115,7 @@ impl Size {
}
}
pub fn from_axis(axis: Axis, aligned: Len, ortho: Len) -> Self {
pub fn from_axis(axis: Axis, aligned: LayoutLen, ortho: LayoutLen) -> Self {
match axis {
Axis::X => Self {
x: aligned,
@@ -93,7 +128,7 @@ impl Size {
}
}
pub fn axis(&self, axis: Axis) -> Len {
pub fn axis(&self, axis: Axis) -> LayoutLen {
match axis {
Axis::X => self.x,
Axis::Y => self.y,
@@ -101,7 +136,7 @@ impl Size {
}
}
impl Len {
impl LayoutLen {
pub const ZERO: Self = Self {
abs: 0.0,
dp: 0.0,
@@ -119,7 +154,7 @@ impl Len {
/// Resolves to a `UiScalar`, folding `dp` into `abs` pixels against
/// `density` (physical pixels per dp -- 1.0 on a desktop or an
/// unscaled display, `content_scale` on Android; see `dp`'s field
/// doc). Every other component of `Len` is already resolution-
/// doc). Every other component of `LayoutLen` is already resolution-
/// independent (`rel` is a fraction of the parent; `rest` becomes a
/// fraction too, below), so `density` only ever touches this one term.
pub fn apply_rest(&self, density: f32) -> UiScalar {
@@ -129,11 +164,11 @@ impl Len {
}
}
/// The same fold as [`Self::apply_rest`] but staying a `Len`, so
/// The same fold as [`Self::apply_rest`] but staying a `LayoutLen`, so
/// `rest` survives: `dp` becomes physical pixels and every other
/// component is left alone.
///
/// **A `Len` a widget *reports* must have been through this.** `dp` is
/// **A `LayoutLen` a widget *reports* must have been through this.** `dp` is
/// an input unit -- a number the widget author wrote -- and the
/// containers that consume a reported length read `abs`/`rel`/`rest`
/// directly (`Span::draw`'s placement arithmetic, `Pad`'s addition),
@@ -186,35 +221,68 @@ impl Len {
}
}
impl Len {
pub const ZERO: Self = Self {
abs: 0.0,
dp: 0.0,
rel: 0.0,
};
pub fn abs(abs: impl UiNum) -> Self {
Self {
abs: abs.to_f32(),
dp: 0.0,
rel: 0.0,
}
}
pub fn dp(dp: impl UiNum) -> Self {
Self {
abs: 0.0,
dp: dp.to_f32(),
rel: 0.0,
}
}
pub fn rel(rel: impl UiNum) -> Self {
Self {
abs: 0.0,
dp: 0.0,
rel: rel.to_f32(),
}
}
pub const fn fold_dp(self, density: f32) -> Self {
Self {
abs: self.abs + self.dp * density,
dp: 0.0,
rel: self.rel,
}
}
pub const fn resolve(self, density: f32) -> UiScalar {
let folded = self.fold_dp(density);
UiScalar {
rel: folded.rel,
abs: folded.abs,
}
}
}
pub mod len_fns {
use super::*;
pub fn abs(abs: impl UiNum) -> Len {
Len {
abs: abs.to_f32(),
dp: 0.0,
rel: 0.0,
rest: 0.0,
}
Len::abs(abs)
}
pub fn dp(dp: impl UiNum) -> Len {
Len {
abs: 0.0,
dp: dp.to_f32(),
rel: 0.0,
rest: 0.0,
}
Len::dp(dp)
}
pub fn rel(rel: impl UiNum) -> Len {
Len {
abs: 0.0,
dp: 0.0,
rel: rel.to_f32(),
rest: 0.0,
}
Len::rel(rel)
}
pub fn rest(ratio: impl UiNum) -> Len {
Len {
pub fn rest(ratio: impl UiNum) -> LayoutLen {
LayoutLen {
abs: 0.0,
dp: 0.0,
rel: 0.0,
@@ -223,25 +291,49 @@ pub mod len_fns {
}
}
impl_op!(Len Add add; abs dp rel rest);
impl_op!(Len Sub sub; abs dp rel rest);
impl_op!(LayoutLen Add add; abs dp rel rest);
impl_op!(LayoutLen Sub sub; abs dp rel rest);
impl_op!(Len Add add; abs dp rel);
impl_op!(Len Sub sub; abs dp rel);
impl std::ops::Add<Len> for LayoutLen {
type Output = Self;
fn add(self, rhs: Len) -> Self::Output {
self + Self::from(rhs)
}
}
impl std::ops::Sub<Len> for LayoutLen {
type Output = Self;
fn sub(self, rhs: Len) -> Self::Output {
self - Self::from(rhs)
}
}
impl_op!(Size Add add; x y);
impl_op!(Size Sub sub; x y);
impl Default for Len {
impl Default for LayoutLen {
fn default() -> Self {
Self::rest(1.0)
}
}
impl Default for Len {
fn default() -> Self {
Self::ZERO
}
}
impl std::fmt::Display for Size {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl std::fmt::Display for Len {
impl std::fmt::Display for LayoutLen {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.abs != 0.0 {
write!(f, "{} abs;", self.abs)?;
@@ -258,3 +350,55 @@ impl std::fmt::Display for Len {
Ok(())
}
}
impl std::fmt::Display for Len {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.abs != 0.0 {
write!(f, "{} abs;", self.abs)?;
}
if self.dp != 0.0 {
write!(f, "{} dp;", self.dp)?;
}
if self.rel != 0.0 {
write!(f, "{} rel;", self.rel)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_ordinary_length_enters_layout_without_claiming_rest() {
let layout = LayoutLen::from(Len {
abs: 3.0,
dp: 4.0,
rel: 0.5,
});
assert_eq!(layout.abs, 3.0);
assert_eq!(layout.dp, 4.0);
assert_eq!(layout.rel, 0.5);
assert_eq!(layout.rest, 0.0);
}
#[test]
fn ordinary_and_layout_lengths_keep_their_own_defaults() {
assert_eq!(Len::default(), Len::ZERO);
assert_eq!(LayoutLen::default(), LayoutLen::REST);
}
#[test]
fn adding_an_ordinary_length_preserves_a_layout_claim() {
assert_eq!(
LayoutLen::rest(2) + Len::dp(8),
LayoutLen {
abs: 0.0,
dp: 8.0,
rel: 0.0,
rest: 2.0,
}
);
}
}
+5 -5
View File
@@ -1,7 +1,7 @@
use crate::{
Axis, Len, MoveOffset, PaintId, RegionAlign, RenderedText, Size, StrongWidget, TextHandle,
TextResources, TextureHandle, UiData, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2,
WidgetId,
Axis, LayoutLen, MoveOffset, PaintId, RegionAlign, RenderedText, Size, StrongWidget,
TextHandle, TextResources, TextureHandle, UiData, UiRegion, UiRenderState, UiRsc, UiScalar,
UiVec2, WidgetId,
render::{
Drawn, GlyphPrimitive, IMAGE_BINDING, Mask, MaskIdx, MoveIdx, NOT_DRAWN, Primitive,
PrimitiveHandle, PrimitiveInst, RectPrimitive,
@@ -253,7 +253,7 @@ impl<'a> Painter<'a> {
}
}
pub fn known_len<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
pub fn known_len<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
let len = if let Some(len) = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis) {
Some(len.fold_dp(self.density()))
} else if !self.reuse_child_sizes[match axis {
@@ -480,7 +480,7 @@ impl<'a> Painter<'a> {
}
/// Physical pixels per `dp` -- see `UiRenderState::density`'s field
/// doc. What `Len::dp`'s `apply_rest` call resolves against.
/// doc. What `LayoutLen::dp`'s `apply_rest` call resolves against.
pub fn density(&self) -> f32 {
self.render_state.density
}
+3 -3
View File
@@ -31,7 +31,7 @@ pub struct UiRenderState {
pub primitives: Primitives,
pub layers: PrimitiveLayers,
pub(super) output_size: Vec2,
/// Physical pixels per `dp` -- see `Len::dp`'s field doc. `1.0` (an
/// Physical pixels per `dp` -- see `LayoutLen::dp`'s field doc. `1.0` (an
/// unscaled display) until a backend that knows its own density calls
/// `set_density` (Android's `content_scale`, read at `surface_changed`
/// time); the winit backend has no analogous per-monitor value wired up
@@ -207,7 +207,7 @@ impl UiRenderState {
self.resized = true;
}
/// Sets the physical-pixels-per-dp ratio every `Len::dp` in the tree
/// Sets the physical-pixels-per-dp ratio every `LayoutLen::dp` in the tree
/// resolves against from the next layout pass on -- see `density`'s
/// field doc. Not folded into `resize` because the two change on
/// different triggers (a surface resize on every rotation or keyboard
@@ -485,7 +485,7 @@ impl UiRenderState {
debug_assert!(
size.x.dp == 0.0 && size.y.dp == 0.0,
"widget {id:?} reported an unresolved `dp` size ({size:?}); \
report `Len::fold_dp(painter.density())` instead"
report `LayoutLen::fold_dp(painter.density())` instead"
);
for (axis, hint) in [Axis::X, Axis::Y]
.into_iter()
+4 -4
View File
@@ -1,4 +1,4 @@
use crate::{Axis, Len, Painter, Size};
use crate::{Axis, LayoutLen, Painter, Size};
use std::any::Any;
mod data;
@@ -28,7 +28,7 @@ pub enum ChildOrder {
pub trait Widget: Any {
fn draw(&mut self, painter: &mut Painter);
fn size_hint(&self, _axis: Axis) -> Option<Len> {
fn size_hint(&self, _axis: Axis) -> Option<LayoutLen> {
None
}
@@ -63,8 +63,8 @@ impl Widget for () {
true
}
fn size_hint(&self, _axis: Axis) -> Option<Len> {
Some(Len::ZERO)
fn size_hint(&self, _axis: Axis) -> Option<LayoutLen> {
Some(LayoutLen::ZERO)
}
}