Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2dba90bd0f | ||
|
|
b295c8b97a | ||
|
|
c2b8bf83de | ||
|
|
77ed7a24c0 | ||
|
|
f8aa0c5cdf | ||
|
|
b7b8d09e40 | ||
|
|
1096c3167a | ||
|
|
445287c95c |
No files matched your search
@@ -25,6 +25,12 @@ tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"]
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = ["core", "macro", "rig-input"]
|
members = ["core", "macro", "rig-input"]
|
||||||
|
|
||||||
|
# Full debug info was the bulk of what the linker wrote here and almost none of
|
||||||
|
# what anything read. `dev` keeps line tables and scopes, which is what stepping
|
||||||
|
# through an example wants; the tests keep the line tables alone, which is what
|
||||||
|
# a backtrace reads. Measured when the tests became one target: relinking them
|
||||||
|
# went from 9.8 s to 7.7 s with these, and target/ from 45 GB to 13 GB with the
|
||||||
|
# two changes together.
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
debug = 1
|
debug = 1
|
||||||
|
|
||||||
|
|||||||
@@ -131,14 +131,6 @@ impl<const SHIFT: u32> Fixed<SHIFT> {
|
|||||||
self.0 as f32 / Self::one().0 as f32
|
self.0 as f32 / Self::one().0 as f32
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The same value on another grid, rounded where the new one is coarser.
|
|
||||||
pub const fn to_scale<const TO: u32>(self) -> Fixed<TO> {
|
|
||||||
Fixed(match TO >= SHIFT {
|
|
||||||
true => self.0 << (TO - SHIFT),
|
|
||||||
false => shift_round(self.0 as i64, SHIFT - TO) as i32,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn add(self, rhs: Self) -> Self {
|
pub const fn add(self, rhs: Self) -> Self {
|
||||||
Self(self.0.wrapping_add(rhs.0))
|
Self(self.0.wrapping_add(rhs.0))
|
||||||
}
|
}
|
||||||
@@ -246,16 +238,6 @@ impl<const SHIFT: u32> Fixed<SHIFT> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Back to a single step, rounding halves away from zero so that a value and
|
|
||||||
/// its negation round to the same distance.
|
|
||||||
const fn shift_round(v: i64, bits: u32) -> i64 {
|
|
||||||
let half = (1i64 << bits) >> 1;
|
|
||||||
match v < 0 {
|
|
||||||
true => -((-v + half) >> bits),
|
|
||||||
false => (v + half) >> bits,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fn div_round(num: i64, den: i64) -> i64 {
|
const fn div_round(num: i64, den: i64) -> i64 {
|
||||||
let (q, rem) = (num / den, num % den);
|
let (q, rem) = (num / den, num % den);
|
||||||
match rem.unsigned_abs() * 2 >= den.unsigned_abs() {
|
match rem.unsigned_abs() * 2 >= den.unsigned_abs() {
|
||||||
@@ -531,15 +513,6 @@ mod tests {
|
|||||||
assert_eq!(Px::from_f32(-1e12), Px::MIN);
|
assert_eq!(Px::from_f32(-1e12), Px::MIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_coarser_grid_rounds_and_a_finer_one_does_not() {
|
|
||||||
// A third, which neither grid holds exactly.
|
|
||||||
let third = Rel::ONE / Rel::from_int(3);
|
|
||||||
assert_eq!(third.to_scale::<6>(), Fixed::<6>::from_raw(21));
|
|
||||||
let coarse = Fixed::<6>::from_raw(21);
|
|
||||||
assert_eq!(coarse.to_scale::<24>().to_scale::<6>(), coarse);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lerp_takes_the_fraction_as_the_receiver() {
|
fn lerp_takes_the_fraction_as_the_receiver() {
|
||||||
let (from, to) = (Px::from_int(10), Px::from_int(20));
|
let (from, to) = (Px::from_int(10), Px::from_int(20));
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{Px, Rel};
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Align {
|
pub struct Align {
|
||||||
pub x: Option<AxisAlign>,
|
pub x: Option<AxisAlign>,
|
||||||
pub y: Option<AxisAlign>,
|
pub y: Option<AxisAlign>,
|
||||||
@@ -151,13 +151,15 @@ impl Vec2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Len {
|
impl Len {
|
||||||
|
/// This length placed in the box it is measured in: the alignment names a
|
||||||
|
/// point along that box, and the two ends are that point less the part of
|
||||||
|
/// the length falling before it and plus the part falling after.
|
||||||
pub const fn align(&self, align: AxisAlign) -> UiSpan {
|
pub const fn align(&self, align: AxisAlign) -> UiSpan {
|
||||||
let rel = align.rel();
|
let rel = align.rel();
|
||||||
let rest = Rel::ONE.sub(rel);
|
|
||||||
let at = Len::from_parts(rel, Px::ZERO);
|
let at = Len::from_parts(rel, Px::ZERO);
|
||||||
UiSpan {
|
UiSpan {
|
||||||
start: Len::from_parts(at.rel.sub(self.rel.mul(rel)), at.px.sub(self.px.mul(rel))),
|
start: at - self.scale(rel),
|
||||||
end: Len::from_parts(at.rel.add(self.rel.mul(rest)), at.px.add(self.px.mul(rest))),
|
end: at + self.scale(Rel::ONE.sub(rel)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,3 +214,4 @@ impl RegionAlign {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl_axis_index!(RegionAlign => AxisAlign);
|
impl_axis_index!(RegionAlign => AxisAlign);
|
||||||
|
impl_axis_index!(Align => Option<AxisAlign>);
|
||||||
@@ -71,50 +71,5 @@ impl Vec2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const trait AxisT {
|
|
||||||
fn get() -> Axis;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct XAxis;
|
|
||||||
const impl AxisT for XAxis {
|
|
||||||
fn get() -> Axis {
|
|
||||||
Axis::X
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct YAxis;
|
|
||||||
const impl AxisT for YAxis {
|
|
||||||
fn get() -> Axis {
|
|
||||||
Axis::Y
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Default)]
|
|
||||||
pub struct BothAxis<T> {
|
|
||||||
pub x: T,
|
|
||||||
pub y: T,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> BothAxis<T> {
|
|
||||||
pub const fn axis<A: const AxisT>(&mut self) -> &mut T {
|
|
||||||
match A::get() {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn take_axis<A: const AxisT>(self) -> T {
|
|
||||||
match A::get() {
|
|
||||||
Axis::X => self.x,
|
|
||||||
Axis::Y => self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn axis_dyn(&mut self, axis: Axis) -> &mut T {
|
|
||||||
match axis {
|
|
||||||
Axis::X => &mut self.x,
|
|
||||||
Axis::Y => &mut self.y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_axis_index!({const SHIFT: u32} FixedVec2<SHIFT> => Fixed<SHIFT>);
|
impl_axis_index!({const SHIFT: u32} FixedVec2<SHIFT> => Fixed<SHIFT>);
|
||||||
impl_axis_index!(Vec2 => f32);
|
impl_axis_index!(Vec2 => f32);
|
||||||
@@ -148,20 +148,20 @@ impl LayoutLen {
|
|||||||
/// Only pixels: the same number of them whatever box it lands in, and
|
/// 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
|
/// 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.
|
/// of a box or of what is left over is not one.
|
||||||
pub fn is_px(self) -> bool {
|
pub fn is_px(&self) -> bool {
|
||||||
self.rel == Rel::ZERO && self.leftover == Weight::ZERO
|
self.rel == Rel::ZERO && self.leftover == Weight::ZERO
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Nothing but a claim on what is left over, so there is no length here
|
/// Nothing but a claim on what is left over, so there is no length here
|
||||||
/// at all where nothing is.
|
/// at all where nothing is.
|
||||||
pub fn is_only_leftover(self) -> bool {
|
pub fn is_only_leftover(&self) -> bool {
|
||||||
self.leftover > Weight::ZERO && self.without_leftover() == Len::ZERO
|
self.leftover > Weight::ZERO && self.without_leftover() == Len::ZERO
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This as a length of a box, where it is one. `leftover` is not: a
|
/// 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,
|
/// 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.
|
/// so it passes up in the reported size instead and is resolved there.
|
||||||
pub fn declared(self) -> Option<Len> {
|
pub fn declared(&self) -> Option<Len> {
|
||||||
(self.leftover == Weight::ZERO).then(|| self.without_leftover())
|
(self.leftover == Weight::ZERO).then(|| self.without_leftover())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,7 +169,7 @@ impl LayoutLen {
|
|||||||
/// anyone not dividing a box between siblings, where a share is a claim
|
/// 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.
|
/// on someone else's room rather than a length of its own.
|
||||||
/// [`Self::apply_leftover`] is the opposite reading of the same value.
|
/// [`Self::apply_leftover`] is the opposite reading of the same value.
|
||||||
pub const fn without_leftover(self) -> Len {
|
pub const fn without_leftover(&self) -> Len {
|
||||||
Len::from_parts(self.rel, self.px)
|
Len::from_parts(self.rel, self.px)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -281,15 +281,6 @@ impl UiSpan {
|
|||||||
pub const fn len(&self) -> Len {
|
pub const fn len(&self) -> Len {
|
||||||
self.end - self.start
|
self.end - self.start
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Both ends by the same amount, which is what moving a box without
|
|
||||||
/// changing its length does to every part of it.
|
|
||||||
pub const fn translated(self, by: Len) -> Self {
|
|
||||||
Self {
|
|
||||||
start: self.start + by,
|
|
||||||
end: self.end + by,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
@@ -300,17 +291,6 @@ pub struct UiRegion {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl UiRegion {
|
impl UiRegion {
|
||||||
/// Every part of the box by the same amount on each axis. Done to the
|
|
||||||
/// whole region rather than an end at a time, because that is what it is
|
|
||||||
/// -- and because four adds in a row are four adds, where four asked for
|
|
||||||
/// separately are four sequences.
|
|
||||||
pub const fn translated(self, x: Len, y: Len) -> Self {
|
|
||||||
Self {
|
|
||||||
x: self.x.translated(x),
|
|
||||||
y: self.y.translated(y),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const FULL: Self = Self {
|
pub const FULL: Self = Self {
|
||||||
x: UiSpan::FULL,
|
x: UiSpan::FULL,
|
||||||
y: UiSpan::FULL,
|
y: UiSpan::FULL,
|
||||||
|
|||||||
@@ -133,10 +133,6 @@ impl TextBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_empty() -> Self {
|
|
||||||
Self::new("")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(&self) -> &str {
|
pub fn text(&self) -> &str {
|
||||||
&self.text
|
&self.text
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,10 +167,6 @@ impl GlyphAtlas {
|
|||||||
pub fn page_count(&self) -> u32 {
|
pub fn page_count(&self) -> u32 {
|
||||||
self.pages.len() as u32
|
self.pages.len() as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn glyph_count(&self) -> usize {
|
|
||||||
self.entries.len()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
|
|||||||
+13
-4
@@ -23,13 +23,22 @@ pub use primitive::*;
|
|||||||
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
const PRELUDE: &str = include_str!("./shader/prelude.wgsl");
|
||||||
|
|
||||||
fn module_source(wgsl: &str) -> String {
|
fn module_source(wgsl: &str) -> String {
|
||||||
// The steps come from the same constants the CPU counts in, rather than
|
// Every number both sides count in, written once here rather than a
|
||||||
// a second copy of them written into the shader: a grid the two disagree
|
// second time in the shader: a grid the two disagree about puts every
|
||||||
// about puts every coordinate somewhere else.
|
// coordinate somewhere else, and a sentinel they disagree about makes one
|
||||||
|
// of them walk a chain from a slot the other says is not there.
|
||||||
format!(
|
format!(
|
||||||
"const PX_STEP: f32 = 1.0 / {}.0;\nconst REL_STEP: f32 = 1.0 / {}.0;\n{PRELUDE}\n{wgsl}",
|
"const PX_STEP: f32 = 1.0 / {}.0;\n\
|
||||||
|
const REL_STEP: f32 = 1.0 / {}.0;\n\
|
||||||
|
const MASK_NONE: u32 = {}u;\n\
|
||||||
|
const MOVE_NONE: u32 = {}u;\n\
|
||||||
|
const CHAIN_LIMIT: u32 = {}u;\n\
|
||||||
|
{PRELUDE}\n{wgsl}",
|
||||||
1u32 << crate::PX_SHIFT,
|
1u32 << crate::PX_SHIFT,
|
||||||
1u32 << crate::REL_SHIFT,
|
1u32 << crate::REL_SHIFT,
|
||||||
|
MaskIdx::NONE.idx(),
|
||||||
|
MoveIdx::NONE.idx(),
|
||||||
|
crate::CHAIN_LIMIT,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,9 +26,11 @@ struct MoveOffset {
|
|||||||
parent: u32,
|
parent: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
// `PX_STEP` and `REL_STEP` are prepended from `iris_core`'s own constants:
|
// `PX_STEP`, `REL_STEP`, `MASK_NONE`, `MOVE_NONE` and `CHAIN_LIMIT` are
|
||||||
// what it stores is a whole count of each, both powers of two, so decoding
|
// prepended from `iris_core`'s own constants, so none of them is written
|
||||||
// is exact and the number here is the number the CPU decided.
|
// twice. What the CPU stores is a whole count of each step, and both steps
|
||||||
|
// are powers of two, so decoding is exact and the number here is the number
|
||||||
|
// the CPU decided.
|
||||||
|
|
||||||
// Every coordinate the CPU decided is a whole count of `PX_STEP`, so one that
|
// Every coordinate the CPU decided is a whole count of `PX_STEP`, so one that
|
||||||
// composes to within half a step of a pixel boundary is on that boundary and
|
// composes to within half a step of a pixel boundary is on that boundary and
|
||||||
@@ -70,12 +72,6 @@ struct Region {
|
|||||||
y: UiSpan,
|
y: UiSpan,
|
||||||
}
|
}
|
||||||
|
|
||||||
const MOVE_NONE: u32 = 4294967295u;
|
|
||||||
// Keep in step with `iris_core::CHAIN_LIMIT`. It bounds a malformed cycle
|
|
||||||
// rather than any real tree, and the CPU walk uses the same number so both
|
|
||||||
// resolve a deep one the same way.
|
|
||||||
const CHAIN_LIMIT: u32 = 64u;
|
|
||||||
|
|
||||||
// The same expression `Len::within` uses, in floats rather than on the
|
// The same expression `Len::within` uses, in floats rather than on the
|
||||||
// CPU's grid: a move is resolved here so that scrolling a subtree writes one
|
// CPU's grid: a move is resolved here so that scrolling a subtree writes one
|
||||||
// entry instead of walking it. What has to hold is that this agrees with
|
// entry instead of walking it. What has to hold is that this agrees with
|
||||||
@@ -171,7 +167,7 @@ fn vs_main(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
|
fn masked(in: VertexOutput, color: vec4<f32>) -> vec4<f32> {
|
||||||
if in.mask_idx == 4294967295u {
|
if in.mask_idx == MASK_NONE {
|
||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
let mask = masks[in.mask_idx];
|
let mask = masks[in.mask_idx];
|
||||||
|
|||||||
@@ -29,17 +29,17 @@ impl Holds {
|
|||||||
Self { lo: len, hi: len }
|
Self { lo: len, hi: len }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const fn contains(self, len: Px) -> bool {
|
pub const fn contains(&self, len: Px) -> bool {
|
||||||
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
|
/// Every length `other` holds for is one this holds for, so a drawing
|
||||||
/// made under this range is still good wherever `other` is.
|
/// made under this range is still good wherever `other` is.
|
||||||
pub const fn covers(self, other: Self) -> bool {
|
pub const fn covers(&self, other: Self) -> bool {
|
||||||
self.lo.raw() <= other.lo.raw() && self.hi.raw() >= other.hi.raw()
|
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),
|
||||||
hi: self.hi.min(other.hi),
|
hi: self.hi.min(other.hi),
|
||||||
@@ -57,7 +57,7 @@ impl Holds {
|
|||||||
/// boxes therefore give one length. That is a floor rather than an
|
/// boxes therefore give one length. That is a floor rather than an
|
||||||
/// allowance: inverting it is two divisions and nothing else, and the
|
/// allowance: inverting it is two divisions and nothing else, and the
|
||||||
/// whole of a box maps back to itself.
|
/// whole of a box maps back to itself.
|
||||||
pub const fn through(self, len: Len) -> Self {
|
pub const fn through(&self, len: Len) -> Self {
|
||||||
if self.lo.raw() == Px::MIN.raw() && self.hi.raw() == Px::MAX.raw() {
|
if self.lo.raw() == Px::MIN.raw() && self.hi.raw() == Px::MAX.raw() {
|
||||||
return Self::ANY;
|
return Self::ANY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ impl AxisHolds {
|
|||||||
region_len: None,
|
region_len: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn and(self, other: Self) -> Self {
|
pub fn and(&self, other: Self) -> Self {
|
||||||
// Two pins of the same length disagreeing would mean one drawing was
|
// Two pins of the same length disagreeing would mean one drawing was
|
||||||
// a fraction of two different lengths at once.
|
// a fraction of two different lengths at once.
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
@@ -53,7 +53,7 @@ impl AxisHolds {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn covers(self, other: Self) -> bool {
|
pub fn covers(&self, other: Self) -> bool {
|
||||||
self.window.covers(other.window)
|
self.window.covers(other.window)
|
||||||
&& self.region.covers(other.region)
|
&& self.region.covers(other.region)
|
||||||
&& self
|
&& self
|
||||||
@@ -64,7 +64,7 @@ impl AxisHolds {
|
|||||||
|
|
||||||
/// Whether a widget in a box `len` long, with that rel base, in that
|
/// Whether a widget in a box `len` long, with that rel base, in that
|
||||||
/// window, is one this drawing holds for.
|
/// window, is one this drawing holds for.
|
||||||
pub fn contains(self, window: Px, rel_base: Len, len: Len) -> bool {
|
pub fn contains(&self, window: Px, rel_base: Len, len: Len) -> bool {
|
||||||
self.window.contains(window)
|
self.window.contains(window)
|
||||||
&& self.rel_base.is_none_or(|pinned| pinned == rel_base)
|
&& self.rel_base.is_none_or(|pinned| pinned == rel_base)
|
||||||
&& self.region.contains(len.to_px(window))
|
&& self.region.contains(len.to_px(window))
|
||||||
@@ -87,18 +87,18 @@ impl LayoutHolds {
|
|||||||
y: AxisHolds::ANY,
|
y: AxisHolds::ANY,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn and(self, other: Self) -> Self {
|
pub fn and(&self, other: Self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
x: self.x.and(other.x),
|
x: self.x.and(other.x),
|
||||||
y: self.y.and(other.y),
|
y: self.y.and(other.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn covers(self, other: Self) -> bool {
|
pub fn covers(&self, other: Self) -> bool {
|
||||||
self.x.covers(other.x) && self.y.covers(other.y)
|
self.x.covers(other.x) && self.y.covers(other.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains(self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
|
pub fn contains(&self, window: PxVec2, rel_base: UiVec2, region: UiRegion) -> bool {
|
||||||
Axis::BOTH
|
Axis::BOTH
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.all(|axis| self[axis].contains(window[axis], rel_base[axis], region[axis].len()))
|
.all(|axis| self[axis].contains(window[axis], rel_base[axis], region[axis].len()))
|
||||||
|
|||||||
+111
-39
@@ -2,8 +2,8 @@
|
|||||||
use crate::layout_diagnostics::{self as diag, Counter};
|
use crate::layout_diagnostics::{self as diag, Counter};
|
||||||
use crate::{
|
use crate::{
|
||||||
Axis, Declared, Holds, LayoutHolds, LayoutLen, Len, PlaceDesc, 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, TextureHandle,
|
||||||
TextureHandle, UiRegion, UiRenderState, UiRsc, UiVec2, Weight, WidgetId, Widgets,
|
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,
|
||||||
@@ -187,10 +187,20 @@ impl<'a> Painter<'a> {
|
|||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
place: impl Into<PlaceDesc>,
|
place: impl Into<PlaceDesc>,
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
let place = self.resolve_rel_base(place.into());
|
let mut 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 align = self.rsc.widgets().alignment(id.id());
|
let align = self.rsc.widgets().alignment(id.id());
|
||||||
|
// A share fills what the pixels and fraction beside it leave of the
|
||||||
|
// box and overflows where they are longer, which is the rule a span
|
||||||
|
// follows with one child. Only the overflow is a box of the child's
|
||||||
|
// own: a share that fits is the box it was given, which is what this
|
||||||
|
// place already says.
|
||||||
|
for axis in Axis::BOTH {
|
||||||
|
if let Some(len) = self.share_past_the_offer(id.id(), place, align, axis) {
|
||||||
|
place[axis] = len.as_desc().fills();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let declared = self.declared_lens(id);
|
||||||
let (rel_base, region) =
|
let (rel_base, region) =
|
||||||
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
place.rel_base_and_region(self.region, self.rel_base, declared, align);
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
@@ -291,27 +301,59 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a widget's rules declare its lengths to be, which whoever draws
|
/// What a rule or a hint declares a widget's lengths to be, which whoever
|
||||||
/// it resolves into its rel base. Reading them depends on nothing -- the box
|
/// draws 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>) -> Declared {
|
fn declared_lens<W: ?Sized>(&self, id: &StrongWidget<W>) -> Declared {
|
||||||
self.rsc.widgets().declared_lens(id.id())
|
self.rsc.widgets().declared_lens(id.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The box a child's own share asks for where that is longer than the box
|
||||||
|
/// `place` gives it, and nothing where the share fits.
|
||||||
|
///
|
||||||
|
/// A share is a length only to whoever divides one, and nothing divides a
|
||||||
|
/// box handed to one child: what is left of it after the pixels and the
|
||||||
|
/// fraction beside the share is what the share takes, so the length comes
|
||||||
|
/// to the whole box until those are longer than it and to them once they
|
||||||
|
/// are. Only that second case is a box this widget did not give, and the
|
||||||
|
/// crossing between them is a question in pixels, so this widget's drawing
|
||||||
|
/// holds for the windows on one side of it. Narrowed rather than stated,
|
||||||
|
/// because this widget may have read its own box as well, and a range it
|
||||||
|
/// pinned for that still holds.
|
||||||
|
fn share_past_the_offer(
|
||||||
|
&mut self,
|
||||||
|
id: WidgetId,
|
||||||
|
place: PlaceDesc,
|
||||||
|
align: RegionAlign,
|
||||||
|
axis: Axis,
|
||||||
|
) -> Option<Len> {
|
||||||
|
// A place that is the child's placement outright is a box its parent
|
||||||
|
// decided, and a parent that divides one has already given the share
|
||||||
|
// whatever it was owed. Only an offer -- a box with the answer still
|
||||||
|
// to be placed inside it -- is a box a share reads.
|
||||||
|
if place[axis].fills {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// A share with nothing beside it is the box whatever the box is, so
|
||||||
|
// there is no comparison to make and no range to keep for one.
|
||||||
|
let stated = self.rsc.widgets().exact_len(id, axis)?;
|
||||||
|
if stated.leftover == Weight::ZERO || stated.is_only_leftover() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let fixed = stated
|
||||||
|
.without_leftover()
|
||||||
|
.within_len(place.base(axis, self.rel_base));
|
||||||
|
let offer = place.of(self.region, align)[axis].len();
|
||||||
|
self.longer_than(fixed, offer, axis).then_some(fixed)
|
||||||
|
}
|
||||||
|
|
||||||
/// 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 rel base, which is the rel base 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 hint = self.rsc.widgets().exact_len(id.id(), axis);
|
||||||
// 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.
|
|
||||||
let hint = widgets.size_rules(id.id())[axis].exact().or_else(|| {
|
|
||||||
widgets
|
|
||||||
.get_dyn(id.id())
|
|
||||||
.and_then(|widget| widget.size_hint(axis))
|
|
||||||
});
|
|
||||||
let rel_base = self.rel_base[axis];
|
let rel_base = self.rel_base[axis];
|
||||||
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
let resolved = hint.map(|hint| hint.within_len(rel_base));
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
@@ -482,6 +524,40 @@ impl<'a> Painter<'a> {
|
|||||||
len.to_px(window)
|
len.to_px(window)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether `len` is longer than `than`, kept as the windows that comparison
|
||||||
|
/// comes out the same way on: a drawing that took one of two lengths holds
|
||||||
|
/// where the same one is the longer, and nowhere else.
|
||||||
|
///
|
||||||
|
/// Which is longer is a question in pixels -- `rel(0.5)` is longer than 300
|
||||||
|
/// px at a box of 600 and shorter at 400 -- and it is asked of the
|
||||||
|
/// difference and answered back through that same difference, so the
|
||||||
|
/// boundary is the drawing's own rather than a second way of finding it.
|
||||||
|
/// Narrowed rather than stated, because whatever else this widget read
|
||||||
|
/// about the window is a reason its drawing holds where it does too.
|
||||||
|
///
|
||||||
|
/// This is the one operation a length that is the longer of two needs: the
|
||||||
|
/// room a container has left for the shares it divides, and a share that
|
||||||
|
/// overflows the box it was given because the pixels beside it are longer
|
||||||
|
/// than the box.
|
||||||
|
pub fn longer_than(&mut self, len: Len, than: Len, axis: Axis) -> bool {
|
||||||
|
let over = len - than;
|
||||||
|
let window = self.window[axis];
|
||||||
|
let longer = over.to_px(window) > Px::ZERO;
|
||||||
|
let side = match longer {
|
||||||
|
true => Px::STEP..=Px::MAX,
|
||||||
|
false => Px::MIN..=Px::ZERO,
|
||||||
|
};
|
||||||
|
let holds = Holds::from(side).through(over);
|
||||||
|
debug_assert!(
|
||||||
|
holds.contains(window),
|
||||||
|
"'{}' ({:?}) compared two lengths and kept a range without this window",
|
||||||
|
self.label(),
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
self.own[axis].window = self.own[axis].window.and(holds);
|
||||||
|
longer
|
||||||
|
}
|
||||||
|
|
||||||
/// The windows this drawing holds for, stated rather than taken: a
|
/// The windows this drawing holds for, stated rather than taken: a
|
||||||
/// container that branched on a length in pixels says which side of the
|
/// container that branched on a length in pixels says which side of the
|
||||||
/// boundary it was on, which is wider than the one window reading that
|
/// boundary it was on, which is wider than the one window reading that
|
||||||
@@ -497,10 +573,6 @@ impl<'a> Painter<'a> {
|
|||||||
self.own[axis].window = holds;
|
self.own[axis].window = holds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_data(&mut self) -> &mut TextData {
|
|
||||||
&mut self.rsc.ui_mut().text
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn child_layer(&mut self) {
|
pub fn child_layer(&mut self) {
|
||||||
self.layer = self.state.layers.child(self.layer);
|
self.layer = self.state.layers.child(self.layer);
|
||||||
}
|
}
|
||||||
@@ -651,24 +723,27 @@ impl Painter<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Widgets {
|
impl Widgets {
|
||||||
/// What a widget's box is where a rule or its own hint says so outright.
|
/// What says a widget's length on one axis without drawing it, if anything
|
||||||
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
/// does. A rule is the answer where there is one: it wins over whatever the
|
||||||
let rules = self.size_rules(id);
|
/// widget would draw, so it has to win over what the widget says too -- a
|
||||||
let widget = self.get_dyn(id);
|
/// share included, since a share is a length only to whoever divides one,
|
||||||
Declared::from_axes(|axis| {
|
/// and that is the parent rather than this widget.
|
||||||
rules[axis].declared().or_else(|| {
|
fn exact_len(&self, id: WidgetId, axis: Axis) -> Option<LayoutLen> {
|
||||||
// A hint still narrows the box where no rule does, which is
|
self.size_rules(id)[axis].exact().or_else(|| {
|
||||||
// how a widget with a natural pixel size -- an image, a gap
|
// A hint still narrows the box where no rule does, which is how a
|
||||||
// -- gets that size rather than the whole offer. That is the
|
// widget with a natural pixel size -- an image, a gap -- gets that
|
||||||
// offer's business rather than a declaration's, and this
|
// size rather than the whole offer. That is the offer's business
|
||||||
// falls away once a widget occupies its reported size inside
|
// rather than a declaration's, and this falls away once a widget
|
||||||
// the box it was offered.
|
// occupies its reported size inside the box it was offered.
|
||||||
widget
|
self.get_dyn(id)?.size_hint(axis)
|
||||||
.and_then(|widget| widget.size_hint(axis))
|
|
||||||
.and_then(LayoutLen::declared)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a widget's box is where a rule or its own hint gives one outright,
|
||||||
|
/// rather than a share for whoever draws it to divide.
|
||||||
|
pub(super) fn declared_lens(&self, id: WidgetId) -> Declared {
|
||||||
|
Declared::from_axes(|axis| self.exact_len(id, axis)?.declared())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LayoutLen {
|
impl LayoutLen {
|
||||||
@@ -679,7 +754,7 @@ impl LayoutLen {
|
|||||||
/// the rule already gave the region its length, and the rule's length is
|
/// 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
|
/// what the widget reports there. And an axis the parent decided from
|
||||||
/// the answer is the answer already.
|
/// the answer is the answer already.
|
||||||
pub(super) fn fills(self, declared: Option<Len>, decided: bool) -> bool {
|
pub(super) fn fills(&self, declared: Option<Len>, decided: bool) -> bool {
|
||||||
self.leftover != Weight::ZERO || declared.is_some() || decided
|
self.leftover != Weight::ZERO || declared.is_some() || decided
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -733,10 +808,7 @@ impl PlaceDesc {
|
|||||||
let mut rel_base = parent_rel_base;
|
let mut rel_base = parent_rel_base;
|
||||||
let mut region = given;
|
let mut region = given;
|
||||||
for axis in Axis::BOTH {
|
for axis in Axis::BOTH {
|
||||||
let base = match self[axis].rel_base {
|
let base = self.base(axis, parent_rel_base);
|
||||||
RelBase::Len(len) => len,
|
|
||||||
RelBase::Inherit | RelBase::WithRegion => parent_rel_base[axis],
|
|
||||||
};
|
|
||||||
let len = declared[axis]
|
let len = declared[axis]
|
||||||
.map(|len| len.within_len(base))
|
.map(|len| len.within_len(base))
|
||||||
.unwrap_or(base);
|
.unwrap_or(base);
|
||||||
|
|||||||
+11
-1
@@ -1,5 +1,5 @@
|
|||||||
use crate::util::impl_axis_index;
|
use crate::util::impl_axis_index;
|
||||||
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan};
|
use crate::{Axis, AxisAlign, Len, PrimitiveHandle, RegionAlign, UiRegion, UiSpan, UiVec2};
|
||||||
|
|
||||||
/// How a child's region along one axis comes from the region of the widget
|
/// How a child's region along one axis comes from the region of the widget
|
||||||
/// asking, and what its fractions are of.
|
/// asking, and what its fractions are of.
|
||||||
@@ -123,6 +123,16 @@ impl PlaceDesc {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// What a child's fractions on one axis are of, as a length of the
|
||||||
|
/// window: a length this place names, or the rel base of the widget
|
||||||
|
/// giving it, which is `parent_rel_base`.
|
||||||
|
pub(super) fn base(&self, axis: Axis, parent_rel_base: UiVec2) -> Len {
|
||||||
|
match self[axis].rel_base {
|
||||||
|
RelBase::Len(len) => len,
|
||||||
|
RelBase::Inherit | RelBase::WithRegion => parent_rel_base[axis],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The box each axis names, in the coordinates `own` is in.
|
/// The box each axis names, in the coordinates `own` is in.
|
||||||
pub fn of(self, own: UiRegion, align: RegionAlign) -> UiRegion {
|
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))
|
UiRegion::new(self.x.of(own.x, align.x), self.y.of(own.y, align.y))
|
||||||
|
|||||||
@@ -1167,7 +1167,7 @@ impl Size {
|
|||||||
/// in. Both are lengths of the window, so the comparison is in its
|
/// in. Both are lengths of the window, so the comparison is in its
|
||||||
/// pixels. A share is a length only to whoever divides one, so it is not
|
/// pixels. A share is a length only to whoever divides one, so it is not
|
||||||
/// a claim about this box and cannot exceed it.
|
/// a claim about this box and cannot exceed it.
|
||||||
fn within_box(self, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
fn within_box(&self, region: UiRegion, window: PxVec2, axis: Axis) -> bool {
|
||||||
let len = self[axis];
|
let len = self[axis];
|
||||||
let window = window[axis];
|
let window = window[axis];
|
||||||
len.leftover != Weight::ZERO
|
len.leftover != Weight::ZERO
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl<T, I: IdNum> Arena<T, I> {
|
|||||||
self.data[i]
|
self.data[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
pub fn get_mut(&mut self, id: Id<I>) -> &mut T {
|
||||||
&mut self.data[id.idx()]
|
&mut self.data[id.idx()]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ 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.
|
/// give one.
|
||||||
pub fn declared(&self) -> Option<Len> {
|
pub fn declared(&self) -> Option<Len> {
|
||||||
self.exact().and_then(LayoutLen::declared)
|
self.exact().and_then(|len| len.declared())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The length this rule gives outright, whatever the widget reports --
|
/// The length this rule gives outright, whatever the widget reports --
|
||||||
|
|||||||
@@ -30,6 +30,14 @@ impl Widgets {
|
|||||||
!self.needs_redraw.is_empty()
|
!self.needs_redraw.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Marks this widget for the next frame to draw again, with nothing about
|
||||||
|
/// it changed. Taking a widget mutably marks it too, which is the ordinary
|
||||||
|
/// content-change signal; this is for a change the borrow cannot express,
|
||||||
|
/// and for asking for the same tree over again.
|
||||||
|
pub fn mark_for_redraw(&mut self, id: impl IdLike) {
|
||||||
|
self.needs_redraw.insert(id.id());
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_dyn(&self, id: WidgetId) -> Option<&dyn Widget> {
|
pub fn get_dyn(&self, id: WidgetId) -> Option<&dyn Widget> {
|
||||||
Some(self.vec.get(id)?.widget.as_ref())
|
Some(self.vec.get(id)?.widget.as_ref())
|
||||||
}
|
}
|
||||||
@@ -41,14 +49,14 @@ impl Widgets {
|
|||||||
|
|
||||||
/// get_dyn but dynamic borrow checking of widgets
|
/// get_dyn but dynamic borrow checking of widgets
|
||||||
/// lets you do recursive (tree) operations, like the painter does
|
/// lets you do recursive (tree) operations, like the painter does
|
||||||
pub(crate) fn get_dyn_dynamic<'a>(&self, id: WidgetId) -> WidgetWrapper<'a> {
|
pub(crate) fn get_dyn_dynamic<'a>(&self, id: WidgetId) -> DynBorrower<'a, dyn Widget> {
|
||||||
// SAFETY: must guarantee no other mutable references to this widget exist
|
// SAFETY: must guarantee no other mutable references to this widget exist
|
||||||
// done through the borrow variable
|
// done through the borrow variable
|
||||||
let data = unsafe { forget_mut(to_mut(self.vec.get(id).unwrap())) };
|
let data = unsafe { forget_mut(to_mut(self.vec.get(id).unwrap())) };
|
||||||
if data.borrowed {
|
if data.borrowed {
|
||||||
panic!("tried to mutably borrow the same widget twice");
|
panic!("tried to mutably borrow the same widget twice");
|
||||||
}
|
}
|
||||||
WidgetWrapper::new(data.widget.as_mut(), &mut data.borrowed)
|
DynBorrower::new(data.widget.as_mut(), &mut data.borrowed)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
|
pub fn get<I: IdLike>(&self, id: &I) -> Option<&I::Widget>
|
||||||
@@ -154,7 +162,7 @@ impl Widgets {
|
|||||||
self.needs_redraw.insert(id);
|
self.needs_redraw.insert(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Both axes at once, for a caller holding a pair.
|
/// Both axes at once.
|
||||||
pub fn set_size_rules(
|
pub fn set_size_rules(
|
||||||
&mut self,
|
&mut self,
|
||||||
id: impl IdLike,
|
id: impl IdLike,
|
||||||
@@ -188,8 +196,6 @@ impl Default for Widgets {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type WidgetWrapper<'a> = DynBorrower<'a, dyn Widget>;
|
|
||||||
|
|
||||||
impl<I: IdLike> std::ops::Index<I> for Widgets
|
impl<I: IdLike> std::ops::Index<I> for Widgets
|
||||||
where
|
where
|
||||||
I::Widget: Sized + Widget,
|
I::Widget: Sized + Widget,
|
||||||
|
|||||||
+11
-6
@@ -106,11 +106,16 @@ export WAYLAND_DISPLAY
|
|||||||
|
|
||||||
echo "run-headless: $WAYLAND_DISPLAY (sway $(swaymsg -t get_version --raw | sed -n 's/.*"human_readable":"\([^"]*\)".*/\1/p'))" >&2
|
echo "run-headless: $WAYLAND_DISPLAY (sway $(swaymsg -t get_version --raw | sed -n 's/.*"human_readable":"\([^"]*\)".*/\1/p'))" >&2
|
||||||
|
|
||||||
swaymsg output HEADLESS-1 mode "$mode" >/dev/null
|
# The extent `replay-touch` positions against, so a script's coordinates are
|
||||||
# The extent `replay-touch` positions against, so a script's coordinates
|
# the output's own pixels. Set beside every mode change, since a gesture
|
||||||
# are the output's own pixels.
|
# scaled against a mode the output no longer has lands somewhere else and
|
||||||
out_w=${mode%x*}
|
# still looks like a run that worked.
|
||||||
out_h=${mode#*x}; out_h=${out_h%@*}
|
set_mode() {
|
||||||
|
swaymsg output HEADLESS-1 mode "$1" >/dev/null
|
||||||
|
out_w=${1%x*}
|
||||||
|
out_h=${1#*x}; out_h=${out_h%@*}
|
||||||
|
}
|
||||||
|
set_mode "$mode"
|
||||||
|
|
||||||
# Built before the app starts, so a compile error is not reported as a
|
# Built before the app starts, so a compile error is not reported as a
|
||||||
# window that failed to move.
|
# window that failed to move.
|
||||||
@@ -149,7 +154,7 @@ while [ $i -lt "$((seconds * 2))" ]; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
if [ -n "$resize" ] && kill -0 "$pid" 2>/dev/null; then
|
if [ -n "$resize" ] && kill -0 "$pid" 2>/dev/null; then
|
||||||
swaymsg output HEADLESS-1 mode "$resize" >/dev/null
|
set_mode "$resize"
|
||||||
echo "run-headless: resized to $resize" >&2
|
echo "run-headless: resized to $resize" >&2
|
||||||
sleep 2
|
sleep 2
|
||||||
fi
|
fi
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 191 B |
+4
-3
@@ -15,9 +15,10 @@ where
|
|||||||
let region = ctx.data.render.window_region(&id).unwrap();
|
let region = ctx.data.render.window_region(&id).unwrap();
|
||||||
let id_pos = region.top_left;
|
let id_pos = region.top_left;
|
||||||
let container_pos = ctx.data.render.window_region(&container).unwrap().top_left;
|
let container_pos = ctx.data.render.window_region(&container).unwrap().top_left;
|
||||||
// The pointer arrives from the platform in floats; everything
|
// The two regions are on the grid and the pointer is not, so the
|
||||||
// it is compared against is on the grid.
|
// step between them is taken there and the pointer keeps the
|
||||||
let pos = (PxVec2::from_f32(ctx.data.pos) + container_pos - id_pos).to_f32();
|
// precision the platform gave it.
|
||||||
|
let pos = ctx.data.pos + (container_pos - id_pos).to_f32();
|
||||||
let size = region.size().to_f32();
|
let size = region.size().to_f32();
|
||||||
select(
|
select(
|
||||||
rsc,
|
rsc,
|
||||||
|
|||||||
+54
-29
@@ -8,23 +8,16 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// The declared lengths of one widget carrying a size rule, by axis.
|
|
||||||
pub type Lens = [Option<LayoutLen>; 2];
|
|
||||||
|
|
||||||
/// Where one widget carrying an alignment sits, by axis. `None` uses the
|
|
||||||
/// centered default.
|
|
||||||
pub type Aligns = [Option<AxisAlign>; 2];
|
|
||||||
|
|
||||||
/// What a test changes between two trees grown from the same seed, so the
|
/// What a test changes between two trees grown from the same seed, so the
|
||||||
/// warm one can be mutated and the cold one grown that way to begin with.
|
/// warm one can be mutated and the cold one grown that way to begin with.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Edits {
|
pub struct Edits {
|
||||||
/// Declared sizes, by the order the rules were put on.
|
/// Declared sizes, by the order the rules were put on.
|
||||||
pub sizes: HashMap<usize, Lens>,
|
pub sizes: HashMap<usize, SizeRules>,
|
||||||
/// Which children a span has, by the order the spans were made.
|
/// Which children a span has, by the order the spans were made.
|
||||||
pub spans: HashMap<usize, SpanEdit>,
|
pub spans: HashMap<usize, SpanEdit>,
|
||||||
/// Alignments, by the order they were put on.
|
/// Alignments, by the order they were put on.
|
||||||
pub aligns: HashMap<usize, Aligns>,
|
pub aligns: HashMap<usize, Align>,
|
||||||
/// Which widgets own a movable region, by the order they were offered
|
/// Which widgets own a movable region, by the order they were offered
|
||||||
/// one. Region nodes change what a move writes and how deep a primitive's
|
/// one. Region nodes change what a move writes and how deep a primitive's
|
||||||
/// chain is, so a tree that never grows one leaves both untested.
|
/// chain is, so a tree that never grows one leaves both untested.
|
||||||
@@ -176,9 +169,11 @@ pub struct Plan {
|
|||||||
/// it one and the offer is taken or declined; a second offer to the same
|
/// it one and the offer is taken or declined; a second offer to the same
|
||||||
/// widget is dropped, because two rules on one widget would settle in the
|
/// widget is dropped, because two rules on one widget would settle in the
|
||||||
/// order they were applied rather than in grow order.
|
/// order they were applied rather than in grow order.
|
||||||
pub size: Option<Lens>,
|
pub size: Option<SizeRules>,
|
||||||
/// The alignment it carries, under the same one-offer rule.
|
/// The alignment it carries, under the same one-offer rule. An axis left
|
||||||
pub align: Option<Aligns>,
|
/// out takes the centered default, which is what [`RegionAlign`] reads it
|
||||||
|
/// as.
|
||||||
|
pub align: Option<Align>,
|
||||||
/// Whether it was offered a movable region of its own and what it
|
/// Whether it was offered a movable region of its own and what it
|
||||||
/// answered. `Some(false)` is an offer declined, which still uses up the
|
/// answered. `Some(false)` is an offer declined, which still uses up the
|
||||||
/// one offer, where `None` is an offer never made.
|
/// one offer, where `None` is an offer never made.
|
||||||
@@ -195,6 +190,9 @@ pub enum Kind {
|
|||||||
color: usize,
|
color: usize,
|
||||||
alpha: u8,
|
alpha: u8,
|
||||||
},
|
},
|
||||||
|
/// The one leaf whose own length is a number of pixels it knows before it
|
||||||
|
/// is drawn, which is the hint a rule beside it has to win over.
|
||||||
|
Image,
|
||||||
/// Scrolling reads the pixel length of its box, which nothing else here
|
/// Scrolling reads the pixel length of its box, which nothing else here
|
||||||
/// does, and gives its child a box longer than its own.
|
/// does, and gives its child a box longer than its own.
|
||||||
Scroll {
|
Scroll {
|
||||||
@@ -448,9 +446,11 @@ impl Kind {
|
|||||||
}
|
}
|
||||||
match self {
|
match self {
|
||||||
// The one leaf that reads the width it is given, then the one
|
// The one leaf that reads the width it is given, then the one
|
||||||
// that does not, then the one that measures nothing at all.
|
// that does not, then the one that measures nothing at all. A
|
||||||
|
// picture measures nothing either, but its length is its own, so
|
||||||
|
// it steps to the leaf that takes whatever it is given.
|
||||||
Kind::Wrapped => out.push(Kind::OneLine),
|
Kind::Wrapped => out.push(Kind::OneLine),
|
||||||
Kind::OneLine => out.push(Kind::Rect {
|
Kind::OneLine | Kind::Image => out.push(Kind::Rect {
|
||||||
color: 0,
|
color: 0,
|
||||||
alpha: 255,
|
alpha: 255,
|
||||||
}),
|
}),
|
||||||
@@ -632,9 +632,10 @@ struct Sow<'a> {
|
|||||||
|
|
||||||
impl Sow<'_> {
|
impl Sow<'_> {
|
||||||
fn leaf(&mut self) -> Plan {
|
fn leaf(&mut self) -> Plan {
|
||||||
Plan::bare(match self.rng.below(4) {
|
Plan::bare(match self.rng.below(5) {
|
||||||
0 => Kind::Wrapped,
|
0 => Kind::Wrapped,
|
||||||
1 => Kind::OneLine,
|
1 => Kind::OneLine,
|
||||||
|
2 => Kind::Image,
|
||||||
_ => {
|
_ => {
|
||||||
let color = self.rng.below(COLORS.len());
|
let color = self.rng.below(COLORS.len());
|
||||||
let alpha = (self.rng.below(5) * 63) as u8;
|
let alpha = (self.rng.below(5) * 63) as u8;
|
||||||
@@ -651,7 +652,7 @@ impl Sow<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn align(&mut self) -> Aligns {
|
fn align(&mut self) -> Align {
|
||||||
let axis = |s: &mut Self| match s.rng.below(4) {
|
let axis = |s: &mut Self| match s.rng.below(4) {
|
||||||
0 => None,
|
0 => None,
|
||||||
1 => Some(AxisAlign::NEG),
|
1 => Some(AxisAlign::NEG),
|
||||||
@@ -661,15 +662,21 @@ impl Sow<'_> {
|
|||||||
let (x, y) = (axis(self), axis(self));
|
let (x, y) = (axis(self), axis(self));
|
||||||
// Aligning on neither axis leaves the branch unexercised.
|
// Aligning on neither axis leaves the branch unexercised.
|
||||||
match x.is_none() && y.is_none() {
|
match x.is_none() && y.is_none() {
|
||||||
true => [Some(AxisAlign::CENTER), y],
|
true => Align {
|
||||||
false => [x, y],
|
x: Some(AxisAlign::CENTER),
|
||||||
|
y,
|
||||||
|
},
|
||||||
|
false => Align { x, y },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A declared size over half the tree, kept where a test can change it.
|
/// A declared size over half the tree, kept where a test can change it.
|
||||||
fn sized(&mut self, inner: &mut Plan) {
|
fn sized(&mut self, inner: &mut Plan) {
|
||||||
let take = self.rng.chance();
|
let take = self.rng.chance();
|
||||||
let lens = [self.len(), self.len()];
|
let lens = SizeRules {
|
||||||
|
x: self.len().into(),
|
||||||
|
y: self.len().into(),
|
||||||
|
};
|
||||||
if !take || inner.size.is_some() {
|
if !take || inner.size.is_some() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -773,10 +780,6 @@ impl Sow<'_> {
|
|||||||
self.spans += 1;
|
self.spans += 1;
|
||||||
let edit = self.edits.spans.get(&idx).cloned().unwrap_or_default();
|
let edit = self.edits.spans.get(&idx).cloned().unwrap_or_default();
|
||||||
let dir = self.rng.below(4);
|
let dir = self.rng.below(4);
|
||||||
// A row takes the height it is given rather than its tallest child,
|
|
||||||
// which is a rule beside it. Derived from an existing choice and
|
|
||||||
// consuming no randomness: a seed must keep growing the same tree
|
|
||||||
// when the generator gains another configuration.
|
|
||||||
let gap = self.rng.below(3) as i32 * 4;
|
let gap = self.rng.below(3) as i32 * 4;
|
||||||
let grown: Vec<usize> = (0..children.len()).collect();
|
let grown: Vec<usize> = (0..children.len()).collect();
|
||||||
let order = span_edited(&grown, children.len(), spares.len(), &edit);
|
let order = span_edited(&grown, children.len(), spares.len(), &edit);
|
||||||
@@ -796,6 +799,7 @@ pub fn build<Rsc: UiRsc + 'static>(rsc: &mut Rsc, plan: &Plan) -> (StrongWidget,
|
|||||||
let mut build = Build {
|
let mut build = Build {
|
||||||
rsc,
|
rsc,
|
||||||
tree: Tree::default(),
|
tree: Tree::default(),
|
||||||
|
checkerboard: None,
|
||||||
};
|
};
|
||||||
let root = build.node(plan);
|
let root = build.node(plan);
|
||||||
(root, build.tree)
|
(root, build.tree)
|
||||||
@@ -804,6 +808,10 @@ pub fn build<Rsc: UiRsc + 'static>(rsc: &mut Rsc, plan: &Plan) -> (StrongWidget,
|
|||||||
struct Build<'a, Rsc> {
|
struct Build<'a, Rsc> {
|
||||||
rsc: &'a mut Rsc,
|
rsc: &'a mut Rsc,
|
||||||
tree: Tree,
|
tree: Tree,
|
||||||
|
/// The checkerboard, uploaded when the first image in this tree is built.
|
||||||
|
/// A handle is a reference to the texture, so every image after that one
|
||||||
|
/// clones this rather than uploading the same picture again.
|
||||||
|
checkerboard: Option<TextureHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
||||||
@@ -811,16 +819,14 @@ impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
|||||||
let built = self.kind(&plan.kind);
|
let built = self.kind(&plan.kind);
|
||||||
let id = built.id();
|
let id = built.id();
|
||||||
if let Some(lens) = plan.size {
|
if let Some(lens) = plan.size {
|
||||||
self.rsc
|
self.rsc.ui_mut().widgets.set_size_rules(id, lens.x, lens.y);
|
||||||
.ui_mut()
|
|
||||||
.widgets
|
|
||||||
.set_size_rules(id, lens[0], lens[1]);
|
|
||||||
self.tree.sized.push(id);
|
self.tree.sized.push(id);
|
||||||
}
|
}
|
||||||
if let Some(align) = plan.align {
|
if let Some(align) = plan.align {
|
||||||
|
let resolved = RegionAlign::from(align);
|
||||||
let widgets = &mut self.rsc.ui_mut().widgets;
|
let widgets = &mut self.rsc.ui_mut().widgets;
|
||||||
for (axis, align) in [Axis::X, Axis::Y].into_iter().zip(align) {
|
for axis in Axis::BOTH {
|
||||||
widgets.set_alignment(id, axis, align.unwrap_or_default());
|
widgets.set_alignment(id, axis, resolved[axis]);
|
||||||
}
|
}
|
||||||
self.tree.aligned.push(id);
|
self.tree.aligned.push(id);
|
||||||
}
|
}
|
||||||
@@ -831,6 +837,20 @@ impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
|||||||
built
|
built
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The one picture the generated trees draw: a 64x64 checkerboard of purple
|
||||||
|
/// and black in 8 px cells. Committed rather than drawn here, so that one
|
||||||
|
/// seed is one tree whatever anything else does, and included rather than
|
||||||
|
/// opened, so that growing a tree does not depend on a working directory.
|
||||||
|
fn checkerboard(&mut self) -> TextureHandle {
|
||||||
|
if self.checkerboard.is_none() {
|
||||||
|
let image = include_bytes!("assets/checkerboard.png")
|
||||||
|
.get_image()
|
||||||
|
.expect("the checkerboard is committed beside this file");
|
||||||
|
self.checkerboard = Some(self.rsc.ui_mut().textures.add(image));
|
||||||
|
}
|
||||||
|
self.checkerboard.clone().unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
fn kind(&mut self, kind: &Kind) -> StrongWidget {
|
fn kind(&mut self, kind: &Kind) -> StrongWidget {
|
||||||
let id: StrongWidget = match kind {
|
let id: StrongWidget = match kind {
|
||||||
Kind::Wrapped => wtext(WORDS).size(16).wrap(true).add_strong(self.rsc),
|
Kind::Wrapped => wtext(WORDS).size(16).wrap(true).add_strong(self.rsc),
|
||||||
@@ -839,6 +859,7 @@ impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
|||||||
.wrap(false)
|
.wrap(false)
|
||||||
.add_strong(self.rsc),
|
.add_strong(self.rsc),
|
||||||
Kind::Rect { color, alpha } => rect(COLORS[*color].alpha(*alpha)).add_strong(self.rsc),
|
Kind::Rect { color, alpha } => rect(COLORS[*color].alpha(*alpha)).add_strong(self.rsc),
|
||||||
|
Kind::Image => Image::new(self.checkerboard()).add_strong(self.rsc),
|
||||||
Kind::Scroll { axis, inner } => {
|
Kind::Scroll { axis, inner } => {
|
||||||
let inner = self.node(inner);
|
let inner = self.node(inner);
|
||||||
let id = Scroll::new(inner, *axis).add(self.rsc);
|
let id = Scroll::new(inner, *axis).add(self.rsc);
|
||||||
@@ -915,6 +936,10 @@ impl<Rsc: UiRsc + 'static> Build<'_, Rsc> {
|
|||||||
gap: Px::from_int(*gap),
|
gap: Px::from_int(*gap),
|
||||||
}
|
}
|
||||||
.add(self.rsc);
|
.add(self.rsc);
|
||||||
|
// A row takes the height it is given rather than its tallest
|
||||||
|
// child, which is a rule beside the span rather than anything
|
||||||
|
// it draws. Derived from `dir` rather than stored, so a plan
|
||||||
|
// that says the direction says this too.
|
||||||
if dir.axis == Axis::X {
|
if dir.axis == Axis::X {
|
||||||
self.rsc
|
self.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
|
|||||||
@@ -16,6 +16,15 @@ impl Widget for Image {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
/// One texture already uploaded, for a caller holding its handle: [`image`]
|
||||||
|
/// uploads what it is given, and several widgets showing one picture want
|
||||||
|
/// one upload and one slot between them.
|
||||||
|
pub fn new(handle: TextureHandle) -> Self {
|
||||||
|
Self { handle }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
||||||
let image = image.get_image().expect("Failed to load image");
|
let image = image.get_image().expect("Failed to load image");
|
||||||
move |state| Image {
|
move |state| Image {
|
||||||
|
|||||||
@@ -24,36 +24,37 @@ impl Widget for Scroll {
|
|||||||
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()[self.axis];
|
// Reading the box in pixels above holds this drawing to that one
|
||||||
// Content of a fixed length that fits sits at the start of any box it
|
// length, so these two say where it holds more widely.
|
||||||
// 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
|
// Content of a fixed length that fits is handed the whole box below,
|
||||||
// the drawing holds for that length alone. One scrolled part way sits
|
// and nothing here reads the box again, so every longer box gives the
|
||||||
// where it is until the box shrinks past what is left of it. Kept to
|
// same drawing: it holds from the length the content needs upwards,
|
||||||
// the end, it moves with every length.
|
// and shrinking past that is what changes it. Where it sits in a box
|
||||||
|
// longer than itself is not this widget's to say -- placing its
|
||||||
|
// answer in the whole box is its own alignment, and that placement is
|
||||||
|
// a fraction of the box, so it holds at every length too.
|
||||||
|
//
|
||||||
|
// One scrolled part way sits where it is until the box shrinks past
|
||||||
|
// what is left of it. Kept to the end, it moves with every length.
|
||||||
let answer_is_px = answer_len.is_px();
|
let answer_is_px = answer_len.is_px();
|
||||||
if answer_is_px && self.content_len <= self.container_len && align == AxisAlign::NEG {
|
if answer_is_px && self.content_len <= self.container_len {
|
||||||
painter.holds(self.axis, answer_px..=Px::MAX);
|
painter.holds(self.axis, answer_px..=Px::MAX);
|
||||||
} else if answer_is_px && !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);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Content shorter than the viewport has room to sit in, and where it
|
// Content that fills the viewport is the viewport, and is handed back
|
||||||
// sits is this widget's own alignment -- the same property that would
|
// as it came -- it has nothing to scroll through, so the clamp above
|
||||||
// have placed the whole scroll in a box longer than it.
|
// has already put `amt` at zero. Writing the same box as its own
|
||||||
let slack = (self.container_len - self.content_len).max(Px::ZERO);
|
// length in pixels is the same box in another form, and the two do
|
||||||
let anchor = slack.mul(align.rel());
|
// not round alike: a part centred in `rel 1` lands a step from one
|
||||||
// Content that fills the viewport and has not been scrolled is the
|
// centred in `px 900`, since halving a difference is not halving each
|
||||||
// viewport, and is handed back as it came. Writing the same box as
|
// part of it.
|
||||||
// its own length in pixels is the same box in another form, and the
|
let content = match self.content_len > self.container_len {
|
||||||
// two do not round alike: a part centred in `rel 1` lands a step from
|
|
||||||
// one centred in `px 900`, since halving a difference is not halving
|
|
||||||
// each part of it.
|
|
||||||
let moved = anchor != Px::ZERO || self.amt != Px::ZERO;
|
|
||||||
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, -self.amt);
|
||||||
UiSpan::new(start, start.offset(self.content_len)).shifted_desc()
|
UiSpan::new(start, start.offset(self.content_len)).shifted_desc()
|
||||||
}
|
}
|
||||||
false => PlaceDescAxis::WHOLE,
|
false => PlaceDescAxis::WHOLE,
|
||||||
|
|||||||
@@ -52,25 +52,15 @@ 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 rel base rather than a number of pixels.
|
// fixed, as a length of the rel base rather than a number of pixels.
|
||||||
let room = row - total.without_leftover();
|
let all_fixed = total.without_leftover();
|
||||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
let room = row - all_fixed;
|
||||||
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
// The three cases a rounded division needed -- the fixed parts
|
||||||
// itself, and answered back through the same expression, so the
|
|
||||||
// boundary is the drawing's own and not a second way of finding it:
|
|
||||||
// the three cases a rounded division needed -- the fixed parts
|
|
||||||
// growing slower than the box, faster, or exactly with it -- are the
|
// growing slower than the box, faster, or exactly with it -- are the
|
||||||
// sign of `room.rel`, which `through` already reads. What the
|
// sign of `room.rel`, which the range `longer_than` keeps already
|
||||||
// generated oracle checks is the consequence, since which children
|
// reads. What the generated oracle checks is the consequence, since
|
||||||
// exist at all turns on this.
|
// which children exist at all turns on this.
|
||||||
let any_leftover = total.leftover > Weight::ZERO;
|
let any_leftover = total.leftover > Weight::ZERO;
|
||||||
let has_room = any_leftover && painter.to_px(room, axis) > Px::ZERO;
|
let has_room = any_leftover && painter.longer_than(row, all_fixed, axis);
|
||||||
if any_leftover {
|
|
||||||
let holds = match has_room {
|
|
||||||
true => Holds::from(Px::STEP..=Px::MAX),
|
|
||||||
false => Holds::from(Px::MIN..=Px::ZERO),
|
|
||||||
};
|
|
||||||
painter.window_holds(axis, holds.through(room));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Across itself a span is as long as its longest child -- unless a
|
// Across itself a span is as long as its longest child -- unless a
|
||||||
// rule beside it gives that length outright, and then reading them
|
// rule beside it gives that length outright, and then reading them
|
||||||
|
|||||||
@@ -321,12 +321,10 @@ impl<'a> TextEditCtx<'a> {
|
|||||||
let old = (self.text.view.buf.text().to_string(), self.text.selection);
|
let old = (self.text.view.buf.text().to_string(), self.text.selection);
|
||||||
let mut undo = false;
|
let mut undo = false;
|
||||||
let res = self.apply_event_inner(event, modifiers, &mut undo);
|
let res = self.apply_event_inner(event, modifiers, &mut undo);
|
||||||
if undo {
|
if undo && let Some((old, selection)) = self.text.history.pop() {
|
||||||
if let Some((old, selection)) = self.text.history.pop() {
|
self.set(&old);
|
||||||
self.set(&old);
|
self.text.selection = selection;
|
||||||
self.text.selection = selection;
|
self.clamp_selection_to_layout();
|
||||||
self.clamp_selection_to_layout();
|
|
||||||
}
|
|
||||||
} else if self.text.view.buf.text() != old.0 {
|
} else if self.text.view.buf.text() != old.0 {
|
||||||
self.text.history.push(old);
|
self.text.history.push(old);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ widget_trait! {
|
|||||||
move |state| {
|
move |state| {
|
||||||
let id = self.add(state);
|
let id = self.add(state);
|
||||||
let widgets = &mut state.ui_mut().widgets;
|
let widgets = &mut state.ui_mut().widgets;
|
||||||
for (axis, align) in [(Axis::X, align.x), (Axis::Y, align.y)] {
|
for axis in Axis::BOTH {
|
||||||
if let Some(align) = align {
|
if let Some(align) = align[axis] {
|
||||||
widgets.set_alignment(id, axis, align);
|
widgets.set_alignment(id, axis, align);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-11
@@ -9,6 +9,7 @@ use std::marker::Unsize;
|
|||||||
///
|
///
|
||||||
/// Its child is optional so it can also be the swappable slot a tab bar
|
/// Its child is optional so it can also be the swappable slot a tab bar
|
||||||
/// needs, which is what it was written for.
|
/// needs, which is what it was written for.
|
||||||
|
#[derive(Default)]
|
||||||
pub struct Wrapper {
|
pub struct Wrapper {
|
||||||
pub inner: Option<StrongWidget>,
|
pub inner: Option<StrongWidget>,
|
||||||
}
|
}
|
||||||
@@ -26,11 +27,7 @@ impl Wrapper {
|
|||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
pub fn empty() -> Self {
|
|
||||||
Self {
|
|
||||||
inner: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: StrongWidget<W>) {
|
pub fn set<W: ?Sized + Unsize<dyn Widget>>(&mut self, to: StrongWidget<W>) {
|
||||||
self.inner = Some(to)
|
self.inner = Some(to)
|
||||||
}
|
}
|
||||||
@@ -42,9 +39,3 @@ impl Wrapper {
|
|||||||
self.inner.replace(to)
|
self.inner.replace(to)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Wrapper {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -69,8 +69,8 @@ fn a_branch_taken_on_a_measurement_holds_across_repaints() {
|
|||||||
assert_ne!(first, (false, false), "threshold {threshold}: neither drew");
|
assert_ne!(first, (false, false), "threshold {threshold}: neither drew");
|
||||||
|
|
||||||
for frame in 0..4 {
|
for frame in 0..4 {
|
||||||
h.rsc.widgets_mut().get_dyn_mut(wide);
|
h.rsc.widgets_mut().mark_for_redraw(wide);
|
||||||
h.rsc.widgets_mut().get_dyn_mut(narrow);
|
h.rsc.widgets_mut().mark_for_redraw(narrow);
|
||||||
h.frame();
|
h.frame();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
taken(&h, wide, narrow),
|
taken(&h, wide, narrow),
|
||||||
@@ -88,7 +88,7 @@ fn a_branch_taken_on_a_measurement_is_the_one_a_cold_start_takes() {
|
|||||||
let (wide, narrow) = plant(&mut warm, threshold);
|
let (wide, narrow) = plant(&mut warm, threshold);
|
||||||
warm.resize((640, 480));
|
warm.resize((640, 480));
|
||||||
warm.frame();
|
warm.frame();
|
||||||
warm.rsc.widgets_mut().get_dyn_mut(wide);
|
warm.rsc.widgets_mut().mark_for_redraw(wide);
|
||||||
warm.frame();
|
warm.frame();
|
||||||
|
|
||||||
let mut cold = Harness::new((640, 480));
|
let mut cold = Harness::new((640, 480));
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ fn a_wrapping_text_in_a_span_settles_on_one_width() {
|
|||||||
let r = h.region(&t.id()).unwrap();
|
let r = h.region(&t.id()).unwrap();
|
||||||
widths.push(r.bot_right.x - r.top_left.x);
|
widths.push(r.bot_right.x - r.top_left.x);
|
||||||
// Redrawing it changes nothing about the state, so nothing may move.
|
// Redrawing it changes nothing about the state, so nothing may move.
|
||||||
h.rsc.widgets_mut().get_dyn_mut(t.id());
|
h.rsc.widgets_mut().mark_for_redraw(t.id());
|
||||||
h.frame();
|
h.frame();
|
||||||
}
|
}
|
||||||
println!("widths over six frames: {widths:?}");
|
println!("widths over six frames: {widths:?}");
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
//! Where a frame puts things, with no window to put them in.
|
//! Where a frame puts things, with no window to put them in.
|
||||||
|
|
||||||
|
use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
use iris::harness::{Harness, assert_corners};
|
use iris::harness::{Harness, assert_corners};
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
|
||||||
@@ -219,6 +221,105 @@ fn an_empty_widget_takes_a_share_of_a_span() {
|
|||||||
assert_corners!(h, right, (300, 0), (400, 200));
|
assert_corners!(h, right, (300, 0), (400, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A widget with a natural pixel size, like an image, which records the box
|
||||||
|
/// it was asked in so a test can see which length decided it.
|
||||||
|
struct NaturalSize {
|
||||||
|
len: f32,
|
||||||
|
asked: Rc<Cell<f32>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for NaturalSize {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
self.asked.set(painter.px_len(Axis::X).to_f32());
|
||||||
|
Size::px(Vec2::new(self.len, self.len))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self, _: Axis) -> Option<LayoutLen> {
|
||||||
|
Some(LayoutLen::px(self.len))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A rule wins over what the widget says about itself, and a share is a rule:
|
||||||
|
/// it is a length only to whoever divides one, and nobody here does, so the
|
||||||
|
/// widget is asked in the whole box rather than in the size it asked for.
|
||||||
|
#[test]
|
||||||
|
fn a_share_rule_beats_the_widgets_own_pixel_size() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let asked = Rc::new(Cell::new(0.0));
|
||||||
|
let natural = NaturalSize {
|
||||||
|
len: 50.0,
|
||||||
|
asked: asked.clone(),
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(natural.wrapper());
|
||||||
|
assert_eq!(asked.get(), 50.0, "its hint gives it its own size");
|
||||||
|
|
||||||
|
h.set_len(natural, Axis::X, LayoutLen::LEFTOVER);
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
assert_eq!(asked.get(), 400.0, "the share is all of the box");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A share with pixels or a fraction beside it is the longer of the two: it
|
||||||
|
/// fills what they leave of the box and overflows the box where they are
|
||||||
|
/// longer than it. A parent that divides nothing gives the same length as a
|
||||||
|
/// span with one child, because in both there is nobody else to divide with.
|
||||||
|
#[test]
|
||||||
|
fn a_share_is_a_minimum_wherever_nothing_divides_it() {
|
||||||
|
let asked = |rule: LayoutLen, in_a_span: bool| {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let probe = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
h.set_len(probe, Axis::X, rule);
|
||||||
|
match in_a_span {
|
||||||
|
true => h.set_root((probe,).span(Dir::RIGHT)),
|
||||||
|
false => h.set_root(probe.wrapper()),
|
||||||
|
}
|
||||||
|
h.region(&probe).unwrap().size().x
|
||||||
|
};
|
||||||
|
for (rule, want) in [
|
||||||
|
(LayoutLen::LEFTOVER, 400),
|
||||||
|
(LayoutLen::px(50) + LayoutLen::LEFTOVER, 400),
|
||||||
|
(LayoutLen::px(500) + LayoutLen::LEFTOVER, 500),
|
||||||
|
(LayoutLen::rel(0.5) + LayoutLen::LEFTOVER, 400),
|
||||||
|
(LayoutLen::px(500), 500),
|
||||||
|
] {
|
||||||
|
let want = Px::from_int(want);
|
||||||
|
assert_eq!(asked(rule, false), want, "{rule:?} where nothing divides");
|
||||||
|
assert_eq!(asked(rule, true), want, "{rule:?} in a span");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Which of the two is longer is a question in pixels, so the box is decided
|
||||||
|
/// again wherever the answer can change: a window that crosses the length the
|
||||||
|
/// pixels ask for, and the rule itself crossing it while the window holds
|
||||||
|
/// still. The first is a range the drawing holds for; the second cannot be
|
||||||
|
/// seen in what the widget declares, since a share declares nothing either
|
||||||
|
/// way, so it reaches the parent as a length only the parent can resolve.
|
||||||
|
#[test]
|
||||||
|
fn a_share_past_the_box_is_decided_again_on_either_side_of_the_crossing() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let probe = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
h.set_len(probe, Axis::X, LayoutLen::px(500) + LayoutLen::LEFTOVER);
|
||||||
|
h.set_root(probe.wrapper());
|
||||||
|
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(500));
|
||||||
|
|
||||||
|
h.resize((900, 200));
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(900));
|
||||||
|
|
||||||
|
h.resize((400, 200));
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(500));
|
||||||
|
|
||||||
|
h.set_len(probe, Axis::X, LayoutLen::px(50) + LayoutLen::LEFTOVER);
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(400));
|
||||||
|
|
||||||
|
h.set_len(probe, Axis::X, LayoutLen::px(500) + LayoutLen::LEFTOVER);
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(h.region(&probe).unwrap().size().x, Px::from_int(500));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_child_drawn_twice_moves_once() {
|
fn a_child_drawn_twice_moves_once() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
|
|||||||
+44
-9
@@ -5,7 +5,9 @@
|
|||||||
//! and the oracle another. And reducing a plan has to end, or a shrinker
|
//! and the oracle another. And reducing a plan has to end, or a shrinker
|
||||||
//! searching for the smallest counterexample never returns.
|
//! searching for the smallest counterexample never returns.
|
||||||
|
|
||||||
use iris::random::{Edits, Kind, Plan, Rng, SpanEdit, plan};
|
use iris::harness::Harness;
|
||||||
|
use iris::prelude::*;
|
||||||
|
use iris::random::{Edits, Kind, Plan, Rng, SpanEdit, grow, plan};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn some_edits(seed: u64, of: &Plan) -> Edits {
|
fn some_edits(seed: u64, of: &Plan) -> Edits {
|
||||||
@@ -25,11 +27,27 @@ fn some_edits(seed: u64, of: &Plan) -> Edits {
|
|||||||
Edits {
|
Edits {
|
||||||
sizes: pick(sized, &mut rng)
|
sizes: pick(sized, &mut rng)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|i| (i, [Some(LayoutLen::LEFTOVER), None]))
|
.map(|i| {
|
||||||
|
(
|
||||||
|
i,
|
||||||
|
SizeRules {
|
||||||
|
x: SizeRule::Exact(LayoutLen::LEFTOVER),
|
||||||
|
y: SizeRule::Free,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
aligns: pick(aligned, &mut rng)
|
aligns: pick(aligned, &mut rng)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|i| (i, [Some(AxisAlign::POS), None]))
|
.map(|i| {
|
||||||
|
(
|
||||||
|
i,
|
||||||
|
Align {
|
||||||
|
x: Some(AxisAlign::POS),
|
||||||
|
y: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
nodes: pick(nodes, &mut rng)
|
nodes: pick(nodes, &mut rng)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@@ -51,8 +69,6 @@ fn some_edits(seed: u64, of: &Plan) -> Edits {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use iris::prelude::*;
|
|
||||||
|
|
||||||
/// The two routes to an edited tree are one tree. `plan` resolves edits out
|
/// The two routes to an edited tree are one tree. `plan` resolves edits out
|
||||||
/// of the random stream as it draws; `edited` puts them on a tree that
|
/// of the random stream as it draws; `edited` puts them on a tree that
|
||||||
/// already exists, which is the only route a shrunk plan has, since no seed
|
/// already exists, which is the only route a shrunk plan has, since no seed
|
||||||
@@ -70,11 +86,13 @@ fn editing_a_plan_is_growing_one_with_those_edits() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every simplification is strictly smaller, so taking them in turn reaches a
|
/// No simplification is larger, which is the half of "the shrinker stops" a
|
||||||
/// fixed point instead of circling. A shrinker that can return to a tree it
|
/// widget count can see. Most are not smaller either -- a dropped alignment
|
||||||
/// has already tried does not stop.
|
/// and a simpler leaf both keep the count -- so what rules out circling is
|
||||||
|
/// that those are one-way too: a `Some` becomes a `None`, and a kind steps
|
||||||
|
/// down a ladder with no way back up.
|
||||||
#[test]
|
#[test]
|
||||||
fn every_simplification_of_a_plan_is_smaller_than_it() {
|
fn no_simplification_of_a_plan_is_larger_than_it() {
|
||||||
for seed in 1..=60 {
|
for seed in 1..=60 {
|
||||||
let tree = plan(seed, 4, &Edits::default());
|
let tree = plan(seed, 4, &Edits::default());
|
||||||
let mut queue = vec![tree];
|
let mut queue = vec![tree];
|
||||||
@@ -119,3 +137,20 @@ fn reducing_a_plan_all_the_way_ends() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Every image in a tree is the same picture, and a handle is a reference to
|
||||||
|
/// the texture rather than a copy of it, so one upload and one slot serve all
|
||||||
|
/// of them however many a tree grows -- and the trees are grown in hundreds.
|
||||||
|
#[test]
|
||||||
|
fn a_tree_of_images_uploads_one_texture() {
|
||||||
|
let mut images = 0;
|
||||||
|
let mut tree = plan(1, 4, &Edits::default());
|
||||||
|
tree.walk_mut(&mut |p| images += (p.kind == Kind::Image) as usize);
|
||||||
|
assert!(images > 1, "a tree of {images} images tests nothing");
|
||||||
|
|
||||||
|
let mut h = Harness::new((900, 1200));
|
||||||
|
let (root, _) = grow(&mut h.rsc, 1, 4, &Edits::default());
|
||||||
|
h.state.root = Some(root);
|
||||||
|
h.frame();
|
||||||
|
assert_eq!(h.rsc.ui().textures.count(), 1);
|
||||||
|
}
|
||||||
+29
-39
@@ -45,7 +45,6 @@ fn counted(h: &mut Harness, size: Size, reads_box: bool) -> (WeakWidget<Counted>
|
|||||||
|
|
||||||
struct Layered {
|
struct Layered {
|
||||||
children: [StrongWidget<Rect>; 2],
|
children: [StrongWidget<Rect>; 2],
|
||||||
_revision: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Layered {
|
impl Widget for Layered {
|
||||||
@@ -65,14 +64,10 @@ fn a_redrawn_layered_widget_keeps_the_layer_it_was_entered_on() {
|
|||||||
rect(Color::RED).add_strong(&mut h.rsc),
|
rect(Color::RED).add_strong(&mut h.rsc),
|
||||||
rect(Color::BLUE).add_strong(&mut h.rsc),
|
rect(Color::BLUE).add_strong(&mut h.rsc),
|
||||||
];
|
];
|
||||||
let root = Layered {
|
let root = Layered { children }.add(&mut h.rsc);
|
||||||
children,
|
|
||||||
_revision: 0,
|
|
||||||
}
|
|
||||||
.add(&mut h.rsc);
|
|
||||||
h.set_root(root);
|
h.set_root(root);
|
||||||
|
|
||||||
h.rsc[root]._revision += 1;
|
h.rsc.widgets_mut().mark_for_redraw(root.id());
|
||||||
h.frame();
|
h.frame();
|
||||||
|
|
||||||
let label = h.rsc.widgets().label(root.id());
|
let label = h.rsc.widgets().label(root.id());
|
||||||
@@ -181,9 +176,9 @@ fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
|||||||
h.set_root((first, second).span(Dir::RIGHT));
|
h.set_root((first, second).span(Dir::RIGHT));
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
|
|
||||||
// Taking mutable access is the ordinary content-change signal. This
|
// Marked with nothing about it changed, and it reports the same size
|
||||||
// widget returns the same size, so the parent has nothing to lay out.
|
// either way, so the parent has nothing to lay out.
|
||||||
let _ = h.rsc.widgets_mut().get_dyn_mut(first.id());
|
h.rsc.widgets_mut().mark_for_redraw(first.id());
|
||||||
h.frame();
|
h.frame();
|
||||||
|
|
||||||
assert_eq!(draws.get(), settled + 1);
|
assert_eq!(draws.get(), settled + 1);
|
||||||
@@ -198,7 +193,7 @@ fn a_span_child_survives_the_next_frame() {
|
|||||||
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
let bottom = rect(Color::BLUE).height(120).add(&mut h.rsc);
|
||||||
h.set_root((top, bottom).span(Dir::DOWN));
|
h.set_root((top, bottom).span(Dir::DOWN));
|
||||||
|
|
||||||
h.rsc.widgets_mut().get_dyn_mut(top.id());
|
h.rsc.widgets_mut().mark_for_redraw(top.id());
|
||||||
h.frame();
|
h.frame();
|
||||||
|
|
||||||
assert_corners!(h, top, (0, 0), (400, 80));
|
assert_corners!(h, top, (0, 0), (400, 80));
|
||||||
@@ -660,7 +655,7 @@ fn a_masked_widget_redrawn_on_its_own_sets_its_mask_again() {
|
|||||||
let masked = inner.masked().add(&mut h.rsc);
|
let masked = inner.masked().add(&mut h.rsc);
|
||||||
let other = rect(Color::RED).width(100).add(&mut h.rsc);
|
let other = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
h.set_root((other, masked).span(Dir::RIGHT));
|
h.set_root((other, masked).span(Dir::RIGHT));
|
||||||
h.rsc.widgets_mut().get_dyn_mut(masked.id());
|
h.rsc.widgets_mut().mark_for_redraw(masked.id());
|
||||||
h.frame();
|
h.frame();
|
||||||
assert_corners!(h, inner, (100, 0), (400, 200));
|
assert_corners!(h, inner, (100, 0), (400, 200));
|
||||||
}
|
}
|
||||||
@@ -774,6 +769,17 @@ fn a_subtree_that_changed_parents_settles_at_the_depth_it_moved_to() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Where a mask slot clips, in window pixels: the region it holds, carried
|
||||||
|
/// through whatever move entry it hangs from. Taken by slot rather than by
|
||||||
|
/// widget, so a test can name the slot it expects a redraw to keep.
|
||||||
|
fn mask_bounds(h: &Harness, mask: MaskIdx) -> PixelRegion {
|
||||||
|
let mask = &h.rsc.ui().masks[mask.idx()];
|
||||||
|
h.render
|
||||||
|
.moves
|
||||||
|
.resolve(mask.move_idx, mask.region)
|
||||||
|
.to_px(h.render.output_size())
|
||||||
|
}
|
||||||
|
|
||||||
fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec<PixelRegion> {
|
fn primitive_bounds(h: &Harness, id: WidgetId) -> Vec<PixelRegion> {
|
||||||
h.render.active[&id]
|
h.render.active[&id]
|
||||||
.primitives
|
.primitives
|
||||||
@@ -919,15 +925,10 @@ fn resizing_a_fixed_frame_recomposes_its_contents_without_drawing_them() {
|
|||||||
primitive_bounds(&warm, leaf.id()),
|
primitive_bounds(&warm, leaf.id()),
|
||||||
primitive_bounds(&cold, other.id())
|
primitive_bounds(&cold, other.id())
|
||||||
);
|
);
|
||||||
let mask = |h: &Harness, id: WidgetId| {
|
assert_eq!(
|
||||||
let active = &h.render.active[&id];
|
mask_bounds(&warm, warm.render.active[&leaf.id()].mask),
|
||||||
let mask = &h.rsc.ui().masks[active.mask.idx()];
|
mask_bounds(&cold, cold.render.active[&other.id()].mask)
|
||||||
h.render
|
);
|
||||||
.moves
|
|
||||||
.resolve(mask.move_idx, mask.region)
|
|
||||||
.to_px(h.render.output_size())
|
|
||||||
};
|
|
||||||
assert_eq!(mask(&warm, leaf.id()), mask(&cold, other.id()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -994,7 +995,7 @@ fn glyph_origins_compose_identically_when_drawn_and_when_retained() {
|
|||||||
assert_eq!(draws.get(), before);
|
assert_eq!(draws.get(), before);
|
||||||
let retained = primitive_bounds(&h, text.id());
|
let retained = primitive_bounds(&h, text.id());
|
||||||
assert!(!retained.is_empty());
|
assert!(!retained.is_empty());
|
||||||
let _ = h.rsc.widgets_mut().get_dyn_mut(text.id());
|
h.rsc.widgets_mut().mark_for_redraw(text.id());
|
||||||
h.frame();
|
h.frame();
|
||||||
assert!(draws.get() > before);
|
assert!(draws.get() > before);
|
||||||
assert_eq!(retained, primitive_bounds(&h, text.id()));
|
assert_eq!(retained, primitive_bounds(&h, text.id()));
|
||||||
@@ -1184,15 +1185,10 @@ fn padding_and_stack_boxes_follow_the_region_without_drawing_again() {
|
|||||||
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));
|
||||||
}
|
}
|
||||||
let mask = |h: &Harness, id: WidgetId| {
|
assert_eq!(
|
||||||
let active = &h.render.active[&id];
|
mask_bounds(&warm, warm.render.active[&leaf.id()].mask),
|
||||||
let mask = &h.rsc.ui().masks[active.mask.idx()];
|
mask_bounds(&cold, cold.render.active[&other.id()].mask)
|
||||||
h.render
|
);
|
||||||
.moves
|
|
||||||
.resolve(mask.move_idx, mask.region)
|
|
||||||
.to_px(h.render.output_size())
|
|
||||||
};
|
|
||||||
assert_eq!(mask(&warm, leaf.id()), mask(&cold, other.id()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1374,20 +1370,14 @@ fn a_redrawn_mask_keeps_reused_primitives_clipped_when_it_moves() {
|
|||||||
h.set_root((first, masked).span(Dir::DOWN));
|
h.set_root((first, masked).span(Dir::DOWN));
|
||||||
let mask = h.render.active[&masked.id()].mask;
|
let mask = h.render.active[&masked.id()].mask;
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
h.rsc.widgets_mut().get_dyn_mut(masked.id());
|
h.rsc.widgets_mut().mark_for_redraw(masked.id());
|
||||||
h.frame();
|
h.frame();
|
||||||
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
|
assert_eq!(primitive_masks(&h, inner.id()), vec![mask]);
|
||||||
assert_eq!(draws.get(), settled, "a mask repaint must reuse its child");
|
assert_eq!(draws.get(), settled, "a mask repaint must reuse its child");
|
||||||
assert_eq!(h.render.active[&masked.id()].mask, mask);
|
assert_eq!(h.render.active[&masked.id()].mask, mask);
|
||||||
h.set_len(first, Axis::Y, 10);
|
h.set_len(first, Axis::Y, 10);
|
||||||
h.frame();
|
h.frame();
|
||||||
let clip = h.rsc.ui().masks[mask.idx()];
|
assert_eq!(mask_bounds(&h, mask), h.region(&masked).unwrap());
|
||||||
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));
|
assert_corners!(h, inner, (0, 10), (400, 200));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ fn wrapping_content_beside_a_fixed_length_is_stable_warm_and_cold() {
|
|||||||
|
|
||||||
let mut warm = Harness::new((900, 300));
|
let mut warm = Harness::new((900, 300));
|
||||||
let (text, content) = plant(&mut warm);
|
let (text, content) = plant(&mut warm);
|
||||||
warm.rsc.widgets_mut().get_dyn_mut(text);
|
warm.rsc.widgets_mut().mark_for_redraw(text);
|
||||||
warm.frame();
|
warm.frame();
|
||||||
|
|
||||||
let mut cold = Harness::new((900, 300));
|
let mut cold = Harness::new((900, 300));
|
||||||
@@ -142,8 +142,8 @@ fn a_clipping_widget_reporting_more_than_its_box_is_caught() {
|
|||||||
let mut h = Harness::new((100, 100));
|
let mut h = Harness::new((100, 100));
|
||||||
let tall = rect(Color::RED).height(400).add_strong(&mut h.rsc);
|
let tall = rect(Color::RED).height(400).add_strong(&mut h.rsc);
|
||||||
let clipper = Clipper(tall).add(&mut h.rsc);
|
let clipper = Clipper(tall).add(&mut h.rsc);
|
||||||
|
// `set_root` lays the tree out, so this is where it is caught.
|
||||||
h.set_root(clipper);
|
h.set_root(clipper);
|
||||||
h.frame();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Content that fits sits in the viewport, not in a box of the window's
|
/// Content that fits sits in the viewport, not in a box of the window's
|
||||||
|
|||||||
+59
-102
@@ -9,17 +9,45 @@
|
|||||||
//! reached through a region node's own entry rather than through the offer
|
//! reached through a region node's own entry rather than through the offer
|
||||||
//! that node was given. The last is a wrapping text handed back the width
|
//! that node was given. The last is a wrapping text handed back the width
|
||||||
//! it measured, rounded to a step below the line it measured there.
|
//! it measured, rounded to a step below the line it measured there.
|
||||||
|
//!
|
||||||
|
//! Each says which seed it was shrunk from, of the generator as it stood when
|
||||||
|
//! it was found. Those numbers no longer grow those trees -- a seed names one
|
||||||
|
//! only while the generator draws the same things in the same order, and the
|
||||||
|
//! leaves have grown an image since -- so what is written out below is the
|
||||||
|
//! record of the case, and the seed is where it came from.
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use iris::harness::Harness;
|
use iris::harness::Harness;
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use iris::random::Branch;
|
use iris::random::Branch;
|
||||||
|
|
||||||
|
/// Every widget in the same place warm as cold, reported all at once: which
|
||||||
|
/// of a dozen boxes moved is the whole of what a shrunk case has to say.
|
||||||
|
///
|
||||||
|
/// A list that names one widget twice is an error rather than a redundant
|
||||||
|
/// check. `width`, `sized` and `align` give back the widget they were handed,
|
||||||
|
/// so a fixture built through them can name one text three times, and then a
|
||||||
|
/// case comparing six boxes compares four and says nothing about it. One
|
||||||
|
/// fixture builds both lists, so checking the warm one checks both.
|
||||||
|
#[track_caller]
|
||||||
fn assert_same_regions(
|
fn assert_same_regions(
|
||||||
warm: &Harness,
|
warm: &Harness,
|
||||||
warm_ids: &[WidgetId],
|
warm_ids: &[WidgetId],
|
||||||
cold: &Harness,
|
cold: &Harness,
|
||||||
cold_ids: &[WidgetId],
|
cold_ids: &[WidgetId],
|
||||||
) {
|
) {
|
||||||
|
assert_eq!(
|
||||||
|
warm_ids.len(),
|
||||||
|
cold_ids.len(),
|
||||||
|
"the warm and cold fixtures list different widgets"
|
||||||
|
);
|
||||||
|
let named: HashSet<&WidgetId> = warm_ids.iter().collect();
|
||||||
|
assert_eq!(
|
||||||
|
named.len(),
|
||||||
|
warm_ids.len(),
|
||||||
|
"a widget is listed twice: {warm_ids:?}"
|
||||||
|
);
|
||||||
let mut wrong = Vec::new();
|
let mut wrong = Vec::new();
|
||||||
for (i, (&w, &c)) in warm_ids.iter().zip(cold_ids).enumerate() {
|
for (i, (&w, &c)) in warm_ids.iter().zip(cold_ids).enumerate() {
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
let (got, want) = (warm.region(&w), cold.region(&c));
|
||||||
@@ -92,7 +120,7 @@ fn repainting_a_stack_uses_the_box_its_sizing_child_decided() {
|
|||||||
let mut warm = Harness::new((900, 1200));
|
let mut warm = Harness::new((900, 1200));
|
||||||
let ids = plant_stack_in_its_sizing_childs_box(&mut warm);
|
let ids = plant_stack_in_its_sizing_childs_box(&mut warm);
|
||||||
for &id in &ids {
|
for &id in &ids {
|
||||||
warm.rsc.widgets_mut().get_dyn_mut(id);
|
warm.rsc.widgets_mut().mark_for_redraw(id);
|
||||||
}
|
}
|
||||||
warm.frame();
|
warm.frame();
|
||||||
|
|
||||||
@@ -188,36 +216,31 @@ fn reordering_nested_spans_keeps_the_answer_from_the_decided_box() {
|
|||||||
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Six widgets, shrunk from a 402-widget tree the fuzzer found. Nothing about
|
/// Four widgets, shrunk from a 402-widget tree the fuzzer found. Nothing about
|
||||||
/// the tree changes -- every widget is marked for redraw and the frame is
|
/// the tree changes -- every widget is marked for redraw and the frame is
|
||||||
/// taken again -- so no box may move, and a warm frame has to land where a
|
/// taken again -- so no box may move, and a warm frame has to land where a
|
||||||
/// cold one does.
|
/// cold one does.
|
||||||
fn plant(h: &mut Harness) -> Vec<WidgetId> {
|
fn plant(h: &mut Harness) -> Vec<WidgetId> {
|
||||||
let plain = wtext("Wrapping").size(16).wrap(false).add(&mut h.rsc);
|
let plain = wtext("Wrapping").size(16).wrap(false).add(&mut h.rsc);
|
||||||
let wrapped = wtext("Wrapping shapes").size(16).wrap(true).add(&mut h.rsc);
|
let wrapped = wtext("Wrapping shapes")
|
||||||
let sized = wrapped.width(76).add(&mut h.rsc);
|
.size(16)
|
||||||
let aligned = sized;
|
.wrap(true)
|
||||||
|
.width(76)
|
||||||
|
.add(&mut h.rsc);
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(sized, Axis::X, AxisAlign::POS);
|
.set_alignment(wrapped, Axis::X, AxisAlign::POS);
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(sized, Axis::Y, AxisAlign::POS);
|
.set_alignment(wrapped, Axis::Y, AxisAlign::POS);
|
||||||
let stack = Stack {
|
let stack = Stack {
|
||||||
children: vec![plain.add_strong(&mut h.rsc), aligned.add_strong(&mut h.rsc)],
|
children: vec![plain.add_strong(&mut h.rsc), wrapped.add_strong(&mut h.rsc)],
|
||||||
size: StackSize::Child(0),
|
size: StackSize::Child(0),
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
let root = (stack,).span(Dir::RIGHT).add(&mut h.rsc);
|
let root = (stack,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
h.set_root(root);
|
h.set_root(root);
|
||||||
vec![
|
vec![plain.id(), wrapped.id(), stack.id(), root.id()]
|
||||||
plain.id(),
|
|
||||||
wrapped.id(),
|
|
||||||
sized.id(),
|
|
||||||
aligned.id(),
|
|
||||||
stack.id(),
|
|
||||||
root.id(),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The first frame does not reach the layout a second one does, so "cold" is
|
/// The first frame does not reach the layout a second one does, so "cold" is
|
||||||
@@ -230,7 +253,7 @@ fn one_frame_is_enough() {
|
|||||||
let first = h.region(&ids[1]).unwrap();
|
let first = h.region(&ids[1]).unwrap();
|
||||||
for _ in 0..3 {
|
for _ in 0..3 {
|
||||||
for &id in &ids {
|
for &id in &ids {
|
||||||
h.rsc.widgets_mut().get_dyn_mut(id);
|
h.rsc.widgets_mut().mark_for_redraw(id);
|
||||||
}
|
}
|
||||||
h.frame();
|
h.frame();
|
||||||
}
|
}
|
||||||
@@ -252,46 +275,30 @@ fn repainting_everything_moves_nothing() {
|
|||||||
let mut warm = Harness::new((640, 900));
|
let mut warm = Harness::new((640, 900));
|
||||||
let ids = plant(&mut warm);
|
let ids = plant(&mut warm);
|
||||||
for &id in &ids {
|
for &id in &ids {
|
||||||
warm.rsc.widgets_mut().get_dyn_mut(id);
|
warm.rsc.widgets_mut().mark_for_redraw(id);
|
||||||
}
|
}
|
||||||
warm.frame();
|
warm.frame();
|
||||||
|
|
||||||
let mut cold = Harness::new((640, 900));
|
let mut cold = Harness::new((640, 900));
|
||||||
let cold_ids = plant(&mut cold);
|
let cold_ids = plant(&mut cold);
|
||||||
|
|
||||||
let mut wrong = Vec::new();
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
|
||||||
if got != want {
|
|
||||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Six widgets, shrunk from 905. Everything inside the declared 189x176 box
|
/// Four widgets, shrunk from 905. Everything inside the declared 189x176 box
|
||||||
/// is the same size whatever the output is, so a resize may not change any of
|
/// is the same size whatever the output is, so a resize may not change any of
|
||||||
/// it -- but the text comes out 3.92px narrower warm than cold.
|
/// it -- but the text comes out 3.92px narrower warm than cold.
|
||||||
fn plant_fixed(h: &mut Harness) -> Vec<WidgetId> {
|
fn plant_fixed(h: &mut Harness) -> Vec<WidgetId> {
|
||||||
let words = "Wrapping shapes one source into as many lines as the box leaves";
|
let words = "Wrapping shapes one source into as many lines as the box leaves";
|
||||||
let text = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
let text = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
||||||
let aligned = text;
|
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(text, Axis::X, AxisAlign::NEG);
|
.set_alignment(text, Axis::X, AxisAlign::NEG);
|
||||||
let inner = (aligned,).span(Dir::RIGHT).add(&mut h.rsc);
|
let inner = (text,).span(Dir::RIGHT).sized((189, 176)).add(&mut h.rsc);
|
||||||
let sized = inner.sized((189, 176)).add(&mut h.rsc);
|
|
||||||
let filler = rect(Color::RED).add(&mut h.rsc);
|
let filler = rect(Color::RED).add(&mut h.rsc);
|
||||||
let root = (filler, sized).span(Dir::RIGHT).add(&mut h.rsc);
|
let root = (filler, inner).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
h.state.root = Some(root.add_strong(&mut h.rsc));
|
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||||
vec![
|
vec![text.id(), inner.id(), filler.id(), root.id()]
|
||||||
text.id(),
|
|
||||||
aligned.id(),
|
|
||||||
inner.id(),
|
|
||||||
sized.id(),
|
|
||||||
filler.id(),
|
|
||||||
root.id(),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -306,17 +313,10 @@ fn a_resize_does_not_reach_inside_a_box_of_declared_pixels() {
|
|||||||
let cold_ids = plant_fixed(&mut cold);
|
let cold_ids = plant_fixed(&mut cold);
|
||||||
cold.frame();
|
cold.frame();
|
||||||
|
|
||||||
let mut wrong = Vec::new();
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
|
||||||
if got != want {
|
|
||||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Four widgets, shrunk from 486. A span's two children are swapped: warm by
|
/// Three widgets, shrunk from 486. A span's two children are swapped: warm by
|
||||||
/// moving them, cold by growing them that way. Same widgets, same sizes, one
|
/// moving them, cold by growing them that way. Same widgets, same sizes, one
|
||||||
/// ends up 29.9px from where the other does.
|
/// ends up 29.9px from where the other does.
|
||||||
fn plant_pair(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, WeakWidget<Span>) {
|
fn plant_pair(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, WeakWidget<Span>) {
|
||||||
@@ -340,16 +340,11 @@ fn plant_pair(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, WeakWidget<Span
|
|||||||
gap: Px::ZERO,
|
gap: Px::ZERO,
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
let span_handle = span;
|
|
||||||
let aligned = span;
|
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(span, Axis::X, AxisAlign::CENTER);
|
.set_alignment(span, Axis::X, AxisAlign::CENTER);
|
||||||
h.state.root = Some(aligned.add_strong(&mut h.rsc));
|
h.state.root = Some(span.add_strong(&mut h.rsc));
|
||||||
(
|
(vec![wrapped.id(), plain.id(), span.id()], span)
|
||||||
vec![wrapped.id(), plain.id(), span.id(), aligned.id()],
|
|
||||||
span_handle,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -364,17 +359,10 @@ fn swapping_two_children_lands_where_growing_them_that_way_does() {
|
|||||||
let (cold_ids, _) = plant_pair(&mut cold, true);
|
let (cold_ids, _) = plant_pair(&mut cold, true);
|
||||||
cold.frame();
|
cold.frame();
|
||||||
|
|
||||||
let mut wrong = Vec::new();
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
|
||||||
if got != want {
|
|
||||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Eight widgets, shrunk from 80. The scroll decides how wide to make its
|
/// Seven widgets, shrunk from 80. The scroll decides how wide to make its
|
||||||
/// content from what the content says, and hands that box down through a
|
/// content from what the content says, and hands that box down through a
|
||||||
/// pass-through; the span under it was given that box once, so nothing at its
|
/// pass-through; the span under it was given that box once, so nothing at its
|
||||||
/// own edge says the box was its own answer.
|
/// own edge says the box was its own answer.
|
||||||
@@ -393,8 +381,7 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
|||||||
gap: Px::ZERO,
|
gap: Px::ZERO,
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
let block = rect(Color::RED).add(&mut h.rsc);
|
let fixed = rect(Color::RED).width(87).add(&mut h.rsc);
|
||||||
let fixed = block.width(87).add(&mut h.rsc);
|
|
||||||
let mut outer_children: Vec<StrongWidget> =
|
let mut outer_children: Vec<StrongWidget> =
|
||||||
vec![fixed.add_strong(&mut h.rsc), inner.add_strong(&mut h.rsc)];
|
vec![fixed.add_strong(&mut h.rsc), inner.add_strong(&mut h.rsc)];
|
||||||
if swapped {
|
if swapped {
|
||||||
@@ -416,7 +403,6 @@ fn plant_scrolled(h: &mut Harness, swapped: bool) -> (Vec<WidgetId>, [WeakWidget
|
|||||||
text.id(),
|
text.id(),
|
||||||
filler.id(),
|
filler.id(),
|
||||||
inner.id(),
|
inner.id(),
|
||||||
block.id(),
|
|
||||||
fixed.id(),
|
fixed.id(),
|
||||||
outer.id(),
|
outer.id(),
|
||||||
through.id(),
|
through.id(),
|
||||||
@@ -440,14 +426,7 @@ fn a_span_given_the_box_its_answer_decided_matches_a_cold_layout() {
|
|||||||
let (cold_ids, _) = plant_scrolled(&mut cold, true);
|
let (cold_ids, _) = plant_scrolled(&mut cold, true);
|
||||||
cold.frame();
|
cold.frame();
|
||||||
|
|
||||||
let mut wrong = Vec::new();
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
|
||||||
if got != want {
|
|
||||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reports a width derived from the box it is asked in. Reading through the
|
/// Reports a width derived from the box it is asked in. Reading through the
|
||||||
@@ -471,11 +450,10 @@ impl Widget for Wider {
|
|||||||
fn plant_wider(h: &mut Harness, extra: f32) -> (WeakWidget<Wider>, WidgetId) {
|
fn plant_wider(h: &mut Harness, extra: f32) -> (WeakWidget<Wider>, WidgetId) {
|
||||||
let content = Wider { extra }.add(&mut h.rsc);
|
let content = Wider { extra }.add(&mut h.rsc);
|
||||||
let scroll = Scroll::new(content.add_strong(&mut h.rsc), Axis::X).add(&mut h.rsc);
|
let scroll = Scroll::new(content.add_strong(&mut h.rsc), Axis::X).add(&mut h.rsc);
|
||||||
let root = scroll;
|
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(scroll, Axis::X, AxisAlign::NEG);
|
.set_alignment(scroll, Axis::X, AxisAlign::NEG);
|
||||||
h.set_root(root);
|
h.set_root(scroll);
|
||||||
(content, scroll.id())
|
(content, scroll.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,14 +545,7 @@ fn a_box_that_only_rounds_past_its_fixed_children_leaves_nothing_over() {
|
|||||||
let (cold_ids, _) = plant_boundary(&mut cold, true);
|
let (cold_ids, _) = plant_boundary(&mut cold, true);
|
||||||
cold.frame();
|
cold.frame();
|
||||||
|
|
||||||
let mut wrong = Vec::new();
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
|
||||||
if got != want {
|
|
||||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Five widgets, shrunk by `tests/shrink.rs` from the 277 the oracle's seed
|
/// Five widgets, shrunk by `tests/shrink.rs` from the 277 the oracle's seed
|
||||||
@@ -616,20 +587,13 @@ fn plant_nested_scrolls(h: &mut Harness) -> Vec<WidgetId> {
|
|||||||
fn redrawing_one_widget_does_not_move_what_scrolls_around_it() {
|
fn redrawing_one_widget_does_not_move_what_scrolls_around_it() {
|
||||||
let mut warm = Harness::new((900, 1200));
|
let mut warm = Harness::new((900, 1200));
|
||||||
let ids = plant_nested_scrolls(&mut warm);
|
let ids = plant_nested_scrolls(&mut warm);
|
||||||
warm.rsc.widgets_mut().get_dyn_mut(ids[0]);
|
warm.rsc.widgets_mut().mark_for_redraw(ids[0]);
|
||||||
warm.frame();
|
warm.frame();
|
||||||
|
|
||||||
let mut cold = Harness::new((900, 1200));
|
let mut cold = Harness::new((900, 1200));
|
||||||
let cold_ids = plant_nested_scrolls(&mut cold);
|
let cold_ids = plant_nested_scrolls(&mut cold);
|
||||||
|
|
||||||
let mut wrong = Vec::new();
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
|
||||||
if got != want {
|
|
||||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ten widgets, of the shape `tests/shrink.rs` reduces the oracle's seed 220
|
/// Ten widgets, of the shape `tests/shrink.rs` reduces the oracle's seed 220
|
||||||
@@ -720,14 +684,7 @@ fn a_widget_under_a_region_node_is_asked_in_the_box_that_node_was_offered() {
|
|||||||
let (cold_ids, _) = plant_under_a_node(&mut cold, true);
|
let (cold_ids, _) = plant_under_a_node(&mut cold, true);
|
||||||
cold.frame();
|
cold.frame();
|
||||||
|
|
||||||
let mut wrong = Vec::new();
|
assert_same_regions(&warm, &ids, &cold, &cold_ids);
|
||||||
for (i, (&w, &c)) in ids.iter().zip(&cold_ids).enumerate() {
|
|
||||||
let (got, want) = (warm.region(&w), cold.region(&c));
|
|
||||||
if got != want {
|
|
||||||
wrong.push(format!("widget {i}: warm {got:?} cold {want:?}"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PARAGRAPH: &str = "Wrapping shapes one source into as many lines as the \
|
const PARAGRAPH: &str = "Wrapping shapes one source into as many lines as the \
|
||||||
|
|||||||
+5
-29
@@ -11,8 +11,6 @@
|
|||||||
//! The instances are two pixels wide so that vertex work dominates; a chain
|
//! The instances are two pixels wide so that vertex work dominates; a chain
|
||||||
//! walk that does not show up against small quads will not show up against
|
//! walk that does not show up against small quads will not show up against
|
||||||
//! anything.
|
//! anything.
|
||||||
//!
|
|
||||||
//! The instance is leaked deliberately, for the reason `draw_cost.rs` gives.
|
|
||||||
|
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use iris_core::{
|
use iris_core::{
|
||||||
@@ -21,6 +19,9 @@ use iris_core::{
|
|||||||
};
|
};
|
||||||
use wgpu::{Color as GpuColor, *};
|
use wgpu::{Color as GpuColor, *};
|
||||||
|
|
||||||
|
#[path = "gpu/mod.rs"]
|
||||||
|
mod gpu;
|
||||||
|
|
||||||
const SIZE: u32 = 1024;
|
const SIZE: u32 = 1024;
|
||||||
const INSTANCES: usize = 200_000;
|
const INSTANCES: usize = 200_000;
|
||||||
const FRAMES: u32 = 20;
|
const FRAMES: u32 = 20;
|
||||||
@@ -29,18 +30,7 @@ const FRAMES: u32 = 20;
|
|||||||
const BATCHES: u32 = 8;
|
const BATCHES: u32 = 8;
|
||||||
|
|
||||||
fn gpu() -> Option<(Device, Queue, f32)> {
|
fn gpu() -> Option<(Device, Queue, f32)> {
|
||||||
let all = Instance::new(InstanceDescriptor::new_without_display_handle());
|
let adapter = gpu::adapter()?;
|
||||||
let instance = match pollster::block_on(all.request_adapter(&RequestAdapterOptions::default()))
|
|
||||||
{
|
|
||||||
Ok(_) => all,
|
|
||||||
Err(_) => Instance::new(InstanceDescriptor {
|
|
||||||
backends: Backends::GL,
|
|
||||||
..InstanceDescriptor::new_without_display_handle()
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
let instance: &'static Instance = Box::leak(Box::new(instance));
|
|
||||||
let adapter =
|
|
||||||
pollster::block_on(instance.request_adapter(&RequestAdapterOptions::default())).ok()?;
|
|
||||||
if !adapter.features().contains(Features::TIMESTAMP_QUERY) {
|
if !adapter.features().contains(Features::TIMESTAMP_QUERY) {
|
||||||
println!("no timestamp queries on {:?}", adapter.get_info().name);
|
println!("no timestamp queries on {:?}", adapter.get_info().name);
|
||||||
return None;
|
return None;
|
||||||
@@ -55,20 +45,6 @@ fn gpu() -> Option<(Device, Queue, f32)> {
|
|||||||
Some((device, queue, period))
|
Some((device, queue, period))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config(format: TextureFormat) -> SurfaceConfiguration {
|
|
||||||
SurfaceConfiguration {
|
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
format,
|
|
||||||
color_space: SurfaceColorSpace::Auto,
|
|
||||||
width: SIZE,
|
|
||||||
height: SIZE,
|
|
||||||
present_mode: PresentMode::Fifo,
|
|
||||||
desired_maximum_frame_latency: 2,
|
|
||||||
alpha_mode: CompositeAlphaMode::Auto,
|
|
||||||
view_formats: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A chain `depth` slots long, and instances that all resolve through its end.
|
/// A chain `depth` slots long, and instances that all resolve through its end.
|
||||||
fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
|
fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
|
||||||
let kind = ui.primitives.kind::<RectPrimitive>();
|
let kind = ui.primitives.kind::<RectPrimitive>();
|
||||||
@@ -103,7 +79,7 @@ fn fill(ui: &mut UiData, render: &mut UiRenderState, depth: usize) {
|
|||||||
/// Nanoseconds the pass took on the GPU, best of `BATCHES`.
|
/// Nanoseconds the pass took on the GPU, best of `BATCHES`.
|
||||||
fn pass_cost(device: &Device, queue: &Queue, period: f32, depth: usize) -> f64 {
|
fn pass_cost(device: &Device, queue: &Queue, period: f32, depth: usize) -> f64 {
|
||||||
let format = TextureFormat::Bgra8Unorm;
|
let format = TextureFormat::Bgra8Unorm;
|
||||||
let mut node = UiRenderNode::new(device, &config(format));
|
let mut node = UiRenderNode::new(device, &gpu::config(format, SIZE));
|
||||||
let mut ui = UiData::default();
|
let mut ui = UiData::default();
|
||||||
let mut render = UiRenderState::new();
|
let mut render = UiRenderState::new();
|
||||||
fill(&mut ui, &mut render, depth);
|
fill(&mut ui, &mut render, depth);
|
||||||
|
|||||||
+5
-34
@@ -13,10 +13,6 @@
|
|||||||
//! That is how `PrimitiveRender` was measured against a match in the renderer:
|
//! That is how `PrimitiveRender` was measured against a match in the renderer:
|
||||||
//! 6 instructions per list drawn, against the ~5,400 wgpu spends recording
|
//! 6 instructions per list drawn, against the ~5,400 wgpu spends recording
|
||||||
//! one.
|
//! one.
|
||||||
//!
|
|
||||||
//! The instance is leaked deliberately. A Vulkan loader may unload the driver
|
|
||||||
//! when the last one drops, which can fault as a thread that used it exits --
|
|
||||||
//! and every test runs on a spawned thread.
|
|
||||||
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
@@ -27,6 +23,9 @@ use iris_core::{
|
|||||||
};
|
};
|
||||||
use wgpu::{Color as GpuColor, *};
|
use wgpu::{Color as GpuColor, *};
|
||||||
|
|
||||||
|
#[path = "gpu/mod.rs"]
|
||||||
|
mod gpu;
|
||||||
|
|
||||||
const SIZE: u32 = 1024;
|
const SIZE: u32 = 1024;
|
||||||
const FRAMES: u32 = 200;
|
const FRAMES: u32 = 200;
|
||||||
/// Reported as the best of this many batches, since the mean moves by more
|
/// Reported as the best of this many batches, since the mean moves by more
|
||||||
@@ -34,39 +33,11 @@ const FRAMES: u32 = 200;
|
|||||||
const BATCHES: u32 = 8;
|
const BATCHES: u32 = 8;
|
||||||
|
|
||||||
fn gpu() -> Option<(Device, Queue)> {
|
fn gpu() -> Option<(Device, Queue)> {
|
||||||
// Probed rather than assumed: there may be no Vulkan adapter, and GL is
|
let adapter = gpu::adapter()?;
|
||||||
// what is left when there is not.
|
|
||||||
let all = Instance::new(InstanceDescriptor::new_without_display_handle());
|
|
||||||
let instance = match pollster::block_on(all.request_adapter(&RequestAdapterOptions::default()))
|
|
||||||
{
|
|
||||||
Ok(_) => all,
|
|
||||||
Err(_) => Instance::new(InstanceDescriptor {
|
|
||||||
backends: Backends::GL,
|
|
||||||
..InstanceDescriptor::new_without_display_handle()
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
// Leaked rather than dropped: see the note at the top of the file.
|
|
||||||
let instance: &'static Instance = Box::leak(Box::new(instance));
|
|
||||||
let adapter =
|
|
||||||
pollster::block_on(instance.request_adapter(&RequestAdapterOptions::default())).ok()?;
|
|
||||||
println!("adapter: {:?}", adapter.get_info());
|
println!("adapter: {:?}", adapter.get_info());
|
||||||
pollster::block_on(adapter.request_device(&DeviceDescriptor::default())).ok()
|
pollster::block_on(adapter.request_device(&DeviceDescriptor::default())).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config(format: TextureFormat) -> SurfaceConfiguration {
|
|
||||||
SurfaceConfiguration {
|
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
format,
|
|
||||||
color_space: SurfaceColorSpace::Auto,
|
|
||||||
width: SIZE,
|
|
||||||
height: SIZE,
|
|
||||||
present_mode: PresentMode::Fifo,
|
|
||||||
desired_maximum_frame_latency: 2,
|
|
||||||
alpha_mode: CompositeAlphaMode::Auto,
|
|
||||||
view_formats: vec![],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Every layer draws all three primitives, so the renderer takes a different
|
/// Every layer draws all three primitives, so the renderer takes a different
|
||||||
/// path for each list it walks -- which is the case a single-primitive layer
|
/// path for each list it walks -- which is the case a single-primitive layer
|
||||||
/// would never exercise. Images are bound per instance, so there are few.
|
/// would never exercise. Images are bound per instance, so there are few.
|
||||||
@@ -136,7 +107,7 @@ fn fill(
|
|||||||
|
|
||||||
fn frame_cost(device: &Device, queue: &Queue, layers: usize, per_layer: usize) -> f64 {
|
fn frame_cost(device: &Device, queue: &Queue, layers: usize, per_layer: usize) -> f64 {
|
||||||
let format = TextureFormat::Bgra8Unorm;
|
let format = TextureFormat::Bgra8Unorm;
|
||||||
let mut node = UiRenderNode::new(device, &config(format));
|
let mut node = UiRenderNode::new(device, &gpu::config(format, SIZE));
|
||||||
let mut ui = UiData::default();
|
let mut ui = UiData::default();
|
||||||
let mut render = UiRenderState::new();
|
let mut render = UiRenderState::new();
|
||||||
let _handles = fill(&mut ui, &mut render, layers, per_layer);
|
let _handles = fill(&mut ui, &mut render, layers, per_layer);
|
||||||
|
|||||||
+9
-5
@@ -25,10 +25,14 @@ fn depth() -> usize {
|
|||||||
env("IRIS_GENERATED_DEPTH", 4)
|
env("IRIS_GENERATED_DEPTH", 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The seeds the ordinary tests take. Seven that have never failed; 86,
|
/// The seeds the ordinary tests take: a corpus rather than a set of
|
||||||
/// which a `Scroll` fixed point once settled differently on; and 20, which
|
/// regression cases, since a seed names a tree only for as long as the
|
||||||
/// caught a locally redrawn widget being placed twice in the box its parent
|
/// generator draws the same things in the same order. Adding images to the
|
||||||
/// had already placed it in.
|
/// leaves moved every one of them, so 20 and 86 -- which once caught a widget
|
||||||
|
/// placed twice in a box its parent had already placed it in, and a `Scroll`
|
||||||
|
/// fixed point settling differently -- no longer grow those trees. Both
|
||||||
|
/// defects are pinned by the shrunk fixtures in `cases/unsettled.rs`, which
|
||||||
|
/// are trees rather than numbers.
|
||||||
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) {
|
||||||
@@ -104,7 +108,7 @@ fn adding_and_removing_span_children_lands_where_growing_it_that_way_would() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "as many seeds as it is asked for, rather than the nine the others check"]
|
#[ignore = "as many seeds as it is asked for, rather than the ten the others check"]
|
||||||
fn a_long_run_of_seeds_agrees() {
|
fn a_long_run_of_seeds_agrees() {
|
||||||
let depth = depth();
|
let depth = depth();
|
||||||
let seeds: Vec<u64> = match std::env::var("IRIS_GENERATED_SEED")
|
let seeds: Vec<u64> = match std::env::var("IRIS_GENERATED_SEED")
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//! The adapter and the surface configuration the GPU measurement rigs share,
|
||||||
|
//! so the two cannot probe for a device in two different ways.
|
||||||
|
|
||||||
|
use wgpu::*;
|
||||||
|
|
||||||
|
/// An adapter on whatever this machine has, or `None` where there is none.
|
||||||
|
///
|
||||||
|
/// Probed rather than assumed: there may be no Vulkan adapter, and GL is what
|
||||||
|
/// is left when there is not.
|
||||||
|
///
|
||||||
|
/// The instance is leaked deliberately. A Vulkan loader may unload the driver
|
||||||
|
/// when the last one drops, which can fault as a thread that used it exits --
|
||||||
|
/// and every test runs on a spawned thread.
|
||||||
|
pub fn adapter() -> Option<Adapter> {
|
||||||
|
let all = Instance::new(InstanceDescriptor::new_without_display_handle());
|
||||||
|
let instance = match pollster::block_on(all.request_adapter(&RequestAdapterOptions::default()))
|
||||||
|
{
|
||||||
|
Ok(_) => all,
|
||||||
|
Err(_) => Instance::new(InstanceDescriptor {
|
||||||
|
backends: Backends::GL,
|
||||||
|
..InstanceDescriptor::new_without_display_handle()
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let instance: &'static Instance = Box::leak(Box::new(instance));
|
||||||
|
pollster::block_on(instance.request_adapter(&RequestAdapterOptions::default())).ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config(format: TextureFormat, size: u32) -> SurfaceConfiguration {
|
||||||
|
SurfaceConfiguration {
|
||||||
|
usage: TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format,
|
||||||
|
color_space: SurfaceColorSpace::Auto,
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
present_mode: PresentMode::Fifo,
|
||||||
|
desired_maximum_frame_latency: 2,
|
||||||
|
alpha_mode: CompositeAlphaMode::Auto,
|
||||||
|
view_formats: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,6 +22,30 @@ use std::time::Instant;
|
|||||||
|
|
||||||
const OUTPUT: (f32, f32) = (1920.0, 1200.0);
|
const OUTPUT: (f32, f32) = (1920.0, 1200.0);
|
||||||
|
|
||||||
|
/// A scroll whose content fits is the same drawing in every box it still
|
||||||
|
/// fits in, so a longer or shorter one relays out nothing. Where the content
|
||||||
|
/// sits in that box is decided by placing its answer in the whole of it,
|
||||||
|
/// which is a fraction of the box and holds at every length -- so the
|
||||||
|
/// contract must not turn on the alignment. It did, and at the default
|
||||||
|
/// alignment, which is the middle, every box change redrew the scroll.
|
||||||
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
|
#[test]
|
||||||
|
fn a_fitting_scroll_holds_for_every_box_its_content_fits_in() {
|
||||||
|
use iris::core::layout_diagnostics as diag;
|
||||||
|
|
||||||
|
for align in [Align::TOP_LEFT, Align::CENTER, Align::BOT_RIGHT] {
|
||||||
|
let mut harness = Harness::new((400, 200));
|
||||||
|
let inner = rect(Color::RED).height(50).add(&mut harness.rsc);
|
||||||
|
harness.set_root(inner.scrollable().align(align));
|
||||||
|
harness.frame();
|
||||||
|
let _ = diag::take();
|
||||||
|
// Still far longer than the 50 the content needs.
|
||||||
|
harness.resize((400, 180));
|
||||||
|
harness.frame();
|
||||||
|
assert_eq!(diag::take().distinct_widgets(), 0, "{align:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
#[test]
|
#[test]
|
||||||
fn a_selected_widget_retains_its_layout_events() {
|
fn a_selected_widget_retains_its_layout_events() {
|
||||||
@@ -37,8 +61,8 @@ fn a_selected_widget_retains_its_layout_events() {
|
|||||||
diagnostics::trace_widget(leaf.id());
|
diagnostics::trace_widget(leaf.id());
|
||||||
let _ = diagnostics::take();
|
let _ = diagnostics::take();
|
||||||
|
|
||||||
let _ = harness.rsc.widgets_mut().get_dyn_mut(root.id());
|
harness.rsc.widgets_mut().mark_for_redraw(root.id());
|
||||||
let _ = harness.rsc.widgets_mut().get_dyn_mut(leaf.id());
|
harness.rsc.widgets_mut().mark_for_redraw(leaf.id());
|
||||||
harness.frame();
|
harness.frame();
|
||||||
|
|
||||||
let report = diagnostics::take();
|
let report = diagnostics::take();
|
||||||
@@ -195,7 +219,6 @@ fn layout_cost() {
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
let _ = iris::core::layout_diagnostics::take();
|
let _ = iris::core::layout_diagnostics::take();
|
||||||
run("cold", 1, &mut harness, |_, _| {});
|
run("cold", 1, &mut harness, |_, _| {});
|
||||||
drop(tree);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if selected("repaint") {
|
if selected("repaint") {
|
||||||
@@ -203,7 +226,7 @@ fn layout_cost() {
|
|||||||
trace_selected(&tree);
|
trace_selected(&tree);
|
||||||
let leaf = tree.ids[0];
|
let leaf = tree.ids[0];
|
||||||
run("repaint", frames, &mut harness, move |harness, _| {
|
run("repaint", frames, &mut harness, move |harness, _| {
|
||||||
let _ = harness.rsc.widgets_mut().get_dyn_mut(leaf);
|
harness.rsc.widgets_mut().mark_for_redraw(leaf);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +241,7 @@ fn layout_cost() {
|
|||||||
println!("marking {} of {} widgets", dirty.len(), tree.ids.len());
|
println!("marking {} of {} widgets", dirty.len(), tree.ids.len());
|
||||||
run("many", frames, &mut harness, move |harness, _| {
|
run("many", frames, &mut harness, move |harness, _| {
|
||||||
for &id in &dirty {
|
for &id in &dirty {
|
||||||
harness.rsc.widgets_mut().get_dyn_mut(id);
|
harness.rsc.widgets_mut().mark_for_redraw(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -251,6 +274,5 @@ fn layout_cost() {
|
|||||||
run("resize", frames, &mut harness, |harness, frame| {
|
run("resize", frames, &mut harness, |harness, frame| {
|
||||||
harness.resize((OUTPUT.0 - ((frame + 1) % 2) as f32 * 8.0, OUTPUT.1));
|
harness.resize((OUTPUT.0 - ((frame + 1) % 2) as f32 * 8.0, OUTPUT.1));
|
||||||
});
|
});
|
||||||
drop(tree);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +194,9 @@ fn text_memory() {
|
|||||||
h.frame();
|
h.frame();
|
||||||
}
|
}
|
||||||
report("after 40 resizes");
|
report("after 40 resizes");
|
||||||
// Settled: the output holds still and one leaf repaints per frame.
|
// Settled: the output holds still and one leaf repaints per frame. Marked
|
||||||
|
// by taking it mutably because the revision at the top of this file has no
|
||||||
|
// `mark_for_redraw`, and the same source has to build against both.
|
||||||
for _ in 0..10 {
|
for _ in 0..10 {
|
||||||
let _ = h.rsc.widgets_mut().get_dyn_mut(paragraphs[0]);
|
let _ = h.rsc.widgets_mut().get_dyn_mut(paragraphs[0]);
|
||||||
h.frame();
|
h.frame();
|
||||||
|
|||||||
+16
-11
@@ -13,7 +13,7 @@
|
|||||||
|
|
||||||
use iris::harness::Harness;
|
use iris::harness::Harness;
|
||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
use iris::random::{Aligns, Edits, Kind, Lens, Plan, Rng, SpanEdit, Tree, build};
|
use iris::random::{Edits, Kind, Plan, Rng, SpanEdit, Tree, build};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// A seed per thread but one, since a seed grows, lays out and drops its tree
|
/// A seed per thread but one, since a seed grows, lays out and drops its tree
|
||||||
@@ -190,7 +190,7 @@ impl Case {
|
|||||||
|
|
||||||
fn mark(warm: &mut Harness, tree: &Tree, step: usize) {
|
fn mark(warm: &mut Harness, tree: &Tree, step: usize) {
|
||||||
for &id in tree.ids.iter().step_by(step) {
|
for &id in tree.ids.iter().step_by(step) {
|
||||||
warm.rsc.widgets_mut().get_dyn_mut(id);
|
warm.rsc.widgets_mut().mark_for_redraw(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -198,27 +198,32 @@ fn a_len(rng: &mut Rng) -> Option<LayoutLen> {
|
|||||||
Some(LayoutLen::px(20.0 + rng.below(180) as f32))
|
Some(LayoutLen::px(20.0 + rng.below(180) as f32))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Lens {
|
fn resize_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> SizeRules {
|
||||||
let lens = [a_len(rng), a_len(rng)];
|
let lens = SizeRules {
|
||||||
|
x: a_len(rng).into(),
|
||||||
|
y: a_len(rng).into(),
|
||||||
|
};
|
||||||
warm.rsc
|
warm.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_size_rules(tree.sized[idx], lens[0], lens[1]);
|
.set_size_rules(tree.sized[idx], lens.x, lens.y);
|
||||||
lens
|
lens
|
||||||
}
|
}
|
||||||
|
|
||||||
fn realign_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Aligns {
|
fn realign_one(warm: &mut Harness, tree: &Tree, idx: usize, rng: &mut Rng) -> Align {
|
||||||
let side = |rng: &mut Rng| match rng.below(4) {
|
let side = |rng: &mut Rng| match rng.below(4) {
|
||||||
0 => None,
|
0 => None,
|
||||||
1 => Some(AxisAlign::NEG),
|
1 => Some(AxisAlign::NEG),
|
||||||
2 => Some(AxisAlign::CENTER),
|
2 => Some(AxisAlign::CENTER),
|
||||||
_ => Some(AxisAlign::POS),
|
_ => Some(AxisAlign::POS),
|
||||||
};
|
};
|
||||||
let align = [side(rng), side(rng)];
|
let align = Align {
|
||||||
|
x: side(rng),
|
||||||
|
y: side(rng),
|
||||||
|
};
|
||||||
let id = tree.aligned[idx];
|
let id = tree.aligned[idx];
|
||||||
for (axis, align) in [Axis::X, Axis::Y].into_iter().zip(align) {
|
let taken = RegionAlign::from(align);
|
||||||
warm.rsc
|
for axis in Axis::BOTH {
|
||||||
.widgets_mut()
|
warm.rsc.widgets_mut().set_alignment(id, axis, taken[axis]);
|
||||||
.set_alignment(id, axis, align.unwrap_or_default());
|
|
||||||
}
|
}
|
||||||
align
|
align
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-29
@@ -1,5 +1,5 @@
|
|||||||
//! Traces the six-widget tree in `unsettled.rs`, to see what box its text is
|
//! Traces the four-widget trees in `unsettled.rs`, to see what box their text
|
||||||
//! actually drawn in on a first frame against a settled one.
|
//! is actually drawn in on a first frame against a settled one.
|
||||||
|
|
||||||
#![cfg(feature = "layout-diagnostics")]
|
#![cfg(feature = "layout-diagnostics")]
|
||||||
|
|
||||||
@@ -9,30 +9,25 @@ use iris::prelude::*;
|
|||||||
|
|
||||||
fn plant(h: &mut Harness) -> Vec<WidgetId> {
|
fn plant(h: &mut Harness) -> Vec<WidgetId> {
|
||||||
let plain = wtext("Wrapping").size(16).wrap(false).add(&mut h.rsc);
|
let plain = wtext("Wrapping").size(16).wrap(false).add(&mut h.rsc);
|
||||||
let wrapped = wtext("Wrapping shapes").size(16).wrap(true).add(&mut h.rsc);
|
let wrapped = wtext("Wrapping shapes")
|
||||||
let sized = wrapped.width(76).add(&mut h.rsc);
|
.size(16)
|
||||||
let aligned = sized;
|
.wrap(true)
|
||||||
|
.width(76)
|
||||||
|
.add(&mut h.rsc);
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(sized, Axis::X, AxisAlign::POS);
|
.set_alignment(wrapped, Axis::X, AxisAlign::POS);
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(sized, Axis::Y, AxisAlign::POS);
|
.set_alignment(wrapped, Axis::Y, AxisAlign::POS);
|
||||||
let stack = Stack {
|
let stack = Stack {
|
||||||
children: vec![plain.add_strong(&mut h.rsc), aligned.add_strong(&mut h.rsc)],
|
children: vec![plain.add_strong(&mut h.rsc), wrapped.add_strong(&mut h.rsc)],
|
||||||
size: StackSize::Child(0),
|
size: StackSize::Child(0),
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
let root = (stack,).span(Dir::RIGHT).add(&mut h.rsc);
|
let root = (stack,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
h.state.root = Some(root.add_strong(&mut h.rsc));
|
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||||
vec![
|
vec![plain.id(), wrapped.id(), stack.id(), root.id()]
|
||||||
plain.id(),
|
|
||||||
wrapped.id(),
|
|
||||||
sized.id(),
|
|
||||||
aligned.id(),
|
|
||||||
stack.id(),
|
|
||||||
root.id(),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dump(label: &str, report: &diag::Report, text: WidgetId) {
|
fn dump(label: &str, report: &diag::Report, text: WidgetId) {
|
||||||
@@ -81,7 +76,7 @@ fn what_box_the_text_is_drawn_in() {
|
|||||||
|
|
||||||
for _ in 0..2 {
|
for _ in 0..2 {
|
||||||
for &id in &ids {
|
for &id in &ids {
|
||||||
h.rsc.widgets_mut().get_dyn_mut(id);
|
h.rsc.widgets_mut().mark_for_redraw(id);
|
||||||
}
|
}
|
||||||
let _ = diag::take();
|
let _ = diag::take();
|
||||||
h.frame();
|
h.frame();
|
||||||
@@ -93,23 +88,14 @@ fn what_box_the_text_is_drawn_in() {
|
|||||||
fn plant_fixed(h: &mut Harness) -> Vec<WidgetId> {
|
fn plant_fixed(h: &mut Harness) -> Vec<WidgetId> {
|
||||||
let words = "Wrapping shapes one source into as many lines as the box leaves";
|
let words = "Wrapping shapes one source into as many lines as the box leaves";
|
||||||
let text = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
let text = wtext(words).size(16).wrap(true).add(&mut h.rsc);
|
||||||
let aligned = text;
|
|
||||||
h.rsc
|
h.rsc
|
||||||
.widgets_mut()
|
.widgets_mut()
|
||||||
.set_alignment(text, Axis::X, AxisAlign::NEG);
|
.set_alignment(text, Axis::X, AxisAlign::NEG);
|
||||||
let inner = (aligned,).span(Dir::RIGHT).add(&mut h.rsc);
|
let inner = (text,).span(Dir::RIGHT).sized((189, 176)).add(&mut h.rsc);
|
||||||
let sized = inner.sized((189, 176)).add(&mut h.rsc);
|
|
||||||
let filler = rect(Color::RED).add(&mut h.rsc);
|
let filler = rect(Color::RED).add(&mut h.rsc);
|
||||||
let root = (filler, sized).span(Dir::RIGHT).add(&mut h.rsc);
|
let root = (filler, inner).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
h.state.root = Some(root.add_strong(&mut h.rsc));
|
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||||
vec![
|
vec![text.id(), inner.id(), filler.id(), root.id()]
|
||||||
text.id(),
|
|
||||||
aligned.id(),
|
|
||||||
inner.id(),
|
|
||||||
sized.id(),
|
|
||||||
filler.id(),
|
|
||||||
root.id(),
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in new issue
Block a user