Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ebd4d3a05 | ||
|
|
7e2b4cd9db | ||
|
|
3da1c71870 | ||
|
|
6c84b6f2cb | ||
|
|
8d2b7a512b | ||
|
|
40b89c1f79 | ||
|
|
4d42f1c8ca | ||
|
|
2807a925af | ||
|
|
55df32a33c | ||
|
|
58ce74dd7d | ||
|
|
c55be21761 | ||
|
|
beb138632a | ||
|
|
aeb60e50f5 | ||
|
|
a904cf4f36 | ||
|
|
5642f2010a | ||
|
|
84dad211f5 | ||
|
|
add6774980 | ||
|
|
cadfba05dd | ||
|
|
38b3a81053 |
No files matched your search
@@ -14,3 +14,13 @@ WidgetRef<W> or smth instead of Id
|
|||||||
vecs for each widget type?
|
vecs for each widget type?
|
||||||
|
|
||||||
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
POTENTIAL BUG: closures that store IDs will not decrement the id!!! need to not increment id if moved into closure somehow??? wait no, need to decrement ID every time an event fn is added...... only if the id is used in it..??
|
||||||
|
|
||||||
|
transforms on a move entry (scale + rotation)
|
||||||
|
an entry is a translation today; composing through one scales the rel
|
||||||
|
part and passes px through untouched, so fixed-size content and glyphs
|
||||||
|
do not follow a shortened entry
|
||||||
|
want a real transform per entry, resolved in resolve_move the way the
|
||||||
|
translation already is, so a whole subtree transforms with one buffer
|
||||||
|
write and no redraw
|
||||||
|
wanted for compose-style stretch at the end of a scroll area, and for
|
||||||
|
rotation generally
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
//! reuse, size, placement, and text events for one suspicious widget. The
|
//! reuse, size, placement, and text events for one suspicious widget. The
|
||||||
//! selection is a set and survives [`take`] until cleared.
|
//! selection is a set and survives [`take`] until cleared.
|
||||||
|
|
||||||
use crate::{Axis, LayoutLen, PxVec2, Size, UiRegion, WidgetId};
|
use crate::{Axis, LayoutHolds, LayoutLen, PxVec2, Size, UiRegion, UiVec2, WidgetId};
|
||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
@@ -53,12 +53,12 @@ pub(crate) enum Counter {
|
|||||||
TextBreaks,
|
TextBreaks,
|
||||||
GlyphPlacements,
|
GlyphPlacements,
|
||||||
OutsidePinnedLen,
|
OutsidePinnedLen,
|
||||||
OutsideFrame,
|
OutsideRelBase,
|
||||||
OutsideExtent,
|
OutsideRegion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Counter {
|
impl Counter {
|
||||||
const COUNT: usize = Self::OutsideExtent as usize + 1;
|
const COUNT: usize = Self::OutsideRegion as usize + 1;
|
||||||
|
|
||||||
const NAMES: [&'static str; Self::COUNT] = [
|
const NAMES: [&'static str; Self::COUNT] = [
|
||||||
"updates",
|
"updates",
|
||||||
@@ -89,8 +89,8 @@ impl Counter {
|
|||||||
"text line breaks",
|
"text line breaks",
|
||||||
"glyph placements",
|
"glyph placements",
|
||||||
"reuse outside: the length it was pinned to",
|
"reuse outside: the length it was pinned to",
|
||||||
"reuse outside: a frame length",
|
"reuse outside: a rel base",
|
||||||
"reuse outside: an extent length",
|
"reuse outside: a region length",
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +264,7 @@ pub enum TraceEvent {
|
|||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
parent: Option<WidgetId>,
|
parent: Option<WidgetId>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
pixel_size: PxVec2,
|
region_px: PxVec2,
|
||||||
region_node: bool,
|
region_node: bool,
|
||||||
},
|
},
|
||||||
Reuse {
|
Reuse {
|
||||||
@@ -360,7 +360,7 @@ pub(crate) fn draw_request(
|
|||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
parent: Option<WidgetId>,
|
parent: Option<WidgetId>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
pixel_size: PxVec2,
|
region_px: PxVec2,
|
||||||
region_node: bool,
|
region_node: bool,
|
||||||
) {
|
) {
|
||||||
trace(
|
trace(
|
||||||
@@ -369,7 +369,7 @@ pub(crate) fn draw_request(
|
|||||||
id,
|
id,
|
||||||
parent,
|
parent,
|
||||||
region,
|
region,
|
||||||
pixel_size,
|
region_px,
|
||||||
region_node,
|
region_node,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -379,6 +379,39 @@ pub(crate) fn reuse(id: WidgetId, outcome: ReuseOutcome) {
|
|||||||
trace(id, TraceEvent::Reuse { id, outcome });
|
trace(id, TraceEvent::Reuse { id, outcome });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A drawing that cannot be reused because the box on offer is outside what
|
||||||
|
/// it holds for, and which of the three contracts said so. They overlap: a
|
||||||
|
/// drawing can be outside two of them at once, and counting each is what
|
||||||
|
/// says where a rel base redrawing more than it should is coming from.
|
||||||
|
pub(crate) fn outside(
|
||||||
|
id: WidgetId,
|
||||||
|
holds: LayoutHolds,
|
||||||
|
region: UiRegion,
|
||||||
|
rel_base: UiVec2,
|
||||||
|
window: PxVec2,
|
||||||
|
) {
|
||||||
|
for axis in Axis::BOTH {
|
||||||
|
let holds = holds[axis];
|
||||||
|
let len = region[axis].len();
|
||||||
|
let window = window[axis];
|
||||||
|
if holds.region_len.is_some_and(|pinned| pinned != len) {
|
||||||
|
bump(Counter::OutsidePinnedLen);
|
||||||
|
}
|
||||||
|
if !holds.window.contains(window)
|
||||||
|
|| holds
|
||||||
|
.rel_base
|
||||||
|
.is_some_and(|pinned| pinned != rel_base[axis])
|
||||||
|
{
|
||||||
|
bump(Counter::OutsideRelBase);
|
||||||
|
}
|
||||||
|
if !holds.region.contains(len.to_px(window)) {
|
||||||
|
bump(Counter::OutsideRegion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bump(Counter::ReuseOutside);
|
||||||
|
reuse(id, ReuseOutcome::Outside);
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn size_reported(id: WidgetId, size: Size) {
|
pub(crate) fn size_reported(id: WidgetId, size: Size) {
|
||||||
trace(id, TraceEvent::SizeReported { id, size });
|
trace(id, TraceEvent::SizeReported { id, size });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#![feature(unsize)]
|
#![feature(unsize)]
|
||||||
#![feature(coerce_unsized)]
|
#![feature(coerce_unsized)]
|
||||||
#![feature(option_into_flat_iter)]
|
#![feature(option_into_flat_iter)]
|
||||||
|
#![feature(const_index)]
|
||||||
|
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
pub mod layout_diagnostics;
|
pub mod layout_diagnostics;
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::util::impl_axis_index;
|
||||||
use crate::{Px, Rel};
|
use crate::{Px, Rel};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -89,20 +90,6 @@ impl RegionAlign {
|
|||||||
x: AxisAlign::NEG,
|
x: AxisAlign::NEG,
|
||||||
y: AxisAlign::NEG,
|
y: AxisAlign::NEG,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn axis(&self, axis: Axis) -> AxisAlign {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut AxisAlign {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegionAlign {
|
impl RegionAlign {
|
||||||
@@ -231,3 +218,5 @@ impl RegionAlign {
|
|||||||
UiVec2::from(self)
|
UiVec2::from(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_axis_index!(RegionAlign => AxisAlign);
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::util::impl_axis_index;
|
||||||
use crate::{Fixed, FixedVec2};
|
use crate::{Fixed, FixedVec2};
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||||
@@ -8,6 +9,9 @@ pub enum Axis {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Axis {
|
impl Axis {
|
||||||
|
/// Both of them, for the layout code that asks the same question of each.
|
||||||
|
pub const BOTH: [Self; 2] = [Self::X, Self::Y];
|
||||||
|
|
||||||
/// A per-axis pair with `aligned` on this axis and `ortho` on the other,
|
/// A per-axis pair with `aligned` on this axis and `ortho` on the other,
|
||||||
/// which is what `from_axis` does for a vector.
|
/// which is what `from_axis` does for a vector.
|
||||||
pub fn pair<T>(self, aligned: T, ortho: T) -> [T; 2] {
|
pub fn pair<T>(self, aligned: T, ortho: T) -> [T; 2] {
|
||||||
@@ -53,20 +57,6 @@ pub enum Sign {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<const SHIFT: u32> FixedVec2<SHIFT> {
|
impl<const SHIFT: u32> FixedVec2<SHIFT> {
|
||||||
pub const fn axis(&self, axis: Axis) -> Fixed<SHIFT> {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn axis_mut(&mut self, axis: Axis) -> &mut Fixed<SHIFT> {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from_axis(axis: Axis, aligned: Fixed<SHIFT>, ortho: Fixed<SHIFT>) -> Self {
|
pub const fn from_axis(axis: Axis, aligned: Fixed<SHIFT>, ortho: Fixed<SHIFT>) -> Self {
|
||||||
match axis {
|
match axis {
|
||||||
Axis::X => Self::new(aligned, ortho),
|
Axis::X => Self::new(aligned, ortho),
|
||||||
@@ -76,20 +66,6 @@ impl<const SHIFT: u32> FixedVec2<SHIFT> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Vec2 {
|
impl Vec2 {
|
||||||
pub fn axis(&self, axis: Axis) -> f32 {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut f32 {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
|
pub const fn from_axis(axis: Axis, aligned: f32, ortho: f32) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: match axis {
|
x: match axis {
|
||||||
@@ -148,3 +124,6 @@ impl<T> BothAxis<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_axis_index!({const SHIFT: u32} FixedVec2<SHIFT> => Fixed<SHIFT>);
|
||||||
|
impl_axis_index!(Vec2 => f32);
|
||||||
+32
-15
@@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::util::impl_axis_index;
|
||||||
use crate::{Px, PxVec2, Rel, UiNum, Weight, util::impl_op};
|
use crate::{Px, PxVec2, Rel, UiNum, Weight, util::impl_op};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
||||||
@@ -118,20 +119,6 @@ impl Size {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn axis(&self, axis: Axis) -> LayoutLen {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut LayoutLen {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutLen {
|
impl LayoutLen {
|
||||||
@@ -158,11 +145,39 @@ impl LayoutLen {
|
|||||||
Len::from_parts(self.rel.add(share), self.px)
|
Len::from_parts(self.rel.add(share), self.px)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Only pixels: the same number of them whatever box it lands in, and
|
||||||
|
/// whatever anyone else in the row asks for. A length that is any part
|
||||||
|
/// of a box or of what is left over is not one.
|
||||||
|
pub fn is_px(self) -> bool {
|
||||||
|
self.rel == Rel::ZERO && self.leftover == Weight::ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Nothing but a claim on what is left over, so there is no length here
|
||||||
|
/// at all where nothing is.
|
||||||
|
pub fn is_only_leftover(self) -> bool {
|
||||||
|
self.leftover > Weight::ZERO && self.without_leftover() == Len::ZERO
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This as a length of a box, where it is one. `leftover` is not: a
|
||||||
|
/// share of what is left over is a length only to whoever divides one,
|
||||||
|
/// so it passes up in the reported size instead and is resolved there.
|
||||||
|
pub fn declared(self) -> Option<Len> {
|
||||||
|
(self.leftover == Weight::ZERO).then(|| self.without_leftover())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What this takes whatever is left over: the reading of a length for
|
||||||
|
/// anyone not dividing a box between siblings, where a share is a claim
|
||||||
|
/// on someone else's room rather than a length of its own.
|
||||||
|
/// [`Self::apply_leftover`] is the opposite reading of the same value.
|
||||||
|
pub const fn without_leftover(self) -> Len {
|
||||||
|
Len::from_parts(self.rel, self.px)
|
||||||
|
}
|
||||||
|
|
||||||
/// This length, given as a part of a box `len` long, as a part of the
|
/// This length, given as a part of a box `len` long, as a part of the
|
||||||
/// box `len` is itself a part of. The share is untouched: it is a claim
|
/// box `len` is itself a part of. The share is untouched: it is a claim
|
||||||
/// on whoever divides the room, not a fraction of anything.
|
/// on whoever divides the room, not a fraction of anything.
|
||||||
pub const fn within_len(self, len: Len) -> Self {
|
pub const fn within_len(self, len: Len) -> Self {
|
||||||
let part = Len::from_parts(self.rel, self.px).within_len(len);
|
let part = self.without_leftover().within_len(len);
|
||||||
Self {
|
Self {
|
||||||
px: part.px,
|
px: part.px,
|
||||||
rel: part.rel,
|
rel: part.rel,
|
||||||
@@ -236,3 +251,5 @@ impl std::fmt::Display for LayoutLen {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_axis_index!(Size => LayoutLen);
|
||||||
+13
-40
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::util::impl_axis_index;
|
||||||
use std::{fmt::Display, marker::Destruct};
|
use std::{fmt::Display, marker::Destruct};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -61,20 +62,6 @@ impl UiVec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis(&self, axis: Axis) -> Len {
|
|
||||||
match axis {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Resolved against a box of `size`, which is where a fraction stops
|
/// Resolved against a box of `size`, which is where a fraction stops
|
||||||
/// being one and becomes a place.
|
/// being one and becomes a place.
|
||||||
pub fn to_px(&self, size: PxVec2) -> PxVec2 {
|
pub fn to_px(&self, size: PxVec2) -> PxVec2 {
|
||||||
@@ -176,14 +163,6 @@ impl Len {
|
|||||||
Self::from_parts(Rel::ZERO, Px::from_f32(px))
|
Self::from_parts(Rel::ZERO, Px::from_f32(px))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn rel_min() -> Self {
|
|
||||||
Self::ZERO
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn rel_max() -> Self {
|
|
||||||
Self::FULL
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn max(&self, other: Self) -> Self {
|
pub const fn max(&self, other: Self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rel: self.rel.max(other.rel),
|
rel: self.rel.max(other.rel),
|
||||||
@@ -226,10 +205,6 @@ impl Len {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_len(&self, len: Len) -> Self {
|
|
||||||
len.within_len(*self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn flip(&mut self) {
|
pub const fn flip(&mut self) {
|
||||||
self.rel = Rel::ONE.sub(self.rel);
|
self.rel = Rel::ONE.sub(self.rel);
|
||||||
self.px = self.px.neg();
|
self.px = self.px.neg();
|
||||||
@@ -294,6 +269,15 @@ impl UiSpan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A box `len` long inside this one, on the side `align` says. Both must
|
||||||
|
/// be lengths of the same rel base: it subtracts one from the other
|
||||||
|
/// rather than composing it in, which is what keeps a fraction the same
|
||||||
|
/// fraction however long this box turns out to be.
|
||||||
|
pub const fn place(self, len: Len, align: AxisAlign) -> Self {
|
||||||
|
let start = self.start + (self.len() - len).scale(align.rel());
|
||||||
|
Self::new(start, start + len)
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn len(&self) -> Len {
|
pub const fn len(&self) -> Len {
|
||||||
self.end - self.start
|
self.end - self.start
|
||||||
}
|
}
|
||||||
@@ -348,20 +332,6 @@ impl UiRegion {
|
|||||||
y: self.y.within(&parent.y),
|
y: self.y.within(&parent.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub const fn axis(&self, axis: Axis) -> &UiSpan {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &self.x,
|
|
||||||
Axis::Y => &self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn axis_mut(&mut self, axis: Axis) -> &mut UiSpan {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn flip(&mut self, axis: Axis) {
|
pub const fn flip(&mut self, axis: Axis) {
|
||||||
match axis {
|
match axis {
|
||||||
Axis::X => self.x.flip(),
|
Axis::X => self.x.flip(),
|
||||||
@@ -462,3 +432,6 @@ impl Display for PixelRegion {
|
|||||||
write!(f, "{} -> {}", self.top_left, self.bot_right)
|
write!(f, "{} -> {}", self.top_left, self.bot_right)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_axis_index!(UiVec2 => Len);
|
||||||
|
impl_axis_index!(UiRegion => UiSpan);
|
||||||
+35
-25
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
LayerId, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx, Place, RegionAlign, RetainedPrimitive,
|
Declared, LayerId, LayoutHolds, MaskIdx, MoveIdx, PlaceDesc, RegionAlign, RetainedPrimitive,
|
||||||
Size, TextureHandle, UiRegion, UiVec2, WidgetId,
|
Size, TextureHandle, UiRegion, UiVec2, WidgetId,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -10,29 +10,24 @@ use crate::{
|
|||||||
pub struct ActiveData {
|
pub struct ActiveData {
|
||||||
pub id: WidgetId,
|
pub id: WidgetId,
|
||||||
/// Where its drawing goes, in its region node's coordinates.
|
/// Where its drawing goes, in its region node's coordinates.
|
||||||
pub extent: UiRegion,
|
pub placement: UiRegion,
|
||||||
/// What a fraction declared or reported under this widget is a fraction
|
/// What a fraction declared or reported under this widget is a fraction
|
||||||
/// of, as a length of the window.
|
/// of, as a length of the window.
|
||||||
pub frame: UiVec2,
|
pub rel_base: UiVec2,
|
||||||
/// A frame its parent decided for it on each axis -- a row's slot, or
|
/// Where its drawing was put, and where it was asked. The two differ
|
||||||
/// padding's frame less its pixels -- as a length of the window. `None`
|
/// where a container asks in one place and puts the answer in another --
|
||||||
/// forwards the parent's frame. What it declared is kept separately in
|
/// a row measures from its cursor and puts the child in its slot. Each
|
||||||
/// `declared` and is a fraction of whichever of the two reached it.
|
/// carries the rel base that ask stated, so asking again from either is
|
||||||
pub narrow: [Option<Len>; 2],
|
/// the same question it was.
|
||||||
/// Where its drawing was put, and where it was asked, each as a part of
|
pub placed: PlaceDesc,
|
||||||
/// its parent's box. The two differ where a container asks in one place
|
pub asked: PlaceDesc,
|
||||||
/// and puts the answer in another -- a row measures from its cursor and
|
|
||||||
/// puts the child in its slot. A part is a length from the box's start,
|
|
||||||
/// so a box that moved re-places every child by re-adding that start.
|
|
||||||
pub placed: [Place; 2],
|
|
||||||
pub asked: [Place; 2],
|
|
||||||
/// The box it was asked in, in the parent's region-node coordinates: the
|
/// The box it was asked in, in the parent's region-node coordinates: the
|
||||||
/// box its drawing was made in and the one its contract is about. Its
|
/// box its drawing was made in and the one its contract is about. Its
|
||||||
/// drawing is placed elsewhere by re-expression, never by asking again.
|
/// drawing is placed elsewhere by re-expression, never by asking again.
|
||||||
pub part: UiRegion,
|
pub region: UiRegion,
|
||||||
/// The measured answer and its dependencies. A hint-only dependency or
|
/// The measured answer and its dependencies. A hint-only dependency or
|
||||||
/// a widget first encountered during placement has no measurement yet.
|
/// a widget first encountered during placement has no measurement yet.
|
||||||
pub answer: Option<(Size, LayoutHolds)>,
|
pub answer: Option<Answer>,
|
||||||
/// Asked more than once in its parent's last draw -- measured in one box
|
/// Asked more than once in its parent's last draw -- measured in one box
|
||||||
/// and then asked in the one the parent decided. The parent's layout
|
/// and then asked in the one the parent decided. The parent's layout
|
||||||
/// rests on the first answer and its drawing on the last, so only the
|
/// rests on the first answer and its drawing on the last, so only the
|
||||||
@@ -40,8 +35,8 @@ pub struct ActiveData {
|
|||||||
pub re_asked: bool,
|
pub re_asked: bool,
|
||||||
/// What the widget reported, in window-unit lengths.
|
/// What the widget reported, in window-unit lengths.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
/// The window and extent reads that this drawing holds for, and the
|
/// The window and region reads that this drawing holds for, and the
|
||||||
/// frame and box it pinned.
|
/// rel base and region it pinned.
|
||||||
pub holds: LayoutHolds,
|
pub holds: LayoutHolds,
|
||||||
pub drawn: bool,
|
pub drawn: bool,
|
||||||
pub parent: Option<WidgetId>,
|
pub parent: Option<WidgetId>,
|
||||||
@@ -51,8 +46,9 @@ pub struct ActiveData {
|
|||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
pub textures: Vec<TextureHandle>,
|
pub textures: Vec<TextureHandle>,
|
||||||
/// Its primitives, each keeping the box it was written in -- in this
|
/// Its primitives, each keeping the box it was written in -- in this
|
||||||
/// widget's extent coordinates, which is what a move recomposes from.
|
/// widget's placement coordinates, which is what a move recomposes from.
|
||||||
pub primitives: Vec<RetainedPrimitive>,
|
pub primitives: Vec<RetainedPrimitive>,
|
||||||
|
/// An owned mask holds one reference independently of its primitives.
|
||||||
pub mask_region: Option<UiRegion>,
|
pub mask_region: Option<UiRegion>,
|
||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
@@ -60,15 +56,15 @@ pub struct ActiveData {
|
|||||||
/// The movable region its primitives are positioned through: its own when
|
/// The movable region its primitives are positioned through: its own when
|
||||||
/// opted in, otherwise the nearest ancestor's.
|
/// opted in, otherwise the nearest ancestor's.
|
||||||
pub move_idx: MoveIdx,
|
pub move_idx: MoveIdx,
|
||||||
/// The declared lengths whoever drew this widget resolved into its frame.
|
/// The declared lengths whoever drew this widget resolved into its rel base.
|
||||||
/// A change to one moves a box this widget cannot fix by drawing again,
|
/// A change to one moves a box this widget cannot fix by drawing again,
|
||||||
/// and comparing them is what says so.
|
/// and comparing them is what says so.
|
||||||
pub declared: [Option<LayoutLen>; 2],
|
pub declared: Declared,
|
||||||
/// Its alignment when it was last drawn, which a change to the property
|
/// Its alignment when it was last drawn, which a change to the property
|
||||||
/// is found against.
|
/// is found against.
|
||||||
pub own_align: RegionAlign,
|
pub own_align: RegionAlign,
|
||||||
/// The movable region whose coordinates `extent` uses when this widget
|
/// The movable region whose coordinates its placement is in when this
|
||||||
/// does not own a region node.
|
/// widget does not own a region node.
|
||||||
pub parent_move: MoveIdx,
|
pub parent_move: MoveIdx,
|
||||||
/// The mask its drawing is clipped to: one it set itself, or the one it
|
/// The mask its drawing is clipped to: one it set itself, or the one it
|
||||||
/// inherited from whoever drew it.
|
/// inherited from whoever drew it.
|
||||||
@@ -86,6 +82,20 @@ impl ActiveData {
|
|||||||
/// all. Not `size`, which is what its last drawing reported: a drawing
|
/// all. Not `size`, which is what its last drawing reported: a drawing
|
||||||
/// re-expressed in the box that answer chose is not a second answer.
|
/// re-expressed in the box that answer chose is not a second answer.
|
||||||
pub fn measured(&self) -> Option<Size> {
|
pub fn measured(&self) -> Option<Size> {
|
||||||
self.answer.map(|(size, _)| size)
|
self.answer.map(|answer| answer.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether it owns a region node rather than sharing the one it was drawn
|
||||||
|
/// under, which is what its two move indices being different says.
|
||||||
|
pub fn is_region_node(&self) -> bool {
|
||||||
|
self.move_idx != self.parent_move
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a widget answered when it was asked: the size it reported, and the
|
||||||
|
/// boxes and windows that answer holds for.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Answer {
|
||||||
|
pub size: Size,
|
||||||
|
pub holds: LayoutHolds,
|
||||||
|
}
|
||||||
@@ -33,6 +33,12 @@ impl Holds {
|
|||||||
len.raw() >= self.lo.raw() && len.raw() <= self.hi.raw()
|
len.raw() >= self.lo.raw() && len.raw() <= self.hi.raw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every length `other` holds for is one this holds for, so a drawing
|
||||||
|
/// made under this range is still good wherever `other` is.
|
||||||
|
pub const fn covers(self, other: Self) -> bool {
|
||||||
|
self.lo.raw() <= other.lo.raw() && self.hi.raw() >= other.hi.raw()
|
||||||
|
}
|
||||||
|
|
||||||
pub const fn and(self, other: Self) -> Self {
|
pub const fn and(self, other: Self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
lo: self.lo.max(other.lo),
|
lo: self.lo.max(other.lo),
|
||||||
|
|||||||
+82
-53
@@ -1,79 +1,108 @@
|
|||||||
use crate::{Axis, Holds, Len, PxVec2, UiRegion, UiVec2};
|
use crate::util::impl_axis_index;
|
||||||
|
use crate::{Axis, Holds, Len, Px, PxVec2, UiRegion, UiVec2};
|
||||||
|
|
||||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
/// What one evaluation of a widget depends on along one axis: the window
|
||||||
|
/// lengths its reads hold for, the pixel lengths of its own box, and the
|
||||||
/// What one evaluation of a widget depends on: the window lengths its reads
|
/// symbolic lengths of that box and of its rel base where either one is what
|
||||||
/// hold for, the pixel lengths of its own box, and the symbolic lengths of
|
/// it was expressed in.
|
||||||
/// that box and of its frame where either one is what it was expressed in.
|
|
||||||
///
|
///
|
||||||
/// The symbolic lengths are pins rather than ranges: a container places its
|
/// The symbolic lengths are pins rather than ranges: a container places its
|
||||||
/// children as lengths of its frame measured from where its own box starts,
|
/// children as lengths of its rel base measured from where its own box starts,
|
||||||
/// so what it draws turns on that box's length and on nothing about where it
|
/// so what it draws turns on that box's length and on nothing about where it
|
||||||
/// is. A box pin reaches the parent only where the box it pinned is the
|
/// is. A box pin reaches the parent only where the box it pinned is the
|
||||||
/// parent's own; anywhere else the parent chose that length itself, and a
|
/// parent's own; anywhere else the parent chose that length itself, and a
|
||||||
/// widget pinned this way is checked when it is re-placed.
|
/// widget pinned this way is checked when it is re-placed.
|
||||||
///
|
///
|
||||||
/// A frame pin says the answer or the drawing is a fraction of the frame,
|
/// A rel base pin says the answer or the drawing is a fraction of the rel base,
|
||||||
/// which is a different length wherever the frame is a different one -- at
|
/// which is a different length wherever the rel base is a different one -- at
|
||||||
/// the same window size, so no range of window pixels can say it. A length
|
/// the same window size, so no range of window pixels can say it. A length
|
||||||
/// of the frame that is only pixels is not one: it is that many pixels
|
/// of the rel base that is only pixels is not one: it is that many pixels
|
||||||
/// whatever the frame turns out to be.
|
/// whatever the rel base turns out to be.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct AxisHolds {
|
||||||
|
pub window: Holds,
|
||||||
|
pub rel_base: Option<Len>,
|
||||||
|
pub region: Holds,
|
||||||
|
pub region_len: Option<Len>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AxisHolds {
|
||||||
|
pub const ANY: Self = Self {
|
||||||
|
window: Holds::ANY,
|
||||||
|
rel_base: None,
|
||||||
|
region: Holds::ANY,
|
||||||
|
region_len: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn and(self, other: Self) -> Self {
|
||||||
|
// Two pins of the same length disagreeing would mean one drawing was
|
||||||
|
// a fraction of two different lengths at once.
|
||||||
|
debug_assert!(
|
||||||
|
self.region_len.is_none()
|
||||||
|
|| other.region_len.is_none()
|
||||||
|
|| self.region_len == other.region_len
|
||||||
|
);
|
||||||
|
debug_assert!(
|
||||||
|
self.rel_base.is_none() || other.rel_base.is_none() || self.rel_base == other.rel_base
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
window: self.window.and(other.window),
|
||||||
|
rel_base: self.rel_base.or(other.rel_base),
|
||||||
|
region: self.region.and(other.region),
|
||||||
|
region_len: self.region_len.or(other.region_len),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn covers(self, other: Self) -> bool {
|
||||||
|
self.window.covers(other.window)
|
||||||
|
&& self.region.covers(other.region)
|
||||||
|
&& self
|
||||||
|
.region_len
|
||||||
|
.is_none_or(|len| other.region_len == Some(len))
|
||||||
|
&& self.rel_base.is_none_or(|len| other.rel_base == Some(len))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether a widget in a box `len` long, with that rel base, in that
|
||||||
|
/// window, is one this drawing holds for.
|
||||||
|
pub fn contains(self, window: Px, rel_base: Len, len: Len) -> bool {
|
||||||
|
self.window.contains(window)
|
||||||
|
&& self.rel_base.is_none_or(|pinned| pinned == rel_base)
|
||||||
|
&& self.region.contains(len.to_px(window))
|
||||||
|
&& self.region_len.is_none_or(|pinned| pinned == len)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// [`AxisHolds`] on both axes. Every question asked of it is asked of one
|
||||||
|
/// axis at a time, since a widget that read one length holds for any length
|
||||||
|
/// of the other.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct LayoutHolds {
|
pub struct LayoutHolds {
|
||||||
pub window: [Holds; 2],
|
pub x: AxisHolds,
|
||||||
pub frame_len: [Option<Len>; 2],
|
pub y: AxisHolds,
|
||||||
pub extent: [Holds; 2],
|
|
||||||
pub extent_len: [Option<Len>; 2],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutHolds {
|
impl LayoutHolds {
|
||||||
pub const ANY: Self = Self {
|
pub const ANY: Self = Self {
|
||||||
window: [Holds::ANY; 2],
|
x: AxisHolds::ANY,
|
||||||
frame_len: [None; 2],
|
y: AxisHolds::ANY,
|
||||||
extent: [Holds::ANY; 2],
|
|
||||||
extent_len: [None; 2],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn and(self, other: Self) -> Self {
|
pub fn and(self, other: Self) -> Self {
|
||||||
let mut result = Self::ANY;
|
Self {
|
||||||
for n in 0..2 {
|
x: self.x.and(other.x),
|
||||||
result.window[n] = self.window[n].and(other.window[n]);
|
y: self.y.and(other.y),
|
||||||
result.extent[n] = self.extent[n].and(other.extent[n]);
|
|
||||||
debug_assert!(
|
|
||||||
self.extent_len[n].is_none()
|
|
||||||
|| other.extent_len[n].is_none()
|
|
||||||
|| self.extent_len[n] == other.extent_len[n]
|
|
||||||
);
|
|
||||||
debug_assert!(
|
|
||||||
self.frame_len[n].is_none()
|
|
||||||
|| other.frame_len[n].is_none()
|
|
||||||
|| self.frame_len[n] == other.frame_len[n]
|
|
||||||
);
|
|
||||||
result.extent_len[n] = self.extent_len[n].or(other.extent_len[n]);
|
|
||||||
result.frame_len[n] = self.frame_len[n].or(other.frame_len[n]);
|
|
||||||
}
|
}
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn covers(self, other: Self) -> bool {
|
pub fn covers(self, other: Self) -> bool {
|
||||||
(0..2).all(|n| {
|
self.x.covers(other.x) && self.y.covers(other.y)
|
||||||
self.window[n].lo <= other.window[n].lo
|
|
||||||
&& self.window[n].hi >= other.window[n].hi
|
|
||||||
&& self.extent[n].lo <= other.extent[n].lo
|
|
||||||
&& self.extent[n].hi >= other.extent[n].hi
|
|
||||||
&& self.extent_len[n].is_none_or(|len| other.extent_len[n] == Some(len))
|
|
||||||
&& self.frame_len[n].is_none_or(|len| other.frame_len[n] == Some(len))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(self, window: PxVec2, frame: UiVec2, extent: UiRegion) -> bool {
|
pub fn contains(self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
|
||||||
AXES.into_iter().all(|axis| {
|
Axis::BOTH
|
||||||
let n = axis as usize;
|
.into_iter()
|
||||||
let len = extent.axis(axis).len();
|
.all(|axis| self[axis].contains(window[axis], rel_base[axis], region[axis].len()))
|
||||||
self.window[n].contains(window.axis(axis))
|
|
||||||
&& self.frame_len[n].is_none_or(|pinned| pinned == frame.axis(axis))
|
|
||||||
&& self.extent[n].contains(len.to_px(window.axis(axis)))
|
|
||||||
&& self.extent_len[n].is_none_or(|pinned| pinned == len)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_axis_index!(LayoutHolds => AxisHolds);
|
||||||
+1
-1
@@ -20,7 +20,7 @@ pub use active::*;
|
|||||||
pub use holds::*;
|
pub use holds::*;
|
||||||
pub use layout_holds::*;
|
pub use layout_holds::*;
|
||||||
pub use painter::{Painter, PrimitiveLike};
|
pub use painter::{Painter, PrimitiveLike};
|
||||||
pub use place::*;
|
pub use place::{PlaceDesc, PlaceDescAxis, RetainedPrimitive};
|
||||||
pub use render_state::*;
|
pub use render_state::*;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|||||||
+260
-251
@@ -1,33 +1,32 @@
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
use crate::layout_diagnostics::{self as diag, Counter};
|
use crate::layout_diagnostics::{self as diag, Counter};
|
||||||
use crate::{
|
use crate::{
|
||||||
Axis, Holds, LayoutHolds, LayoutLen, Len, Part, Place, Px, PxVec2, RegionAlign, Rel,
|
Axis, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, Px, PxVec2, RegionAlign, Rel,
|
||||||
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
RenderedText, RetainedPrimitive, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
||||||
render::{
|
render::{
|
||||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveInst, PrimitiveKind,
|
||||||
TexturePrimitive,
|
TexturePrimitive,
|
||||||
},
|
},
|
||||||
ui::render_state::{DrawInfo, Placing},
|
ui::{
|
||||||
|
place::{PlaceSpan, RelBase},
|
||||||
|
render_state::{DrawInfo, Placing},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
|
||||||
|
|
||||||
/// makes your surfaces look pretty
|
/// makes your surfaces look pretty
|
||||||
pub struct Painter<'a> {
|
pub struct Painter<'a> {
|
||||||
pub(super) state: &'a mut UiRenderState,
|
pub(super) state: &'a mut UiRenderState,
|
||||||
pub(super) rsc: &'a mut dyn UiRsc,
|
pub(super) rsc: &'a mut dyn UiRsc,
|
||||||
|
|
||||||
/// This widget's frame, per axis: a length of the window, and what a
|
/// This widget's rel base, per axis: a length of the window, and what a
|
||||||
/// fraction it or anything under it declares or reports is a fraction
|
/// fraction it or anything under it declares or reports is a fraction
|
||||||
/// of. A length rather than a box, so padding can take from both the
|
/// of. A length rather than a box, so padding can take from both the
|
||||||
/// frame and the box without either becoming the other.
|
/// rel base and the box without either becoming the other.
|
||||||
pub(super) frame: UiVec2,
|
pub(super) rel_base: UiVec2,
|
||||||
/// Where this widget's drawing goes, in its region node's coordinates.
|
/// The box this widget was asked in, in its region node's coordinates:
|
||||||
pub(super) extent: UiRegion,
|
/// what it draws in, and what its children's places are parts of.
|
||||||
/// The extent's symbolic length where this draw read it, which makes the
|
pub(super) region: UiRegion,
|
||||||
/// drawing one that holds for that length alone -- the way reading a
|
|
||||||
/// length in pixels makes it hold for that number of pixels.
|
|
||||||
pub(super) extent_len: [Option<Len>; 2],
|
|
||||||
/// The window in pixels. Frames and boxes become pixels against this one
|
/// The window in pixels. Frames and boxes become pixels against this one
|
||||||
/// unit, regardless of region-node boundaries.
|
/// unit, regardless of region-node boundaries.
|
||||||
pub(super) window: PxVec2,
|
pub(super) window: PxVec2,
|
||||||
@@ -35,19 +34,19 @@ pub struct Painter<'a> {
|
|||||||
pub(super) textures: Vec<TextureHandle>,
|
pub(super) textures: Vec<TextureHandle>,
|
||||||
pub(super) primitives: Vec<RetainedPrimitive>,
|
pub(super) primitives: Vec<RetainedPrimitive>,
|
||||||
pub(super) mask_region: Option<UiRegion>,
|
pub(super) mask_region: Option<UiRegion>,
|
||||||
|
/// The previous drawing's owned mask, available for this draw to reclaim.
|
||||||
|
pub(super) mask_slot: Option<MaskIdx>,
|
||||||
/// Only children whose answers were read constrain this widget's answer.
|
/// Only children whose answers were read constrain this widget's answer.
|
||||||
pub(super) answer_under: LayoutHolds,
|
pub(super) answer_under: LayoutHolds,
|
||||||
pub(super) children: Vec<WidgetId>,
|
pub(super) children: Vec<WidgetId>,
|
||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub(super) size_deps: Vec<WidgetId>,
|
pub(super) size_deps: Vec<WidgetId>,
|
||||||
/// What this draw itself read of the window in pixels, per axis: every
|
/// What this draw itself reads, as against what its children's drawings
|
||||||
/// window until it reads one, then that one, unless it says otherwise.
|
/// hold for: every window and every length of its own region until it
|
||||||
pub(super) window_own: [Holds; 2],
|
/// reads one, then that one unless it says otherwise, and the rel base or
|
||||||
/// Its frame's symbolic length where this draw read it, which makes the
|
/// region length it read symbolically, each of which makes the drawing
|
||||||
/// drawing one that holds for that frame alone.
|
/// hold for that length alone.
|
||||||
pub(super) frame_own_len: [Option<Len>; 2],
|
pub(super) own: LayoutHolds,
|
||||||
/// The window reads' equivalent for its own box.
|
|
||||||
pub(super) extent_own: [Holds; 2],
|
|
||||||
/// What each child's drawing depends on. Asking a child again replaces
|
/// What each child's drawing depends on. Asking a child again replaces
|
||||||
/// its drawing, so it replaces this too rather than narrowing it.
|
/// its drawing, so it replaces this too rather than narrowing it.
|
||||||
pub(super) under: Vec<(WidgetId, LayoutHolds)>,
|
pub(super) under: Vec<(WidgetId, LayoutHolds)>,
|
||||||
@@ -73,10 +72,10 @@ impl<'a> Painter<'a> {
|
|||||||
self.write_resolved(kind, primitive, region, self.resolve(region));
|
self.write_resolved(kind, primitive, region, self.resolve(region));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A box in this widget's extent coordinates, composed into its region
|
/// A box in this widget's region, composed into its region node's
|
||||||
/// node's coordinates.
|
/// coordinates.
|
||||||
fn resolve(&self, region: UiRegion) -> UiRegion {
|
fn resolve(&self, region: UiRegion) -> UiRegion {
|
||||||
region.within(&self.extent)
|
region.within(&self.region)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_resolved<P: Primitive>(
|
fn write_resolved<P: Primitive>(
|
||||||
@@ -104,7 +103,6 @@ impl<'a> Painter<'a> {
|
|||||||
|
|
||||||
fn push_primitive(&mut self, h: RetainedPrimitive) {
|
fn push_primitive(&mut self, h: RetainedPrimitive) {
|
||||||
if self.mask != MaskIdx::NONE {
|
if self.mask != MaskIdx::NONE {
|
||||||
// TODO: I have no clue if this works at all :joy:
|
|
||||||
self.rsc.ui_mut().masks.push_ref(self.mask);
|
self.rsc.ui_mut().masks.push_ref(self.mask);
|
||||||
}
|
}
|
||||||
self.primitives.push(h);
|
self.primitives.push(h);
|
||||||
@@ -129,59 +127,83 @@ impl<'a> Painter<'a> {
|
|||||||
assert!(self.mask == MaskIdx::NONE);
|
assert!(self.mask == MaskIdx::NONE);
|
||||||
let resolved = self.resolve(region);
|
let resolved = self.resolve(region);
|
||||||
let move_idx = self.move_idx;
|
let move_idx = self.move_idx;
|
||||||
self.mask = self.rsc.ui_mut().masks.push(Mask {
|
let mask = Mask {
|
||||||
region: resolved,
|
region: resolved,
|
||||||
move_idx,
|
move_idx,
|
||||||
});
|
};
|
||||||
|
let masks = &mut self.rsc.ui_mut().masks;
|
||||||
|
self.mask = match self.mask_slot.take() {
|
||||||
|
Some(idx) => {
|
||||||
|
*masks.get_mut(idx) = mask;
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let idx = masks.push(mask);
|
||||||
|
// The owner keeps the slot alive even with no primitives.
|
||||||
|
masks.push_ref(idx);
|
||||||
|
idx
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget in the whole of this widget's own box, with the frame
|
/// Draws a widget in the whole of this widget's own box, with the rel
|
||||||
/// forwarded unchanged: what a container that is only a wrapper around
|
/// base forwarded unchanged: what a container that is only a wrapper
|
||||||
/// one child wants, and what every transparent container passes for the
|
/// around one child wants.
|
||||||
/// frame.
|
|
||||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||||
self.widget_at(id, [None; 2], [Place::Within(Part::All); 2])
|
self.widget_at(id, UiRegion::FULL)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolves what the place says about the child's rel base into a length,
|
||||||
|
/// where that is this widget's own narrowed the way the region is. An
|
||||||
|
/// axis the region leaves whole is not read at all, so a wrapper that
|
||||||
|
/// only moves its child does not pin its drawing to a rel base.
|
||||||
|
fn resolve_rel_base(&mut self, mut place: PlaceDesc) -> PlaceDesc {
|
||||||
|
for axis in Axis::BOTH {
|
||||||
|
let at = &mut place[axis];
|
||||||
|
let (RelBase::WithRegion, PlaceSpan::Within(span)) = (at.rel_base, at.span) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let len = span.len();
|
||||||
|
at.rel_base = match len == Len::FULL {
|
||||||
|
true => RelBase::Inherit,
|
||||||
|
false => RelBase::Len(len.within_len(self.rel_base(axis))),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
place
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Asks a child, saying what its fractions are of and where it is asked.
|
/// Asks a child, saying what its fractions are of and where it is asked.
|
||||||
///
|
///
|
||||||
/// `narrow` is a length this widget decided for the child's frame, per
|
/// `place` says where the child goes and what its fractions are of:
|
||||||
/// axis, as a length of this widget's own frame: a resolved share, or a
|
/// see [`PlaceDesc`]. A `UiRegion` converts into the common case, which
|
||||||
/// box a sibling's answer decided. `None` forwards this widget's frame,
|
/// is a box of this widget's own with the answer placed inside it.
|
||||||
/// which is what a container that only divides room passes, so a
|
|
||||||
/// fraction under it means the same wherever it sits and however deeply
|
|
||||||
/// it is nested. A declared length narrows the frame here whatever the
|
|
||||||
/// caller says. A narrowed frame is placed in the part by the child's
|
|
||||||
/// alignment and is the box the child is asked in.
|
|
||||||
///
|
///
|
||||||
/// `place` is where the child is asked, per axis, as a part of this
|
/// The child draws once, in the region that comes of it, and its answer
|
||||||
/// widget's box: see [`Place`]. The child draws once, in that box, and
|
/// is placed inside that region by re-expressing the drawing. Nothing is
|
||||||
/// its answer is placed inside it by re-expressing the drawing. Nothing
|
/// drawn again in a box an answer chose; a container that puts the
|
||||||
/// is drawn again in a box an answer chose; a container that puts the
|
|
||||||
/// answer somewhere else says so with [`Self::place_at`].
|
/// answer somewhere else says so with [`Self::place_at`].
|
||||||
pub fn widget_at<'s, W: ?Sized>(
|
pub fn widget_at<'s, W: ?Sized>(
|
||||||
&'s mut self,
|
&'s mut self,
|
||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
narrow: [Option<Len>; 2],
|
place: impl Into<PlaceDesc>,
|
||||||
place: [Place; 2],
|
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
|
let place = self.resolve_rel_base(place.into());
|
||||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||||
let declared = self.declared_lens(id);
|
let declared = self.declared_lens(id);
|
||||||
let align = self.rsc.widgets().alignment(id.id());
|
let align = self.rsc.widgets().alignment(id.id());
|
||||||
let (frame, extent) =
|
let (rel_base, region) =
|
||||||
frame_and_extent(self.extent, self.frame, place, narrow, declared, align);
|
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
if region_node {
|
if region_node {
|
||||||
diag::bump(Counter::RegionNodeDraws);
|
diag::bump(Counter::RegionNodeDraws);
|
||||||
diag::region_node(id.id(), self.id, extent);
|
diag::region_node(id.id(), self.id, region);
|
||||||
}
|
}
|
||||||
// A child listed twice would be moved twice.
|
// A child listed twice would be moved twice.
|
||||||
let re_asked = self.children.contains(&id.id());
|
let re_asked = self.children.contains(&id.id());
|
||||||
if !re_asked {
|
if !re_asked {
|
||||||
self.children.push(id.id());
|
self.children.push(id.id());
|
||||||
}
|
}
|
||||||
let px = frame.to_px(self.window);
|
let drawn = self.state.draw_inner(
|
||||||
let (size, answer_holds, holds) = self.state.draw_inner(
|
|
||||||
id.id(),
|
id.id(),
|
||||||
DrawInfo {
|
DrawInfo {
|
||||||
layer: self.layer,
|
layer: self.layer,
|
||||||
@@ -190,19 +212,17 @@ impl<'a> Painter<'a> {
|
|||||||
parent_move: self.move_idx,
|
parent_move: self.move_idx,
|
||||||
region_node,
|
region_node,
|
||||||
mask: self.mask,
|
mask: self.mask,
|
||||||
frame,
|
rel_base,
|
||||||
part: extent,
|
region,
|
||||||
placed: place,
|
placed: place,
|
||||||
asked: place,
|
asked: place,
|
||||||
narrow,
|
|
||||||
re_asked,
|
re_asked,
|
||||||
px,
|
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
self.rsc,
|
self.rsc,
|
||||||
);
|
);
|
||||||
let holds = self.in_parent(holds, extent, place, narrow, declared);
|
let holds = self.in_parent(drawn.drawing_holds, region, place, declared);
|
||||||
let answer_holds = self.in_parent(answer_holds, extent, place, narrow, declared);
|
let answer_holds = self.in_parent(drawn.answer.holds, region, place, declared);
|
||||||
match self.under.iter_mut().find(|(child, _)| *child == id.id()) {
|
match self.under.iter_mut().find(|(child, _)| *child == id.id()) {
|
||||||
Some((_, kept)) => *kept = holds,
|
Some((_, kept)) => *kept = holds,
|
||||||
None => self.under.push((id.id(), holds)),
|
None => self.under.push((id.id(), holds)),
|
||||||
@@ -210,7 +230,7 @@ impl<'a> Painter<'a> {
|
|||||||
DrawResult {
|
DrawResult {
|
||||||
child: id,
|
child: id,
|
||||||
painter: self,
|
painter: self,
|
||||||
size,
|
size: drawn.answer.size,
|
||||||
answer_holds,
|
answer_holds,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -224,27 +244,47 @@ impl<'a> Painter<'a> {
|
|||||||
self.state.undraw_rec(id.id(), self.rsc);
|
self.state.undraw_rec(id.id(), self.rsc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Puts a child asked about in this draw somewhere else in this
|
/// Puts a child in `place` of this widget's box, where that box is the
|
||||||
/// widget's box: its answer, placed in this part instead. The drawing
|
/// answer the child already gave: the drawing is re-expressed there
|
||||||
/// is re-expressed there rather than made again -- what a row does once
|
/// rather than made again -- what a row does once it knows every slot,
|
||||||
/// it knows every slot, having measured each child from its cursor.
|
/// having measured each child from its cursor.
|
||||||
pub fn place_at<W: ?Sized>(&mut self, id: &StrongWidget<W>, place: [Place; 2]) {
|
///
|
||||||
debug_assert!(
|
/// A child this draw has not asked about, and one whose rel base this
|
||||||
self.children.contains(&id.id()),
|
/// narrows, is asked here instead: there is no answer to re-express, or
|
||||||
"'{}' placed a child it did not ask about in this draw",
|
/// the question has changed. So a container that places every child the
|
||||||
self.label()
|
/// same way says it once, and which of the two happens is this widget's
|
||||||
);
|
/// business rather than the caller's.
|
||||||
|
pub fn place_at<'s, W: ?Sized>(
|
||||||
|
&'s mut self,
|
||||||
|
id: &'s StrongWidget<W>,
|
||||||
|
place: impl Into<PlaceDesc>,
|
||||||
|
) -> DrawResult<'s, 'a, W> {
|
||||||
|
let place = self.resolve_rel_base(place.into());
|
||||||
|
let states_rel_base = Axis::BOTH
|
||||||
|
.iter()
|
||||||
|
.any(|&axis| matches!(place[axis].rel_base, RelBase::Len(_)));
|
||||||
|
if states_rel_base || !self.children.contains(&id.id()) {
|
||||||
|
return self.widget_at(id, place);
|
||||||
|
}
|
||||||
let at = self.placing();
|
let at = self.placing();
|
||||||
self.state.place_in(id.id(), &at, place, self.rsc);
|
self.state.place_in(id.id(), &at, place, self.rsc);
|
||||||
|
let active = &self.state.active[&id.id()];
|
||||||
|
let size = active.measured().unwrap_or(active.size);
|
||||||
|
DrawResult {
|
||||||
|
child: id,
|
||||||
|
painter: self,
|
||||||
|
size,
|
||||||
|
// Read where it was asked; moving it is not a second answer.
|
||||||
|
answer_holds: LayoutHolds::ANY,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This widget as the thing its children are placed within.
|
/// This widget as the thing its children are placed within.
|
||||||
fn placing(&self) -> Placing {
|
fn placing(&self) -> Placing {
|
||||||
Placing {
|
Placing {
|
||||||
id: self.id,
|
id: self.id,
|
||||||
extent: self.extent,
|
region: self.region,
|
||||||
frame: self.frame,
|
rel_base: self.rel_base,
|
||||||
window: self.window,
|
|
||||||
depth: self.depth,
|
depth: self.depth,
|
||||||
move_idx: self.move_idx,
|
move_idx: self.move_idx,
|
||||||
mask: self.mask,
|
mask: self.mask,
|
||||||
@@ -252,51 +292,47 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
/// What a widget's rules declare its lengths to be, which whoever draws
|
||||||
/// it resolves into its frame. Reading them depends on nothing -- the box
|
/// it resolves into its rel base. Reading them depends on nothing -- the box
|
||||||
/// that comes of them is kept on the child, and `redraw` compares it
|
/// that comes of them is kept on the child, and `redraw` compares it
|
||||||
/// there.
|
/// there.
|
||||||
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> [Option<LayoutLen>; 2] {
|
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> Declared {
|
||||||
declared_lens(self.rsc.widgets(), id.id())
|
self.rsc.widgets().declared_lens(id.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a child says its length is without being drawn, if it can say,
|
/// What a child says its length is without being drawn, if it can say,
|
||||||
/// as the length its draw would report: a fraction in it is resolved
|
/// as the length its draw would report: a fraction in it is resolved
|
||||||
/// against this widget's frame, which is the frame a child asked with
|
/// against this widget's rel base, which is the rel base a child asked with
|
||||||
/// nothing narrowed gets. Asking counts as reading its size.
|
/// nothing narrowed gets. Asking counts as reading its size.
|
||||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<LayoutLen> {
|
||||||
let widgets = self.rsc.widgets();
|
let widgets = self.rsc.widgets();
|
||||||
// A rule is the answer where there is one: it wins over whatever the
|
// A rule is the answer where there is one: it wins over whatever the
|
||||||
// widget would draw, so it has to win over what the widget says too.
|
// widget would draw, so it has to win over what the widget says too.
|
||||||
let hint = widgets
|
let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
|
||||||
.size_rules(id.id())
|
|
||||||
.axis(axis)
|
|
||||||
.exact()
|
|
||||||
.or_else(|| {
|
|
||||||
widgets
|
widgets
|
||||||
.get_dyn(id.id())
|
.get_dyn(id.id())
|
||||||
.and_then(|widget| widget.size_hint(axis))
|
.and_then(|widget| widget.size_hint(axis))
|
||||||
})
|
});
|
||||||
.map(|hint| hint.within_len(self.frame.axis(axis)));
|
let rel_base = self.rel_base[axis];
|
||||||
|
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::hint_read(id.id(), self.id, axis, hint);
|
{
|
||||||
match hint {
|
diag::hint_read(id.id(), self.id, axis, resolved);
|
||||||
Some(hint) => {
|
diag::bump(match resolved {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
Some(_) => Counter::HintHits,
|
||||||
diag::bump(Counter::HintHits);
|
None => Counter::HintMisses,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Some(hint) = hint {
|
||||||
self.depend_on(id);
|
self.depend_on(id);
|
||||||
// A fraction was just resolved against this frame, so what
|
// Resolving a fraction against this rel base makes this draw a
|
||||||
// this draw does with it is a function of the frame's length.
|
// function of the rel base's length. The fraction to ask about is
|
||||||
|
// the child's own: resolved against a rel base of pixels, none is
|
||||||
|
// left to see it by.
|
||||||
if hint.rel != Rel::ZERO {
|
if hint.rel != Rel::ZERO {
|
||||||
self.frame_own_len[axis as usize] = Some(self.frame.axis(axis));
|
self.own[axis].rel_base = Some(rel_base);
|
||||||
}
|
|
||||||
Some(hint)
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
|
||||||
diag::bump(Counter::HintMisses);
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||||
@@ -317,11 +353,11 @@ impl<'a> Painter<'a> {
|
|||||||
ui.text.render(buffer, attrs, width)
|
ui.text.render(buffer, attrs, width)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes glyphs in the selected frame or extent coordinates.
|
/// Writes glyphs in the selected rel base or region coordinates.
|
||||||
// TODO: merge the text methods into the primitive ones.
|
// TODO: merge the text methods into the primitive ones.
|
||||||
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
pub fn glyphs(&mut self, text: &RenderedText, origin: UiRegion) {
|
||||||
// Glyph offsets and sizes are pixels, which compose additively.
|
// Glyph offsets and sizes are pixels, which compose additively.
|
||||||
// Only the shared origin needs composing through the extent.
|
// Only the shared origin needs composing through the region.
|
||||||
let resolved = self.resolve(origin);
|
let resolved = self.resolve(origin);
|
||||||
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
let kind = self.rsc.ui_mut().primitives.kind::<GlyphPrimitive>();
|
||||||
for glyph in text.glyphs.iter() {
|
for glyph in text.glyphs.iter() {
|
||||||
@@ -353,25 +389,25 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The symbolic length of this widget's own box along one axis, in the
|
/// The symbolic length of this widget's own box along one axis, in the
|
||||||
/// lengths of its frame that it places its children in. Reading it pins
|
/// lengths of its rel base that it places its children in. Reading it pins
|
||||||
/// the drawing to that length -- and to nothing about where the box
|
/// the drawing to that length -- and to nothing about where the box
|
||||||
/// starts, which is what lets a container move without being drawn
|
/// starts, which is what lets a container move without being drawn
|
||||||
/// again. One axis at a time, because a container that divides one axis
|
/// again. One axis at a time, because a container that divides one axis
|
||||||
/// holds for any length of the other.
|
/// holds for any length of the other.
|
||||||
pub fn extent_len(&mut self, axis: Axis) -> Len {
|
pub fn region_len(&mut self, axis: Axis) -> Len {
|
||||||
let len = self.extent.axis(axis).len();
|
let len = self.region[axis].len();
|
||||||
self.extent_len[axis as usize] = Some(len);
|
self.own[axis].region_len = Some(len);
|
||||||
len
|
len
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The symbolic length of this widget's frame along one axis: what a
|
/// This widget's rel base along one axis: what a fraction it or anything
|
||||||
/// fraction it or anything under it declares is a fraction of. A
|
/// under it declares or reports is a fraction of. A container reads it
|
||||||
/// container reads it to hand a length of it down -- padding, which
|
/// to hand a length of it down -- padding, which takes its pixels off.
|
||||||
/// takes its pixels off. Reading it pins the drawing to that frame, the
|
/// Reading it pins the drawing to that rel base, the way
|
||||||
/// way [`Self::extent_len`] pins it to the box.
|
/// [`Self::region_len`] pins it to the box.
|
||||||
pub fn frame_len(&mut self, axis: Axis) -> Len {
|
pub fn rel_base(&mut self, axis: Axis) -> Len {
|
||||||
let len = self.frame.axis(axis);
|
let len = self.rel_base[axis];
|
||||||
self.frame_own_len[axis as usize] = Some(len);
|
self.own[axis].rel_base = Some(len);
|
||||||
len
|
len
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -392,10 +428,7 @@ impl<'a> Painter<'a> {
|
|||||||
/// worth anything, since reading one is also what makes its own size
|
/// worth anything, since reading one is also what makes its own size
|
||||||
/// depend on it.
|
/// depend on it.
|
||||||
pub fn has_exact_size(&self, axis: Axis) -> bool {
|
pub fn has_exact_size(&self, axis: Axis) -> bool {
|
||||||
self.rsc
|
self.rsc.widgets().size_rules(self.id)[axis]
|
||||||
.widgets()
|
|
||||||
.size_rules(self.id)
|
|
||||||
.axis(axis)
|
|
||||||
.exact()
|
.exact()
|
||||||
.is_some()
|
.is_some()
|
||||||
}
|
}
|
||||||
@@ -409,13 +442,13 @@ impl<'a> Painter<'a> {
|
|||||||
/// One axis of this widget's own box in pixels. Prefer this to
|
/// One axis of this widget's own box in pixels. Prefer this to
|
||||||
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
||||||
pub fn px_len(&mut self, axis: Axis) -> Px {
|
pub fn px_len(&mut self, axis: Axis) -> Px {
|
||||||
let part = self.extent.axis(axis).len();
|
let len = self.region[axis].len();
|
||||||
let len = part.to_px(self.window.axis(axis));
|
let px = len.to_px(self.window[axis]);
|
||||||
let own = &mut self.extent_own[axis as usize];
|
let own = &mut self.own[axis].region;
|
||||||
if *own == Holds::ANY {
|
if *own == Holds::ANY {
|
||||||
*own = Holds::at(len);
|
*own = Holds::at(px);
|
||||||
}
|
}
|
||||||
len
|
px
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The lengths of this widget's own box on `axis` that what it is drawing
|
/// The lengths of this widget's own box on `axis` that what it is drawing
|
||||||
@@ -423,15 +456,15 @@ impl<'a> Painter<'a> {
|
|||||||
/// of the box, and the same reported size. A widget that read its length
|
/// of the box, and the same reported size. A widget that read its length
|
||||||
/// in pixels holds for that one alone until it says otherwise.
|
/// in pixels holds for that one alone until it says otherwise.
|
||||||
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
pub fn holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||||
let part = self.extent.axis(axis).len();
|
let len = self.region[axis].len();
|
||||||
let holds = holds.into();
|
let holds = holds.into();
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
holds.contains(part.to_px(self.window.axis(axis))),
|
holds.contains(len.to_px(self.window[axis])),
|
||||||
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
||||||
self.label(),
|
self.label(),
|
||||||
self.id
|
self.id
|
||||||
);
|
);
|
||||||
self.extent_own[axis as usize] = holds;
|
self.own[axis].region = holds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A window length in pixels, which is what every length in layout is
|
/// A window length in pixels, which is what every length in layout is
|
||||||
@@ -439,9 +472,9 @@ impl<'a> Painter<'a> {
|
|||||||
/// length is a fraction of it; one that is only pixels is that many
|
/// length is a fraction of it; one that is only pixels is that many
|
||||||
/// pixels in any window and pins nothing.
|
/// pixels in any window and pins nothing.
|
||||||
pub fn to_px(&mut self, len: Len, axis: Axis) -> Px {
|
pub fn to_px(&mut self, len: Len, axis: Axis) -> Px {
|
||||||
let window = self.window.axis(axis);
|
let window = self.window[axis];
|
||||||
if len.rel != Rel::ZERO {
|
if len.rel != Rel::ZERO {
|
||||||
let own = &mut self.window_own[axis as usize];
|
let own = &mut self.own[axis].window;
|
||||||
if *own == Holds::ANY {
|
if *own == Holds::ANY {
|
||||||
*own = Holds::at(window);
|
*own = Holds::at(window);
|
||||||
}
|
}
|
||||||
@@ -456,12 +489,12 @@ impl<'a> Painter<'a> {
|
|||||||
pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
pub fn window_holds(&mut self, axis: Axis, holds: impl Into<Holds>) {
|
||||||
let holds = holds.into();
|
let holds = holds.into();
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
holds.contains(self.window.axis(axis)),
|
holds.contains(self.window[axis]),
|
||||||
"'{}' ({:?}) says its drawing holds for windows that leave out this one",
|
"'{}' ({:?}) says its drawing holds for windows that leave out this one",
|
||||||
self.label(),
|
self.label(),
|
||||||
self.id
|
self.id
|
||||||
);
|
);
|
||||||
self.window_own[axis as usize] = holds;
|
self.own[axis].window = holds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_data(&mut self) -> &mut TextData {
|
pub fn text_data(&mut self) -> &mut TextData {
|
||||||
@@ -520,7 +553,7 @@ impl<W: ?Sized> DrawResult<'_, '_, W> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(self, axis: Axis) -> LayoutLen {
|
pub fn len(self, axis: Axis) -> LayoutLen {
|
||||||
self.size().axis(axis)
|
self.size()[axis]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,50 +582,46 @@ impl PrimitiveLike for &TextureHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Moves what a child depends on into this widget's own terms: this
|
|
||||||
/// method's `impl` block is where a `Painter`'s own boxes are, so it takes
|
|
||||||
/// only what the child was asked with.
|
|
||||||
impl Painter<'_> {
|
impl Painter<'_> {
|
||||||
/// Window ranges are already about the one unit and combine directly.
|
/// Moves what a child depends on into this widget's own terms, taking
|
||||||
/// A frame pin becomes this widget's own frame wherever a length of it
|
/// only what the child was asked with.
|
||||||
/// is what reached the child; where only pixels did, no length of this
|
|
||||||
/// frame can change the child's and the pin stops here.
|
|
||||||
///
|
///
|
||||||
/// Extent validity maps back through the part of this widget's box,
|
/// Window ranges are already about the one unit and combine directly.
|
||||||
|
/// A rel base pin becomes this widget's own rel base wherever a length of it
|
||||||
|
/// is what reached the child; where only pixels did, no length of this
|
||||||
|
/// rel base can change the child's and the pin stops here.
|
||||||
|
///
|
||||||
|
/// A child's validity maps back through the part of this widget's box,
|
||||||
/// where the box the child was asked in is that part; a declared length
|
/// where the box the child was asked in is that part; a declared length
|
||||||
/// places the box inside the part instead, and then only that length
|
/// places the box inside the part instead, and then only that length
|
||||||
/// reaches the child. A narrowed frame is not one of these: it decides
|
/// reaches the child. A narrowed rel base is not one of these: it decides
|
||||||
/// what fractions under the child mean and leaves the box the part it
|
/// what fractions under the child mean and leaves the box the part it
|
||||||
/// was given.
|
/// was given.
|
||||||
fn in_parent(
|
fn in_parent(
|
||||||
&self,
|
&self,
|
||||||
holds: LayoutHolds,
|
holds: LayoutHolds,
|
||||||
extent: UiRegion,
|
region: UiRegion,
|
||||||
place: [Place; 2],
|
place: PlaceDesc,
|
||||||
narrow: [Option<Len>; 2],
|
declared: Declared,
|
||||||
declared: [Option<LayoutLen>; 2],
|
|
||||||
) -> LayoutHolds {
|
) -> LayoutHolds {
|
||||||
let mut result = LayoutHolds::ANY;
|
let mut result = LayoutHolds::ANY;
|
||||||
for axis in AXES {
|
for axis in Axis::BOTH {
|
||||||
let n = axis as usize;
|
let declared = declared[axis];
|
||||||
|
let holds = holds[axis];
|
||||||
|
let at = place[axis];
|
||||||
|
let result = &mut result[axis];
|
||||||
// Every read became pixels against the window, so a range on
|
// Every read became pixels against the window, so a range on
|
||||||
// it is already in this widget's terms.
|
// it is already in this widget's terms.
|
||||||
result.window[n] = holds.window[n];
|
result.window = holds.window;
|
||||||
let reaches = narrow[n].is_none()
|
// A length this widget named -- a resolved share, a box a sibling
|
||||||
&& !matches!(place[n].part(), Part::Sized(_))
|
// decided, a box it sized outright, which is its own base -- is
|
||||||
&& declared[n].is_none_or(|len| len.rel != Rel::ZERO);
|
// not a length of this widget's rel base, so a pin on it stops
|
||||||
result.frame_len[n] = holds.frame_len[n].and(reaches.then(|| self.frame.axis(axis)));
|
// here. So does a declaration in pixels: no length of either base
|
||||||
match (place[n].part(), declared[n].is_some()) {
|
// is in it to see.
|
||||||
// Its box is this widget's own, or a part of it in that
|
let reaches = !matches!(at.rel_base, RelBase::Len(_))
|
||||||
// box's own lengths: so what it holds for is a range on this
|
&& declared.is_none_or(|len| len.rel != Rel::ZERO);
|
||||||
// widget's own box, which is what lets that box move without
|
result.rel_base = holds.rel_base.and(reaches.then(|| self.rel_base[axis]));
|
||||||
// a redraw. A length it pinned is this widget's length
|
match (at.span, declared) {
|
||||||
// wherever the part is the whole of it, and pins the same
|
|
||||||
// way.
|
|
||||||
(Part::All, false) => {
|
|
||||||
result.extent[n] = holds.extent[n];
|
|
||||||
result.extent_len[n] = holds.extent_len[n];
|
|
||||||
}
|
|
||||||
// Its box is a part of this widget's own box, in that box's
|
// Its box is a part of this widget's own box, in that box's
|
||||||
// own lengths, so what it holds for maps back through that
|
// own lengths, so what it holds for maps back through that
|
||||||
// part into a range on this widget's box. A length it pinned
|
// part into a range on this widget's box. A length it pinned
|
||||||
@@ -600,21 +629,20 @@ impl Painter<'_> {
|
|||||||
// part is the whole of the box less pixels, which is the one
|
// part is the whole of the box less pixels, which is the one
|
||||||
// shape that inverts exactly; any other part pins this
|
// shape that inverts exactly; any other part pins this
|
||||||
// widget's own length.
|
// widget's own length.
|
||||||
(Part::Of(span), false) => {
|
(PlaceSpan::Within(span), None) => {
|
||||||
let part_len = span.len();
|
let part_len = span.len();
|
||||||
result.extent[n] = holds.extent[n].through(part_len);
|
result.region = holds.region.through(part_len);
|
||||||
result.extent_len[n] = holds.extent_len[n].map(|pinned| match part_len.rel {
|
result.region_len = holds.region_len.map(|pinned| match part_len.rel {
|
||||||
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
|
Rel::ONE => pinned - Len::from_parts(Rel::ZERO, part_len.px),
|
||||||
_ => self.extent.axis(axis).len(),
|
_ => self.region[axis].len(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Its box is a length this widget decided, from its own
|
// Its box is a length this widget decided, from its own
|
||||||
// frame or from a sibling's answer: no length of this
|
// rel base or from a sibling's answer: no length of this
|
||||||
// widget's box reaches it, so what it holds for is a range
|
// widget's box reaches it, so what it holds for is a range
|
||||||
// on the window and none of it on that box.
|
// on the window and none of it on that box.
|
||||||
_ => {
|
_ => {
|
||||||
result.window[n] =
|
result.window = result.window.and(holds.region.through(region[axis].len()));
|
||||||
result.window[n].and(holds.extent[n].through(extent.axis(axis).len()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -622,120 +650,101 @@ impl Painter<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a widget declares a length of its box to be. `leftover` is not one: a
|
impl Widgets {
|
||||||
/// share of what is left over is only a length to the widget dividing one,
|
/// What a widget's box is where a rule or its own hint says so outright.
|
||||||
/// so it passes up in the size instead.
|
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
||||||
pub(crate) fn declared_lens(widgets: &Widgets, id: WidgetId) -> [Option<LayoutLen>; 2] {
|
let rules = self.size_rules(id);
|
||||||
let rules = widgets.size_rules(id);
|
let widget = self.get_dyn(id);
|
||||||
let widget = widgets.get_dyn(id);
|
Declared::from_axes(|axis| {
|
||||||
AXES.map(|axis| {
|
rules[axis].declared().or_else(|| {
|
||||||
rules.axis(axis).declared().or_else(|| {
|
// A hint still narrows the box where no rule does, which is
|
||||||
// A hint still narrows the box where no rule does, which is how a
|
// how a widget with a natural pixel size -- an image, a gap
|
||||||
// widget with a natural pixel size -- an image, a gap -- gets that
|
// -- gets that size rather than the whole offer. That is the
|
||||||
// size rather than the whole offer. That is the offer's business
|
// offer's business rather than a declaration's, and this
|
||||||
// rather than a declaration's, and this falls away once a widget
|
// falls away once a widget occupies its reported size inside
|
||||||
// occupies its reported size inside the box it was offered.
|
// the box it was offered.
|
||||||
widget
|
widget
|
||||||
.and_then(|widget| widget.size_hint(axis))
|
.and_then(|widget| widget.size_hint(axis))
|
||||||
.filter(|len| len.leftover == Weight::ZERO)
|
.and_then(LayoutLen::declared)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether what a widget reported along an axis is the whole of the box it
|
|
||||||
/// is in rather than a part to be placed inside it. A share fills, because a
|
|
||||||
/// share is a length only to whoever divides one, and whoever did is the one
|
|
||||||
/// that handed down this box. A declared axis does too: the rule already gave
|
|
||||||
/// the region its length, and the rule's length is what the widget reports
|
|
||||||
/// there. And an axis the parent decided from the answer is
|
|
||||||
/// the answer already.
|
|
||||||
pub(crate) fn fills(reported: LayoutLen, declared: Option<LayoutLen>, decided: bool) -> bool {
|
|
||||||
reported.leftover != Weight::ZERO || declared.is_some() || decided
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl LayoutLen {
|
||||||
|
/// Whether what a widget reported along an axis is the whole of the box
|
||||||
|
/// it is in rather than a part to be placed inside it. A share fills,
|
||||||
|
/// because a share is a length only to whoever divides one, and whoever
|
||||||
|
/// did is the one that handed down this box. A declared axis does too:
|
||||||
|
/// the rule already gave the region its length, and the rule's length is
|
||||||
|
/// what the widget reports there. And an axis the parent decided from
|
||||||
|
/// the answer is the answer already.
|
||||||
|
pub(super) fn fills(self, declared: Option<Len>, decided: bool) -> bool {
|
||||||
|
self.leftover != Weight::ZERO || declared.is_some() || decided
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlaceDesc {
|
||||||
/// Where a widget's drawing goes inside the part its parent gave it: what
|
/// Where a widget's drawing goes inside the part its parent gave it: what
|
||||||
/// it reported, on the side of the part its alignment says, and the whole
|
/// it reported, on the side of the part its alignment says, and the whole
|
||||||
/// part wherever the answer fills it.
|
/// part wherever the answer fills it.
|
||||||
///
|
///
|
||||||
/// The length it reported is a length of its frame, and the part is one too,
|
/// The length it reported is a length of its rel base, and the part is one
|
||||||
/// so this takes one from the other rather than composing it into the part.
|
/// too, so this takes one from the other rather than composing it into the
|
||||||
/// That is what makes a fraction the same fraction wherever the part it is
|
/// part. That is what makes a fraction the same fraction wherever the part
|
||||||
/// placed in sits and however long it is -- the fraction is resolved once,
|
/// it is placed in sits and however long it is -- the fraction is resolved
|
||||||
/// here, against the frame it was reported of.
|
/// once, here, against the rel base it was reported of.
|
||||||
pub(crate) fn placed_extent(
|
pub(super) fn placement(
|
||||||
part: UiRegion,
|
self,
|
||||||
|
region: UiRegion,
|
||||||
size: Size,
|
size: Size,
|
||||||
declared: [Option<LayoutLen>; 2],
|
declared: Declared,
|
||||||
fill: [bool; 2],
|
|
||||||
align: RegionAlign,
|
align: RegionAlign,
|
||||||
) -> UiRegion {
|
) -> UiRegion {
|
||||||
let mut placed = part;
|
let mut placed = region;
|
||||||
for axis in AXES {
|
for axis in Axis::BOTH {
|
||||||
let n = axis as usize;
|
let reported = size[axis];
|
||||||
let reported = size.axis(axis);
|
if reported.fills(declared[axis], self[axis].fills) {
|
||||||
if fills(reported, declared[n], fill[n]) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let len = Len::from_parts(reported.rel, reported.px);
|
placed[axis] = placed[axis].place(reported.without_leftover(), align[axis]);
|
||||||
let span = placed.axis_mut(axis);
|
|
||||||
span.start += (span.len() - len).scale(align.axis(axis).rel());
|
|
||||||
span.end = span.start + len;
|
|
||||||
}
|
}
|
||||||
placed
|
placed
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The frame length and the box a child is asked in, in the coordinates the
|
/// The rel base length and the box a child is asked in, in the coordinates the
|
||||||
/// widget asking draws in.
|
/// widget asking draws in.
|
||||||
///
|
///
|
||||||
/// `own` is that widget's own box, and `place` what of it the child is
|
/// `own` is that widget's own box, and `place` what of it the child is
|
||||||
/// given. `narrow` is a frame the container decided for the child -- a row's
|
/// given, including any rel base it states -- a row's slot, or padding's rel
|
||||||
/// slot, or padding's frame less its pixels -- and [`Part::Sized`] one a
|
/// base less its pixels. That is a window length, like every other length
|
||||||
/// sibling's answer decided; both are window lengths, like every other
|
/// here, since a slot of a row is not a fraction of anything the row can
|
||||||
/// length here, since a slot of a row is not a fraction of anything the row
|
/// name. The child's declaration is a fraction of whichever reached it, and
|
||||||
/// can name. The child's declaration is a fraction of whichever reached it,
|
/// is the only one that also places the box: a box the caller decided is
|
||||||
/// and is the only one of the three that also places the box: a box the
|
/// what `place` names.
|
||||||
/// caller decided is what `place` names.
|
pub(super) fn rel_base_and_region(
|
||||||
pub(crate) fn frame_and_extent(
|
self,
|
||||||
own: UiRegion,
|
own: UiRegion,
|
||||||
parent_frame: UiVec2,
|
parent_rel_base: UiVec2,
|
||||||
place: [Place; 2],
|
declared: Declared,
|
||||||
narrow: [Option<Len>; 2],
|
|
||||||
declared: [Option<LayoutLen>; 2],
|
|
||||||
align: RegionAlign,
|
align: RegionAlign,
|
||||||
) -> (UiVec2, UiRegion) {
|
) -> (UiVec2, UiRegion) {
|
||||||
let part = part_of(own, place, align);
|
let given = self.of(own, align);
|
||||||
let mut frame = parent_frame;
|
let mut rel_base = parent_rel_base;
|
||||||
let mut extent = part;
|
let mut region = given;
|
||||||
for axis in AXES {
|
for axis in Axis::BOTH {
|
||||||
let n = axis as usize;
|
let base = match self[axis].rel_base {
|
||||||
let sized = match place[n].part() {
|
RelBase::Len(len) => len,
|
||||||
Part::Sized(len) => Some(len),
|
RelBase::Inherit | RelBase::WithRegion => parent_rel_base[axis],
|
||||||
_ => None,
|
|
||||||
};
|
};
|
||||||
let base = sized
|
let len = declared[axis]
|
||||||
.or(narrow[n])
|
.map(|len| len.within_len(base))
|
||||||
.unwrap_or_else(|| parent_frame.axis(axis));
|
|
||||||
let len = declared[n]
|
|
||||||
.map(|len| Len::from_parts(len.rel, len.px).within_len(base))
|
|
||||||
.unwrap_or(base);
|
.unwrap_or(base);
|
||||||
*frame.axis_mut(axis) = len;
|
rel_base[axis] = len;
|
||||||
if declared[n].is_some() {
|
if declared[axis].is_some() {
|
||||||
let slot = part.axis(axis);
|
region[axis] = given[axis].place(len, align[axis]);
|
||||||
let start = slot.start + (slot.len() - len).scale(align.axis(axis).rel());
|
|
||||||
*extent.axis_mut(axis) = UiSpan::new(start, start + len);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(frame, extent)
|
(rel_base, region)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The part of a widget's own box a `place` names, in the coordinates that
|
|
||||||
/// box is in.
|
|
||||||
fn part_of(extent: UiRegion, place: [Place; 2], align: RegionAlign) -> UiRegion {
|
|
||||||
let mut part = extent;
|
|
||||||
for axis in AXES {
|
|
||||||
*part.axis_mut(axis) = place[axis as usize]
|
|
||||||
.part()
|
|
||||||
.of(*extent.axis(axis), align.axis(axis));
|
|
||||||
}
|
|
||||||
part
|
|
||||||
}
|
}
|
||||||
+183
-52
@@ -1,66 +1,195 @@
|
|||||||
use crate::{AxisAlign, Len, PrimitiveHandle, UiRegion, UiSpan};
|
use crate::util::impl_axis_index;
|
||||||
|
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan};
|
||||||
|
|
||||||
/// What of a widget's own box a child is given, along one axis.
|
/// How a child's region along one axis comes from the region of the widget
|
||||||
|
/// asking, and what its fractions are of.
|
||||||
|
///
|
||||||
|
/// The three ways of saying a region are the three the geometry already has:
|
||||||
|
/// a span composed into the caller's box, a span shifted to where that box
|
||||||
|
/// starts, and a length placed in it by alignment. Which one is meant cannot
|
||||||
|
/// be read off the numbers, since two of them take the same span and apply
|
||||||
|
/// it differently, so it is said here.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Part {
|
pub struct PlaceDescAxis {
|
||||||
/// The whole of it.
|
pub span: PlaceSpan,
|
||||||
All,
|
pub fills: bool,
|
||||||
/// Window lengths from where the box starts, which is what a container
|
pub rel_base: RelBase,
|
||||||
/// dividing room among its children speaks: a child's report is a window
|
}
|
||||||
/// length, so the cursor that sums those reports is one too. A moved box
|
|
||||||
/// re-places every child by re-adding its start, exactly. A fraction
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
/// here is a fraction of the window and not of the box -- the whole of a
|
pub enum PlaceSpan {
|
||||||
/// box is [`Self::All`], not a `rel(1.0)` span.
|
Within(UiSpan),
|
||||||
From(UiSpan),
|
Shifted(UiSpan),
|
||||||
/// A part of the box in its own coordinates, which is what a container
|
|
||||||
/// that insets one speaks: taking eleven pixels off the end needs no
|
|
||||||
/// length, where saying the same thing in window lengths would make the
|
|
||||||
/// container read its own box -- and a box chosen from its own answer
|
|
||||||
/// then feeds back into the answer.
|
|
||||||
Of(UiSpan),
|
|
||||||
/// A box of this length, wherever in the parent's box the child's own
|
|
||||||
/// alignment puts it, and that same length as its frame. Unlike `From`,
|
|
||||||
/// it is a length decided from above rather than a place along a
|
|
||||||
/// container's cursor -- what a stack's sizing child decides for the
|
|
||||||
/// rest.
|
|
||||||
Sized(Len),
|
Sized(Len),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Part {
|
/// What a child's fractions are of. [`PlaceSpan::Sized`] is a length the
|
||||||
/// Where it lands in the coordinates `extent` is in.
|
/// caller named, which is always its own base, so nothing here constructs one
|
||||||
pub(crate) fn of(self, extent: UiSpan, align: AxisAlign) -> UiSpan {
|
/// beside anything but [`Self::Len`].
|
||||||
match self {
|
|
||||||
Self::All => extent,
|
|
||||||
Self::From(span) => UiSpan::new(extent.start + span.start, extent.start + span.end),
|
|
||||||
Self::Of(span) => span.within(&extent),
|
|
||||||
Self::Sized(len) => {
|
|
||||||
let start = extent.start + (extent.len() - len).scale(align.rel());
|
|
||||||
UiSpan::new(start, start + len)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where a child goes along one axis, as a part of this widget's box.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum Place {
|
pub enum RelBase {
|
||||||
/// The child's answer, aligned inside the part by the child's alignment.
|
/// The caller's own, unchanged.
|
||||||
Within(Part),
|
Inherit,
|
||||||
/// Exactly the part; the answer is not placed inside it again.
|
/// The caller's own, narrowed the way the region is.
|
||||||
Fill(Part),
|
WithRegion,
|
||||||
|
/// This length of the window.
|
||||||
|
Len(Len),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Place {
|
impl PlaceDescAxis {
|
||||||
pub(crate) fn part(self) -> Part {
|
/// The whole of the caller's box.
|
||||||
match self {
|
pub const WHOLE: Self = UiSpan::FULL.within_desc();
|
||||||
Self::Within(part) | Self::Fill(part) => part,
|
|
||||||
|
/// This region is the child's placement: its answer is not placed inside
|
||||||
|
/// it again. A container uses it where it hands back exactly what the
|
||||||
|
/// child asked for -- a row placing a child at the length it reported.
|
||||||
|
pub const fn fills(mut self) -> Self {
|
||||||
|
self.fills = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This along `axis`, and the whole of the caller's box across it: what
|
||||||
|
/// a container dividing one axis says, since nothing divides the other.
|
||||||
|
/// [`PlaceDesc::from_axis`] says the across one where it is not the
|
||||||
|
/// whole.
|
||||||
|
pub const fn on_axis(self, axis: Axis) -> PlaceDesc {
|
||||||
|
PlaceDesc::from_axis(axis, self, Self::WHOLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What the child's fractions are of, as a length of the window: a
|
||||||
|
/// resolved share, or a box a sibling's answer decided.
|
||||||
|
pub const fn rel_base(mut self, len: Len) -> Self {
|
||||||
|
self.rel_base = RelBase::Len(len);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Where it lands in the coordinates `own` is in.
|
||||||
|
pub fn of(self, own: UiSpan, align: AxisAlign) -> UiSpan {
|
||||||
|
match self.span {
|
||||||
|
PlaceSpan::Within(span) => span.within(&own),
|
||||||
|
PlaceSpan::Shifted(mut span) => {
|
||||||
|
span.shift(own.start);
|
||||||
|
span
|
||||||
|
}
|
||||||
|
PlaceSpan::Sized(len) => own.place(len, align),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the part is the drawing's box outright, rather than the box
|
/// Where a child is asked, on both axes. A [`UiRegion`] converts into the
|
||||||
/// the answer is placed inside.
|
/// common case: that box of the caller's own, the answer placed inside it.
|
||||||
pub(crate) fn fills(self) -> bool {
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
matches!(self, Self::Fill(_))
|
pub struct PlaceDesc {
|
||||||
|
pub x: PlaceDescAxis,
|
||||||
|
pub y: PlaceDescAxis,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PlaceDesc {
|
||||||
|
/// The whole of the caller's box, on both axes.
|
||||||
|
pub const WHOLE: Self = Self::splat(PlaceDescAxis::WHOLE);
|
||||||
|
|
||||||
|
pub const fn new(x: PlaceDescAxis, y: PlaceDescAxis) -> Self {
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The same on both axes.
|
||||||
|
pub const fn splat(place: PlaceDescAxis) -> Self {
|
||||||
|
Self { x: place, y: place }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A description per axis, where the two differ and neither is the
|
||||||
|
/// axis a container divides.
|
||||||
|
pub fn from_axes(f: impl Fn(Axis) -> PlaceDescAxis) -> Self {
|
||||||
|
Self::new(f(Axis::X), f(Axis::Y))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `aligned` on `axis` and `ortho` on the other, which is how a
|
||||||
|
/// container that divides one axis says what it is doing.
|
||||||
|
pub const fn from_axis(axis: Axis, aligned: PlaceDescAxis, ortho: PlaceDescAxis) -> Self {
|
||||||
|
match axis {
|
||||||
|
Axis::X => Self::new(aligned, ortho),
|
||||||
|
Axis::Y => Self::new(ortho, aligned),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Both regions are the child's placement. See [`PlaceDescAxis::fills`].
|
||||||
|
pub const fn fills(self) -> Self {
|
||||||
|
Self::new(self.x.fills(), self.y.fills())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The child's rel base on one axis. See [`PlaceDescAxis::rel_base`].
|
||||||
|
pub const fn rel_base(mut self, axis: Axis, len: Len) -> Self {
|
||||||
|
self[axis] = self[axis].rel_base(len);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The box each axis names, in the coordinates `own` is in.
|
||||||
|
pub fn of(self, own: UiRegion, align: RegionAlign) -> UiRegion {
|
||||||
|
UiRegion::new(self.x.of(own.x, align.x), self.y.of(own.y, align.y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UiSpan {
|
||||||
|
/// This span composed into the caller's own box, so it moves and scales
|
||||||
|
/// with it: [`UiSpan::within`], which is what a container that insets
|
||||||
|
/// one speaks. Taking eleven pixels off the end needs no length, where
|
||||||
|
/// saying the same thing in window lengths would make the container read
|
||||||
|
/// its own box -- and a box chosen from its own answer then feeds back
|
||||||
|
/// into the answer.
|
||||||
|
///
|
||||||
|
/// The child's rel base is narrowed the same way, so padding takes its
|
||||||
|
/// pixels off both and `rel(1)` under it fills the caller rather than
|
||||||
|
/// overflowing it.
|
||||||
|
pub const fn within_desc(self) -> PlaceDescAxis {
|
||||||
|
PlaceDescAxis {
|
||||||
|
span: PlaceSpan::Within(self),
|
||||||
|
fills: false,
|
||||||
|
rel_base: RelBase::WithRegion,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This span shifted to where the caller's own box starts: window
|
||||||
|
/// lengths along a cursor, which is what a container dividing room among
|
||||||
|
/// its children speaks. A child's report is a window length, so the
|
||||||
|
/// cursor that sums those reports is one too, and a moved box re-places
|
||||||
|
/// every child by re-adding its start, exactly.
|
||||||
|
///
|
||||||
|
/// The child's rel base passes through: how far along the cursor a child
|
||||||
|
/// sits says nothing about what a fraction under it is of. The same span
|
||||||
|
/// says [`Self::within_desc`] as a part of that box instead, and which is
|
||||||
|
/// meant cannot be read off the numbers.
|
||||||
|
pub const fn shifted_desc(self) -> PlaceDescAxis {
|
||||||
|
PlaceDescAxis {
|
||||||
|
span: PlaceSpan::Shifted(self),
|
||||||
|
fills: false,
|
||||||
|
rel_base: RelBase::Inherit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Len {
|
||||||
|
/// A box this long, placed in the caller's own by the child's alignment:
|
||||||
|
/// the rule that places an answer, with the length given from above
|
||||||
|
/// rather than reported. What a stack's sizing child decides for the
|
||||||
|
/// rest. It is the child's rel base too.
|
||||||
|
pub const fn as_desc(self) -> PlaceDescAxis {
|
||||||
|
PlaceDescAxis {
|
||||||
|
span: PlaceSpan::Sized(self),
|
||||||
|
fills: false,
|
||||||
|
rel_base: RelBase::Len(self),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UiRegion> for PlaceDesc {
|
||||||
|
fn from(region: UiRegion) -> Self {
|
||||||
|
Self::new(region.x.within_desc(), region.y.within_desc())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PlaceDescAxis> for PlaceDesc {
|
||||||
|
fn from(place: PlaceDescAxis) -> Self {
|
||||||
|
Self::splat(place)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,3 +200,5 @@ pub struct RetainedPrimitive {
|
|||||||
pub handle: PrimitiveHandle,
|
pub handle: PrimitiveHandle,
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_axis_index!(PlaceDesc => PlaceDescAxis);
|
||||||
+247
-293
@@ -1,15 +1,12 @@
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind};
|
||||||
use crate::ui::painter::{declared_lens, frame_and_extent, placed_extent};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx, MoveIdx,
|
ActiveData, Answer, Axis, Declared, DrawLayers, IdLike, LayoutHolds, LayoutLen, Len, MaskIdx,
|
||||||
Moves, Painter, Part, PixelRegion, Place, PxVec2, Rel, Size, StrongWidget, UiRegion, UiRsc,
|
MoveIdx, Moves, Painter, PixelRegion, PlaceDesc, PxVec2, Rel, Size, StrongWidget, UiRegion,
|
||||||
UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
UiRsc, UiSpan, UiVec2, Weight, WidgetId, Widgets,
|
||||||
util::{HashMap, Vec2},
|
util::{HashMap, Vec2},
|
||||||
};
|
};
|
||||||
|
|
||||||
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
|
||||||
|
|
||||||
/// Where a widget is drawn: what its parent decides about the draw besides
|
/// Where a widget is drawn: what its parent decides about the draw besides
|
||||||
/// the boxes themselves.
|
/// the boxes themselves.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -22,39 +19,33 @@ pub(super) struct DrawInfo {
|
|||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
/// What a fraction declared or reported under this widget is a fraction
|
/// What a fraction declared or reported under this widget is a fraction
|
||||||
/// of, as a length of the window.
|
/// of, as a length of the window.
|
||||||
pub frame: UiVec2,
|
pub rel_base: UiVec2,
|
||||||
/// The box the widget is asked in, in its parent region node's
|
/// The box the widget is asked in, in its parent region node's
|
||||||
/// coordinates.
|
/// coordinates.
|
||||||
pub part: UiRegion,
|
pub region: UiRegion,
|
||||||
/// Where the widget is put, and where it was asked, as parts of the
|
/// Where the widget is put, and where it was asked, as parts of the
|
||||||
/// parent's box. See [`Place`]. The two are one ask's place until the
|
/// parent's box. See [`PlaceDesc`]. The two are one ask's place until the
|
||||||
/// parent puts the answer somewhere else.
|
/// parent puts the answer somewhere else.
|
||||||
pub placed: [Place; 2],
|
pub placed: PlaceDesc,
|
||||||
pub asked: [Place; 2],
|
pub asked: PlaceDesc,
|
||||||
/// A frame the parent decided for it on each axis, as a length of the
|
|
||||||
/// window, which the widget's own declaration is a fraction of.
|
|
||||||
pub narrow: [Option<Len>; 2],
|
|
||||||
/// Whether the parent already asked about this widget in this draw.
|
/// Whether the parent already asked about this widget in this draw.
|
||||||
pub re_asked: bool,
|
pub re_asked: bool,
|
||||||
/// The frame in pixels, resolved once against the window.
|
|
||||||
pub px: PxVec2,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DrawInfo {
|
/// What one draw of a widget came to: the answer it gave, and the boxes and
|
||||||
/// The axes where the part is the drawing's box outright, which are the
|
/// windows the drawing that gave it holds for. The two are separate ranges --
|
||||||
/// axes the answer is not placed inside it again.
|
/// a drawing can be invalid where its answer still stands.
|
||||||
fn fill(&self) -> [bool; 2] {
|
pub(super) struct Drawn {
|
||||||
self.placed.map(Place::fills)
|
pub answer: Answer,
|
||||||
}
|
pub drawing_holds: LayoutHolds,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a widget's children are placed in: its own box, the coordinates its
|
/// What a widget's children are placed in: its own box, the coordinates its
|
||||||
/// drawing is in, and what else one ask of a child is decided from.
|
/// drawing is in, and what else one ask of a child is decided from.
|
||||||
pub(super) struct Placing {
|
pub(super) struct Placing {
|
||||||
pub id: WidgetId,
|
pub id: WidgetId,
|
||||||
pub extent: UiRegion,
|
pub region: UiRegion,
|
||||||
pub frame: UiVec2,
|
pub rel_base: UiVec2,
|
||||||
pub window: PxVec2,
|
|
||||||
pub depth: usize,
|
pub depth: usize,
|
||||||
pub move_idx: MoveIdx,
|
pub move_idx: MoveIdx,
|
||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
@@ -120,13 +111,13 @@ impl UiRenderState {
|
|||||||
let Some(root) = self.old_root else { return };
|
let Some(root) = self.old_root else { return };
|
||||||
let stands = self.active.get(&root).is_some_and(|active| {
|
let stands = self.active.get(&root).is_some_and(|active| {
|
||||||
// Nothing above the root chose anything, so the box it was first
|
// Nothing above the root chose anything, so the box it was first
|
||||||
// asked about is the whole of its frame. Both its answer and its
|
// asked about is the whole of its rel base. Both its answer and its
|
||||||
// drawing have to stand in the new window, since nothing above
|
// drawing have to stand in the new window, since nothing above
|
||||||
// it will ask either again.
|
// it will ask either again.
|
||||||
let answer = active
|
let answer = active
|
||||||
.answer
|
.answer
|
||||||
.is_some_and(|(_, holds)| holds.contains(size, active.frame, active.part));
|
.is_some_and(|answer| answer.holds.contains(size, active.rel_base, active.region));
|
||||||
answer && active.holds.contains(size, active.frame, active.part)
|
answer && active.holds.contains(size, active.rel_base, active.region)
|
||||||
});
|
});
|
||||||
if !stands {
|
if !stands {
|
||||||
widgets.needs_redraw.insert(root);
|
widgets.needs_redraw.insert(root);
|
||||||
@@ -134,9 +125,8 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The root is asked about in the output. Its own rules narrow both its
|
/// The root is asked about in the output. Its own rules narrow both its
|
||||||
/// frame and box; nothing above it chose a different one.
|
/// rel base and box; nothing above it chose a different one.
|
||||||
fn root_info(&self, frame: UiVec2, extent: UiRegion) -> DrawInfo {
|
fn root_info(&self, rel_base: UiVec2, region: UiRegion) -> DrawInfo {
|
||||||
let px = frame.to_px(self.output_size);
|
|
||||||
DrawInfo {
|
DrawInfo {
|
||||||
layer: 0,
|
layer: 0,
|
||||||
parent: None,
|
parent: None,
|
||||||
@@ -144,13 +134,11 @@ impl UiRenderState {
|
|||||||
parent_move: MoveIdx::NONE,
|
parent_move: MoveIdx::NONE,
|
||||||
region_node: false,
|
region_node: false,
|
||||||
mask: MaskIdx::NONE,
|
mask: MaskIdx::NONE,
|
||||||
frame,
|
rel_base,
|
||||||
part: extent,
|
region,
|
||||||
placed: [Place::Within(Part::All); 2],
|
placed: PlaceDesc::WHOLE,
|
||||||
asked: [Place::Within(Part::All); 2],
|
asked: PlaceDesc::WHOLE,
|
||||||
narrow: [None; 2],
|
|
||||||
re_asked: false,
|
re_asked: false,
|
||||||
px,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,22 +184,20 @@ impl UiRenderState {
|
|||||||
let _layout = diag::timer(TimerKind::FullLayout);
|
let _layout = diag::timer(TimerKind::FullLayout);
|
||||||
self.clear(rsc);
|
self.clear(rsc);
|
||||||
if let Some(id) = root {
|
if let Some(id) = root {
|
||||||
let (frame, extent) = Self::root_layout(id.id(), rsc.widgets());
|
let (rel_base, region) = Self::root_layout(id.id(), rsc.widgets());
|
||||||
let info = self.root_info(frame, extent);
|
let info = self.root_info(rel_base, region);
|
||||||
self.draw_inner(id.id(), info, None, rsc);
|
self.draw_inner(id.id(), info, None, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The root's frame and box: the window, taken in by the root's own
|
/// The root's rel base and box: the window, taken in by the root's own
|
||||||
/// rules. Nothing above it narrowed anything or chose where it goes, so
|
/// rules. Nothing above it narrowed anything or chose where it goes, so
|
||||||
/// its declaration is the whole of what decides either.
|
/// its declaration is the whole of what decides either.
|
||||||
fn root_layout(id: WidgetId, widgets: &Widgets) -> (UiVec2, UiRegion) {
|
fn root_layout(id: WidgetId, widgets: &Widgets) -> (UiVec2, UiRegion) {
|
||||||
frame_and_extent(
|
PlaceDesc::WHOLE.rel_base_and_region(
|
||||||
UiRegion::FULL,
|
UiRegion::FULL,
|
||||||
UiVec2::FULL_SIZE,
|
UiVec2::FULL_SIZE,
|
||||||
[Place::Within(Part::All); 2],
|
widgets.declared_lens(id),
|
||||||
[None; 2],
|
|
||||||
declared_lens(widgets, id),
|
|
||||||
widgets.alignment(id),
|
widgets.alignment(id),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -222,15 +208,25 @@ impl UiRenderState {
|
|||||||
info: DrawInfo,
|
info: DrawInfo,
|
||||||
mut old: Option<ActiveData>,
|
mut old: Option<ActiveData>,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
) -> (Size, LayoutHolds, LayoutHolds) {
|
) -> Drawn {
|
||||||
let part = info.part;
|
let old_parent = old
|
||||||
|
.as_ref()
|
||||||
|
.or_else(|| self.active.get(&id))
|
||||||
|
.and_then(|a| a.parent);
|
||||||
|
let region = info.region;
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
{
|
||||||
diag::bump(Counter::DrawRequests);
|
diag::bump(Counter::DrawRequests);
|
||||||
diag::draw_request(id, info.parent, part, info.px, info.region_node);
|
diag::draw_request(
|
||||||
|
id,
|
||||||
|
info.parent,
|
||||||
|
region,
|
||||||
|
region.to_px(self.output_size).size(),
|
||||||
|
info.region_node,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
let align = rsc.widgets().alignment(id);
|
let align = rsc.widgets().alignment(id);
|
||||||
let declared = declared_lens(rsc.widgets(), id);
|
let declared = rsc.widgets().declared_lens(id);
|
||||||
// Nothing this widget measured can be dirty while it draws: layout is
|
// Nothing this widget measured can be dirty while it draws: layout is
|
||||||
// one bottom-up walk, so anything deeper has settled or deferred to
|
// one bottom-up walk, so anything deeper has settled or deferred to
|
||||||
// its own parent, and a deferred one leaves that parent marked.
|
// its own parent, and a deferred one leaves that parent marked.
|
||||||
@@ -241,87 +237,87 @@ impl UiRenderState {
|
|||||||
// an answer is kept only with the drawing that gave it, and both
|
// an answer is kept only with the drawing that gave it, and both
|
||||||
// have to hold for the box asked about.
|
// have to hold for the box asked about.
|
||||||
let reused = (!stale)
|
let reused = (!stale)
|
||||||
.then(|| self.retained_answer(id, part, info))
|
.then(|| self.retained_answer(id, region, info))
|
||||||
.flatten()
|
.flatten()
|
||||||
.and_then(|answer| {
|
.and_then(|answer| {
|
||||||
let extent = placed_extent(part, answer.0, declared, info.fill(), align);
|
let placed = info.placed.placement(region, answer.size, declared, align);
|
||||||
self.try_reuse(id, part, extent, info, rsc).map(|()| answer)
|
self.try_reuse(id, region, placed, info, rsc)
|
||||||
|
.then_some(answer)
|
||||||
});
|
});
|
||||||
let answer = reused.unwrap_or_else(|| {
|
let answer = reused.unwrap_or_else(|| {
|
||||||
if old.is_none() {
|
if old.is_none() {
|
||||||
old = self.remove(id, false, rsc);
|
old = self.remove(id, false, rsc);
|
||||||
}
|
}
|
||||||
let answer = self.draw_at(id, part, info, old.take(), rsc);
|
let answer = self.draw_at(id, region, info, old.take(), rsc);
|
||||||
// Where the drawing goes: the part its parent gave it, with the
|
// Where the drawing goes: the part its parent gave it, with the
|
||||||
// answer placed inside that part on any axis the parent left
|
// answer placed inside that part on any axis the parent left
|
||||||
// open.
|
// open.
|
||||||
let extent = placed_extent(part, answer.0, declared, info.fill(), align);
|
let placed = info.placed.placement(region, answer.size, declared, align);
|
||||||
if extent != part {
|
if placed != region {
|
||||||
self.relocate(id, extent, info, rsc);
|
self.relocate(id, placed, info, rsc);
|
||||||
}
|
}
|
||||||
answer
|
answer
|
||||||
});
|
});
|
||||||
|
|
||||||
let drawing_holds = self.active[&id].holds;
|
let drawing_holds = self.active[&id].holds;
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
// Whoever asked owns how the boxes were reached: the frame it stated,
|
// Whoever asked owns how the boxes were reached: the rel base it stated,
|
||||||
// and what of its own box it asked in. A local redraw asks the same
|
// and what of its own box it asked in. A local redraw asks the same
|
||||||
// question again from these.
|
// question again from these.
|
||||||
active.frame = info.frame;
|
active.rel_base = info.rel_base;
|
||||||
active.narrow = info.narrow;
|
|
||||||
active.re_asked = info.re_asked;
|
active.re_asked = info.re_asked;
|
||||||
active.answer = Some(answer);
|
active.answer = Some(answer);
|
||||||
active.asked = info.asked;
|
active.asked = info.asked;
|
||||||
active.part = part;
|
active.region = region;
|
||||||
active.placed = info.placed;
|
active.placed = info.placed;
|
||||||
active.own_align = align;
|
active.own_align = align;
|
||||||
// A subtree can be reused whole under a different parent -- same box,
|
// The previous parent must stop owning the subtree before it can
|
||||||
// same layer, same region node -- and nothing in the drawing says it
|
// undraw it, whether changing hands reused the drawing or replaced it.
|
||||||
// changed hands. Two things read who its parent is: a deferral, which
|
active.parent = info.parent;
|
||||||
// marks whoever has it to draw, and the old parent's list of children,
|
|
||||||
// which its next draw undraws whatever is missing from.
|
|
||||||
let old_parent = std::mem::replace(&mut active.parent, info.parent);
|
|
||||||
if old_parent != info.parent
|
if old_parent != info.parent
|
||||||
&& let Some(old_parent) = old_parent
|
&& let Some(old_parent) = old_parent
|
||||||
&& let Some(old_parent) = self.active.get_mut(&old_parent)
|
&& let Some(old_parent) = self.active.get_mut(&old_parent)
|
||||||
{
|
{
|
||||||
old_parent.children.retain(|child| *child != id);
|
old_parent.children.retain(|child| *child != id);
|
||||||
}
|
}
|
||||||
(answer.0, answer.1, drawing_holds)
|
Drawn {
|
||||||
|
answer,
|
||||||
|
drawing_holds,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calls a widget's `draw` and keeps what it drew in `extent` of `frame`.
|
/// Calls a widget's `draw` and keeps what it drew in `region`.
|
||||||
fn draw_at(
|
fn draw_at(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
extent: UiRegion,
|
region: UiRegion,
|
||||||
info: DrawInfo,
|
info: DrawInfo,
|
||||||
old: Option<ActiveData>,
|
old: Option<ActiveData>,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
) -> (Size, LayoutHolds) {
|
) -> Answer {
|
||||||
let frame = info.frame;
|
let rel_base = info.rel_base;
|
||||||
let (move_idx, extent, retired_move) = match info.region_node {
|
let (move_idx, region, retired_move) = match info.region_node {
|
||||||
// A node entry is only a translation. Its local box keeps the
|
// A node entry is only a translation. Its local box keeps the
|
||||||
// same window-unit length as the box in its parent's node.
|
// same window-unit length as the box in its parent's node.
|
||||||
true => (
|
true => (
|
||||||
self.move_slot(id, info.parent_move, translation(extent)),
|
self.move_slot(id, info.parent_move, region.as_translation()),
|
||||||
local_region(extent),
|
region.at_origin(),
|
||||||
None,
|
None,
|
||||||
),
|
),
|
||||||
// Keep the old entry alive until every descendant has migrated.
|
// Keep the old entry alive until every descendant has migrated.
|
||||||
// Reusing its index sooner could make an old parent look current.
|
// Reusing its index sooner could make an old parent look current.
|
||||||
false => (info.parent_move, extent, self.slots.remove(&id)),
|
false => (info.parent_move, region, self.slots.remove(&id)),
|
||||||
};
|
};
|
||||||
|
let mask_slot = old
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|old| old.mask_region.map(|_| old.mask));
|
||||||
let old_children = old.map_or_else(Vec::new, |old| old.children);
|
let old_children = old.map_or_else(Vec::new, |old| old.children);
|
||||||
rsc.widgets_mut().needs_redraw.remove(&id);
|
rsc.widgets_mut().needs_redraw.remove(&id);
|
||||||
let px = info.px;
|
|
||||||
|
|
||||||
let window = self.output_size;
|
let window = self.output_size;
|
||||||
let mut painter = Painter {
|
let mut painter = Painter {
|
||||||
state: self,
|
state: self,
|
||||||
frame,
|
rel_base,
|
||||||
extent,
|
region,
|
||||||
extent_len: [None; 2],
|
|
||||||
window,
|
window,
|
||||||
mask: info.mask,
|
mask: info.mask,
|
||||||
layer: info.layer,
|
layer: info.layer,
|
||||||
@@ -330,12 +326,11 @@ impl UiRenderState {
|
|||||||
textures: Vec::new(),
|
textures: Vec::new(),
|
||||||
primitives: Vec::new(),
|
primitives: Vec::new(),
|
||||||
mask_region: None,
|
mask_region: None,
|
||||||
|
mask_slot,
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
size_deps: Vec::new(),
|
size_deps: Vec::new(),
|
||||||
window_own: [Holds::ANY; 2],
|
own: LayoutHolds::ANY,
|
||||||
frame_own_len: [None; 2],
|
|
||||||
under: Vec::new(),
|
under: Vec::new(),
|
||||||
extent_own: [Holds::ANY; 2],
|
|
||||||
answer_under: LayoutHolds::ANY,
|
answer_under: LayoutHolds::ANY,
|
||||||
depth: info.depth,
|
depth: info.depth,
|
||||||
move_idx,
|
move_idx,
|
||||||
@@ -356,20 +351,18 @@ impl UiRenderState {
|
|||||||
let Painter {
|
let Painter {
|
||||||
state: _,
|
state: _,
|
||||||
rsc: _,
|
rsc: _,
|
||||||
frame: _,
|
rel_base: _,
|
||||||
extent: _,
|
region: _,
|
||||||
window: _,
|
window: _,
|
||||||
mask,
|
mask,
|
||||||
textures,
|
textures,
|
||||||
primitives,
|
primitives,
|
||||||
mask_region,
|
mask_region,
|
||||||
extent_own,
|
mask_slot,
|
||||||
extent_len,
|
own,
|
||||||
answer_under,
|
answer_under,
|
||||||
children,
|
children,
|
||||||
size_deps,
|
size_deps,
|
||||||
window_own,
|
|
||||||
frame_own_len,
|
|
||||||
under,
|
under,
|
||||||
move_idx,
|
move_idx,
|
||||||
layer,
|
layer,
|
||||||
@@ -386,18 +379,18 @@ impl UiRenderState {
|
|||||||
// A rule wins on the axis it names, and the draw answers the rest.
|
// A rule wins on the axis it names, and the draw answers the rest.
|
||||||
// Applied here so it is one place rather than every widget that could
|
// Applied here so it is one place rather than every widget that could
|
||||||
// carry one, and so the widget under a rule never learns of it. The
|
// carry one, and so the widget under a rule never learns of it. The
|
||||||
// frame is the answer where the rule gave a length outright: it was
|
// rel base is the answer where the rule gave a length outright: it was
|
||||||
// resolved into the frame when the child was asked, and resolving it
|
// resolved into the rel base when the child was asked, and resolving it
|
||||||
// again here would take the fraction of a fraction.
|
// again here would take the fraction of a fraction.
|
||||||
let rules = rsc.widgets().size_rules(id);
|
let rules = rsc.widgets().size_rules(id);
|
||||||
let ruled = |axis: Axis, reported: LayoutLen| match rules.axis(axis).exact() {
|
let ruled = |axis: Axis, reported: LayoutLen| match rules[axis].exact() {
|
||||||
None => reported,
|
None => reported,
|
||||||
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
|
Some(len) if len.leftover == Weight::ZERO => LayoutLen {
|
||||||
rel: info.frame.axis(axis).rel,
|
rel: info.rel_base[axis].rel,
|
||||||
px: info.frame.axis(axis).px,
|
px: info.rel_base[axis].px,
|
||||||
leftover: Weight::ZERO,
|
leftover: Weight::ZERO,
|
||||||
},
|
},
|
||||||
Some(len) => len.within_len(info.frame.axis(axis)),
|
Some(len) => len.within_len(info.rel_base[axis]),
|
||||||
};
|
};
|
||||||
let size = Size {
|
let size = Size {
|
||||||
x: ruled(Axis::X, size.x),
|
x: ruled(Axis::X, size.x),
|
||||||
@@ -410,48 +403,46 @@ impl UiRenderState {
|
|||||||
// was offered reports the height it needs.
|
// was offered reports the height it needs.
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
mask == info.mask
|
mask == info.mask
|
||||||
|| AXES
|
|| Axis::BOTH.into_iter().all(|axis| size.within_box(
|
||||||
.into_iter()
|
region,
|
||||||
.all(|axis| within_box(size, extent, self.output_size, axis)),
|
self.output_size,
|
||||||
"'{}' ({id:?}) clips to {px:?} and reports {size}",
|
axis
|
||||||
|
)),
|
||||||
|
"'{}' ({id:?}) clips to {} and reports {size}",
|
||||||
rsc.widgets().label(id),
|
rsc.widgets().label(id),
|
||||||
|
region.to_px(window),
|
||||||
);
|
);
|
||||||
for c in &old_children {
|
for c in &old_children {
|
||||||
if !children.contains(c) {
|
if !children.contains(c) {
|
||||||
self.undraw_rec(*c, rsc);
|
self.undraw_rec(*c, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(idx) = mask_slot {
|
||||||
|
rsc.ui_mut().masks.remove(idx);
|
||||||
|
}
|
||||||
if let Some(idx) = retired_move {
|
if let Some(idx) = retired_move {
|
||||||
self.moves.remove(idx);
|
self.moves.remove(idx);
|
||||||
}
|
}
|
||||||
// A rule that is a fraction of the frame is answered with the
|
// A rule that is a fraction of the rel base is answered with the
|
||||||
// frame's own length, so the answer is that frame's and not just
|
// rel base's own length, so the answer is that rel base's and not just
|
||||||
// that many pixels of this window -- the same pin a widget that read
|
// that many pixels of this window -- the same pin a widget that read
|
||||||
// its frame took for its drawing.
|
// its rel base took for its drawing.
|
||||||
let frame_len = AXES.map(|axis| {
|
let mut own_holds = own;
|
||||||
let fraction = rules
|
for axis in Axis::BOTH {
|
||||||
.axis(axis)
|
let fraction = rules[axis].exact().is_some_and(|len| len.rel != Rel::ZERO);
|
||||||
.exact()
|
if fraction {
|
||||||
.is_some_and(|len| len.rel != Rel::ZERO);
|
own_holds[axis].rel_base = Some(info.rel_base[axis]);
|
||||||
match fraction {
|
}
|
||||||
true => Some(info.frame.axis(axis)),
|
|
||||||
false => frame_own_len[axis as usize],
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
let own_holds = LayoutHolds {
|
|
||||||
window: window_own,
|
|
||||||
frame_len,
|
|
||||||
extent: extent_own,
|
|
||||||
extent_len,
|
|
||||||
};
|
|
||||||
let answer_holds = own_holds.and(answer_under);
|
let answer_holds = own_holds.and(answer_under);
|
||||||
let holds = under
|
let holds = under
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.fold(answer_holds, |holds, (_, child)| holds.and(child));
|
.fold(answer_holds, |holds, (_, child)| holds.and(child));
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
holds.contains(self.output_size, info.frame, extent),
|
holds.contains(self.output_size, info.rel_base, region),
|
||||||
"'{}' ({id:?}) drew in {px:?}, outside the ranges it reported: {holds:?}",
|
"'{}' ({id:?}) drew in {}, outside the ranges it reported: {holds:?}",
|
||||||
rsc.widgets().label(id),
|
rsc.widgets().label(id),
|
||||||
|
region.to_px(window),
|
||||||
);
|
);
|
||||||
// What it asked about and did not draw is still something it asked,
|
// What it asked about and did not draw is still something it asked,
|
||||||
// and a change there has to reach it. Asking answered whatever mark
|
// and a change there has to reach it. Asking answered whatever mark
|
||||||
@@ -467,13 +458,11 @@ impl UiRenderState {
|
|||||||
parent_move: move_idx,
|
parent_move: move_idx,
|
||||||
region_node: false,
|
region_node: false,
|
||||||
mask,
|
mask,
|
||||||
frame: UiVec2::FULL_SIZE,
|
rel_base: UiVec2::FULL_SIZE,
|
||||||
part: UiRegion::FULL,
|
region: UiRegion::FULL,
|
||||||
placed: [Place::Within(Part::All); 2],
|
placed: PlaceDesc::WHOLE,
|
||||||
asked: [Place::Within(Part::All); 2],
|
asked: PlaceDesc::WHOLE,
|
||||||
narrow: [None; 2],
|
|
||||||
re_asked: false,
|
re_asked: false,
|
||||||
px,
|
|
||||||
},
|
},
|
||||||
rsc,
|
rsc,
|
||||||
);
|
);
|
||||||
@@ -483,12 +472,11 @@ impl UiRenderState {
|
|||||||
|
|
||||||
let active = ActiveData {
|
let active = ActiveData {
|
||||||
id,
|
id,
|
||||||
extent,
|
placement: region,
|
||||||
frame: info.frame,
|
rel_base: info.rel_base,
|
||||||
narrow: info.narrow,
|
|
||||||
placed: info.placed,
|
placed: info.placed,
|
||||||
asked: info.asked,
|
asked: info.asked,
|
||||||
part: extent,
|
region,
|
||||||
// Whoever asked writes the answer.
|
// Whoever asked writes the answer.
|
||||||
answer: None,
|
answer: None,
|
||||||
re_asked: info.re_asked,
|
re_asked: info.re_asked,
|
||||||
@@ -502,7 +490,7 @@ impl UiRenderState {
|
|||||||
mask_region,
|
mask_region,
|
||||||
children,
|
children,
|
||||||
size_deps,
|
size_deps,
|
||||||
declared: declared_lens(rsc.widgets(), id),
|
declared: rsc.widgets().declared_lens(id),
|
||||||
own_align: rsc.widgets().alignment(id),
|
own_align: rsc.widgets().alignment(id),
|
||||||
move_idx,
|
move_idx,
|
||||||
parent_move: info.parent_move,
|
parent_move: info.parent_move,
|
||||||
@@ -512,7 +500,10 @@ impl UiRenderState {
|
|||||||
};
|
};
|
||||||
rsc.on_draw(&active);
|
rsc.on_draw(&active);
|
||||||
self.active.insert(id, active);
|
self.active.insert(id, active);
|
||||||
(size, answer_holds)
|
Answer {
|
||||||
|
size,
|
||||||
|
holds: answer_holds,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keeps a region node's entry across redraws because descendants retain
|
/// Keeps a region node's entry across redraws because descendants retain
|
||||||
@@ -539,37 +530,31 @@ impl UiRenderState {
|
|||||||
/// drawing ended up. Alignment is exactly that case: the first box is the
|
/// drawing ended up. Alignment is exactly that case: the first box is the
|
||||||
/// question and the smaller placed box holds the drawing. Whether the
|
/// question and the smaller placed box holds the drawing. Whether the
|
||||||
/// answer is stale at all is its caller's question, asked once there.
|
/// answer is stale at all is its caller's question, asked once there.
|
||||||
fn retained_answer(
|
fn retained_answer(&self, id: WidgetId, region: UiRegion, info: DrawInfo) -> Option<Answer> {
|
||||||
&self,
|
|
||||||
id: WidgetId,
|
|
||||||
part: UiRegion,
|
|
||||||
info: DrawInfo,
|
|
||||||
) -> Option<(Size, LayoutHolds)> {
|
|
||||||
let active = self.active.get(&id)?;
|
let active = self.active.get(&id)?;
|
||||||
let has_region_node = active.move_idx != active.parent_move;
|
|
||||||
if !active.drawn
|
if !active.drawn
|
||||||
|| has_region_node != info.region_node
|
|| active.is_region_node() != info.region_node
|
||||||
|| active.parent_move != info.parent_move
|
|| active.parent_move != info.parent_move
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let answer = active.answer?;
|
let answer = active.answer?;
|
||||||
answer
|
answer
|
||||||
.1
|
.holds
|
||||||
.contains(self.output_size, info.frame, part)
|
.contains(self.output_size, info.rel_base, region)
|
||||||
.then_some(answer)
|
.then_some(answer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Keeps the retained drawing if its contract holds for `part`, the box
|
/// Keeps the retained drawing if its contract holds for `part`, the box
|
||||||
/// asked about, and puts it at `extent`, where the answer places it.
|
/// asked about, and puts it at `placed`, where the answer places it.
|
||||||
fn try_reuse(
|
fn try_reuse(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: WidgetId,
|
id: WidgetId,
|
||||||
part: UiRegion,
|
region: UiRegion,
|
||||||
extent: UiRegion,
|
placed: UiRegion,
|
||||||
info: DrawInfo,
|
info: DrawInfo,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
) -> Option<()> {
|
) -> bool {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::ReuseAttempts);
|
diag::bump(Counter::ReuseAttempts);
|
||||||
if rsc.widgets().needs_redraw.contains(&id) {
|
if rsc.widgets().needs_redraw.contains(&id) {
|
||||||
@@ -578,19 +563,20 @@ impl UiRenderState {
|
|||||||
diag::bump(Counter::ReuseDirty);
|
diag::bump(Counter::ReuseDirty);
|
||||||
diag::reuse(id, ReuseOutcome::Dirty);
|
diag::reuse(id, ReuseOutcome::Dirty);
|
||||||
}
|
}
|
||||||
return None;
|
return false;
|
||||||
}
|
}
|
||||||
let active = self.active.get(&id)?;
|
let Some(active) = self.active.get(&id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
if !active.drawn {
|
if !active.drawn {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::reuse(id, ReuseOutcome::Undrawn);
|
diag::reuse(id, ReuseOutcome::Undrawn);
|
||||||
return None;
|
return false;
|
||||||
}
|
}
|
||||||
let has_region_node = active.move_idx != active.parent_move;
|
if active.is_region_node() != info.region_node {
|
||||||
if has_region_node != info.region_node {
|
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::ReuseWrongNode);
|
diag::bump(Counter::ReuseWrongNode);
|
||||||
return None;
|
return false;
|
||||||
}
|
}
|
||||||
// Drawn on another layer: the drawing sits in that layer's list and
|
// Drawn on another layer: the drawing sits in that layer's list and
|
||||||
// paints at its moment, which no amount of geometry says. A container
|
// paints at its moment, which no amount of geometry says. A container
|
||||||
@@ -603,7 +589,10 @@ impl UiRenderState {
|
|||||||
diag::bump(Counter::ReuseWrongLayer);
|
diag::bump(Counter::ReuseWrongLayer);
|
||||||
diag::reuse(id, ReuseOutcome::WrongLayer);
|
diag::reuse(id, ReuseOutcome::WrongLayer);
|
||||||
}
|
}
|
||||||
return None;
|
return false;
|
||||||
|
}
|
||||||
|
if active.parent_mask != info.mask {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
// Drawn somewhere else in the tree: its box is in coordinates it no
|
// Drawn somewhere else in the tree: its box is in coordinates it no
|
||||||
// longer sits in, and its slot names the wrong parent.
|
// longer sits in, and its slot names the wrong parent.
|
||||||
@@ -613,89 +602,59 @@ impl UiRenderState {
|
|||||||
diag::bump(Counter::ReuseWrongParent);
|
diag::bump(Counter::ReuseWrongParent);
|
||||||
diag::reuse(id, ReuseOutcome::WrongParent);
|
diag::reuse(id, ReuseOutcome::WrongParent);
|
||||||
}
|
}
|
||||||
return None;
|
return false;
|
||||||
}
|
}
|
||||||
// In pixels, because the box is a fraction of the window and that
|
// In pixels, because the box is a fraction of the window and that
|
||||||
// may be what changed -- an unchanged fraction of a window half the
|
// may be what changed -- an unchanged fraction of a window half the
|
||||||
// size is half the widget.
|
// size is half the widget.
|
||||||
if !active.holds.contains(self.output_size, info.frame, part) {
|
if !active
|
||||||
|
.holds
|
||||||
|
.contains(self.output_size, info.rel_base, region)
|
||||||
|
{
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
diag::outside(id, active.holds, region, info.rel_base, self.output_size);
|
||||||
// Which of the three said no, so a frame that redraws more
|
return false;
|
||||||
// than it should says where to look. They overlap: a drawing
|
|
||||||
// can be outside two of them at once.
|
|
||||||
let holds = active.holds;
|
|
||||||
for axis in AXES {
|
|
||||||
let n = axis as usize;
|
|
||||||
if holds.extent_len[n].is_some_and(|pinned| pinned != part.axis(axis).len()) {
|
|
||||||
diag::bump(Counter::OutsidePinnedLen);
|
|
||||||
}
|
}
|
||||||
if !holds.window[n].contains(self.output_size.axis(axis))
|
self.relocate(id, placed, info, rsc);
|
||||||
|| holds.frame_len[n].is_some_and(|pinned| pinned != info.frame.axis(axis))
|
true
|
||||||
{
|
|
||||||
diag::bump(Counter::OutsideFrame);
|
|
||||||
}
|
|
||||||
if !holds.extent[n]
|
|
||||||
.contains(part.axis(axis).len().to_px(self.output_size.axis(axis)))
|
|
||||||
{
|
|
||||||
diag::bump(Counter::OutsideExtent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
diag::bump(Counter::ReuseOutside);
|
|
||||||
diag::reuse(id, ReuseOutcome::Outside);
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
self.relocate(id, extent, info, rsc);
|
|
||||||
Some(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Puts a retained drawing where its parent now has it, without drawing:
|
/// Puts a retained drawing where its parent now has it, without drawing:
|
||||||
/// a widget with a node of its own writes that node's translation, and
|
/// a widget with a node of its own writes that node's translation, and
|
||||||
/// one without re-expresses its own drawing and everything inside it.
|
/// one without re-expresses its own drawing and everything inside it.
|
||||||
fn relocate(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
fn relocate(&mut self, id: WidgetId, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||||
let active = &self.active[&id];
|
let active = &self.active[&id];
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
!rsc.widgets().needs_redraw.contains(&id),
|
!rsc.widgets().needs_redraw.contains(&id),
|
||||||
"'{}' ({id:?}) placed while marked to draw",
|
"'{}' ({id:?}) placed while marked to draw",
|
||||||
rsc.widgets().label(id)
|
rsc.widgets().label(id)
|
||||||
);
|
);
|
||||||
let has_region_node = active.move_idx != active.parent_move;
|
let is_region_node = active.is_region_node();
|
||||||
let local = match has_region_node {
|
let local = match is_region_node {
|
||||||
true => local_region(extent),
|
true => placed.at_origin(),
|
||||||
false => extent,
|
false => placed,
|
||||||
};
|
};
|
||||||
let moved = active.extent != local;
|
let moved = active.placement != local;
|
||||||
let slot = active.move_idx;
|
let slot = active.move_idx;
|
||||||
if has_region_node {
|
if is_region_node {
|
||||||
self.moves.set(slot, translation(extent));
|
self.moves.set(slot, placed.as_translation());
|
||||||
}
|
}
|
||||||
if moved {
|
if moved {
|
||||||
self.reposition(id, local, info, rsc);
|
self.reposition(id, local, info, rsc);
|
||||||
}
|
}
|
||||||
self.redepth(id, info.depth);
|
self.redepth(id, info.depth);
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
active.frame = info.frame;
|
active.rel_base = info.rel_base;
|
||||||
active.placed = info.placed;
|
active.placed = info.placed;
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
{
|
||||||
match (moved, has_region_node) {
|
let (counter, outcome) = match (moved, is_region_node) {
|
||||||
(true, true) => diag::bump(Counter::ReuseMoved),
|
(true, true) => (Counter::ReuseMoved, ReuseOutcome::Moved),
|
||||||
(true, false) => diag::bump(Counter::ReuseRemapped),
|
(true, false) => (Counter::ReuseRemapped, ReuseOutcome::Remapped),
|
||||||
(false, _) => diag::bump(Counter::ReuseExact),
|
(false, _) => (Counter::ReuseExact, ReuseOutcome::Exact),
|
||||||
}
|
};
|
||||||
diag::reuse(
|
diag::bump(counter);
|
||||||
id,
|
diag::reuse(id, outcome);
|
||||||
if moved {
|
|
||||||
if has_region_node {
|
|
||||||
ReuseOutcome::Moved
|
|
||||||
} else {
|
|
||||||
ReuseOutcome::Remapped
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ReuseOutcome::Exact
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -712,16 +671,15 @@ impl UiRenderState {
|
|||||||
&mut self,
|
&mut self,
|
||||||
child: WidgetId,
|
child: WidgetId,
|
||||||
at: &Placing,
|
at: &Placing,
|
||||||
place: [Place; 2],
|
place: PlaceDesc,
|
||||||
rsc: &mut dyn UiRsc,
|
rsc: &mut dyn UiRsc,
|
||||||
) {
|
) {
|
||||||
let active = &self.active[&child];
|
let active = &self.active[&child];
|
||||||
let (frame, part) = Self::ask_again(active, at, place);
|
let (rel_base, region) = Self::ask_again(active, at, place);
|
||||||
let extent = placed_extent(
|
let placed = place.placement(
|
||||||
part,
|
region,
|
||||||
active.measured().unwrap_or(active.size),
|
active.measured().unwrap_or(active.size),
|
||||||
active.declared,
|
active.declared,
|
||||||
place.map(Place::fills),
|
|
||||||
active.own_align,
|
active.own_align,
|
||||||
);
|
);
|
||||||
let info = DrawInfo {
|
let info = DrawInfo {
|
||||||
@@ -729,67 +687,59 @@ impl UiRenderState {
|
|||||||
parent: Some(at.id),
|
parent: Some(at.id),
|
||||||
depth: at.depth + 1,
|
depth: at.depth + 1,
|
||||||
parent_move: at.move_idx,
|
parent_move: at.move_idx,
|
||||||
region_node: active.move_idx != active.parent_move,
|
region_node: active.is_region_node(),
|
||||||
mask: at.mask,
|
mask: at.mask,
|
||||||
frame,
|
rel_base,
|
||||||
part,
|
region,
|
||||||
placed: place,
|
placed: place,
|
||||||
asked: active.asked,
|
asked: active.asked,
|
||||||
narrow: active.narrow,
|
|
||||||
re_asked: active.re_asked,
|
re_asked: active.re_asked,
|
||||||
px: frame.to_px(at.window),
|
|
||||||
};
|
};
|
||||||
self.relocate(child, extent, info, rsc);
|
self.relocate(child, placed, info, rsc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The frame and the box a widget already drawn is given at `place` of
|
/// The rel base and the box a widget already drawn is given at `place` of
|
||||||
/// the box its parent is being taken as. What narrowed its frame and what
|
/// the box its parent is being taken as. What narrowed its rel base and what
|
||||||
/// it declared are its own record's, so both are resolved against that
|
/// it declared are its own record's, so both are resolved against that
|
||||||
/// parent's frame again exactly as the first ask resolved them.
|
/// parent's rel base again exactly as the first ask resolved them.
|
||||||
fn ask_again(active: &ActiveData, at: &Placing, place: [Place; 2]) -> (UiVec2, UiRegion) {
|
fn ask_again(active: &ActiveData, at: &Placing, place: PlaceDesc) -> (UiVec2, UiRegion) {
|
||||||
frame_and_extent(
|
place.rel_base_and_region(at.region, at.rel_base, active.declared, active.own_align)
|
||||||
at.extent,
|
|
||||||
at.frame,
|
|
||||||
place,
|
|
||||||
active.narrow,
|
|
||||||
active.declared,
|
|
||||||
active.own_align,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Re-places everything inside a widget whose own box moved. Every child
|
/// Re-places everything inside a widget whose own box moved. Every child
|
||||||
/// is placed as a part of that box, so each one's new box is its retained
|
/// is placed as a part of that box, so each one's new box is its retained
|
||||||
/// part re-added to the new start -- and a child whose own box then did
|
/// part re-added to the new start -- and a child whose own box then did
|
||||||
/// not change is not touched at all.
|
/// not change is not touched at all.
|
||||||
fn reposition(&mut self, id: WidgetId, extent: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
fn reposition(&mut self, id: WidgetId, placed: UiRegion, info: DrawInfo, rsc: &mut dyn UiRsc) {
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
active.extent = extent;
|
active.placement = placed;
|
||||||
for primitive in &active.primitives {
|
for primitive in &active.primitives {
|
||||||
let handle = &primitive.handle;
|
let handle = &primitive.handle;
|
||||||
*self.layers[handle.layer].region_mut(handle) = primitive.region.within(&extent);
|
*self.layers[handle.layer].region_mut(handle) = primitive.region.within(&placed);
|
||||||
}
|
}
|
||||||
if let Some(mask_region) = active.mask_region {
|
if let Some(mask_region) = active.mask_region {
|
||||||
rsc.ui_mut().masks.get_mut(active.mask).region = mask_region.within(&extent);
|
rsc.ui_mut().masks.get_mut(active.mask).region = mask_region.within(&placed);
|
||||||
}
|
}
|
||||||
let at = Placing {
|
let at = Placing {
|
||||||
id,
|
id,
|
||||||
extent,
|
region: placed,
|
||||||
frame: info.frame,
|
rel_base: info.rel_base,
|
||||||
window: self.output_size,
|
|
||||||
depth: info.depth,
|
depth: info.depth,
|
||||||
move_idx: active.move_idx,
|
move_idx: active.move_idx,
|
||||||
mask: active.mask,
|
mask: active.mask,
|
||||||
};
|
};
|
||||||
let children = active.children.len();
|
// Taken out and put back so that placing a child can borrow the state
|
||||||
for index in 0..children {
|
// it needs; nothing on that path reads this widget's own child list.
|
||||||
let child = self.active[&id].children[index];
|
let children = std::mem::take(&mut self.active.get_mut(&id).unwrap().children);
|
||||||
|
for &child in &children {
|
||||||
self.place_child(child, &at, rsc);
|
self.place_child(child, &at, rsc);
|
||||||
}
|
}
|
||||||
|
self.active.get_mut(&id).unwrap().children = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reused subtree keeps its shape, so every widget in it moves by the
|
/// A reused subtree keeps its shape, so each widget in it keeps its depth
|
||||||
/// same amount -- and where the top of it did not move, none of it did,
|
/// under the top -- and where the top's own depth did not change, none of
|
||||||
/// which is what makes this free in the ordinary case.
|
/// them did, which is what makes this free in the ordinary case.
|
||||||
fn redepth(&mut self, id: WidgetId, depth: usize) {
|
fn redepth(&mut self, id: WidgetId, depth: usize) {
|
||||||
let Some(active) = self.active.get_mut(&id) else {
|
let Some(active) = self.active.get_mut(&id) else {
|
||||||
return;
|
return;
|
||||||
@@ -798,22 +748,22 @@ impl UiRenderState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
active.depth = depth;
|
active.depth = depth;
|
||||||
let children = active.children.len();
|
// Taken out and put back so the walk can borrow the state it needs;
|
||||||
for index in 0..children {
|
// it only ever goes further down, so it reads no list but its own.
|
||||||
let child = self.active[&id].children[index];
|
let children = std::mem::take(&mut active.children);
|
||||||
|
for &child in &children {
|
||||||
self.redepth(child, depth + 1);
|
self.redepth(child, depth + 1);
|
||||||
}
|
}
|
||||||
|
self.active.get_mut(&id).unwrap().children = children;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
fn hints_agree(id: WidgetId, size: Size, rsc: &dyn UiRsc) -> bool {
|
||||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
AXES.into_iter().all(|axis| {
|
Axis::BOTH
|
||||||
widget
|
.into_iter()
|
||||||
.size_hint(axis)
|
.all(|axis| widget.size_hint(axis).is_none_or(|hint| hint == size[axis]))
|
||||||
.is_none_or(|hint| hint == size.axis(axis))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes a widget's record out and frees what it drew.
|
/// Takes a widget's record out and frees what it drew.
|
||||||
@@ -826,6 +776,9 @@ impl UiRenderState {
|
|||||||
rsc.ui_mut().masks.remove(mask);
|
rsc.ui_mut().masks.remove(mask);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if undraw && active.mask_region.take().is_some() {
|
||||||
|
rsc.ui_mut().masks.remove(active.mask);
|
||||||
|
}
|
||||||
active.primitives.clear();
|
active.primitives.clear();
|
||||||
active.textures.clear();
|
active.textures.clear();
|
||||||
rsc.ui_mut().textures.free();
|
rsc.ui_mut().textures.free();
|
||||||
@@ -875,12 +828,11 @@ impl UiRenderState {
|
|||||||
id,
|
id,
|
||||||
ActiveData {
|
ActiveData {
|
||||||
id,
|
id,
|
||||||
extent: UiRegion::FULL,
|
placement: UiRegion::FULL,
|
||||||
frame: UiVec2::FULL_SIZE,
|
rel_base: UiVec2::FULL_SIZE,
|
||||||
narrow: [None; 2],
|
placed: PlaceDesc::WHOLE,
|
||||||
placed: [Place::Within(Part::All); 2],
|
asked: PlaceDesc::WHOLE,
|
||||||
asked: [Place::Within(Part::All); 2],
|
region: UiRegion::FULL,
|
||||||
part: UiRegion::FULL,
|
|
||||||
answer: None,
|
answer: None,
|
||||||
re_asked: false,
|
re_asked: false,
|
||||||
size,
|
size,
|
||||||
@@ -894,7 +846,7 @@ impl UiRenderState {
|
|||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
size_deps: Vec::new(),
|
size_deps: Vec::new(),
|
||||||
move_idx: info.parent_move,
|
move_idx: info.parent_move,
|
||||||
declared: [None; 2],
|
declared: Declared::NONE,
|
||||||
own_align: rsc.widgets().alignment(id),
|
own_align: rsc.widgets().alignment(id),
|
||||||
parent_move: info.parent_move,
|
parent_move: info.parent_move,
|
||||||
mask: info.mask,
|
mask: info.mask,
|
||||||
@@ -913,6 +865,7 @@ impl UiRenderState {
|
|||||||
self.slots.clear();
|
self.slots.clear();
|
||||||
self.moves.clear();
|
self.moves.clear();
|
||||||
self.layers.clear();
|
self.layers.clear();
|
||||||
|
rsc.ui_mut().masks = Default::default();
|
||||||
rsc.widgets_mut().needs_redraw.clear();
|
rsc.widgets_mut().needs_redraw.clear();
|
||||||
self.free(rsc);
|
self.free(rsc);
|
||||||
}
|
}
|
||||||
@@ -1064,7 +1017,7 @@ impl UiRenderState {
|
|||||||
let active = self.active.get(&id.id())?;
|
let active = self.active.get(&id.id())?;
|
||||||
active.drawn.then(|| {
|
active.drawn.then(|| {
|
||||||
self.moves
|
self.moves
|
||||||
.resolve(active.move_idx, active.extent)
|
.resolve(active.move_idx, active.placement)
|
||||||
.to_px(self.output_size)
|
.to_px(self.output_size)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1083,7 +1036,7 @@ impl UiRenderState {
|
|||||||
// to draw -- with the mark left on, so the parent draws it rather
|
// to draw -- with the mark left on, so the parent draws it rather
|
||||||
// than keeping it. So is a widget the parent asked twice: its
|
// than keeping it. So is a widget the parent asked twice: its
|
||||||
// layout rests on an answer this widget cannot give again alone.
|
// layout rests on an answer this widget cannot give again alone.
|
||||||
let declared_changed = declared_lens(rsc.widgets(), id) != active.declared;
|
let declared_changed = rsc.widgets().declared_lens(id) != active.declared;
|
||||||
let alignment_changed = rsc.widgets().alignment(id) != active.own_align;
|
let alignment_changed = rsc.widgets().alignment(id) != active.own_align;
|
||||||
if let Some(parent) = active.parent
|
if let Some(parent) = active.parent
|
||||||
&& (declared_changed
|
&& (declared_changed
|
||||||
@@ -1106,10 +1059,10 @@ impl UiRenderState {
|
|||||||
// box is its own to work out again against the output. Every other
|
// box is its own to work out again against the output. Every other
|
||||||
// widget was given one.
|
// widget was given one.
|
||||||
let Some(parent) = active.parent else {
|
let Some(parent) = active.parent else {
|
||||||
let (frame, extent) = Self::root_layout(id, rsc.widgets());
|
let (rel_base, region) = Self::root_layout(id, rsc.widgets());
|
||||||
let info = DrawInfo {
|
let info = DrawInfo {
|
||||||
mask: active.parent_mask,
|
mask: active.parent_mask,
|
||||||
..self.root_info(frame, extent)
|
..self.root_info(rel_base, region)
|
||||||
};
|
};
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::LocalRedraws);
|
diag::bump(Counter::LocalRedraws);
|
||||||
@@ -1123,8 +1076,8 @@ impl UiRenderState {
|
|||||||
// draw ran in and what its children's parts are of. Where the
|
// draw ran in and what its children's parts are of. Where the
|
||||||
// parent's answer put its own drawing is not a question anybody
|
// parent's answer put its own drawing is not a question anybody
|
||||||
// asked, and nothing is asked in it here either.
|
// asked, and nothing is asked in it here either.
|
||||||
let parent_at = self.placing_of(parent, self.active[&parent].part);
|
let parent_at = self.placing_of(parent, self.active[&parent].region);
|
||||||
let (frame, part) = Self::ask_again(active, &parent_at, active.asked);
|
let (rel_base, region) = Self::ask_again(active, &parent_at, active.asked);
|
||||||
let info = DrawInfo {
|
let info = DrawInfo {
|
||||||
layer: active.layer,
|
layer: active.layer,
|
||||||
parent: active.parent,
|
parent: active.parent,
|
||||||
@@ -1132,30 +1085,28 @@ impl UiRenderState {
|
|||||||
parent_move: active.parent_move,
|
parent_move: active.parent_move,
|
||||||
region_node: rsc.widgets().is_region_node(id),
|
region_node: rsc.widgets().is_region_node(id),
|
||||||
mask: active.parent_mask,
|
mask: active.parent_mask,
|
||||||
frame,
|
rel_base,
|
||||||
part,
|
region,
|
||||||
placed: active.asked,
|
placed: active.asked,
|
||||||
asked: active.asked,
|
asked: active.asked,
|
||||||
narrow: active.narrow,
|
|
||||||
re_asked: false,
|
re_asked: false,
|
||||||
px: frame.to_px(self.output_size),
|
|
||||||
};
|
};
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::LocalRedraws);
|
diag::bump(Counter::LocalRedraws);
|
||||||
|
|
||||||
let old = self.remove(id, false, rsc);
|
let old = self.remove(id, false, rsc);
|
||||||
let answer = self.draw_inner(id, info, old, rsc);
|
let drawn = self.draw_inner(id, info, old, rsc);
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
// A wider contract does not invalidate the guarantee the parent kept.
|
// A wider contract does not invalidate the guarantee the parent kept.
|
||||||
// Retain that guarantee so widening and narrowing back do not churn it.
|
// Retain that guarantee so widening and narrowing back do not churn it.
|
||||||
if let Some((size, holds)) = was_answer
|
if let Some(was) = was_answer
|
||||||
&& answer.0 == size
|
&& drawn.answer.size == was.size
|
||||||
&& answer.1.covers(holds)
|
&& drawn.answer.holds.covers(was.holds)
|
||||||
{
|
{
|
||||||
active.answer = was_answer;
|
active.answer = was_answer;
|
||||||
}
|
}
|
||||||
if active.holds.covers(was_holds)
|
if active.holds.covers(was_holds)
|
||||||
&& was_holds.contains(self.output_size, active.frame, active.extent)
|
&& was_holds.contains(self.output_size, active.rel_base, active.placement)
|
||||||
{
|
{
|
||||||
active.holds = was_holds;
|
active.holds = was_holds;
|
||||||
}
|
}
|
||||||
@@ -1172,22 +1123,21 @@ impl UiRenderState {
|
|||||||
// The answer stands, so where the parent put it stands: the
|
// The answer stands, so where the parent put it stands: the
|
||||||
// fresh drawing goes back there -- the same place, of the box
|
// fresh drawing goes back there -- the same place, of the box
|
||||||
// the parent's answer chose rather than the one it was asked in.
|
// the parent's answer chose rather than the one it was asked in.
|
||||||
let at = self.placing_of(parent, self.active[&parent].extent);
|
let at = self.placing_of(parent, self.active[&parent].placement);
|
||||||
self.place_in(id, &at, was_place, rsc);
|
self.place_in(id, &at, was_place, rsc);
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A drawn widget as the thing its children are placed within, with
|
/// A drawn widget as the thing its children are placed within, with
|
||||||
/// `extent` as the box their parts are of: the box it was asked in for
|
/// `region` as the box their parts are of: the box it was asked in for
|
||||||
/// asking one of them again, the box its answer chose for placing one.
|
/// asking one of them again, the box its answer chose for placing one.
|
||||||
fn placing_of(&self, id: WidgetId, extent: UiRegion) -> Placing {
|
fn placing_of(&self, id: WidgetId, region: UiRegion) -> Placing {
|
||||||
let active = &self.active[&id];
|
let active = &self.active[&id];
|
||||||
Placing {
|
Placing {
|
||||||
id,
|
id,
|
||||||
extent,
|
region,
|
||||||
frame: active.frame,
|
rel_base: active.rel_base,
|
||||||
window: self.output_size,
|
|
||||||
depth: active.depth,
|
depth: active.depth,
|
||||||
move_idx: active.move_idx,
|
move_idx: active.move_idx,
|
||||||
mask: active.mask,
|
mask: active.mask,
|
||||||
@@ -1195,21 +1145,24 @@ impl UiRenderState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether what a widget reports along `axis` is inside the box it drew in.
|
impl Size {
|
||||||
/// Both are lengths of the window, so the comparison is in its pixels. A
|
/// Whether what a widget reports along `axis` is inside the box it drew
|
||||||
/// share is a length only to whoever divides one, so it is not a claim about
|
/// in. Both are lengths of the window, so the comparison is in its
|
||||||
/// this box and cannot exceed it.
|
/// pixels. A share is a length only to whoever divides one, so it is not
|
||||||
fn within_box(size: Size, extent: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
/// a claim about this box and cannot exceed it.
|
||||||
let len = size.axis(axis);
|
fn within_box(self, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||||
let window = window.axis(axis);
|
let len = self[axis];
|
||||||
|
let window = window[axis];
|
||||||
len.leftover != Weight::ZERO
|
len.leftover != Weight::ZERO
|
||||||
|| Len::from_parts(len.rel, len.px).to_px(window) <= extent.axis(axis).len().to_px(window)
|
|| len.without_leftover().to_px(window) <= region[axis].len().to_px(window)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A box in a fresh region node keeps its window-unit length and starts at
|
impl UiRegion {
|
||||||
/// that node's origin.
|
/// A box in a fresh region node keeps its window-unit length and starts
|
||||||
fn local_region(region: UiRegion) -> UiRegion {
|
/// at that node's origin.
|
||||||
let size = region.size();
|
fn at_origin(self) -> UiRegion {
|
||||||
|
let size = self.size();
|
||||||
UiRegion::new(
|
UiRegion::new(
|
||||||
UiSpan::new(Len::ZERO, size.x),
|
UiSpan::new(Len::ZERO, size.x),
|
||||||
UiSpan::new(Len::ZERO, size.y),
|
UiSpan::new(Len::ZERO, size.y),
|
||||||
@@ -1218,10 +1171,11 @@ fn local_region(region: UiRegion) -> UiRegion {
|
|||||||
|
|
||||||
/// A region node changes only the origin. A full relative span anchored at
|
/// A region node changes only the origin. A full relative span anchored at
|
||||||
/// the box start composes as that translation in both the CPU and shader.
|
/// the box start composes as that translation in both the CPU and shader.
|
||||||
fn translation(region: UiRegion) -> UiRegion {
|
fn as_translation(self) -> UiRegion {
|
||||||
UiRegion {
|
UiRegion {
|
||||||
x: UiSpan::new(region.x.start, region.x.start + Len::FULL),
|
x: UiSpan::new(self.x.start, self.x.start + Len::FULL),
|
||||||
y: UiSpan::new(region.y.start, region.y.start + Len::FULL),
|
y: UiSpan::new(self.y.start, self.y.start + Len::FULL),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -93,3 +93,31 @@ macro_rules! impl_op {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) use impl_op;
|
pub(crate) use impl_op;
|
||||||
|
|
||||||
|
/// `Index<Axis>` for a pair, which is how every pair here is read by axis.
|
||||||
|
/// The generics clause is given in braces where the type has one.
|
||||||
|
macro_rules! impl_axis_index {
|
||||||
|
($({$($gen:tt)*})? $T:ty => $Out:ty) => {
|
||||||
|
const impl $(<$($gen)*>)? std::ops::Index<crate::Axis> for $T {
|
||||||
|
type Output = $Out;
|
||||||
|
|
||||||
|
fn index(&self, axis: crate::Axis) -> &$Out {
|
||||||
|
match axis {
|
||||||
|
crate::Axis::X => &self.x,
|
||||||
|
crate::Axis::Y => &self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const impl $(<$($gen)*>)? std::ops::IndexMut<crate::Axis> for $T {
|
||||||
|
fn index_mut(&mut self, axis: crate::Axis) -> &mut $Out {
|
||||||
|
match axis {
|
||||||
|
crate::Axis::X => &mut self.x,
|
||||||
|
crate::Axis::Y => &mut self.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use impl_axis_index;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{Axis, LayoutLen, Weight};
|
use crate::util::impl_axis_index;
|
||||||
|
use crate::{Axis, LayoutLen, Len};
|
||||||
|
|
||||||
/// What a widget's length on one axis is, as a rule its parent applies where
|
/// What a widget's length on one axis is, as a rule its parent applies where
|
||||||
/// it draws it rather than an answer the widget gives about itself.
|
/// it draws it rather than an answer the widget gives about itself.
|
||||||
@@ -19,14 +20,9 @@ pub enum SizeRule {
|
|||||||
|
|
||||||
impl SizeRule {
|
impl SizeRule {
|
||||||
/// The length this rule gives without the widget being drawn, if it can
|
/// The length this rule gives without the widget being drawn, if it can
|
||||||
/// give one. `leftover` is never among them: a share is a length only to
|
/// give one.
|
||||||
/// whoever divides one, so it passes up in the reported size instead and
|
pub fn declared(&self) -> Option<Len> {
|
||||||
/// is resolved there.
|
self.exact().and_then(LayoutLen::declared)
|
||||||
pub fn declared(&self) -> Option<LayoutLen> {
|
|
||||||
match self {
|
|
||||||
Self::Exact(len) if len.leftover == Weight::ZERO => Some(*len),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The length this rule gives outright, whatever the widget reports --
|
/// The length this rule gives outright, whatever the widget reports --
|
||||||
@@ -70,18 +66,29 @@ pub struct SizeRules {
|
|||||||
pub y: SizeRule,
|
pub y: SizeRule,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SizeRules {
|
impl_axis_index!(SizeRules => SizeRule);
|
||||||
pub fn axis(&self, axis: Axis) -> SizeRule {
|
|
||||||
match axis {
|
/// What a widget's box is on each axis where something says so outright,
|
||||||
Axis::X => self.x,
|
/// before it is drawn: a rule beside it, or a hint it gives about itself.
|
||||||
Axis::Y => self.y,
|
/// Whoever draws the widget resolves these against its rel base.
|
||||||
|
///
|
||||||
|
/// A [`Len`] rather than a [`LayoutLen`], because a share can never be one
|
||||||
|
/// -- see [`LayoutLen::declared`].
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub struct Declared {
|
||||||
|
pub x: Option<Len>,
|
||||||
|
pub y: Option<Len>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Declared {
|
||||||
|
pub const NONE: Self = Self { x: None, y: None };
|
||||||
|
|
||||||
|
pub fn from_axes(f: impl Fn(Axis) -> Option<Len>) -> Self {
|
||||||
|
Self {
|
||||||
|
x: f(Axis::X),
|
||||||
|
y: f(Axis::Y),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut SizeRule {
|
impl_axis_index!(Declared => Option<Len>);
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -130,10 +130,10 @@ impl Widgets {
|
|||||||
pub fn set_size_rule(&mut self, id: impl IdLike, axis: Axis, rule: SizeRule) {
|
pub fn set_size_rule(&mut self, id: impl IdLike, axis: Axis, rule: SizeRule) {
|
||||||
let id = id.id();
|
let id = id.id();
|
||||||
let data = self.data_mut(id).unwrap();
|
let data = self.data_mut(id).unwrap();
|
||||||
if *data.size.axis_mut(axis) == rule {
|
if data.size[axis] == rule {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*data.size.axis_mut(axis) = rule;
|
data.size[axis] = rule;
|
||||||
self.needs_redraw.insert(id);
|
self.needs_redraw.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,10 +147,10 @@ impl Widgets {
|
|||||||
pub fn set_alignment(&mut self, id: impl IdLike, axis: Axis, align: AxisAlign) {
|
pub fn set_alignment(&mut self, id: impl IdLike, axis: Axis, align: AxisAlign) {
|
||||||
let id = id.id();
|
let id = id.id();
|
||||||
let data = self.data_mut(id).unwrap();
|
let data = self.data_mut(id).unwrap();
|
||||||
if *data.align.axis_mut(axis) == align {
|
if data.align[axis] == align {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*data.align.axis_mut(axis) = align;
|
data.align[axis] = align;
|
||||||
self.needs_redraw.insert(id);
|
self.needs_redraw.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+6
-6
@@ -118,9 +118,9 @@ pub struct Branch {
|
|||||||
impl Widget for Branch {
|
impl Widget for Branch {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
||||||
let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut)));
|
let top = UiSpan::new(Len::ZERO, cut).shifted_desc();
|
||||||
let measured = painter
|
let measured = painter
|
||||||
.widget_at(&self.probe, [None; 2], [Place::Within(Part::All), top])
|
.widget_at(&self.probe, top.on_axis(Axis::Y))
|
||||||
.len(Axis::X);
|
.len(Axis::X);
|
||||||
let len = measured.apply_leftover();
|
let len = measured.apply_leftover();
|
||||||
let px = painter.to_px(len, Axis::X);
|
let px = painter.to_px(len, Axis::X);
|
||||||
@@ -134,11 +134,11 @@ impl Widget for Branch {
|
|||||||
};
|
};
|
||||||
painter.window_holds(Axis::X, holds.through(len));
|
painter.window_holds(Axis::X, holds.through(len));
|
||||||
|
|
||||||
let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y))));
|
let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
|
||||||
let place = [Place::Within(Part::All), below];
|
let place = below.on_axis(Axis::Y);
|
||||||
match px > threshold {
|
match px > threshold {
|
||||||
true => painter.widget_at(&self.wide, [None; 2], place),
|
true => painter.widget_at(&self.wide, place),
|
||||||
false => painter.widget_at(&self.narrow, [None; 2], place),
|
false => painter.widget_at(&self.narrow, place),
|
||||||
};
|
};
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ impl Widget for Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn size_hint(&self, axis: Axis) -> Option<LayoutLen> {
|
fn size_hint(&self, axis: Axis) -> Option<LayoutLen> {
|
||||||
Some(LayoutLen::px(self.handle.size().axis(axis)))
|
Some(LayoutLen::px(self.handle.size()[axis]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,14 +7,8 @@ pub struct Offset {
|
|||||||
|
|
||||||
impl Widget for Offset {
|
impl Widget for Offset {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
// The whole of this widget's box, moved: the frame passes through, so
|
painter
|
||||||
// what the child declares or reports means the same as it would
|
.widget_at(&self.inner, UiRegion::FULL.offset(self.amt))
|
||||||
// without the offset.
|
.size()
|
||||||
let moved = |len: Len, amt: Len| Place::Within(Part::From(UiSpan::new(amt, len + amt)));
|
|
||||||
let place = [
|
|
||||||
moved(painter.extent_len(Axis::X), self.amt.x),
|
|
||||||
moved(painter.extent_len(Axis::Y), self.amt.y),
|
|
||||||
];
|
|
||||||
painter.widget_at(&self.inner, [None; 2], place).size()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,31 +14,12 @@ impl Widget for Pad {
|
|||||||
// widget -- the slack is the inner's to sit in, and forcing the near
|
// widget -- the slack is the inner's to sit in, and forcing the near
|
||||||
// edge pinned it to a corner it had not asked for.
|
// edge pinned it to a corner it had not asked for.
|
||||||
//
|
//
|
||||||
// Padding is an inset of both: it comes off the frame, so `rel(1)`
|
// Padding is an inset of both: it comes off the rel base, so `rel(1)`
|
||||||
// under it fills this widget rather than overflowing it by the
|
// under it fills this widget rather than overflowing it by the
|
||||||
// padding, and it comes off the box, so what is drawn sits inside.
|
// padding, and it comes off the box, so what is drawn sits inside.
|
||||||
// The two stay distinct -- the box can be narrower still, where a row
|
// The two stay distinct -- the box can be narrower still, where a row
|
||||||
// asked this widget in the room left, and a text wraps at that.
|
// asked this widget in the room left, and a text wraps at that.
|
||||||
let inset = |lead: Px, trail: Px| {
|
let inner = painter.widget_at(&self.inner, self.padding.region()).size();
|
||||||
Place::Within(Part::Of(UiSpan::new(
|
|
||||||
Len::from_parts(Rel::ZERO, lead),
|
|
||||||
Len::from_parts(Rel::ONE, -trail),
|
|
||||||
)))
|
|
||||||
};
|
|
||||||
let place = [
|
|
||||||
inset(self.padding.left, self.padding.right),
|
|
||||||
inset(self.padding.top, self.padding.bottom),
|
|
||||||
];
|
|
||||||
// Read from this widget's own frame rather than written as a
|
|
||||||
// fraction of it: a frame is a length of the window like everything
|
|
||||||
// else here, and taking the padding off is the whole of what this
|
|
||||||
// widget does to it.
|
|
||||||
let narrow = [
|
|
||||||
(Axis::X, self.padding.left + self.padding.right),
|
|
||||||
(Axis::Y, self.padding.top + self.padding.bottom),
|
|
||||||
]
|
|
||||||
.map(|(axis, pixels)| Some(painter.frame_len(axis) - Len::from_parts(Rel::ZERO, pixels)));
|
|
||||||
let inner = painter.widget_at(&self.inner, narrow, place).size();
|
|
||||||
Size {
|
Size {
|
||||||
x: LayoutLen {
|
x: LayoutLen {
|
||||||
px: inner.x.px + self.padding.left + self.padding.right,
|
px: inner.x.px + self.padding.left + self.padding.right,
|
||||||
|
|||||||
@@ -14,27 +14,27 @@ impl Widget for Scroll {
|
|||||||
let container_len = painter.px_len(self.axis);
|
let container_len = painter.px_len(self.axis);
|
||||||
// Asked in the whole viewport, then put at the scrolled offset.
|
// Asked in the whole viewport, then put at the scrolled offset.
|
||||||
let answer_len = painter
|
let answer_len = painter
|
||||||
.widget_at(&self.inner, [None; 2], [Place::Fill(Part::All); 2])
|
.widget_at(&self.inner, PlaceDesc::WHOLE.fills())
|
||||||
.len(self.axis);
|
.len(self.axis);
|
||||||
let fixed = painter.to_px(Len::from_parts(answer_len.rel, answer_len.px), self.axis);
|
let answer_px = painter.to_px(answer_len.without_leftover(), self.axis);
|
||||||
self.container_len = container_len;
|
self.container_len = container_len;
|
||||||
self.content_len = fixed.max(container_len);
|
self.content_len = answer_px.max(container_len);
|
||||||
|
|
||||||
if self.snap_end {
|
if self.snap_end {
|
||||||
self.amt = self.content_len - self.container_len;
|
self.amt = self.content_len - self.container_len;
|
||||||
}
|
}
|
||||||
self.update_amt();
|
self.update_amt();
|
||||||
let align = painter.alignment().axis(self.axis);
|
let align = painter.alignment()[self.axis];
|
||||||
// Content of a fixed length that fits sits at the start of any box it
|
// Content of a fixed length that fits sits at the start of any box it
|
||||||
// fits in -- but only anchored there. Anywhere else it is a part of
|
// fits in -- but only anchored there. Anywhere else it is a part of
|
||||||
// the room left over, so it moves with every length the box takes and
|
// the room left over, so it moves with every length the box takes and
|
||||||
// the drawing holds for that length alone. One scrolled part way sits
|
// the drawing holds for that length alone. One scrolled part way sits
|
||||||
// where it is until the box shrinks past what is left of it. Kept to
|
// where it is until the box shrinks past what is left of it. Kept to
|
||||||
// the end, it moves with every length.
|
// the end, it moves with every length.
|
||||||
let fixed_len = answer_len.rel == Rel::ZERO && answer_len.leftover == Weight::ZERO;
|
let answer_is_px = answer_len.is_px();
|
||||||
if fixed_len && self.content_len <= self.container_len && align == AxisAlign::NEG {
|
if answer_is_px && self.content_len <= self.container_len && align == AxisAlign::NEG {
|
||||||
painter.holds(self.axis, fixed..=Px::MAX);
|
painter.holds(self.axis, answer_px..=Px::MAX);
|
||||||
} else if fixed_len && !self.snap_end {
|
} else if answer_is_px && !self.snap_end {
|
||||||
let left = self.content_len - self.amt;
|
let left = self.content_len - self.amt;
|
||||||
painter.holds(self.axis, Px::MIN..=left);
|
painter.holds(self.axis, Px::MIN..=left);
|
||||||
}
|
}
|
||||||
@@ -54,18 +54,15 @@ impl Widget for Scroll {
|
|||||||
let content = match moved || self.content_len != self.container_len {
|
let content = match moved || self.content_len != self.container_len {
|
||||||
true => {
|
true => {
|
||||||
let start = Len::from_parts(Rel::ZERO, anchor - self.amt);
|
let start = Len::from_parts(Rel::ZERO, anchor - self.amt);
|
||||||
Part::From(UiSpan::new(start, start.offset(self.content_len)))
|
UiSpan::new(start, start.offset(self.content_len)).shifted_desc()
|
||||||
}
|
}
|
||||||
false => Part::All,
|
false => PlaceDescAxis::WHOLE,
|
||||||
};
|
};
|
||||||
// The viewport is the inner's frame, so a fraction it declares or
|
// The viewport is the inner's rel base, so a fraction it declares or
|
||||||
// reports is a fraction of what is on screen rather than of the
|
// reports is a fraction of what is on screen rather than of the
|
||||||
// content box its own answer decided. Where it goes is the content
|
// content box its own answer decided. Where it goes is the content
|
||||||
// box, scrolled: its drawing moved there, not made again there.
|
// box, scrolled: its drawing moved there, not made again there.
|
||||||
painter.place_at(
|
painter.place_at(&self.inner, content.on_axis(self.axis).fills());
|
||||||
&self.inner,
|
|
||||||
self.axis.pair(Place::Fill(content), Place::Fill(Part::All)),
|
|
||||||
);
|
|
||||||
// What it occupies is its box, on both axes: it clips its content to
|
// What it occupies is its box, on both axes: it clips its content to
|
||||||
// that box, so it can neither take less of one nor honestly ask for
|
// that box, so it can neither take less of one nor honestly ask for
|
||||||
// more. The content's length is what it scrolls through, not what it
|
// more. The content's length is what it scrolls through, not what it
|
||||||
|
|||||||
+58
-80
@@ -10,48 +10,32 @@ pub struct Span {
|
|||||||
impl Widget for Span {
|
impl Widget for Span {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let axis = self.dir.axis;
|
let axis = self.dir.axis;
|
||||||
// The row: this span's own box, as a length of the frame its children
|
// The row this span lays its children out along, as a length of the
|
||||||
// are laid out against. Its start is nothing's business -- a slot is
|
// rel base they are laid out against. Where it starts is nothing's
|
||||||
// a length from it -- so what this reads is the length alone.
|
// business -- a slot is a length from there -- so what this reads is
|
||||||
let far = painter.extent_len(axis);
|
// the length alone.
|
||||||
let along = |from: Len, to: Len| match self.dir.sign {
|
let row = painter.region_len(axis);
|
||||||
Sign::Pos => UiSpan::new(from, to),
|
|
||||||
Sign::Neg => UiSpan::new(far - to, far - from),
|
|
||||||
};
|
|
||||||
// Across itself the child sits where its own alignment says, in the
|
|
||||||
// whole of the row: a span is what contains its children there, and
|
|
||||||
// nothing divides that axis.
|
|
||||||
let across = Place::Within(Part::All);
|
|
||||||
// A length for every child before their final slots are chosen: from
|
// A length for every child before their final slots are chosen: from
|
||||||
// a hint where one says, and from drawing otherwise. The frame passes
|
// a hint where one says, and from drawing otherwise. The rel base passes
|
||||||
// through unchanged, so `rel(0.5)` is half the area this span was
|
// through unchanged, so `rel(0.5)` is half the area this span was
|
||||||
// given whatever else is in it and wherever this child sits among
|
// given whatever else is in it and wherever this child sits among
|
||||||
// them; what a drawn child is asked in is the room left from the
|
// them; what a drawn child is asked in is the room left from the
|
||||||
// cursor, because a text has to wrap at the width actually there.
|
// cursor, because a text has to wrap at the width actually there.
|
||||||
// This is the one ask a drawn fixed child gets: its slot is its
|
let mut cursor = Len::ZERO;
|
||||||
// answer, and the drawing is moved there once the shares are known.
|
|
||||||
// A hinted child is asked once, in its slot.
|
|
||||||
let mut cursor = Len::rel_min();
|
|
||||||
let mut lens = Vec::with_capacity(self.children.len());
|
let mut lens = Vec::with_capacity(self.children.len());
|
||||||
let mut measured = Vec::with_capacity(self.children.len());
|
|
||||||
for child in &self.children {
|
for child in &self.children {
|
||||||
let size = match painter.size_hint(child, axis) {
|
let len = match painter.size_hint(child, axis) {
|
||||||
Some(len) => {
|
Some(len) => len,
|
||||||
measured.push(None);
|
|
||||||
len
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
let room = Place::Within(Part::From(along(cursor, far)));
|
// Across itself the child sits where its own alignment
|
||||||
let size = painter
|
// says, in the whole of the row: a span is what contains
|
||||||
.widget_at(child, [None; 2], axis.pair(room, across))
|
// its children there, and nothing divides that axis.
|
||||||
.size();
|
let room = self.slot(row, cursor, row).shifted_desc().on_axis(axis);
|
||||||
measured.push(Some(size));
|
painter.widget_at(child, room).len(axis)
|
||||||
size.axis(axis)
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let len = size;
|
cursor += len.without_leftover();
|
||||||
cursor.px += len.px + self.gap;
|
cursor.px += self.gap;
|
||||||
cursor.rel += len.rel;
|
|
||||||
lens.push(len);
|
lens.push(len);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +51,8 @@ impl Widget for Span {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// What is left for the shares to divide: the row less everything
|
// What is left for the shares to divide: the row less everything
|
||||||
// fixed, as a length of the frame rather than a number of pixels.
|
// fixed, as a length of the rel base rather than a number of pixels.
|
||||||
let room = far - Len::from_parts(total.rel, total.px);
|
let room = row - total.without_leftover();
|
||||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||||
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
||||||
// itself, and answered back through the same expression, so the
|
// itself, and answered back through the same expression, so the
|
||||||
@@ -78,10 +62,10 @@ impl Widget for Span {
|
|||||||
// sign of `room.rel`, which `through` already reads. What the
|
// sign of `room.rel`, which `through` already reads. What the
|
||||||
// generated oracle checks is the consequence, since which children
|
// generated oracle checks is the consequence, since which children
|
||||||
// exist at all turns on this.
|
// exist at all turns on this.
|
||||||
let mut shares = false;
|
let any_leftover = total.leftover > Weight::ZERO;
|
||||||
if total.leftover > Weight::ZERO {
|
let has_room = any_leftover && painter.to_px(room, axis) > Px::ZERO;
|
||||||
shares = painter.to_px(room, axis) > Px::ZERO;
|
if any_leftover {
|
||||||
let holds = match shares {
|
let holds = match has_room {
|
||||||
true => Holds::from(Px::STEP..=Px::MAX),
|
true => Holds::from(Px::STEP..=Px::MAX),
|
||||||
false => Holds::from(Px::MIN..=Px::ZERO),
|
false => Holds::from(Px::MIN..=Px::ZERO),
|
||||||
};
|
};
|
||||||
@@ -96,65 +80,59 @@ impl Widget for Span {
|
|||||||
let shrinks = !painter.has_exact_size(!axis);
|
let shrinks = !painter.has_exact_size(!axis);
|
||||||
// What the fixed parts and the gaps before here take, which is a sum
|
// What the fixed parts and the gaps before here take, which is a sum
|
||||||
// of lengths and exact, and how much of the leftover weight is
|
// of lengths and exact, and how much of the leftover weight is
|
||||||
// spoken for. A position is one from the other rather than a step
|
// spoken for. Both ends of a slot are read from those two rather
|
||||||
// from the last child: the share of the room is rounded, and taking
|
// than stepped from the last child: the share of the room is
|
||||||
// each from the one before it would carry every rounding along the
|
// rounded, and taking each end from the one before it would carry
|
||||||
// row.
|
// every rounding along the row.
|
||||||
let mut fixed = Len::rel_min();
|
let mut fixed = Len::ZERO;
|
||||||
let mut taken = Weight::ZERO;
|
let mut taken = Weight::ZERO;
|
||||||
let mut start = Len::rel_min();
|
|
||||||
let mut ortho = LayoutLen::ZERO;
|
let mut ortho = LayoutLen::ZERO;
|
||||||
for ((child, len), measured) in self.children.iter().zip(&lens).zip(&measured) {
|
// Nothing divides the room where no child asked for any of it, and a
|
||||||
let len = *len;
|
// ratio of a whole of nothing has no answer.
|
||||||
|
let reached = |fixed: Len, taken: Weight| match any_leftover {
|
||||||
|
false => fixed,
|
||||||
|
true => fixed + room.scale(Rel::ratio(taken, total.leftover)),
|
||||||
|
};
|
||||||
|
for (child, &len) in self.children.iter().zip(&lens) {
|
||||||
// A child asking for nothing but a part of what is left over,
|
// A child asking for nothing but a part of what is left over,
|
||||||
// when nothing is, is not drawn at all. One that also asked for
|
// when nothing is, is not drawn at all. One that also asked for
|
||||||
// pixels or a fraction keeps those and overflows.
|
// pixels or a fraction keeps those and overflows.
|
||||||
if len.leftover > Weight::ZERO && len.px == Px::ZERO && len.rel == Rel::ZERO && !shares
|
if len.is_only_leftover() && !has_room {
|
||||||
{
|
|
||||||
painter.undraw(child);
|
painter.undraw(child);
|
||||||
fixed.px += self.gap;
|
fixed.px += self.gap;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let from = start;
|
let from = reached(fixed, taken);
|
||||||
if len.leftover > Weight::ZERO && shares {
|
if len.leftover > Weight::ZERO && has_room {
|
||||||
taken += len.leftover;
|
taken += len.leftover;
|
||||||
}
|
}
|
||||||
fixed.px += len.px;
|
fixed += len.without_leftover();
|
||||||
fixed.rel += len.rel;
|
let to = reached(fixed, taken);
|
||||||
start = shared(fixed, taken, total.leftover, room);
|
|
||||||
// Along the row the span says where the child goes, and that slot
|
// Along the row the span says where the child goes, and that slot
|
||||||
// is the child's box outright rather than something to place an
|
// is the child's box outright rather than something to place an
|
||||||
// answer inside again. A share is decided here and nowhere
|
// answer inside again. A share is decided here and nowhere
|
||||||
// else: its slot narrows its frame, and the child is asked in
|
// else: its slot narrows its rel base, and the child is asked in
|
||||||
// it, since a text wraps at the width it is actually given. A
|
// it, since a text wraps at the width it is actually given. A
|
||||||
// fixed child's slot is its own answer, so a drawing made in the
|
// fixed child's slot is its own answer, so a drawing made in the
|
||||||
// room is put there as it is, and one not made yet is made here.
|
// room is put there as it is, and one not made yet is made here.
|
||||||
let slot = along(from, start);
|
let slot = self.slot(row, from, to);
|
||||||
let place = axis.pair(Place::Fill(Part::From(slot)), across);
|
let mut place = slot.shifted_desc().fills().on_axis(axis);
|
||||||
let mut narrow = [None; 2];
|
if len.leftover > Weight::ZERO && has_room {
|
||||||
if len.leftover > Weight::ZERO && shares {
|
place = place.rel_base(axis, slot.len());
|
||||||
narrow[axis as usize] = Some(slot.len());
|
|
||||||
}
|
}
|
||||||
let used = match (measured, narrow[axis as usize]) {
|
let used = painter.place_at(child, place).len(!axis);
|
||||||
(Some(size), None) => {
|
|
||||||
painter.place_at(child, place);
|
|
||||||
size.axis(!axis)
|
|
||||||
}
|
|
||||||
_ => painter.widget_at(child, narrow, place).len(!axis),
|
|
||||||
};
|
|
||||||
if shrinks {
|
if shrinks {
|
||||||
// Choosing between a fixed and a relative length from the
|
// Choosing between a fixed and a relative length from the
|
||||||
// span's own eventual width admits multiple fixed points.
|
// span's own eventual width admits multiple fixed points.
|
||||||
// A scalable child therefore makes Children scalable too;
|
// A scalable child therefore makes Children scalable too;
|
||||||
// only fixed children are compared with one another.
|
// only fixed children are compared with one another.
|
||||||
if used.rel != Rel::ZERO || used.leftover != Weight::ZERO {
|
if !used.is_px() {
|
||||||
ortho = LayoutLen::LEFTOVER;
|
ortho = LayoutLen::LEFTOVER;
|
||||||
} else if ortho.leftover == Weight::ZERO {
|
} else if ortho.leftover == Weight::ZERO {
|
||||||
ortho.px = ortho.px.max(used.px);
|
ortho.px = ortho.px.max(used.px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fixed.px += self.gap;
|
fixed.px += self.gap;
|
||||||
start = shared(fixed, taken, total.leftover, room);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carried whole rather than collapsed to one share: a span that sizes
|
// Carried whole rather than collapsed to one share: a span that sizes
|
||||||
@@ -164,26 +142,26 @@ impl Widget for Span {
|
|||||||
// get a quarter each, which collapsing to `leftover(1)` per level does
|
// get a quarter each, which collapsing to `leftover(1)` per level does
|
||||||
// not give. Resolution happens at the nearest ancestor with a length,
|
// not give. Resolution happens at the nearest ancestor with a length,
|
||||||
// and the root always has one.
|
// and the root always has one.
|
||||||
let along = total;
|
|
||||||
let ortho = match shrinks {
|
let ortho = match shrinks {
|
||||||
true => ortho,
|
true => ortho,
|
||||||
false => LayoutLen::rel(1.0),
|
false => LayoutLen::rel(1.0),
|
||||||
};
|
};
|
||||||
Size::from_axis(axis, along, ortho)
|
Size::from_axis(axis, total, ortho)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Where a row has reached: everything fixed before this point, which is a
|
|
||||||
/// sum and exact, plus the share of the room the weights so far are worth,
|
|
||||||
/// which is one rounding wherever it is asked for.
|
|
||||||
fn shared(fixed: Len, taken: Weight, weight: Weight, room: Len) -> Len {
|
|
||||||
if taken == Weight::ZERO {
|
|
||||||
return fixed;
|
|
||||||
}
|
|
||||||
fixed + room.scale(Rel::ratio(taken, weight))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
|
/// The stretch of the row between two distances from where this span
|
||||||
|
/// starts laying children out, as a span of its own box. A negative
|
||||||
|
/// direction lays out from the far end, so the same two distances mirror
|
||||||
|
/// in a row `row` long.
|
||||||
|
fn slot(&self, row: Len, from: Len, to: Len) -> UiSpan {
|
||||||
|
match self.dir.sign {
|
||||||
|
Sign::Pos => from.to(to),
|
||||||
|
Sign::Neg => (row - to).to(row - from),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn empty(dir: Dir) -> Self {
|
pub fn empty(dir: Dir) -> Self {
|
||||||
Self {
|
Self {
|
||||||
children: Vec::new(),
|
children: Vec::new(),
|
||||||
|
|||||||
@@ -22,9 +22,7 @@ impl Widget for Stack {
|
|||||||
// drawing belongs to the layer it was made on.
|
// drawing belongs to the layer it was made on.
|
||||||
Some((i, child)) => {
|
Some((i, child)) => {
|
||||||
painter.child_layer_at(i);
|
painter.child_layer_at(i);
|
||||||
painter
|
painter.widget_at(child, PlaceDesc::WHOLE.fills()).size()
|
||||||
.widget_at(child, [None; 2], [Place::Fill(Part::All); 2])
|
|
||||||
.size()
|
|
||||||
}
|
}
|
||||||
None => Size::LEFTOVER,
|
None => Size::LEFTOVER,
|
||||||
};
|
};
|
||||||
@@ -33,11 +31,11 @@ impl Widget for Stack {
|
|||||||
// fraction under them is a fraction of it. A share leaves the axis
|
// fraction under them is a fraction of it. A share leaves the axis
|
||||||
// to whoever gave the stack its box. Where a child sits in a box
|
// to whoever gave the stack its box. Where a child sits in a box
|
||||||
// bigger than itself is its own business.
|
// bigger than itself is its own business.
|
||||||
let place = [Axis::X, Axis::Y].map(|axis| {
|
let place = PlaceDesc::from_axes(|axis| {
|
||||||
let len = size.axis(axis);
|
let len = size[axis];
|
||||||
match len.leftover == Weight::ZERO {
|
match len.leftover == Weight::ZERO {
|
||||||
true => Place::Fill(Part::Sized(Len::from_parts(len.rel, len.px))),
|
true => len.without_leftover().as_desc().fills(),
|
||||||
false => Place::Within(Part::All),
|
false => PlaceDescAxis::WHOLE,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
for (i, child) in self.children.iter().enumerate() {
|
for (i, child) in self.children.iter().enumerate() {
|
||||||
@@ -45,7 +43,7 @@ impl Widget for Stack {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
painter.child_layer_at(i);
|
painter.child_layer_at(i);
|
||||||
painter.widget_at(child, [None; 2], place);
|
painter.widget_at(child, place);
|
||||||
}
|
}
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,17 +22,17 @@ struct BranchesOnMeasurement {
|
|||||||
impl Widget for BranchesOnMeasurement {
|
impl Widget for BranchesOnMeasurement {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
let cut = Len::from_parts(Rel::ZERO, Px::from_int(40));
|
||||||
let top = Place::Within(Part::From(UiSpan::new(Len::ZERO, cut)));
|
let top = UiSpan::new(Len::ZERO, cut).shifted_desc();
|
||||||
let measured = painter
|
let measured = painter
|
||||||
.widget_at(&self.probe, [None; 2], [Place::Within(Part::All), top])
|
.widget_at(&self.probe, top.on_axis(Axis::Y))
|
||||||
.len(Axis::X);
|
.len(Axis::X);
|
||||||
let px = painter.to_px(measured.apply_leftover(), Axis::X);
|
let px = painter.to_px(measured.apply_leftover(), Axis::X);
|
||||||
|
|
||||||
let below = Place::Within(Part::From(UiSpan::new(cut, painter.extent_len(Axis::Y))));
|
let below = UiSpan::new(cut, painter.region_len(Axis::Y)).shifted_desc();
|
||||||
let place = [Place::Within(Part::All), below];
|
let place = below.on_axis(Axis::Y);
|
||||||
match px > Px::from_f32(self.threshold) {
|
match px > Px::from_f32(self.threshold) {
|
||||||
true => painter.widget_at(&self.wide, [None; 2], place),
|
true => painter.widget_at(&self.wide, place),
|
||||||
false => painter.widget_at(&self.narrow, [None; 2], place),
|
false => painter.widget_at(&self.narrow, place),
|
||||||
};
|
};
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-9
@@ -108,7 +108,7 @@ const PARAGRAPH: &str = "Wrapping shapes one source into as many lines as the bo
|
|||||||
/// the icon's width, while a wrapping text beside it is asked in the room
|
/// the icon's width, while a wrapping text beside it is asked in the room
|
||||||
/// left, 900 - 24 - 32, and wraps there.
|
/// left, 900 - 24 - 32, and wraps there.
|
||||||
#[test]
|
#[test]
|
||||||
fn padding_keeps_the_frame_distinct_from_the_room_left_in_a_row() {
|
fn padding_keeps_the_rel_base_distinct_from_the_room_left_in_a_row() {
|
||||||
let mut h = Harness::new((900, 200));
|
let mut h = Harness::new((900, 200));
|
||||||
let icon = rect(Color::RED).width(24).add(&mut h.rsc);
|
let icon = rect(Color::RED).width(24).add(&mut h.rsc);
|
||||||
let fill = rect(Color::GREEN).width(rel(1.0)).add(&mut h.rsc);
|
let fill = rect(Color::GREEN).width(rel(1.0)).add(&mut h.rsc);
|
||||||
@@ -124,8 +124,8 @@ fn padding_keeps_the_frame_distinct_from_the_room_left_in_a_row() {
|
|||||||
h.set_root((icon, padded).span(Dir::RIGHT).width(rel(1.0)));
|
h.set_root((icon, padded).span(Dir::RIGHT).width(rel(1.0)));
|
||||||
let active = &h.render.active[&text.id()];
|
let active = &h.render.active[&text.id()];
|
||||||
let window = h.render.output_size().x;
|
let window = h.render.output_size().x;
|
||||||
let asked = active.part.x.len().to_px(window);
|
let asked = active.region.x.len().to_px(window);
|
||||||
assert_eq!(active.frame.x.to_px(window), Px::from_int(868));
|
assert_eq!(active.rel_base.x.to_px(window), Px::from_int(868));
|
||||||
assert_eq!(asked, Px::from_int(844));
|
assert_eq!(asked, Px::from_int(844));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ fn a_share_inside_padding_fills_the_slot_it_was_given() {
|
|||||||
/// The same padding in a share instead: the slot is 450, so both the
|
/// The same padding in a share instead: the slot is 450, so both the
|
||||||
/// fraction and the wrap are the slot less the padding, and the two agree.
|
/// fraction and the wrap are the slot less the padding, and the two agree.
|
||||||
#[test]
|
#[test]
|
||||||
fn padding_narrows_both_frame_and_box_inside_a_share() {
|
fn padding_narrows_both_rel_base_and_box_inside_a_share() {
|
||||||
let mut h = Harness::new((900, 200));
|
let mut h = Harness::new((900, 200));
|
||||||
let fill = rect(Color::GREEN).width(rel(1.0)).add(&mut h.rsc);
|
let fill = rect(Color::GREEN).width(rel(1.0)).add(&mut h.rsc);
|
||||||
let padded = fill.pad(16).width(leftover(1)).add(&mut h.rsc);
|
let padded = fill.pad(16).width(leftover(1)).add(&mut h.rsc);
|
||||||
@@ -170,8 +170,8 @@ fn padding_narrows_both_frame_and_box_inside_a_share() {
|
|||||||
h.set_root((padded, other).span(Dir::RIGHT).width(rel(1.0)));
|
h.set_root((padded, other).span(Dir::RIGHT).width(rel(1.0)));
|
||||||
let active = &h.render.active[&text.id()];
|
let active = &h.render.active[&text.id()];
|
||||||
let window = h.render.output_size().x;
|
let window = h.render.output_size().x;
|
||||||
assert_eq!(active.frame.x.to_px(window), Px::from_int(418));
|
assert_eq!(active.rel_base.x.to_px(window), Px::from_int(418));
|
||||||
assert_eq!(active.part.x.len().to_px(window), Px::from_int(418));
|
assert_eq!(active.region.x.len().to_px(window), Px::from_int(418));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -500,11 +500,11 @@ fn a_row_of_equal_shares_fills_it_exactly() {
|
|||||||
/// a step of. Kept in step with `snap_floor` in `prelude.wgsl`.
|
/// a step of. Kept in step with `snap_floor` in `prelude.wgsl`.
|
||||||
fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
|
fn drawn_edges(h: &Harness, id: WidgetId, axis: Axis) -> (f32, f32) {
|
||||||
let active = &h.render.active[&id];
|
let active = &h.render.active[&id];
|
||||||
let region = h.render.moves.resolve(active.move_idx, active.extent);
|
let region = h.render.moves.resolve(active.move_idx, active.placement);
|
||||||
let dim = h.size().axis(axis);
|
let dim = h.size()[axis];
|
||||||
let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor();
|
let snap = |v: f32| (v + Px::STEP.to_f32() * 0.5).floor();
|
||||||
let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32());
|
let edge = |s: Len| snap(s.rel.to_f32() * dim + s.px.to_f32());
|
||||||
let span = region.axis(axis);
|
let span = region[axis];
|
||||||
(edge(span.start), edge(span.end))
|
(edge(span.start), edge(span.end))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -821,3 +821,40 @@ fn a_root_with_a_fraction_rule_is_that_fraction_of_the_window() {
|
|||||||
h.set_root(root);
|
h.set_root(root);
|
||||||
assert_eq!(h.region(&root).unwrap().size().x, Px::from_int(450));
|
assert_eq!(h.region(&root).unwrap().size().x, Px::from_int(450));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_collapsed_share_keeps_the_gaps_before_the_next_slot() {
|
||||||
|
for dir in [Dir::RIGHT, Dir::LEFT, Dir::DOWN, Dir::UP] {
|
||||||
|
for collapsed in [1, 2] {
|
||||||
|
let mut h = Harness::new((400, 400));
|
||||||
|
let head = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
h.set_len(head, dir.axis, 200);
|
||||||
|
let tail = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let tail_len = 200 - 10 * (collapsed + 1);
|
||||||
|
h.set_len(tail, dir.axis, tail_len);
|
||||||
|
let mut children: Vec<StrongWidget> = vec![head.add_strong(&mut h.rsc)];
|
||||||
|
let mut shares = Vec::new();
|
||||||
|
for _ in 0..collapsed {
|
||||||
|
let share = rect(Color::GREEN).add(&mut h.rsc);
|
||||||
|
shares.push(share);
|
||||||
|
children.push(share.add_strong(&mut h.rsc));
|
||||||
|
}
|
||||||
|
children.push(tail.add_strong(&mut h.rsc));
|
||||||
|
h.set_root(Span {
|
||||||
|
children,
|
||||||
|
dir,
|
||||||
|
gap: Px::from_int(10),
|
||||||
|
});
|
||||||
|
for share in shares {
|
||||||
|
assert!(h.region(&share).is_none());
|
||||||
|
}
|
||||||
|
let region = h.region(&tail).unwrap();
|
||||||
|
let (from, to) = match dir.sign {
|
||||||
|
Sign::Pos => (400 - tail_len, 400),
|
||||||
|
Sign::Neg => (0, tail_len),
|
||||||
|
};
|
||||||
|
assert_eq!(region.top_left[dir.axis], Px::from_int(from));
|
||||||
|
assert_eq!(region.bot_right[dir.axis], Px::from_int(to));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+194
-59
@@ -125,7 +125,7 @@ fn moving_an_ordinary_subtree_remaps_its_mask() {
|
|||||||
let active = &h.render.active[&masked.id()];
|
let active = &h.render.active[&masked.id()];
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
h.rsc.ui().masks[active.mask.idx()].region,
|
h.rsc.ui().masks[active.mask.idx()].region,
|
||||||
UiRegion::new(UiSpan::new(Len::px(150.0), Len::rel_max()), UiSpan::FULL,)
|
UiRegion::new(UiSpan::new(Len::px(150.0), Len::FULL), UiSpan::FULL,)
|
||||||
);
|
);
|
||||||
assert_corners!(h, inner, (150, 0), (400, 200));
|
assert_corners!(h, inner, (150, 0), (400, 200));
|
||||||
}
|
}
|
||||||
@@ -214,11 +214,7 @@ impl Widget for FromHint {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
|
let len = painter.size_hint(&self.inner, Axis::Y).unwrap();
|
||||||
let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px));
|
let top = UiSpan::new(Len::ZERO, Len::from_parts(Rel::ZERO, len.px));
|
||||||
painter.widget_at(
|
painter.widget_at(&self.inner, top.shifted_desc().on_axis(Axis::Y));
|
||||||
&self.inner,
|
|
||||||
[None; 2],
|
|
||||||
[Place::Within(Part::All), Place::Within(Part::From(top))],
|
|
||||||
);
|
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -797,7 +793,7 @@ fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec<PixelRegion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn changing_an_inherited_extent_keeps_the_original_measurement_offer() {
|
fn changing_an_inherited_region_keeps_the_original_measurement_offer() {
|
||||||
fn build(h: &mut Harness, width: i32, text: &str) -> (WeakWidget<Text>, WeakWidget<Rect>) {
|
fn build(h: &mut Harness, width: i32, text: &str) -> (WeakWidget<Text>, WeakWidget<Rect>) {
|
||||||
let first = rect(Color::RED).width(width).add(&mut h.rsc);
|
let first = rect(Color::RED).width(width).add(&mut h.rsc);
|
||||||
let words = wtext(text).size(20).wrap(true).add(&mut h.rsc);
|
let words = wtext(text).size(20).wrap(true).add(&mut h.rsc);
|
||||||
@@ -877,11 +873,7 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
painter.widget_at(
|
painter.widget_at(
|
||||||
&self.child,
|
&self.child,
|
||||||
[None; 2],
|
PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()),
|
||||||
[
|
|
||||||
Place::Within(Part::From(self.region.x)),
|
|
||||||
Place::Within(Part::From(self.region.y)),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
@@ -959,18 +951,18 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
|
|||||||
}
|
}
|
||||||
struct Frame {
|
struct Frame {
|
||||||
child: StrongWidget,
|
child: StrongWidget,
|
||||||
|
frame: UiRegion,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
extent: UiRegion,
|
|
||||||
}
|
}
|
||||||
impl Widget for Frame {
|
impl Widget for Frame {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
painter.widget_at(
|
painter.widget_at(
|
||||||
&self.child,
|
&self.child,
|
||||||
[Some(self.region.x.len()), None],
|
PlaceDesc::new(
|
||||||
[
|
self.region.x.shifted_desc().fills(),
|
||||||
Place::Fill(Part::From(self.extent.x)),
|
self.region.y.shifted_desc().fills(),
|
||||||
Place::Fill(Part::From(self.extent.y)),
|
)
|
||||||
],
|
.rel_base(Axis::X, self.frame.x.len()),
|
||||||
);
|
);
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
@@ -986,15 +978,15 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
|
|||||||
h.rsc.widgets_mut().set_region_node(text, node);
|
h.rsc.widgets_mut().set_region_node(text, node);
|
||||||
let root = Frame {
|
let root = Frame {
|
||||||
child: text.add_strong(&mut h.rsc),
|
child: text.add_strong(&mut h.rsc),
|
||||||
|
frame: UiRegion::FULL,
|
||||||
region: UiRegion::FULL,
|
region: UiRegion::FULL,
|
||||||
extent: UiRegion::FULL,
|
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
h.set_root(root);
|
h.set_root(root);
|
||||||
for (start, end) in [(0.13, 0.83), (-0.17, 1.23), (0.31, 0.67)] {
|
for (start, end) in [(0.13, 0.83), (-0.17, 1.23), (0.31, 0.67)] {
|
||||||
let before = draws.get();
|
let before = draws.get();
|
||||||
h.rsc[root].region.x = UiSpan::new(Len::px(13.125), Len::px(287.375));
|
h.rsc[root].frame.x = UiSpan::new(Len::px(13.125), Len::px(287.375));
|
||||||
h.rsc[root].extent = UiRegion::new(
|
h.rsc[root].region = UiRegion::new(
|
||||||
UiSpan::new(Len::rel(start), Len::rel(end)),
|
UiSpan::new(Len::rel(start), Len::rel(end)),
|
||||||
UiSpan::new(Len::px(7.25), Len::rel(end)),
|
UiSpan::new(Len::px(7.25), Len::rel(end)),
|
||||||
);
|
);
|
||||||
@@ -1117,7 +1109,7 @@ fn widening_and_restoring_a_contract_does_not_invalidate_its_reader() {
|
|||||||
assert_eq!(leaf_draws.get(), settled + 1);
|
assert_eq!(leaf_draws.get(), settled + 1);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
fn padding_and_stack_boxes_follow_the_region_without_drawing_again() {
|
||||||
struct Observed<W> {
|
struct Observed<W> {
|
||||||
widget: W,
|
widget: W,
|
||||||
draws: Rc<Cell<usize>>,
|
draws: Rc<Cell<usize>>,
|
||||||
@@ -1130,23 +1122,22 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
|||||||
}
|
}
|
||||||
struct Frame {
|
struct Frame {
|
||||||
child: StrongWidget,
|
child: StrongWidget,
|
||||||
extent: UiRegion,
|
region: UiRegion,
|
||||||
}
|
}
|
||||||
impl Widget for Frame {
|
impl Widget for Frame {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
painter.widget_at(
|
painter.widget_at(
|
||||||
&self.child,
|
&self.child,
|
||||||
[None; 2],
|
PlaceDesc::new(
|
||||||
[
|
self.region.x.shifted_desc().fills(),
|
||||||
Place::Fill(Part::From(self.extent.x)),
|
self.region.y.shifted_desc().fills(),
|
||||||
Place::Fill(Part::From(self.extent.y)),
|
),
|
||||||
],
|
|
||||||
);
|
);
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for node in [false, true] {
|
for node in [false, true] {
|
||||||
let plant = |h: &mut Harness, extent| {
|
let plant = |h: &mut Harness, region| {
|
||||||
let draws = Rc::new(Cell::new(0));
|
let draws = Rc::new(Cell::new(0));
|
||||||
let leaf = rect(Color::BLUE).masked().add(&mut h.rsc);
|
let leaf = rect(Color::BLUE).masked().add(&mut h.rsc);
|
||||||
h.rsc.widgets_mut().set_region_node(leaf, node);
|
h.rsc.widgets_mut().set_region_node(leaf, node);
|
||||||
@@ -1167,7 +1158,7 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
|||||||
draws: draws.clone(),
|
draws: draws.clone(),
|
||||||
}
|
}
|
||||||
.add_strong(&mut h.rsc);
|
.add_strong(&mut h.rsc);
|
||||||
let root = Frame { child: pad, extent }.add(&mut h.rsc);
|
let root = Frame { child: pad, region }.add(&mut h.rsc);
|
||||||
h.set_root(root);
|
h.set_root(root);
|
||||||
(root, leaf, fixed, draws)
|
(root, leaf, fixed, draws)
|
||||||
};
|
};
|
||||||
@@ -1182,13 +1173,13 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
|||||||
let mut warm = Harness::new((403, 211));
|
let mut warm = Harness::new((403, 211));
|
||||||
let (root, leaf, fixed, draws) = plant(&mut warm, at(0.13));
|
let (root, leaf, fixed, draws) = plant(&mut warm, at(0.13));
|
||||||
for start in [0.13, -0.17, 0.31] {
|
for start in [0.13, -0.17, 0.31] {
|
||||||
let extent = at(start);
|
let region = at(start);
|
||||||
let before = draws.get();
|
let before = draws.get();
|
||||||
warm.rsc[root].extent = extent;
|
warm.rsc[root].region = region;
|
||||||
warm.frame();
|
warm.frame();
|
||||||
assert_eq!(draws.get(), before);
|
assert_eq!(draws.get(), before);
|
||||||
let mut cold = Harness::new((403, 211));
|
let mut cold = Harness::new((403, 211));
|
||||||
let (_, other, other_fixed, _) = plant(&mut cold, extent);
|
let (_, other, other_fixed, _) = plant(&mut cold, region);
|
||||||
for (a, b) in [(leaf.id(), other.id()), (fixed.id(), other_fixed.id())] {
|
for (a, b) in [(leaf.id(), other.id()), (fixed.id(), other_fixed.id())] {
|
||||||
assert_eq!(warm.region(&a), cold.region(&b));
|
assert_eq!(warm.region(&a), cold.region(&b));
|
||||||
assert_eq!(primitive_bounds(&warm, a), primitive_bounds(&cold, b));
|
assert_eq!(primitive_bounds(&warm, a), primitive_bounds(&cold, b));
|
||||||
@@ -1207,7 +1198,7 @@ fn padding_and_stack_boxes_follow_the_extent_without_drawing_again() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() {
|
fn moving_a_childs_region_preserves_the_slot_chosen_from_its_measurement() {
|
||||||
struct Measured;
|
struct Measured;
|
||||||
impl Widget for Measured {
|
impl Widget for Measured {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
@@ -1224,14 +1215,12 @@ fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
painter.widget_at(
|
painter.widget_at(
|
||||||
&self.child,
|
&self.child,
|
||||||
[None; 2],
|
PlaceDesc::new(
|
||||||
[
|
UiSpan::new(Len::px(self.start), Len::px(self.start + 200.0))
|
||||||
Place::Fill(Part::From(UiSpan::new(
|
.shifted_desc()
|
||||||
Len::px(self.start),
|
.fills(),
|
||||||
Len::px(self.start + 200.0),
|
UiSpan::FULL.shifted_desc().fills(),
|
||||||
))),
|
),
|
||||||
Place::Fill(Part::From(UiSpan::FULL)),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
Size::LEFTOVER
|
Size::LEFTOVER
|
||||||
}
|
}
|
||||||
@@ -1256,7 +1245,7 @@ fn moving_an_extent_child_preserves_the_slot_chosen_from_its_measurement() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
fn changing_regions_keep_fractional_reports_and_numeric_dependencies_valid() {
|
||||||
struct Container {
|
struct Container {
|
||||||
child: StrongWidget,
|
child: StrongWidget,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
@@ -1266,18 +1255,14 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
|||||||
painter
|
painter
|
||||||
.widget_at(
|
.widget_at(
|
||||||
&self.child,
|
&self.child,
|
||||||
[None; 2],
|
PlaceDesc::new(self.region.x.shifted_desc(), self.region.y.shifted_desc()),
|
||||||
[
|
|
||||||
Place::Within(Part::From(self.region.x)),
|
|
||||||
Place::Within(Part::From(self.region.y)),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.size()
|
.size()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
struct Frame {
|
struct Frame {
|
||||||
child: StrongWidget,
|
child: StrongWidget,
|
||||||
extent: UiRegion,
|
region: UiRegion,
|
||||||
answer: Rc<Cell<Size>>,
|
answer: Rc<Cell<Size>>,
|
||||||
}
|
}
|
||||||
impl Widget for Frame {
|
impl Widget for Frame {
|
||||||
@@ -1286,11 +1271,10 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
|||||||
painter
|
painter
|
||||||
.widget_at(
|
.widget_at(
|
||||||
&self.child,
|
&self.child,
|
||||||
[None; 2],
|
PlaceDesc::new(
|
||||||
[
|
self.region.x.shifted_desc().fills(),
|
||||||
Place::Fill(Part::From(self.extent.x)),
|
self.region.y.shifted_desc().fills(),
|
||||||
Place::Fill(Part::From(self.extent.y)),
|
),
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.size(),
|
.size(),
|
||||||
);
|
);
|
||||||
@@ -1302,7 +1286,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
|||||||
UiRegion::FULL,
|
UiRegion::FULL,
|
||||||
UiRegion::new(UiSpan::new(Len::rel(0.13), Len::rel(0.79)), UiSpan::FULL),
|
UiRegion::new(UiSpan::new(Len::rel(0.13), Len::rel(0.79)), UiSpan::FULL),
|
||||||
] {
|
] {
|
||||||
let plant = |h: &mut Harness, extent| {
|
let plant = |h: &mut Harness, outer| {
|
||||||
let size = if fractional {
|
let size = if fractional {
|
||||||
Size {
|
Size {
|
||||||
x: rel(0.5),
|
x: rel(0.5),
|
||||||
@@ -1320,7 +1304,7 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
|||||||
let answer = Rc::new(Cell::new(Size::ZERO));
|
let answer = Rc::new(Cell::new(Size::ZERO));
|
||||||
let root = Frame {
|
let root = Frame {
|
||||||
child,
|
child,
|
||||||
extent,
|
region: outer,
|
||||||
answer: answer.clone(),
|
answer: answer.clone(),
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
@@ -1330,15 +1314,166 @@ fn extent_frames_keep_fractional_reports_and_numeric_dependencies_valid() {
|
|||||||
let mut warm = Harness::new((403, 211));
|
let mut warm = Harness::new((403, 211));
|
||||||
let (root, leaf, answer) = plant(&mut warm, UiRegion::FULL);
|
let (root, leaf, answer) = plant(&mut warm, UiRegion::FULL);
|
||||||
for width in [191.125, 297.25, 83.75] {
|
for width in [191.125, 297.25, 83.75] {
|
||||||
let extent =
|
let region =
|
||||||
UiRegion::new(UiSpan::new(Len::px(13.125), Len::px(width)), UiSpan::FULL);
|
UiRegion::new(UiSpan::new(Len::px(13.125), Len::px(width)), UiSpan::FULL);
|
||||||
warm.rsc[root].extent = extent;
|
warm.rsc[root].region = region;
|
||||||
warm.frame();
|
warm.frame();
|
||||||
let mut cold = Harness::new((403, 211));
|
let mut cold = Harness::new((403, 211));
|
||||||
let (_, other, other_answer) = plant(&mut cold, extent);
|
let (_, other, other_answer) = plant(&mut cold, region);
|
||||||
assert_eq!(answer.get(), other_answer.get());
|
assert_eq!(answer.get(), other_answer.get());
|
||||||
assert_eq!(warm.region(&leaf), cold.region(&other));
|
assert_eq!(warm.region(&leaf), cold.region(&other));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct OptionalMask {
|
||||||
|
inner: StrongWidget,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for OptionalMask {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
if self.enabled {
|
||||||
|
painter.set_mask(UiRegion::FULL);
|
||||||
|
}
|
||||||
|
painter.widget(&self.inner);
|
||||||
|
Size::LEFTOVER
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn primitive_masks(h: &Harness, id: WidgetId) -> Vec<MaskIdx> {
|
||||||
|
h.render.active[&id]
|
||||||
|
.primitives
|
||||||
|
.iter()
|
||||||
|
.map(|primitive| {
|
||||||
|
let handle = &primitive.handle;
|
||||||
|
h.render.layers[handle.layer].primitives()[handle.kind as usize]
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.instances()[handle.inst_idx]
|
||||||
|
.mask_idx
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_redrawn_mask_keeps_reused_primitives_clipped_when_it_moves() {
|
||||||
|
for node in [false, true] {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let first = rect(Color::RED).height(50).add(&mut h.rsc);
|
||||||
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let draws = Rc::new(Cell::new(0));
|
||||||
|
let child = Stretchy {
|
||||||
|
inner: inner.add_strong(&mut h.rsc),
|
||||||
|
draws: draws.clone(),
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let masked = child.masked().add(&mut h.rsc);
|
||||||
|
h.rsc.widgets_mut().set_region_node(masked, node);
|
||||||
|
h.set_root((first, masked).span(Dir::DOWN));
|
||||||
|
let mask = h.render.active[&masked.id()].mask;
|
||||||
|
let settled = draws.get();
|
||||||
|
h.rsc.widgets_mut().get_dyn_mut(masked.id());
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
|
||||||
|
assert_eq!(draws.get(), settled, "a mask repaint must reuse its child");
|
||||||
|
assert_eq!(h.render.active[&masked.id()].mask, mask);
|
||||||
|
h.set_len(first, Axis::Y, 10);
|
||||||
|
h.frame();
|
||||||
|
let clip = h.rsc.ui().masks[mask.idx()];
|
||||||
|
let clip = h
|
||||||
|
.render
|
||||||
|
.moves
|
||||||
|
.resolve(clip.move_idx, clip.region)
|
||||||
|
.to_px(h.render.output_size());
|
||||||
|
assert_eq!(clip, h.region(&masked).unwrap());
|
||||||
|
assert_corners!(h, inner, (0, 10), (400, 200));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn adding_and_removing_a_mask_updates_existing_primitives() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
let masked = OptionalMask {
|
||||||
|
inner: inner.add_strong(&mut h.rsc),
|
||||||
|
enabled: false,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(masked);
|
||||||
|
for enabled in [true, false, true, false] {
|
||||||
|
h.rsc[masked].enabled = enabled;
|
||||||
|
h.frame();
|
||||||
|
let mask = h.render.active[&masked.id()].mask;
|
||||||
|
assert_eq!(mask == MaskIdx::NONE, !enabled);
|
||||||
|
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
|
||||||
|
}
|
||||||
|
assert_eq!(h.rsc.ui().masks.len(), 1, "retired slots must be reusable");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn an_empty_masks_slot_is_released_when_the_mask_is_removed_or_undrawn() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let (inner, _) = counted(&mut h, Size::LEFTOVER, false);
|
||||||
|
let masked = OptionalMask {
|
||||||
|
inner: inner.add_strong(&mut h.rsc),
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let row = (masked,).span(Dir::DOWN).add(&mut h.rsc);
|
||||||
|
h.set_root(row);
|
||||||
|
for _ in 0..3 {
|
||||||
|
h.rsc[masked].enabled = false;
|
||||||
|
h.frame();
|
||||||
|
h.rsc[masked].enabled = true;
|
||||||
|
h.frame();
|
||||||
|
let child = h.rsc[row].pop().unwrap();
|
||||||
|
h.frame();
|
||||||
|
h.rsc[row].push(child);
|
||||||
|
h.frame();
|
||||||
|
}
|
||||||
|
assert_eq!(h.rsc.ui().masks.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SharedChild(Rc<StrongWidget>);
|
||||||
|
|
||||||
|
impl Widget for SharedChild {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
painter.widget(self.0.as_ref()).size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SwitchParent {
|
||||||
|
choices: [StrongWidget; 2],
|
||||||
|
choice: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for SwitchParent {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
painter.widget(&self.choices[self.choice]).size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_redrawn_subtree_is_not_undrawn_by_the_parent_it_left() {
|
||||||
|
for node in [false, true] {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let leaf = rect(Color::RED).width(40).add(&mut h.rsc);
|
||||||
|
let held: StrongWidget = leaf.add_strong(&mut h.rsc);
|
||||||
|
let shared = Rc::new(held);
|
||||||
|
let first = SharedChild(shared.clone()).add_strong(&mut h.rsc);
|
||||||
|
let second = SharedChild(shared).add_strong(&mut h.rsc);
|
||||||
|
h.rsc.widgets_mut().set_region_node(&second, node);
|
||||||
|
let root = SwitchParent {
|
||||||
|
choices: [first, second],
|
||||||
|
choice: 0,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(root);
|
||||||
|
let before = h.region(&leaf);
|
||||||
|
h.rsc[root].choice = 1;
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(h.region(&leaf), before);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1067,3 +1067,32 @@ fn resizing_under_a_short_scroll_snaps_its_window_tall_content_again() {
|
|||||||
|
|
||||||
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A scroll clamps its position against the box it is drawn in, so drawing it
|
||||||
|
/// once at one viewport and again at another writes state the second draw then
|
||||||
|
/// reads. That the answer is still the one a cold layout gives is a property
|
||||||
|
/// of the clamp, not something the layout enforces.
|
||||||
|
#[test]
|
||||||
|
fn a_scrolled_view_resized_lands_where_a_cold_layout_puts_it() {
|
||||||
|
for amt in [10.0, 40.0, 90.0, 140.0] {
|
||||||
|
let mut warm = Harness::new((100, 100));
|
||||||
|
let (_, warm_scroll) = plant_wider(&mut warm, 100.0);
|
||||||
|
warm.move_to((50.0, 50.0));
|
||||||
|
warm.scroll((-amt, 0.0));
|
||||||
|
warm.frame();
|
||||||
|
warm.resize((160, 100));
|
||||||
|
warm.frame();
|
||||||
|
|
||||||
|
let mut cold = Harness::new((160, 100));
|
||||||
|
let (_, cold_scroll) = plant_wider(&mut cold, 100.0);
|
||||||
|
cold.move_to((50.0, 50.0));
|
||||||
|
cold.scroll((-amt, 0.0));
|
||||||
|
cold.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
warm.region(&warm_scroll),
|
||||||
|
cold.region(&cold_scroll),
|
||||||
|
"scrolled by {amt} then widened"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
-4
@@ -14,7 +14,7 @@
|
|||||||
#[path = "scenario/mod.rs"]
|
#[path = "scenario/mod.rs"]
|
||||||
mod scenario;
|
mod scenario;
|
||||||
|
|
||||||
use iris::random::{Edits, plan};
|
use iris::random::{Edits, Plan, plan};
|
||||||
use scenario::{ALL, Case, diverges, env, over_seeds};
|
use scenario::{ALL, Case, diverges, env, over_seeds};
|
||||||
|
|
||||||
/// How deep the generator branches. The generator widens two to four ways per
|
/// How deep the generator branches. The generator widens two to four ways per
|
||||||
@@ -32,8 +32,11 @@ fn depth() -> usize {
|
|||||||
const SEEDS: [u64; 10] = [1, 2, 3, 5, 8, 10, 13, 20, 86, 98];
|
const SEEDS: [u64; 10] = [1, 2, 3, 5, 8, 10, 13, 20, 86, 98];
|
||||||
|
|
||||||
fn check(seed: u64, depth: usize, case: Case) {
|
fn check(seed: u64, depth: usize, case: Case) {
|
||||||
let grown = plan(seed, depth, &Edits::default());
|
check_plan(&plan(seed, depth, &Edits::default()), seed, depth, case);
|
||||||
if let Some(how) = diverges(&grown, case, seed) {
|
}
|
||||||
|
|
||||||
|
fn check_plan(grown: &Plan, seed: u64, depth: usize, case: Case) {
|
||||||
|
if let Some(how) = diverges(grown, case, seed) {
|
||||||
panic!(
|
panic!(
|
||||||
"seed {seed} at depth {depth} differs after {}: {how}\n\
|
"seed {seed} at depth {depth} differs after {}: {how}\n\
|
||||||
reduce it with SHRINK_SEED={seed} SHRINK_DEPTH={depth} \
|
reduce it with SHRINK_SEED={seed} SHRINK_DEPTH={depth} \
|
||||||
@@ -119,8 +122,9 @@ fn a_long_run_of_seeds_agrees() {
|
|||||||
None => (1..=env("IRIS_GENERATED_SEEDS", 100_u64)).collect(),
|
None => (1..=env("IRIS_GENERATED_SEEDS", 100_u64)).collect(),
|
||||||
};
|
};
|
||||||
over_seeds(seeds, |seed| {
|
over_seeds(seeds, |seed| {
|
||||||
|
let grown = plan(seed, depth, &Edits::default());
|
||||||
for case in ALL {
|
for case in ALL {
|
||||||
check(seed, depth, case);
|
check_plan(&grown, seed, depth, case);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -406,8 +406,8 @@ fn describe_widget(id: WidgetId, h: &Harness) -> String {
|
|||||||
fn record(id: WidgetId, h: &Harness) -> String {
|
fn record(id: WidgetId, h: &Harness) -> String {
|
||||||
let active = &h.render.active[&id];
|
let active = &h.render.active[&id];
|
||||||
format!(
|
format!(
|
||||||
"frame {} ask {} box {} size {}",
|
"rel_base {} region {} placement {} size {}",
|
||||||
active.frame, active.part, active.extent, active.size,
|
active.rel_base, active.region, active.placement, active.size,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -444,12 +444,6 @@ pub fn diverges(plan: &Plan, case: Case, seed: u64) -> Option<String> {
|
|||||||
cold.state.root = Some(root);
|
cold.state.root = Some(root);
|
||||||
cold.frame();
|
cold.frame();
|
||||||
|
|
||||||
let places: HashMap<WidgetId, usize> = tree
|
|
||||||
.ids
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, &id)| (id, i))
|
|
||||||
.collect();
|
|
||||||
let mut drawn = 0;
|
let mut drawn = 0;
|
||||||
for (i, (&w, &c)) in tree.ids.iter().zip(&cold_tree.ids).enumerate() {
|
for (i, (&w, &c)) in tree.ids.iter().zip(&cold_tree.ids).enumerate() {
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
let (got, want) = (warm.region(&w), cold.region(&c));
|
||||||
@@ -457,6 +451,12 @@ pub fn diverges(plan: &Plan, case: Case, seed: u64) -> Option<String> {
|
|||||||
if got == want {
|
if got == want {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
let places: HashMap<WidgetId, usize> = tree
|
||||||
|
.ids
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, &id)| (id, i))
|
||||||
|
.collect();
|
||||||
// Where two trees disagree is rarely where the cause is, so the
|
// Where two trees disagree is rarely where the cause is, so the
|
||||||
// ancestry comes with it, marking the widgets that own a region.
|
// ancestry comes with it, marking the widgets that own a region.
|
||||||
let mut chain = Vec::new();
|
let mut chain = Vec::new();
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ fn dump(label: &str, report: &diag::Report, text: WidgetId) {
|
|||||||
TraceEvent::DrawRequest {
|
TraceEvent::DrawRequest {
|
||||||
id,
|
id,
|
||||||
region,
|
region,
|
||||||
pixel_size,
|
region_px,
|
||||||
..
|
..
|
||||||
} if *id == text => {
|
} if *id == text => {
|
||||||
println!(
|
println!(
|
||||||
" draw in {:.2}x{:.2} region {region:?}",
|
" draw in {:.2}x{:.2} region {region:?}",
|
||||||
pixel_size.x, pixel_size.y
|
region_px.x, region_px.y
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
TraceEvent::SizeReported { id, size } if *id == text => {
|
TraceEvent::SizeReported { id, size } if *id == text => {
|
||||||
|
|||||||
Reference in new issue
Block a user