iris: separate layout allocation from lengths
This commit is contained in:
1 parent
2b9d8c49a0
commit
1f15125992
17 files changed
+395
-168
No files matched your search
+189
-45
@@ -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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user