Track retained layout validity explicitly
This commit is contained in:
1 parent
691e3eb23c
commit
29c7881c8a
25 files changed
+836
-795
No files matched your search
@@ -26,7 +26,6 @@ use std::{
|
|||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) enum Counter {
|
pub(crate) enum Counter {
|
||||||
Updates,
|
Updates,
|
||||||
ResizeDependents,
|
|
||||||
DrawRequests,
|
DrawRequests,
|
||||||
WidgetDraws,
|
WidgetDraws,
|
||||||
PlaceCalls,
|
PlaceCalls,
|
||||||
@@ -40,13 +39,9 @@ pub(crate) enum Counter {
|
|||||||
ReuseDirty,
|
ReuseDirty,
|
||||||
ReuseWrongParent,
|
ReuseWrongParent,
|
||||||
ReuseUnslotted,
|
ReuseUnslotted,
|
||||||
ReuseOwnResize,
|
ReuseOutside,
|
||||||
ReuseDescendantResize,
|
|
||||||
ResizeChecks,
|
|
||||||
ResizeCheckChildren,
|
|
||||||
QueuePops,
|
QueuePops,
|
||||||
DepthReads,
|
DepthReads,
|
||||||
EagerReaderRedraws,
|
|
||||||
LocalRedraws,
|
LocalRedraws,
|
||||||
SizeChanges,
|
SizeChanges,
|
||||||
ReaderEdges,
|
ReaderEdges,
|
||||||
@@ -63,7 +58,6 @@ impl Counter {
|
|||||||
|
|
||||||
const NAMES: [&'static str; Self::COUNT] = [
|
const NAMES: [&'static str; Self::COUNT] = [
|
||||||
"updates",
|
"updates",
|
||||||
"resize dependents",
|
|
||||||
"draw requests",
|
"draw requests",
|
||||||
"widget draws",
|
"widget draws",
|
||||||
"place calls",
|
"place calls",
|
||||||
@@ -77,13 +71,9 @@ impl Counter {
|
|||||||
"reuse: dirty",
|
"reuse: dirty",
|
||||||
"reuse: wrong parent",
|
"reuse: wrong parent",
|
||||||
"reuse: unslotted",
|
"reuse: unslotted",
|
||||||
"reuse: own resize",
|
"reuse: outside what it holds for",
|
||||||
"reuse: descendant resize",
|
|
||||||
"resize checks",
|
|
||||||
"resize children checked",
|
|
||||||
"redraw queue pops",
|
"redraw queue pops",
|
||||||
"depth reads",
|
"depth reads",
|
||||||
"eager reader redraws",
|
|
||||||
"local redraws",
|
"local redraws",
|
||||||
"size changes",
|
"size changes",
|
||||||
"reader edges",
|
"reader edges",
|
||||||
@@ -100,7 +90,6 @@ impl Counter {
|
|||||||
pub(crate) enum TimerKind {
|
pub(crate) enum TimerKind {
|
||||||
Update,
|
Update,
|
||||||
FullLayout,
|
FullLayout,
|
||||||
ResizeMarking,
|
|
||||||
IncrementalLayout,
|
IncrementalLayout,
|
||||||
TextRender,
|
TextRender,
|
||||||
TextShape,
|
TextShape,
|
||||||
@@ -114,7 +103,6 @@ impl TimerKind {
|
|||||||
const NAMES: [&'static str; Self::COUNT] = [
|
const NAMES: [&'static str; Self::COUNT] = [
|
||||||
"update total",
|
"update total",
|
||||||
"full layout",
|
"full layout",
|
||||||
"resize marking",
|
|
||||||
"incremental layout",
|
"incremental layout",
|
||||||
"text render",
|
"text render",
|
||||||
"text shape",
|
"text shape",
|
||||||
@@ -255,8 +243,8 @@ pub enum ReuseOutcome {
|
|||||||
Dirty,
|
Dirty,
|
||||||
WrongParent,
|
WrongParent,
|
||||||
Unslotted,
|
Unslotted,
|
||||||
OwnResize,
|
Outside,
|
||||||
DescendantResize,
|
Undrawn,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One targeted layout event. Events are retained in execution order, making
|
/// One targeted layout event. Events are retained in execution order, making
|
||||||
|
|||||||
+23
-20
@@ -1,23 +1,27 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
LayerId, Len, MaskIdx, MoveIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId,
|
Holds, LayerId, Len, MaskIdx, MoveIdx, PrimitiveHandle, Size, TextureHandle, UiRegion, WidgetId,
|
||||||
util::Vec2,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// important non rendering data for retained drawing
|
/// What is kept of a widget its parent has asked about. `drawn` says whether
|
||||||
|
/// it currently draws; one that does not is kept so that a change to it, or
|
||||||
|
/// under it, still reaches whoever asked.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ActiveData {
|
pub struct ActiveData {
|
||||||
pub id: WidgetId,
|
pub id: WidgetId,
|
||||||
pub region: UiRegion,
|
pub region: UiRegion,
|
||||||
/// What the widget said it used of `region`, the last time it drew.
|
/// The box its parent first asked about it in, as a part of the box the
|
||||||
|
/// parent was itself asked in. Any later box it was given was decided
|
||||||
|
/// knowing its answer, so this is where a question about it is asked
|
||||||
|
/// again -- and it is kept relative so that it follows the parent's.
|
||||||
|
pub offer: UiRegion,
|
||||||
|
/// What it answered there: the size and what that held for.
|
||||||
|
pub answer: (Size, [Holds; 2]),
|
||||||
|
/// What the widget said it used of its box, the last time it drew.
|
||||||
pub size: Size,
|
pub size: Size,
|
||||||
/// The pixel size of the box it drew against. `region` alone cannot say:
|
/// The pixel lengths of its box, per axis, that its drawing and `size`
|
||||||
/// it is a fraction of a slot's box, and the same fraction of a box that
|
/// hold for.
|
||||||
/// has since changed is a different number of pixels.
|
pub holds: [Holds; 2],
|
||||||
pub px: Vec2,
|
pub drawn: bool,
|
||||||
/// The pixel size of the box its parent first asked about it in, before
|
|
||||||
/// knowing what it came to. `px` may be a box derived from that answer,
|
|
||||||
/// and a size measured there is only the same answer asked again.
|
|
||||||
pub offered_px: Vec2,
|
|
||||||
pub parent: Option<WidgetId>,
|
pub parent: Option<WidgetId>,
|
||||||
/// How far down the tree it was drawn, the root being 1. Carried down a
|
/// How far down the tree it was drawn, the root being 1. Carried down a
|
||||||
/// draw rather than worked out by walking up, so it is right for every
|
/// draw rather than worked out by walking up, so it is right for every
|
||||||
@@ -28,14 +32,6 @@ pub struct ActiveData {
|
|||||||
pub children: Vec<WidgetId>,
|
pub children: Vec<WidgetId>,
|
||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub size_deps: Vec<WidgetId>,
|
pub size_deps: Vec<WidgetId>,
|
||||||
/// Offered pixel axes which flowed into this widget's reported size,
|
|
||||||
/// directly or through a child size it read.
|
|
||||||
pub size_box_inputs: [bool; 2],
|
|
||||||
/// Output axes read while producing `size`, distinct from the widget's
|
|
||||||
/// own box when that box has a fixed pixel length.
|
|
||||||
pub size_output_inputs: [bool; 2],
|
|
||||||
/// The output dimensions against which those dependencies were observed.
|
|
||||||
pub output_px: Vec2,
|
|
||||||
/// The slot its primitives are positioned through: its own if its parent
|
/// The slot its primitives are positioned through: its own if its parent
|
||||||
/// placed it, otherwise the nearest ancestor that has one.
|
/// placed it, otherwise the nearest ancestor that has one.
|
||||||
pub move_idx: MoveIdx,
|
pub move_idx: MoveIdx,
|
||||||
@@ -48,3 +44,10 @@ pub struct ActiveData {
|
|||||||
pub mask: MaskIdx,
|
pub mask: MaskIdx,
|
||||||
pub layer: LayerId,
|
pub layer: LayerId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ActiveData {
|
||||||
|
/// Whether its drawing and size hold for a box of these pixel lengths.
|
||||||
|
pub fn holds_at(&self, px: crate::util::Vec2) -> bool {
|
||||||
|
self.holds[0].contains(px.x) && self.holds[1].contains(px.y)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use crate::UiScalar;
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
/// The lengths of a box, in pixels, that one drawing of a widget holds for:
|
||||||
|
/// give the widget any box in this range and it draws the same thing and
|
||||||
|
/// reports the same size. A widget that never reads its box in pixels holds
|
||||||
|
/// for every length; one that does holds for the one it read unless it says
|
||||||
|
/// otherwise, and a parent holds for whatever keeps every child it asked
|
||||||
|
/// about or drew inside its own range.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct Holds {
|
||||||
|
pub lo: f32,
|
||||||
|
pub hi: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How far outside a range a length may fall and still be inside it: a box
|
||||||
|
/// offered back to a widget at the length it reported comes back through the
|
||||||
|
/// chain a few bits off, and nothing a reader could see lives in that gap.
|
||||||
|
pub const HOLDS_EPSILON_PX: f32 = 0.05;
|
||||||
|
|
||||||
|
impl Holds {
|
||||||
|
pub const ANY: Self = Self {
|
||||||
|
lo: f32::NEG_INFINITY,
|
||||||
|
hi: f32::INFINITY,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const fn at(len: f32) -> Self {
|
||||||
|
Self { lo: len, hi: len }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn contains(&self, len: f32) -> bool {
|
||||||
|
len >= self.lo - HOLDS_EPSILON_PX && len <= self.hi + HOLDS_EPSILON_PX
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn and(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
lo: self.lo.max(other.lo),
|
||||||
|
hi: self.hi.min(other.hi),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// What a box has to be for a part of it, `len` of the box long, to stay
|
||||||
|
/// in this range. A part with no relative extent is a fixed length: it
|
||||||
|
/// was drawn at that length and any box keeps it there.
|
||||||
|
pub fn through(self, len: UiScalar) -> Self {
|
||||||
|
if len.rel == 0.0 {
|
||||||
|
return Self::ANY;
|
||||||
|
}
|
||||||
|
let a = (self.lo - len.px) / len.rel;
|
||||||
|
let b = (self.hi - len.px) / len.rel;
|
||||||
|
Self {
|
||||||
|
lo: a.min(b),
|
||||||
|
hi: a.max(b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<RangeInclusive<f32>> for Holds {
|
||||||
|
fn from(range: RangeInclusive<f32>) -> Self {
|
||||||
|
Self {
|
||||||
|
lo: *range.start(),
|
||||||
|
hi: *range.end(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn through_reverses_a_range_for_a_negative_fraction() {
|
||||||
|
assert_eq!(
|
||||||
|
Holds { lo: 20.0, hi: 40.0 }.through(UiScalar::new(-0.5, 10.0)),
|
||||||
|
Holds {
|
||||||
|
lo: -60.0,
|
||||||
|
hi: -20.0
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,10 +10,12 @@ use crate::{
|
|||||||
pub const CHAIN_LIMIT: u32 = 64;
|
pub const CHAIN_LIMIT: u32 = 64;
|
||||||
|
|
||||||
mod active;
|
mod active;
|
||||||
|
mod holds;
|
||||||
mod painter;
|
mod painter;
|
||||||
mod render_state;
|
mod render_state;
|
||||||
|
|
||||||
pub use active::*;
|
pub use active::*;
|
||||||
|
pub use holds::*;
|
||||||
pub use painter::{Painter, PrimitiveLike};
|
pub use painter::{Painter, PrimitiveLike};
|
||||||
pub use render_state::*;
|
pub use render_state::*;
|
||||||
|
|
||||||
|
|||||||
+142
-128
@@ -1,14 +1,18 @@
|
|||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
use crate::layout_diagnostics::{self as diag, Counter};
|
use crate::layout_diagnostics::{self as diag, Counter};
|
||||||
use crate::{
|
use crate::{
|
||||||
Axis, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData, TextureHandle,
|
Axis, Holds, Len, RenderedText, Size, StrongWidget, TextAttrs, TextBuffer, TextData,
|
||||||
UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
TextureHandle, UiRegion, UiRenderState, UiRsc, UiScalar, UiVec2, Widget, WidgetId,
|
||||||
render::{
|
render::{
|
||||||
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
GlyphPrimitive, Mask, MaskIdx, MoveIdx, Primitive, PrimitiveHandle, PrimitiveInst,
|
||||||
PrimitiveKind, TexturePrimitive,
|
PrimitiveKind, TexturePrimitive,
|
||||||
},
|
},
|
||||||
|
ui::render_state::DrawInfo,
|
||||||
util::Vec2,
|
util::Vec2,
|
||||||
};
|
};
|
||||||
|
use std::ops::RangeInclusive;
|
||||||
|
|
||||||
|
const AXES: [Axis; 2] = [Axis::X, Axis::Y];
|
||||||
|
|
||||||
/// makes your surfaces look pretty
|
/// makes your surfaces look pretty
|
||||||
pub struct Painter<'a> {
|
pub struct Painter<'a> {
|
||||||
@@ -21,14 +25,21 @@ pub struct Painter<'a> {
|
|||||||
pub(super) textures: Vec<TextureHandle>,
|
pub(super) textures: Vec<TextureHandle>,
|
||||||
pub(super) primitives: Vec<PrimitiveHandle>,
|
pub(super) primitives: Vec<PrimitiveHandle>,
|
||||||
pub(super) children: Vec<WidgetId>,
|
pub(super) children: Vec<WidgetId>,
|
||||||
/// The children asked about so far, so the first box each was asked
|
/// The children asked about so far, so the first box each was asked in
|
||||||
/// about is the one recorded as its offer.
|
/// is the one recorded as its offer.
|
||||||
pub(super) offered: Vec<WidgetId>,
|
pub(super) offered: Vec<WidgetId>,
|
||||||
|
/// The box this widget was first asked about in, in pixels.
|
||||||
|
pub(super) offered_px: Vec2,
|
||||||
|
/// Whether this draw is in that box, which makes the questions it asks
|
||||||
|
/// the ones a cold layout asks and their answers the ones to keep.
|
||||||
|
pub(super) at_offer: bool,
|
||||||
/// The children whose size this widget read while drawing.
|
/// The children whose size this widget read while drawing.
|
||||||
pub(super) size_deps: Vec<WidgetId>,
|
pub(super) size_deps: Vec<WidgetId>,
|
||||||
/// Offered pixel axes which can affect the size this draw reports.
|
/// What this draw itself read of its box in pixels, per axis: every
|
||||||
pub(super) size_box_inputs: [bool; 2],
|
/// length until it reads one, then that one, unless it says otherwise.
|
||||||
pub(super) size_output_inputs: [bool; 2],
|
pub(super) own: [Holds; 2],
|
||||||
|
/// What the children it asked about and drew keep it to.
|
||||||
|
pub(super) under: [Holds; 2],
|
||||||
/// The slot this widget's primitives are positioned through: its own if
|
/// The slot this widget's primitives are positioned through: its own if
|
||||||
/// its parent placed it, otherwise the nearest ancestor that has one.
|
/// its parent placed it, otherwise the nearest ancestor that has one.
|
||||||
pub(super) move_idx: MoveIdx,
|
pub(super) move_idx: MoveIdx,
|
||||||
@@ -90,14 +101,7 @@ impl<'a> Painter<'a> {
|
|||||||
|
|
||||||
/// Draws a widget within this widget's region.
|
/// Draws a widget within this widget's region.
|
||||||
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
pub fn widget<'s, W: ?Sized>(&'s mut self, id: &'s StrongWidget<W>) -> DrawResult<'s, 'a, W> {
|
||||||
let declared = self.declared_lens(id);
|
self.widget_at(id, UiRegion::FULL, false)
|
||||||
// Composing `FULL` through a box is not quite the identity in f32,
|
|
||||||
// so a child with nothing declared keeps the box it would have had.
|
|
||||||
let region = match declared.iter().any(Option::is_some) {
|
|
||||||
true => declared_box(UiRegion::FULL, declared).within(&self.region),
|
|
||||||
false => self.region,
|
|
||||||
};
|
|
||||||
self.widget_at(id, region, false, declared)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget somewhere within this one.
|
/// Draws a widget somewhere within this one.
|
||||||
@@ -106,9 +110,7 @@ impl<'a> Painter<'a> {
|
|||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
let declared = self.declared_lens(id);
|
self.widget_at(id, region, false)
|
||||||
let region = declared_box(region, declared).within(&self.region);
|
|
||||||
self.widget_at(id, region, false, declared)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What a widget declares its lengths to be, which whoever draws it
|
/// What a widget declares its lengths to be, which whoever draws it
|
||||||
@@ -120,7 +122,7 @@ impl<'a> Painter<'a> {
|
|||||||
let Some(widget) = self.rsc.widgets().get_dyn(id.id()) else {
|
let Some(widget) = self.rsc.widgets().get_dyn(id.id()) else {
|
||||||
return [None; 2];
|
return [None; 2];
|
||||||
};
|
};
|
||||||
[Axis::X, Axis::Y].map(|axis| declared_len(widget, axis))
|
AXES.map(|axis| declared_len(widget, axis))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a child this widget decides the box of, and may decide again
|
/// Draws a child this widget decides the box of, and may decide again
|
||||||
@@ -136,39 +138,75 @@ impl<'a> Painter<'a> {
|
|||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::PlaceCalls);
|
diag::bump(Counter::PlaceCalls);
|
||||||
let declared = self.declared_lens(id);
|
self.widget_at(id, region, true)
|
||||||
let region = declared_box(region, declared).within(&self.region);
|
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
|
||||||
diag::placed(id.id(), self.id, region);
|
|
||||||
self.widget_at(id, region, true, declared)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Takes back a child that was drawn only to find out how long it is.
|
||||||
|
/// Its drawing is dropped and it is not one of this widget's children
|
||||||
|
/// this frame; what it answered is still something this widget asked.
|
||||||
|
pub fn undraw<W: ?Sized>(&mut self, id: &StrongWidget<W>) {
|
||||||
|
self.children.retain(|child| *child != id.id());
|
||||||
|
self.state.undraw_rec(id.id(), self.rsc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `region` in this widget's own coordinates, and with the child's
|
||||||
|
/// declared lengths still to be taken.
|
||||||
fn widget_at<'s, W: ?Sized>(
|
fn widget_at<'s, W: ?Sized>(
|
||||||
&'s mut self,
|
&'s mut self,
|
||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
slotted: bool,
|
slotted: bool,
|
||||||
declared: [Option<Len>; 2],
|
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
|
let declared = self.declared_lens(id);
|
||||||
|
// Composing `FULL` through a box is not quite the identity in f32,
|
||||||
|
// so a child with nothing declared keeps the box it would have had.
|
||||||
|
let local = match declared.iter().any(Option::is_some) {
|
||||||
|
true => declared_box(region, declared),
|
||||||
|
false => region,
|
||||||
|
};
|
||||||
|
let within = match local == UiRegion::FULL {
|
||||||
|
true => self.region,
|
||||||
|
false => local.within(&self.region),
|
||||||
|
};
|
||||||
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
|
if slotted {
|
||||||
|
diag::placed(id.id(), self.id, within);
|
||||||
|
}
|
||||||
// A child listed twice would be moved twice.
|
// A child listed twice would be moved twice.
|
||||||
if !self.children.contains(&id.id()) {
|
if !self.children.contains(&id.id()) {
|
||||||
self.children.push(id.id());
|
self.children.push(id.id());
|
||||||
}
|
}
|
||||||
|
let first_ask = self.offer(id.id());
|
||||||
|
let offer = match first_ask {
|
||||||
|
true => local,
|
||||||
|
false => self.state.active.get(&id.id()).map_or(local, |a| a.offer),
|
||||||
|
};
|
||||||
|
let answers_offer = self.at_offer && local == offer;
|
||||||
let size = self.state.draw_inner(
|
let size = self.state.draw_inner(
|
||||||
self.layer,
|
|
||||||
id.id(),
|
id.id(),
|
||||||
region,
|
within,
|
||||||
Some(self.id),
|
DrawInfo {
|
||||||
self.depth + 1,
|
layer: self.layer,
|
||||||
self.move_idx,
|
parent: Some(self.id),
|
||||||
slotted,
|
depth: self.depth + 1,
|
||||||
self.mask,
|
parent_move: self.move_idx,
|
||||||
|
slotted,
|
||||||
|
mask: self.mask,
|
||||||
|
offer,
|
||||||
|
offered_px: self.px_within_offer(offer),
|
||||||
|
},
|
||||||
None,
|
None,
|
||||||
self.rsc,
|
self.rsc,
|
||||||
);
|
);
|
||||||
self.offer(id.id(), region);
|
let active = self.state.active.get_mut(&id.id()).unwrap();
|
||||||
if let Some(active) = self.state.active.get_mut(&id.id()) {
|
active.declared = declared;
|
||||||
active.declared = declared;
|
if answers_offer {
|
||||||
|
active.answer = (active.size, active.holds);
|
||||||
|
}
|
||||||
|
// Whatever the child's drawing holds for keeps this one to the boxes
|
||||||
|
// that give the child a length inside it.
|
||||||
|
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
||||||
|
*under = under.and(active.holds[axis as usize].through(local.axis(axis).len()));
|
||||||
}
|
}
|
||||||
DrawResult {
|
DrawResult {
|
||||||
child: id,
|
child: id,
|
||||||
@@ -191,7 +229,7 @@ impl<'a> Painter<'a> {
|
|||||||
Some(hint) => {
|
Some(hint) => {
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::HintHits);
|
diag::bump(Counter::HintHits);
|
||||||
self.depend_on_hint(id);
|
self.depend_on(id);
|
||||||
Some(hint)
|
Some(hint)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -202,87 +240,66 @@ impl<'a> Painter<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A retained child length valid under the region it is about to be
|
/// A child's length in the box it is about to be offered, if it can be
|
||||||
/// offered. Unlike a hint, this is contextual: it is kept only when none
|
/// had without drawing it: from its hint, or from a drawing it already
|
||||||
/// of the offered pixel axes which produced it changed.
|
/// has that holds for that box.
|
||||||
pub fn known_len<W: ?Sized>(
|
pub fn known_len<W: ?Sized>(
|
||||||
&mut self,
|
&mut self,
|
||||||
child: &StrongWidget<W>,
|
child: &StrongWidget<W>,
|
||||||
axis: Axis,
|
axis: Axis,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
) -> Option<Len> {
|
) -> Option<Len> {
|
||||||
let region = region.within(&self.region);
|
let declared = self.declared_lens(child);
|
||||||
self.offer(child.id(), region);
|
let local = declared_box(region, declared);
|
||||||
|
let within = local.within(&self.region);
|
||||||
|
let first_ask = self.offer(child.id());
|
||||||
|
if first_ask && let Some(active) = self.state.active.get_mut(&child.id()) {
|
||||||
|
active.offer = local;
|
||||||
|
}
|
||||||
if let Some(hint) = self.size_hint(child, axis) {
|
if let Some(hint) = self.size_hint(child, axis) {
|
||||||
return Some(hint);
|
return Some(hint);
|
||||||
}
|
}
|
||||||
self.retained_size(child, region)
|
let px = self.state.px_of(self.move_idx, within);
|
||||||
.map(|size| size.axis(axis))
|
let (size, holds) =
|
||||||
}
|
|
||||||
|
|
||||||
/// `region` in this widget's own coordinates.
|
|
||||||
fn retained_size<W: ?Sized>(
|
|
||||||
&mut self,
|
|
||||||
child: &StrongWidget<W>,
|
|
||||||
region: UiRegion,
|
|
||||||
) -> Option<Size> {
|
|
||||||
let (size, box_inputs, output_inputs) =
|
|
||||||
self.state
|
self.state
|
||||||
.retained_size(child.id(), region, self.move_idx, self.rsc.widgets())?;
|
.retained_size(child.id(), px, self.move_idx, self.rsc.widgets())?;
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
diag::bump(Counter::RetainedSizeHits);
|
diag::bump(Counter::RetainedSizeHits);
|
||||||
self.depend_on_size_inputs(child, box_inputs, output_inputs);
|
self.depend_on(child);
|
||||||
Some(size)
|
if first_ask {
|
||||||
|
let active = self.state.active.get_mut(&child.id()).unwrap();
|
||||||
|
active.answer = (size, holds);
|
||||||
|
}
|
||||||
|
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
||||||
|
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
||||||
|
}
|
||||||
|
Some(size.axis(axis))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records the box a child was first asked about in this draw. Any later
|
/// Whether this is the first box a child is asked about in during a draw
|
||||||
/// box this draw gives it was decided knowing its answer, so a size the
|
/// that is itself in the box it was asked in -- the question a cold
|
||||||
/// child measures there is not an answer to this widget's question.
|
/// layout asks, whose answer is the one to keep.
|
||||||
fn offer(&mut self, child: WidgetId, region: UiRegion) {
|
fn offer(&mut self, child: WidgetId) -> bool {
|
||||||
if self.offered.contains(&child) {
|
if !self.at_offer || self.offered.contains(&child) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
self.offered.push(child);
|
self.offered.push(child);
|
||||||
let px = self.state.px_of(self.move_idx, region);
|
true
|
||||||
if let Some(active) = self.state.active.get_mut(&child) {
|
|
||||||
active.offered_px = px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Depends on a length the child gave without being drawn. A hint is
|
/// The pixel size of a part of the box this widget was asked in.
|
||||||
/// context-free, so this depends on the child but on no pixel axis.
|
fn px_within_offer(&self, local: UiRegion) -> Vec2 {
|
||||||
fn depend_on_hint<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
let size = local.size();
|
||||||
self.depend_on_size_inputs(child, [false; 2], [false; 2]);
|
Vec2::new(
|
||||||
|
size.x.to_px(self.offered_px.x),
|
||||||
|
size.y.to_px(self.offered_px.y),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Depends on a size the child produced by drawing, which carries
|
fn depend_on<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
||||||
/// whatever the child read to produce it.
|
|
||||||
fn depend_on_drawn_size<W: ?Sized>(&mut self, child: &StrongWidget<W>) {
|
|
||||||
let (box_inputs, output_inputs) = self
|
|
||||||
.state
|
|
||||||
.active
|
|
||||||
.get(&child.id())
|
|
||||||
.map_or(([false; 2], [false; 2]), |active| {
|
|
||||||
(active.size_box_inputs, active.size_output_inputs)
|
|
||||||
});
|
|
||||||
self.depend_on_size_inputs(child, box_inputs, output_inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn depend_on_size_inputs<W: ?Sized>(
|
|
||||||
&mut self,
|
|
||||||
child: &StrongWidget<W>,
|
|
||||||
box_inputs: [bool; 2],
|
|
||||||
output_inputs: [bool; 2],
|
|
||||||
) {
|
|
||||||
if !self.size_deps.contains(&child.id()) {
|
if !self.size_deps.contains(&child.id()) {
|
||||||
self.size_deps.push(child.id());
|
self.size_deps.push(child.id());
|
||||||
}
|
}
|
||||||
for (own, child) in self.size_box_inputs.iter_mut().zip(box_inputs) {
|
|
||||||
*own |= child;
|
|
||||||
}
|
|
||||||
for (own, child) in self.size_output_inputs.iter_mut().zip(output_inputs) {
|
|
||||||
*own |= child;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_text<'b>(
|
pub fn render_text<'b>(
|
||||||
@@ -327,45 +344,42 @@ impl<'a> Painter<'a> {
|
|||||||
self.region
|
self.region
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The output's size in pixels. A widget that reads it draws again when
|
/// This widget's box in pixels. Reading it makes the drawing one that
|
||||||
/// the output changes, since nothing else can put that right.
|
/// holds for this box only, until `holds` says how far it goes.
|
||||||
pub fn output_size(&mut self) -> Vec2 {
|
|
||||||
self.size_output_inputs = [true; 2];
|
|
||||||
self.state.output_size
|
|
||||||
}
|
|
||||||
|
|
||||||
/// One axis of the output in pixels. Prefer this to [`Self::output_size`]
|
|
||||||
/// when the other axis cannot affect the size this widget reports.
|
|
||||||
pub fn output_len(&mut self, axis: Axis) -> f32 {
|
|
||||||
self.size_output_inputs[axis as usize] = true;
|
|
||||||
self.state.output_size.axis(axis)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This widget's box in pixels. Resolved against the output's size and
|
|
||||||
/// the boxes it sits within, so a widget that reads it draws again when
|
|
||||||
/// the output changes.
|
|
||||||
pub fn px_size(&mut self) -> Vec2 {
|
pub fn px_size(&mut self) -> Vec2 {
|
||||||
self.size_box_inputs = [true; 2];
|
let px = self.state.px_of(self.move_idx, self.region);
|
||||||
let region = self.state.moves.resolve(self.move_idx, self.region);
|
for (own, len) in self.own.iter_mut().zip([px.x, px.y]) {
|
||||||
region.size().to_px(self.state.output_size)
|
if *own == Holds::ANY {
|
||||||
|
*own = Holds::at(len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
px
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One axis of this widget's box in pixels. Prefer this to
|
/// One axis of this widget's box in pixels. Prefer this to
|
||||||
/// [`Self::px_size`] when the other axis cannot affect the reported size.
|
/// [`Self::px_size`] when the other axis cannot affect the drawing.
|
||||||
pub fn px_len(&mut self, axis: Axis) -> f32 {
|
pub fn px_len(&mut self, axis: Axis) -> f32 {
|
||||||
self.size_box_inputs[axis as usize] = true;
|
let len = self.state.px_of(self.move_idx, self.region).axis(axis);
|
||||||
self.px_len_for_draw(axis)
|
let own = &mut self.own[axis as usize];
|
||||||
|
if *own == Holds::ANY {
|
||||||
|
*own = Holds::at(len);
|
||||||
|
}
|
||||||
|
len
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One axis of this widget's box in pixels, for a draw whose reported
|
/// The lengths of this widget's box on `axis` that what it is drawing
|
||||||
/// size does not follow from it -- a clamp or a position. Nothing records
|
/// holds for -- the same primitives, in the same fractions and offsets
|
||||||
/// the read, so a size that does depend on it would go stale.
|
/// of the box, and the same reported size. A widget that read its
|
||||||
pub fn px_len_for_draw(&self, axis: Axis) -> f32 {
|
/// length in pixels holds for that one alone until it says otherwise.
|
||||||
let region = self.state.moves.resolve(self.move_idx, self.region);
|
pub fn holds(&mut self, axis: Axis, range: RangeInclusive<f32>) {
|
||||||
region
|
let holds = Holds::from(range);
|
||||||
.size()
|
debug_assert!(
|
||||||
.axis(axis)
|
holds.contains(self.state.px_of(self.move_idx, self.region).axis(axis)),
|
||||||
.to_px(self.state.output_size.axis(axis))
|
"'{}' ({:?}) says its drawing holds for lengths that leave out its own box",
|
||||||
|
self.label(),
|
||||||
|
self.id
|
||||||
|
);
|
||||||
|
self.own[axis as usize] = holds;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn text_data(&mut self) -> &mut TextData {
|
pub fn text_data(&mut self) -> &mut TextData {
|
||||||
@@ -405,7 +419,7 @@ impl<W: ?Sized> DrawResult<'_, '_, W> {
|
|||||||
diag::bump(Counter::SizeReads);
|
diag::bump(Counter::SizeReads);
|
||||||
diag::size_read(self.child.id(), self.painter.id, self.size);
|
diag::size_read(self.child.id(), self.painter.id, self.size);
|
||||||
}
|
}
|
||||||
self.painter.depend_on_drawn_size(self.child);
|
self.painter.depend_on(self.child);
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,7 +465,7 @@ pub(crate) fn declared_len(widget: &dyn Widget, axis: Axis) -> Option<Len> {
|
|||||||
/// reserved the space hands back the same length, so this is the identity
|
/// reserved the space hands back the same length, so this is the identity
|
||||||
/// for it.
|
/// for it.
|
||||||
fn declared_box(mut region: UiRegion, declared: [Option<Len>; 2]) -> UiRegion {
|
fn declared_box(mut region: UiRegion, declared: [Option<Len>; 2]) -> UiRegion {
|
||||||
for (axis, len) in [Axis::X, Axis::Y].into_iter().zip(declared) {
|
for (axis, len) in AXES.into_iter().zip(declared) {
|
||||||
let Some(len) = len else { continue };
|
let Some(len) = len else { continue };
|
||||||
let span = region.axis_mut(axis);
|
let span = region.axis_mut(axis);
|
||||||
span.end = span.start + UiScalar::new(len.rel, len.px);
|
span.end = span.start + UiScalar::new(len.rel, len.px);
|
||||||
|
|||||||
+354
-447
File diff suppressed because it is too large.
Load diff
@@ -15,20 +15,6 @@ pub use tag::*;
|
|||||||
pub use view::*;
|
pub use view::*;
|
||||||
pub use widgets::*;
|
pub use widgets::*;
|
||||||
|
|
||||||
/// What may be done to a widget's drawing when the box it was given changes
|
|
||||||
/// on this axis, instead of drawing it again. Asked per axis, because wrapped
|
|
||||||
/// text reads the width it is offered and not the height.
|
|
||||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
|
||||||
pub enum OnResize {
|
|
||||||
Scale,
|
|
||||||
/// Reserved: nothing reads this yet, so a widget saying it is redrawn.
|
|
||||||
/// Keeping an unchanged drawing in a bigger box needs the widget to say
|
|
||||||
/// *where* in that box it should sit, which is the alignment work.
|
|
||||||
Translate,
|
|
||||||
#[default]
|
|
||||||
Redraw,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Widget: Any {
|
pub trait Widget: Any {
|
||||||
/// Draws the widget, and returns what it used of the box it was given.
|
/// Draws the widget, and returns what it used of the box it was given.
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size;
|
fn draw(&mut self, painter: &mut Painter) -> Size;
|
||||||
@@ -39,10 +25,6 @@ pub trait Widget: Any {
|
|||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _axis: Axis) -> OnResize {
|
|
||||||
OnResize::default()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for () {
|
impl Widget for () {
|
||||||
@@ -54,10 +36,6 @@ impl Widget for () {
|
|||||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||||
Some(Len::default())
|
Some(Len::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _axis: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl dyn Widget {
|
impl dyn Widget {
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ impl Widget for Image {
|
|||||||
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
||||||
Some(Len::px(self.handle.size().axis(axis)))
|
Some(Len::px(self.handle.size().axis(axis)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
||||||
|
|||||||
@@ -9,9 +9,4 @@ impl Widget for Masked {
|
|||||||
painter.set_mask(painter.region());
|
painter.set_mask(painter.region());
|
||||||
painter.widget(&self.inner).size()
|
painter.widget(&self.inner).size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// It clips to the box it was given, not to the part its child used.
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Redraw
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -39,10 +39,4 @@ impl Widget for Aligned {
|
|||||||
let placed = painter.place(&self.inner, region).size();
|
let placed = painter.place(&self.inner, region).size();
|
||||||
if had_size { placed } else { size }
|
if had_size { placed } else { size }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The aligned box is a fraction of its own, so the child keeps its
|
|
||||||
/// length and stays against the edge it was aligned to.
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,4 @@ impl Widget for LayerOffset {
|
|||||||
}
|
}
|
||||||
painter.widget(&self.inner).size()
|
painter.widget(&self.inner).size()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -9,17 +9,17 @@ pub struct MaxSize {
|
|||||||
impl Widget for MaxSize {
|
impl Widget for MaxSize {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let child = painter.widget(&self.inner).size();
|
let child = painter.widget(&self.inner).size();
|
||||||
let output = painter.output_size();
|
let own = painter.px_size();
|
||||||
Size {
|
Size {
|
||||||
x: capped(child.x, self.x, output.x),
|
x: capped(child.x, self.x, own.x),
|
||||||
y: capped(child.y, self.y, output.y),
|
y: capped(child.y, self.y, own.y),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn capped(len: Len, max: Option<Len>, output: f32) -> Len {
|
fn capped(len: Len, max: Option<Len>, own: f32) -> Len {
|
||||||
match max {
|
match max {
|
||||||
Some(max) if len.apply_leftover().to_px(output) > max.apply_leftover().to_px(output) => max,
|
Some(max) if len.apply_leftover().to_px(own) > max.apply_leftover().to_px(own) => max,
|
||||||
_ => len,
|
_ => len,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,8 +10,4 @@ impl Widget for Offset {
|
|||||||
let region = UiRegion::FULL.offset(self.amt);
|
let region = UiRegion::FULL.offset(self.amt);
|
||||||
painter.widget_within(&self.inner, region).size()
|
painter.widget_within(&self.inner, region).size()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -21,12 +21,6 @@ impl Widget for Pad {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The padding is an offset from each edge, so a longer box pads the same
|
|
||||||
/// amount and the child takes what is left over.
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Padding {
|
pub struct Padding {
|
||||||
|
|||||||
@@ -11,31 +11,40 @@ pub struct Scroll {
|
|||||||
|
|
||||||
impl Widget for Scroll {
|
impl Widget for Scroll {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
let output_len = painter.output_len(self.axis);
|
let container_len = painter.px_len(self.axis);
|
||||||
// Its size is its content's, whatever box that is scrolled within.
|
|
||||||
let container_len = UiScalar::px(painter.px_len_for_draw(self.axis));
|
|
||||||
// Draw in the whole container only when its scrolling-axis length is
|
// Draw in the whole container only when its scrolling-axis length is
|
||||||
// not already known, then place it at the scrolled offset.
|
// not already known, then place it at the scrolled offset.
|
||||||
let known_len = painter.known_len(&self.inner, self.axis, UiRegion::FULL);
|
let (answer_len, measured) = match painter.known_len(&self.inner, self.axis, UiRegion::FULL)
|
||||||
let measured = known_len.is_none();
|
{
|
||||||
let child = measured.then(|| painter.place(&self.inner, UiRegion::FULL).size());
|
Some(len) => (len, None),
|
||||||
let content_len = known_len
|
None => {
|
||||||
.unwrap_or_else(|| child.unwrap().axis(self.axis))
|
let size = painter.place(&self.inner, UiRegion::FULL).size();
|
||||||
.apply_leftover()
|
(size.axis(self.axis), Some(size))
|
||||||
.within_len(container_len)
|
}
|
||||||
.to_px(output_len);
|
};
|
||||||
self.container_len = container_len.to_px(output_len);
|
let content = answer_len.apply_leftover();
|
||||||
self.content_len = content_len;
|
self.container_len = container_len;
|
||||||
|
self.content_len = content.to_px(container_len);
|
||||||
|
|
||||||
if self.snap_end {
|
if self.snap_end {
|
||||||
self.amt = self.content_len - self.container_len;
|
self.amt = self.content_len - self.container_len;
|
||||||
}
|
}
|
||||||
self.update_amt();
|
self.update_amt();
|
||||||
|
// Content of a fixed length that fits sits at the start of any box
|
||||||
|
// it fits in; 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.
|
||||||
|
if content.rel == 0.0 && self.content_len <= self.container_len {
|
||||||
|
painter.holds(self.axis, self.content_len..=f32::INFINITY);
|
||||||
|
} else if content.rel == 0.0 && !self.snap_end {
|
||||||
|
let left = self.content_len - self.amt;
|
||||||
|
painter.holds(self.axis, f32::NEG_INFINITY..=left);
|
||||||
|
}
|
||||||
|
|
||||||
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
|
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
|
||||||
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
||||||
let placed = painter.place(&self.inner, region).size();
|
let placed = painter.place(&self.inner, region).size();
|
||||||
child.unwrap_or(placed)
|
measured.unwrap_or_else(|| Size::from_axis(self.axis, answer_len, placed.axis(!self.axis)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,8 +27,4 @@ impl Widget for SetSize {
|
|||||||
Axis::Y => self.y,
|
Axis::Y => self.y,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -32,12 +32,44 @@ impl Widget for Span {
|
|||||||
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
||||||
let total = lens.iter().fold(Len::px(gap), |sum, len| sum + *len);
|
let total = lens.iter().fold(Len::px(gap), |sum, len| sum + *len);
|
||||||
|
|
||||||
|
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||||
|
// beside 300 px is full at 600 and overfull at 400. The answer is
|
||||||
|
// the same on either side of the length the fixed parts alone fill.
|
||||||
|
let fixed = 1.0 - total.rel;
|
||||||
|
let shares = total.leftover > 0.0 && fixed * painter.px_len(axis) > total.px;
|
||||||
|
if total.leftover > 0.0 {
|
||||||
|
let range = if fixed > 0.0 {
|
||||||
|
let full = total.px / fixed;
|
||||||
|
match shares {
|
||||||
|
true => full..=f32::INFINITY,
|
||||||
|
false => f32::NEG_INFINITY..=full,
|
||||||
|
}
|
||||||
|
} else if fixed < 0.0 {
|
||||||
|
let full = total.px / fixed;
|
||||||
|
match shares {
|
||||||
|
true => f32::NEG_INFINITY..=full,
|
||||||
|
false => full..=f32::INFINITY,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f32::NEG_INFINITY..=f32::INFINITY
|
||||||
|
};
|
||||||
|
painter.holds(axis, range);
|
||||||
|
}
|
||||||
|
|
||||||
let mut start = UiScalar::rel_min();
|
let mut start = UiScalar::rel_min();
|
||||||
let mut ortho = Len::ZERO;
|
let mut ortho = Len::ZERO;
|
||||||
for (child, len) in self.children.iter().zip(&lens) {
|
for (child, len) in self.children.iter().zip(&lens) {
|
||||||
|
// A child asking for nothing but a part of what is left over,
|
||||||
|
// when nothing is, is not drawn at all. One that also asked for
|
||||||
|
// pixels or a fraction keeps those and overflows.
|
||||||
|
if len.leftover > 0.0 && len.px == 0.0 && len.rel == 0.0 && !shares {
|
||||||
|
painter.undraw(child);
|
||||||
|
start.px += self.gap;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
let mut span = UiSpan::FULL;
|
let mut span = UiSpan::FULL;
|
||||||
span.start = start;
|
span.start = start;
|
||||||
if len.leftover > 0.0 {
|
if len.leftover > 0.0 && shares {
|
||||||
let offset = UiScalar::new(total.rel, total.px);
|
let offset = UiScalar::new(total.rel, total.px);
|
||||||
let rel_end = UiScalar::rel(len.leftover / total.leftover);
|
let rel_end = UiScalar::rel(len.leftover / total.leftover);
|
||||||
let end = (UiScalar::rel_max() + start) - offset;
|
let end = (UiScalar::rel_max() + start) - offset;
|
||||||
@@ -70,12 +102,6 @@ impl Widget for Span {
|
|||||||
let along = total;
|
let along = total;
|
||||||
Size::from_axis(axis, along, ortho)
|
Size::from_axis(axis, along, ortho)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Every child is placed in fractions and offsets of the span's own box,
|
|
||||||
/// so a longer box holds the same layout and the children follow it.
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Span {
|
impl Span {
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ impl Widget for Stack {
|
|||||||
}
|
}
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
|
|||||||
@@ -41,11 +41,6 @@ impl Widget for Rect {
|
|||||||
fn size_hint(&self, _: Axis) -> Option<Len> {
|
fn size_hint(&self, _: Axis) -> Option<Len> {
|
||||||
Some(Len::LEFTOVER)
|
Some(Len::LEFTOVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Its box is its primitive's own region, so a new one is written there.
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rect(color: UiColor) -> Rect {
|
pub fn rect(color: UiColor) -> Rect {
|
||||||
|
|||||||
@@ -93,10 +93,6 @@ impl Widget for TextEdit {
|
|||||||
);
|
);
|
||||||
size
|
size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, axis: Axis) -> OnResize {
|
|
||||||
self.view.on_resize(axis)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const CARET_WIDTH: f32 = 1.0;
|
const CARET_WIDTH: f32 = 1.0;
|
||||||
|
|||||||
+10
-26
@@ -47,12 +47,16 @@ impl TextView {
|
|||||||
/// answers under the attrs too, so changing those asks a new question
|
/// answers under the attrs too, so changing those asks a new question
|
||||||
/// rather than invalidating anything.
|
/// rather than invalidating anything.
|
||||||
fn render(&mut self, painter: &mut Painter) -> &RenderedText {
|
fn render(&mut self, painter: &mut Painter) -> &RenderedText {
|
||||||
let width = if self.attrs.wrap {
|
let width = self.attrs.wrap.then(|| painter.px_len(Axis::X));
|
||||||
Some(painter.px_len(Axis::X))
|
let text = painter.render_text(&mut self.buf, &self.attrs, width);
|
||||||
} else {
|
// A greedy break is the same break at every width from its longest
|
||||||
None
|
// line up to the one it was made at: each line still fits, and none
|
||||||
};
|
// could take a word that did not fit in the wider box. A line too
|
||||||
painter.render_text(&mut self.buf, &self.attrs, width)
|
// long to fit at all says nothing about narrower boxes.
|
||||||
|
if let Some(width) = width {
|
||||||
|
painter.holds(Axis::X, text.size.x.min(width)..=width);
|
||||||
|
}
|
||||||
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tex(&self) -> Option<&RenderedText> {
|
pub fn tex(&self) -> Option<&RenderedText> {
|
||||||
@@ -78,22 +82,6 @@ impl TextView {
|
|||||||
(region, size)
|
(region, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wrapping reads the width it is offered, so a wider box reshapes it and
|
|
||||||
/// a taller one does not. Alignment matters too, and separately: glyphs
|
|
||||||
/// anchored to the start of an axis stay put when that extent changes,
|
|
||||||
/// but centred or end-aligned ones move even though the shaping stands.
|
|
||||||
pub fn on_resize(&self, axis: Axis) -> OnResize {
|
|
||||||
let reshapes = axis == Axis::X && self.attrs.wrap;
|
|
||||||
let anchored = match axis {
|
|
||||||
Axis::X => self.align.x,
|
|
||||||
Axis::Y => self.align.y,
|
|
||||||
} == AxisAlign::Neg;
|
|
||||||
match reshapes || !anchored {
|
|
||||||
true => OnResize::Redraw,
|
|
||||||
false => OnResize::Translate,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn content(&self) -> String {
|
pub fn content(&self) -> String {
|
||||||
self.buf.text().to_string()
|
self.buf.text().to_string()
|
||||||
}
|
}
|
||||||
@@ -120,10 +108,6 @@ impl Widget for Text {
|
|||||||
self.update_buf();
|
self.update_buf();
|
||||||
self.view.draw(painter).1
|
self.view.draw(painter).1
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, axis: Axis) -> OnResize {
|
|
||||||
self.view.on_resize(axis)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Deref for Text {
|
impl Deref for Text {
|
||||||
|
|||||||
+2
-11
@@ -30,7 +30,7 @@ fn env<T: std::str::FromStr>(name: &str, fallback: T) -> T {
|
|||||||
.and_then(|value| value.parse().ok())
|
.and_then(|value| value.parse().ok())
|
||||||
.unwrap_or(fallback)
|
.unwrap_or(fallback)
|
||||||
}
|
}
|
||||||
const SEEDS: [u64; 7] = [1, 2, 3, 5, 8, 13, 98];
|
const SEEDS: [u64; 9] = [1, 2, 3, 5, 8, 10, 13, 86, 98];
|
||||||
const REGION_EPSILON_PX: f32 = 0.05;
|
const REGION_EPSILON_PX: f32 = 0.05;
|
||||||
|
|
||||||
fn same_coordinate(got: f32, want: f32) -> bool {
|
fn same_coordinate(got: f32, want: f32) -> bool {
|
||||||
@@ -280,7 +280,6 @@ fn changed_size(seed: u64) {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_same(seed, "a size change", (&warm, &grown), (&cold, &same));
|
assert_same(seed, "a size change", (&warm, &grown), (&cold, &same));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,16 +298,9 @@ fn reshuffled(seed: u64, shuffle: Shuffle) {
|
|||||||
if !shuffles {
|
if !shuffles {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let before: Vec<_> = grown.ids.iter().map(|id| warm.region(id)).collect();
|
|
||||||
|
|
||||||
let (spans, _held) = reshuffle(&mut warm, &mut grown, shuffle);
|
let (spans, _held) = reshuffle(&mut warm, &mut grown, shuffle);
|
||||||
warm.frame();
|
warm.frame();
|
||||||
|
|
||||||
// Or the two trees would agree for want of anything having happened.
|
|
||||||
let after = grown.ids.iter().map(|id| warm.region(id));
|
|
||||||
let moved = before.iter().zip(after).filter(|(a, b)| *a != b).count();
|
|
||||||
assert!(moved > 0, "seed {seed}: {shuffle:?} changed nothing");
|
|
||||||
|
|
||||||
let mut cold = Harness::new((900, 1200));
|
let mut cold = Harness::new((900, 1200));
|
||||||
let same = plant(
|
let same = plant(
|
||||||
&mut cold,
|
&mut cold,
|
||||||
@@ -343,7 +335,6 @@ fn changed_every_size(seed: u64) {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_same(seed, "every size at once", (&warm, &grown), (&cold, &same));
|
assert_same(seed, "every size at once", (&warm, &grown), (&cold, &same));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,7 +439,7 @@ fn adding_and_removing_span_children_lands_where_growing_it_that_way_would() {
|
|||||||
/// had decided. `tests/shrink.rs` is how a seed from here becomes a tree
|
/// had decided. `tests/shrink.rs` is how a seed from here becomes a tree
|
||||||
/// small enough to read.
|
/// small enough to read.
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore = "a hundred seeds, rather than the seven the others check"]
|
#[ignore = "a hundred seeds, rather than the nine the others check"]
|
||||||
fn a_long_run_of_seeds_agrees() {
|
fn a_long_run_of_seeds_agrees() {
|
||||||
let seeds = std::env::var("IRIS_GENERATED_SEED")
|
let seeds = std::env::var("IRIS_GENERATED_SEED")
|
||||||
.ok()
|
.ok()
|
||||||
|
|||||||
@@ -349,3 +349,34 @@ fn a_span_out_of_room_shrinks_its_shares_and_not_its_fixed_lengths() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn only_a_pure_leftover_child_disappears_when_nothing_is_left() {
|
||||||
|
let mut h = Harness::new((100, 20));
|
||||||
|
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
|
let leftover = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
|
h.set_root((fixed, leftover).span(Dir::RIGHT));
|
||||||
|
|
||||||
|
assert_corners!(h, fixed, (0, 0), (100, 20));
|
||||||
|
assert_eq!(h.region(&leftover), None);
|
||||||
|
|
||||||
|
// An undrawn child remains a dependency of the span, so making room for
|
||||||
|
// it draws it without rebuilding the tree.
|
||||||
|
h.rsc[fixed].x = Some(Len::px(60));
|
||||||
|
h.frame();
|
||||||
|
assert_corners!(h, leftover, (60, 0), (100, 20));
|
||||||
|
|
||||||
|
let mut h = Harness::new((100, 20));
|
||||||
|
let fixed = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
|
let mixed = SetSize {
|
||||||
|
inner: rect(Color::BLUE).add_strong(&mut h.rsc),
|
||||||
|
x: Some(Len::px(20) + Len::LEFTOVER),
|
||||||
|
y: None,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root((fixed, mixed).span(Dir::RIGHT));
|
||||||
|
|
||||||
|
// Pixels and fractions still overflow; only a child whose entire length
|
||||||
|
// is leftover is omitted.
|
||||||
|
assert_corners!(h, mixed, (100, 0), (120, 20));
|
||||||
|
}
|
||||||
+83
-53
@@ -6,22 +6,22 @@ use iris::harness::{Harness, assert_corners};
|
|||||||
use iris::prelude::*;
|
use iris::prelude::*;
|
||||||
|
|
||||||
/// A leaf that counts its draws and reports whatever size it is given, so a
|
/// A leaf that counts its draws and reports whatever size it is given, so a
|
||||||
/// test can see what the retained path skipped.
|
/// test can see what the retained path skipped. One that reads its box in
|
||||||
|
/// pixels has a drawing that holds for that box alone.
|
||||||
struct Counted {
|
struct Counted {
|
||||||
draws: Rc<Cell<usize>>,
|
draws: Rc<Cell<usize>>,
|
||||||
size: Size,
|
size: Size,
|
||||||
dependence: OnResize,
|
reads_box: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for Counted {
|
impl Widget for Counted {
|
||||||
fn draw(&mut self, _: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
self.draws.set(self.draws.get() + 1);
|
self.draws.set(self.draws.get() + 1);
|
||||||
|
if self.reads_box {
|
||||||
|
painter.px_size();
|
||||||
|
}
|
||||||
self.size
|
self.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
self.dependence
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Counts(Rc<Cell<usize>>);
|
struct Counts(Rc<Cell<usize>>);
|
||||||
@@ -32,22 +32,63 @@ impl Counts {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn counted(h: &mut Harness, size: Size, dependence: OnResize) -> (WeakWidget<Counted>, Counts) {
|
fn counted(h: &mut Harness, size: Size, reads_box: bool) -> (WeakWidget<Counted>, Counts) {
|
||||||
let draws = Rc::new(Cell::new(0));
|
let draws = Rc::new(Cell::new(0));
|
||||||
let id = Counted {
|
let id = Counted {
|
||||||
draws: draws.clone(),
|
draws: draws.clone(),
|
||||||
size,
|
size,
|
||||||
dependence,
|
reads_box,
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
(id, Counts(draws))
|
(id, Counts(draws))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Layered {
|
||||||
|
children: [StrongWidget<Rect>; 2],
|
||||||
|
_revision: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Layered {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
painter.child_layer();
|
||||||
|
painter.widget(&self.children[0]);
|
||||||
|
painter.next_layer();
|
||||||
|
painter.widget(&self.children[1]);
|
||||||
|
Size::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_redrawn_layered_widget_keeps_the_layer_it_was_entered_on() {
|
||||||
|
let mut h = Harness::new((400, 200));
|
||||||
|
let children = [
|
||||||
|
rect(Color::RED).add_strong(&mut h.rsc),
|
||||||
|
rect(Color::BLUE).add_strong(&mut h.rsc),
|
||||||
|
];
|
||||||
|
let root = Layered {
|
||||||
|
children,
|
||||||
|
_revision: 0,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(root);
|
||||||
|
|
||||||
|
h.rsc[root]._revision += 1;
|
||||||
|
h.frame();
|
||||||
|
|
||||||
|
let label = h.rsc.widgets().label(root.id());
|
||||||
|
let active = h
|
||||||
|
.render
|
||||||
|
.debug(h.rsc.widgets(), label)
|
||||||
|
.find(|active| active.id == root.id())
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(active.layer, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/// A fixed-width leaf beside one that takes what is left over, so changing
|
/// A fixed-width leaf beside one that takes what is left over, so changing
|
||||||
/// the first hands the second a different box without the output changing.
|
/// the first hands the second a different box without the output changing.
|
||||||
fn pair(h: &mut Harness, leftover: OnResize) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
fn pair(h: &mut Harness, reads_box: bool) -> (WeakWidget<Counted>, Counts, WidgetId) {
|
||||||
let (first, _) = counted(h, Size::from((100, 200)), OnResize::Translate);
|
let (first, _) = counted(h, Size::from((100, 200)), false);
|
||||||
let (second, draws) = counted(h, Size::LEFTOVER, leftover);
|
let (second, draws) = counted(h, Size::LEFTOVER, reads_box);
|
||||||
h.set_root((first, second).span(Dir::RIGHT));
|
h.set_root((first, second).span(Dir::RIGHT));
|
||||||
(first, draws, second.id())
|
(first, draws, second.id())
|
||||||
}
|
}
|
||||||
@@ -55,7 +96,7 @@ fn pair(h: &mut Harness, leftover: OnResize) -> (WeakWidget<Counted>, Counts, Wi
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, draws, second) = pair(&mut h, OnResize::Scale);
|
let (first, draws, second) = pair(&mut h, false);
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
assert_corners!(h, second, (100, 0), (400, 200));
|
assert_corners!(h, second, (100, 0), (400, 200));
|
||||||
|
|
||||||
@@ -73,7 +114,7 @@ fn a_leaf_that_ignores_its_box_is_not_drawn_again_when_the_box_changes() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, draws, second) = pair(&mut h, OnResize::Redraw);
|
let (first, draws, second) = pair(&mut h, true);
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
|
|
||||||
h.rsc[first].size = Size::from((150, 200));
|
h.rsc[first].size = Size::from((150, 200));
|
||||||
@@ -88,8 +129,8 @@ fn a_leaf_that_depends_on_its_box_is_drawn_again_when_the_box_changes() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_span_child_that_declares_its_length_is_drawn_once() {
|
fn a_span_child_that_declares_its_length_is_drawn_once() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
|
let (told, told_draws) = counted(&mut h, Size::from((100, 200)), false);
|
||||||
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
|
let (asked, asked_draws) = counted(&mut h, Size::from((100, 200)), true);
|
||||||
// The span takes one child's length from its hint and has to draw the
|
// The span takes one child's length from its hint and has to draw the
|
||||||
// other to find out, so only the second is drawn before it is placed.
|
// other to find out, so only the second is drawn before it is placed.
|
||||||
let hinted = told.width(100).add(&mut h.rsc);
|
let hinted = told.width(100).add(&mut h.rsc);
|
||||||
@@ -106,7 +147,7 @@ fn a_span_child_that_declares_its_length_is_drawn_once() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_span_relays_out_when_a_child_it_measured_changes() {
|
fn a_span_relays_out_when_a_child_it_measured_changes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, _, second) = pair(&mut h, OnResize::Translate);
|
let (first, _, second) = pair(&mut h, false);
|
||||||
|
|
||||||
h.rsc[first].size = Size::from((250, 200));
|
h.rsc[first].size = Size::from((250, 200));
|
||||||
h.frame();
|
h.frame();
|
||||||
@@ -118,8 +159,8 @@ fn a_span_relays_out_when_a_child_it_measured_changes() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
fn a_repaint_that_keeps_its_size_does_not_relay_out() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, draws) = counted(&mut h, Size::from((100, 200)), OnResize::Translate);
|
let (first, draws) = counted(&mut h, Size::from((100, 200)), false);
|
||||||
let (second, _) = counted(&mut h, Size::LEFTOVER, OnResize::Translate);
|
let (second, _) = counted(&mut h, Size::LEFTOVER, false);
|
||||||
h.set_root((first, second).span(Dir::RIGHT));
|
h.set_root((first, second).span(Dir::RIGHT));
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
|
|
||||||
@@ -179,20 +220,20 @@ fn a_parent_that_only_read_a_hint_relays_out_when_the_hint_changes() {
|
|||||||
assert_corners!(h, inner, (0, 0), (400, 120));
|
assert_corners!(h, inner, (0, 0), (400, 120));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the output's size, which nothing but its own draw can put right.
|
/// Reads its box's size, which nothing but its own draw can put right.
|
||||||
struct ReadsOutput {
|
struct ReadsBox {
|
||||||
draws: Rc<Cell<usize>>,
|
draws: Rc<Cell<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Widget for ReadsOutput {
|
impl Widget for ReadsBox {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
self.draws.set(self.draws.get() + 1);
|
self.draws.set(self.draws.get() + 1);
|
||||||
Size::px(painter.output_size() / 4.0)
|
Size::px(painter.px_size() / 4.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads the output across one axis only, and says so: its drawing follows
|
/// Reads its box across one axis only, so its drawing holds for a taller
|
||||||
/// a taller box on its own, so only a wider one is worth a draw.
|
/// box on its own and only a wider one is worth a draw.
|
||||||
struct ReadsWidth {
|
struct ReadsWidth {
|
||||||
draws: Rc<Cell<usize>>,
|
draws: Rc<Cell<usize>>,
|
||||||
}
|
}
|
||||||
@@ -200,21 +241,14 @@ struct ReadsWidth {
|
|||||||
impl Widget for ReadsWidth {
|
impl Widget for ReadsWidth {
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
self.draws.set(self.draws.get() + 1);
|
self.draws.set(self.draws.get() + 1);
|
||||||
Size::px((painter.output_len(Axis::X) / 4.0, 20.0).into())
|
Size::px((painter.px_len(Axis::X) / 4.0, 20.0).into())
|
||||||
}
|
|
||||||
|
|
||||||
fn on_resize(&self, axis: Axis) -> OnResize {
|
|
||||||
match axis {
|
|
||||||
Axis::X => OnResize::Redraw,
|
|
||||||
Axis::Y => OnResize::Scale,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_resize_does_not_redraw_what_the_shader_can_move() {
|
fn a_resize_does_not_redraw_what_the_shader_can_move() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, OnResize::Scale);
|
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, false);
|
||||||
h.set_root(leaf);
|
h.set_root(leaf);
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
|
|
||||||
@@ -231,12 +265,12 @@ fn a_resize_does_not_redraw_what_the_shader_can_move() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The output is the root of the box chain, so a resize is a box that changed
|
/// The output is the root of the box chain, so a resize is a box that changed
|
||||||
/// length and `OnResize` answers for it -- there is not a second rule for the
|
/// length like any other -- there is not a second rule for the window. A
|
||||||
/// window. A drawing that does not scale is redrawn whichever box moved.
|
/// drawing that holds for one length is drawn again whichever box moved.
|
||||||
#[test]
|
#[test]
|
||||||
fn a_resize_redraws_what_does_not_scale() {
|
fn a_resize_redraws_what_does_not_scale() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, OnResize::Redraw);
|
let (leaf, draws) = counted(&mut h, Size::LEFTOVER, true);
|
||||||
h.set_root(leaf);
|
h.set_root(leaf);
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
|
|
||||||
@@ -248,10 +282,10 @@ fn a_resize_redraws_what_does_not_scale() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_resize_redraws_what_read_the_output() {
|
fn a_resize_redraws_what_read_its_box() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let draws = Rc::new(Cell::new(0));
|
let draws = Rc::new(Cell::new(0));
|
||||||
let leaf = ReadsOutput {
|
let leaf = ReadsBox {
|
||||||
draws: draws.clone(),
|
draws: draws.clone(),
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
@@ -265,7 +299,7 @@ fn a_resize_redraws_what_read_the_output() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_resize_only_redraws_read_output_axes() {
|
fn a_resize_only_redraws_read_axes() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let draws = Rc::new(Cell::new(0));
|
let draws = Rc::new(Cell::new(0));
|
||||||
let leaf = ReadsWidth {
|
let leaf = ReadsWidth {
|
||||||
@@ -309,7 +343,7 @@ fn subpixel_resize_changes_accumulate_from_the_last_layout() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn subpixel_box_changes_accumulate_from_the_last_draw() {
|
fn subpixel_box_changes_accumulate_from_the_last_draw() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let (first, draws, _) = pair(&mut h, OnResize::Redraw);
|
let (first, draws, _) = pair(&mut h, true);
|
||||||
let settled = draws.get();
|
let settled = draws.get();
|
||||||
|
|
||||||
for width in [100.02, 100.04, 100.05] {
|
for width in [100.02, 100.04, 100.05] {
|
||||||
@@ -327,7 +361,7 @@ fn subpixel_box_changes_accumulate_from_the_last_draw() {
|
|||||||
fn reporting_the_same_output_size_does_not_start_a_resize() {
|
fn reporting_the_same_output_size_does_not_start_a_resize() {
|
||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
let draws = Rc::new(Cell::new(0));
|
let draws = Rc::new(Cell::new(0));
|
||||||
let leaf = ReadsOutput {
|
let leaf = ReadsBox {
|
||||||
draws: draws.clone(),
|
draws: draws.clone(),
|
||||||
}
|
}
|
||||||
.add(&mut h.rsc);
|
.add(&mut h.rsc);
|
||||||
@@ -368,7 +402,7 @@ fn a_change_two_levels_under_its_reader_still_reaches_it() {
|
|||||||
// Every wrapper up to the outer pad read the size below it, so the outer
|
// Every wrapper up to the outer pad read the size below it, so the outer
|
||||||
// pad is what draws again -- and the span it hands the box to is the same
|
// pad is what draws again -- and the span it hands the box to is the same
|
||||||
// size as before, which is what lets a draw reuse its way past the leaf.
|
// size as before, which is what lets a draw reuse its way past the leaf.
|
||||||
let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), OnResize::Redraw);
|
let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), true);
|
||||||
let padded = leaf.pad(10).add(&mut h.rsc);
|
let padded = leaf.pad(10).add(&mut h.rsc);
|
||||||
let below = rect(Color::RED).add(&mut h.rsc);
|
let below = rect(Color::RED).add(&mut h.rsc);
|
||||||
h.set_root((padded, below).span(Dir::DOWN).pad(12));
|
h.set_root((padded, below).span(Dir::DOWN).pad(12));
|
||||||
@@ -380,8 +414,8 @@ fn a_change_two_levels_under_its_reader_still_reaches_it() {
|
|||||||
assert_corners!(h, below, (12, 232), (388, 388));
|
assert_corners!(h, below, (12, 232), (388, 388));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Claims its drawing survives its box changing length, and has a child so
|
/// Reads nothing of its box, so its drawing holds for any length, and has a
|
||||||
/// that the walk looking for what does not has one to reach.
|
/// child so that whatever asks about the subtree has one to reach.
|
||||||
struct Stretchy {
|
struct Stretchy {
|
||||||
inner: StrongWidget,
|
inner: StrongWidget,
|
||||||
draws: Rc<Cell<usize>>,
|
draws: Rc<Cell<usize>>,
|
||||||
@@ -392,10 +426,6 @@ impl Widget for Stretchy {
|
|||||||
self.draws.set(self.draws.get() + 1);
|
self.draws.set(self.draws.get() + 1);
|
||||||
painter.widget(&self.inner).size()
|
painter.widget(&self.inner).size()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_resize(&self, _: Axis) -> OnResize {
|
|
||||||
OnResize::Scale
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -430,8 +460,8 @@ fn a_widened_row_redraws_what_reads_its_length_and_nothing_else() {
|
|||||||
let mut h = Harness::new((400, 200));
|
let mut h = Harness::new((400, 200));
|
||||||
// What a transcript row is: something whose shaping depends on the width
|
// What a transcript row is: something whose shaping depends on the width
|
||||||
// it is given, beside something that only has to be the right shape.
|
// it is given, beside something that only has to be the right shape.
|
||||||
let (wraps, wrap_draws) = counted(&mut h, Size::LEFTOVER, OnResize::Redraw);
|
let (wraps, wrap_draws) = counted(&mut h, Size::LEFTOVER, true);
|
||||||
let (backing, back_draws) = counted(&mut h, Size::LEFTOVER, OnResize::Scale);
|
let (backing, back_draws) = counted(&mut h, Size::LEFTOVER, false);
|
||||||
let row = (backing, wraps).span(Dir::RIGHT).add(&mut h.rsc);
|
let row = (backing, wraps).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
h.set_root((bar, row).span(Dir::RIGHT));
|
h.set_root((bar, row).span(Dir::RIGHT));
|
||||||
@@ -455,9 +485,9 @@ fn a_declared_length_child_is_not_redrawn_when_the_box_around_it_grows() {
|
|||||||
// again would be for a width it does not have. The declared width is what
|
// again would be for a width it does not have. The declared width is what
|
||||||
// lets the span say that without drawing it: a width the span learnt by
|
// lets the span say that without drawing it: a width the span learnt by
|
||||||
// drawing the child in its own box is only an answer for that box.
|
// drawing the child in its own box is only an answer for that box.
|
||||||
let (counter, draws) = counted(&mut h, Size::from((80, 200)), OnResize::Redraw);
|
let (counter, draws) = counted(&mut h, Size::from((80, 200)), true);
|
||||||
let fixed = counter.width(80).add(&mut h.rsc);
|
let fixed = counter.width(80).add(&mut h.rsc);
|
||||||
let (leftover, _) = counted(&mut h, Size::LEFTOVER, OnResize::Scale);
|
let (leftover, _) = counted(&mut h, Size::LEFTOVER, false);
|
||||||
let row = (fixed, leftover).span(Dir::RIGHT).add(&mut h.rsc);
|
let row = (fixed, leftover).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
let bar = rect(Color::RED).width(100).add(&mut h.rsc);
|
||||||
h.set_root((bar, row).span(Dir::RIGHT));
|
h.set_root((bar, row).span(Dir::RIGHT));
|
||||||
|
|||||||
@@ -296,3 +296,46 @@ fn a_span_placed_once_in_a_box_its_answer_decided() {
|
|||||||
}
|
}
|
||||||
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
assert!(wrong.is_empty(), "{}", wrong.join("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reports a width derived from the box it is asked in. Reading through the
|
||||||
|
/// painter is its declaration that the answer holds for that width only.
|
||||||
|
struct Wider {
|
||||||
|
extra: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for Wider {
|
||||||
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
|
Size {
|
||||||
|
x: Len::px(painter.px_len(Axis::X) + self.extra),
|
||||||
|
y: Len::LEFTOVER,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn plant_wider(h: &mut Harness, extra: f32) -> (WeakWidget<Wider>, WidgetId) {
|
||||||
|
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 root = Aligned {
|
||||||
|
inner: scroll.add_strong(&mut h.rsc),
|
||||||
|
align: Align {
|
||||||
|
x: Some(AxisAlign::Neg),
|
||||||
|
y: None,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.set_root(root);
|
||||||
|
(content, scroll.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_scrolls_retained_answer_is_the_one_a_cold_layout_asks_for() {
|
||||||
|
let mut warm = Harness::new((100, 100));
|
||||||
|
let (content, scroll) = plant_wider(&mut warm, 50.0);
|
||||||
|
warm.rsc[content].extra = 70.0;
|
||||||
|
warm.frame();
|
||||||
|
|
||||||
|
let mut cold = Harness::new((100, 100));
|
||||||
|
let (_, cold_scroll) = plant_wider(&mut cold, 70.0);
|
||||||
|
|
||||||
|
assert_eq!(warm.region(&scroll), cold.region(&cold_scroll));
|
||||||
|
}
|
||||||
Reference in new issue
Block a user