Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e44dea34b4 | ||
|
|
a0693acc56 | ||
|
|
25e456e0b5 | ||
|
|
53b00c68e9 |
No files matched your search
@@ -74,4 +74,12 @@ impl ActiveData {
|
|||||||
pub fn holds_at(&self, px: crate::PxVec2) -> bool {
|
pub fn holds_at(&self, px: crate::PxVec2) -> bool {
|
||||||
self.holds[0].contains(px.x) && self.holds[1].contains(px.y)
|
self.holds[0].contains(px.x) && self.holds[1].contains(px.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether what it answered still stands for a box of these pixel
|
||||||
|
/// lengths -- the box it was asked in, where `holds` is about the box its
|
||||||
|
/// answer then chose.
|
||||||
|
pub fn answers_at(&self, px: crate::PxVec2) -> bool {
|
||||||
|
let (_, holds) = self.answer;
|
||||||
|
holds[0].contains(px.x) && holds[1].contains(px.y)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+41
-19
@@ -139,20 +139,30 @@ impl<'a> Painter<'a> {
|
|||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
self.widget_at(id, region, [false; 2])
|
self.widget_at(id, region, region.size(), [false; 2])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a widget in a box this widget chose from the widget's own
|
/// Draws a widget in `region`, saying what the answer means.
|
||||||
/// answer along the `decided` axes. On those the answer is not placed
|
///
|
||||||
/// inside the box again: it already is the box, and a fraction the
|
/// `reports_of` is what a fraction the child reports is a fraction of, as
|
||||||
/// widget reported, taken of this box a second time, would shrink it
|
/// lengths of this widget's own box. It is the box the child was given
|
||||||
/// twice. A container uses this where it hands back exactly what a child
|
/// wherever that box is the child's whole area -- a pad's inset, a stack
|
||||||
/// asked for -- a span placing a child at the length it reported, a
|
/// child, a scroll's content -- and a span passes its own extent along
|
||||||
/// scroll giving its content the content's own length.
|
/// the row instead: it offers each child the room left from its cursor,
|
||||||
|
/// because a text has to wrap at the width actually there, while
|
||||||
|
/// `rel(0.5)` still means half the span wherever the child sits in it.
|
||||||
|
///
|
||||||
|
/// A `decided` axis is one where this box was chosen from the widget's
|
||||||
|
/// own answer. On those the answer is not placed inside the box again: it
|
||||||
|
/// already is the box, and a fraction taken of it a second time would
|
||||||
|
/// shrink it twice. A container uses that where it hands back exactly
|
||||||
|
/// what a child asked for -- a span placing a child at the length it
|
||||||
|
/// reported, a scroll giving its content the content's own length.
|
||||||
pub fn widget_at<'s, W: ?Sized>(
|
pub fn widget_at<'s, W: ?Sized>(
|
||||||
&'s mut self,
|
&'s mut self,
|
||||||
id: &'s StrongWidget<W>,
|
id: &'s StrongWidget<W>,
|
||||||
region: UiRegion,
|
region: UiRegion,
|
||||||
|
reports_of: UiVec2,
|
||||||
decided: [bool; 2],
|
decided: [bool; 2],
|
||||||
) -> DrawResult<'s, 'a, W> {
|
) -> DrawResult<'s, 'a, W> {
|
||||||
let region_node = self.rsc.widgets().is_region_node(id.id());
|
let region_node = self.rsc.widgets().is_region_node(id.id());
|
||||||
@@ -222,18 +232,10 @@ impl<'a> Painter<'a> {
|
|||||||
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
||||||
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
||||||
}
|
}
|
||||||
// The answer as it was given. A fraction in it is a fraction of this
|
|
||||||
// widget's box, which is the same thing a rule beside the child
|
|
||||||
// means and the same thing for every box this widget hands out: a
|
|
||||||
// span offers each child the room left from its cursor, because a
|
|
||||||
// text has to wrap at the width actually there, and `rel(0.5)` is
|
|
||||||
// still half the span. Padding is outside what it pads for the same
|
|
||||||
// reason -- inset the fraction and a child's `rel` would mean the
|
|
||||||
// inner box while its `px` meant the outer one.
|
|
||||||
DrawResult {
|
DrawResult {
|
||||||
child: id,
|
child: id,
|
||||||
painter: self,
|
painter: self,
|
||||||
size,
|
size: in_parent_frame(size, reports_of, declared),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,12 +269,14 @@ impl<'a> Painter<'a> {
|
|||||||
|
|
||||||
/// A child's length in the box it is about to be offered, if it can be
|
/// A child's length in the box it is about to be offered, if it can be
|
||||||
/// had without drawing it: from its hint, or from a drawing it already
|
/// had without drawing it: from its hint, or from a drawing it already
|
||||||
/// has that holds for that box.
|
/// has that holds for that box. `reports_of` is what a fraction in the
|
||||||
|
/// answer is a fraction of, as it is for [`Self::widget_at`].
|
||||||
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,
|
||||||
|
reports_of: UiVec2,
|
||||||
) -> Option<LayoutLen> {
|
) -> Option<LayoutLen> {
|
||||||
let declared = self.declared_lens(child);
|
let declared = self.declared_lens(child);
|
||||||
let align = self.rsc.widgets().alignment(child.id());
|
let align = self.rsc.widgets().alignment(child.id());
|
||||||
@@ -298,7 +302,7 @@ impl<'a> Painter<'a> {
|
|||||||
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
for (axis, under) in AXES.into_iter().zip(self.under.iter_mut()) {
|
||||||
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
*under = under.and(holds[axis as usize].through(local.axis(axis).len()));
|
||||||
}
|
}
|
||||||
Some(size.axis(axis))
|
Some(in_parent_frame(size, reports_of, declared).axis(axis))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether this is the first box a child is asked about in during a draw
|
/// Whether this is the first box a child is asked about in during a draw
|
||||||
@@ -517,6 +521,24 @@ impl PrimitiveLike for &TextureHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A child's answer as lengths of the parent's own box. A widget reports a
|
||||||
|
/// fraction, and `reports_of` is the length that fraction is of: the box the
|
||||||
|
/// child was given wherever that is the child's whole area, and the parent's
|
||||||
|
/// own extent wherever the box is a positional remainder, as a span's is
|
||||||
|
/// after an earlier child. Pixels come through untouched either way, being
|
||||||
|
/// that many pixels wherever they end up. A declared axis is already the
|
||||||
|
/// parent's: it resolved the rule in its own box, and the rule is what the
|
||||||
|
/// report says.
|
||||||
|
fn in_parent_frame(size: Size, reports_of: UiVec2, declared: [Option<LayoutLen>; 2]) -> Size {
|
||||||
|
let mut size = size;
|
||||||
|
for (axis, declared) in AXES.into_iter().zip(declared) {
|
||||||
|
if declared.is_none() {
|
||||||
|
*size.axis_mut(axis) = size.axis(axis).within_len(reports_of.axis(axis));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
/// What a widget declares a length of its box to be. `leftover` is not one: a
|
/// What a widget declares a length of its box to be. `leftover` is not one: a
|
||||||
/// share of what is left over is only a length to the widget dividing one,
|
/// share of what is left over is only a length to the widget dividing one,
|
||||||
/// so it passes up in the size instead.
|
/// so it passes up in the size instead.
|
||||||
|
|||||||
+56
-40
@@ -43,7 +43,9 @@ pub struct UiRenderState {
|
|||||||
|
|
||||||
old_root: Option<WidgetId>,
|
old_root: Option<WidgetId>,
|
||||||
/// Whether the output has changed since the last update. A frame is
|
/// Whether the output has changed since the last update. A frame is
|
||||||
/// owed for that whether or not anything has to be drawn again.
|
/// owed for that whether or not anything has to be drawn again: every
|
||||||
|
/// fraction becomes pixels against the output, in the shader's uniform
|
||||||
|
/// as well as here.
|
||||||
resized: bool,
|
resized: bool,
|
||||||
/// A widget's move slot, which outlives any one `ActiveData`: a redraw
|
/// A widget's move slot, which outlives any one `ActiveData`: a redraw
|
||||||
/// replaces that while its children go on pointing at the slot.
|
/// replaces that while its children go on pointing at the slot.
|
||||||
@@ -83,13 +85,28 @@ impl UiRenderState {
|
|||||||
/// size is applied where a fraction becomes pixels -- here in `to_px`,
|
/// size is applied where a fraction becomes pixels -- here in `to_px`,
|
||||||
/// and in the shader by its uniform. A resize therefore rewrites no
|
/// and in the shader by its uniform. A resize therefore rewrites no
|
||||||
/// retained entry at all.
|
/// retained entry at all.
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
///
|
||||||
|
/// The root is the only widget a resize marks, and only where the new
|
||||||
|
/// output falls outside what its answer holds for: that range is the
|
||||||
|
/// intersection of everything under it, so admitting the new output says
|
||||||
|
/// the whole tree still stands. Where it does not, the ordinary walk
|
||||||
|
/// draws the root, and each widget's own range decides how far down the
|
||||||
|
/// new length reaches.
|
||||||
|
pub fn resize(&mut self, size: impl Into<Vec2>, widgets: &mut Widgets) {
|
||||||
let size = PxVec2::from_f32(size.into());
|
let size = PxVec2::from_f32(size.into());
|
||||||
if size == self.output_size {
|
if size == self.output_size {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
self.output_size = size;
|
self.output_size = size;
|
||||||
self.resized = true;
|
self.resized = true;
|
||||||
|
let Some(root) = self.old_root else { return };
|
||||||
|
let stands = self
|
||||||
|
.active
|
||||||
|
.get(&root)
|
||||||
|
.is_some_and(|active| active.answers_at(active.given_len.to_px(size)));
|
||||||
|
if !stands {
|
||||||
|
widgets.needs_redraw.insert(root);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The root is asked about in the output: the window is where a fraction
|
/// The root is asked about in the output: the window is where a fraction
|
||||||
@@ -143,17 +160,6 @@ impl UiRenderState {
|
|||||||
if self.root_changed(root) {
|
if self.root_changed(root) {
|
||||||
self.redraw_all(root, rsc);
|
self.redraw_all(root, rsc);
|
||||||
self.old_root = root.map(|r| r.id());
|
self.old_root = root.map(|r| r.id());
|
||||||
} else if let Some(root) = root
|
|
||||||
&& self.resized
|
|
||||||
{
|
|
||||||
// The output is the root's box, so a resize is that box changing
|
|
||||||
// length, found the way every other box change is found. Before
|
|
||||||
// anything dirty settles, so that whatever a new output draws
|
|
||||||
// again is drawn once, in the box it will have.
|
|
||||||
let region = Self::root_region(root.id(), rsc.widgets());
|
|
||||||
let info = self.root_info(region);
|
|
||||||
let answer = self.draw_inner(root.id(), region, info, None, rsc);
|
|
||||||
self.active.get_mut(&root.id()).unwrap().answer = answer;
|
|
||||||
}
|
}
|
||||||
self.resized = false;
|
self.resized = false;
|
||||||
if rsc.widgets().has_updates() {
|
if rsc.widgets().has_updates() {
|
||||||
@@ -196,14 +202,10 @@ impl UiRenderState {
|
|||||||
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
diag::draw_request(id, info.parent, region, info.px, info.region_node);
|
||||||
}
|
}
|
||||||
let align = rsc.widgets().alignment(id);
|
let align = rsc.widgets().alignment(id);
|
||||||
// Nothing this widget has is an answer while something it measured
|
// Nothing this widget measured can be dirty while it draws: layout is
|
||||||
// is dirty: settling that changes what it would report, and a widget
|
// one bottom-up walk, so anything deeper has settled or deferred to
|
||||||
// settled inside its parent's draw tells nobody -- the comparison
|
// its own parent, and a deferred one leaves that parent marked.
|
||||||
// that marks a reader is in `redraw`, which is not what asked here.
|
let stale = rsc.widgets().needs_redraw.contains(&id);
|
||||||
// Both retained routes are an answer, so the question is asked once
|
|
||||||
// rather than by each of them.
|
|
||||||
let stale =
|
|
||||||
rsc.widgets().needs_redraw.contains(&id) || self.dirty_size_under(id, rsc.widgets());
|
|
||||||
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
let replace_answer = self.answer_invalid.remove(&id) || (self.replace_answers && stale);
|
||||||
let retained = match replace_answer || stale {
|
let retained = match replace_answer || stale {
|
||||||
true => None,
|
true => None,
|
||||||
@@ -251,7 +253,18 @@ impl UiRenderState {
|
|||||||
active.answer = settled;
|
active.answer = settled;
|
||||||
active.decided = info.decided;
|
active.decided = info.decided;
|
||||||
active.own_align = align;
|
active.own_align = align;
|
||||||
active.depth = info.depth;
|
// A subtree can be reused whole under a different parent -- same box,
|
||||||
|
// same layer, same region node -- and nothing in the drawing says it
|
||||||
|
// changed hands. Two things read who its parent is: a deferral, which
|
||||||
|
// marks whoever has it to draw, and the old parent's list of children,
|
||||||
|
// which its next draw undraws whatever is missing from.
|
||||||
|
let old_parent = std::mem::replace(&mut active.parent, info.parent);
|
||||||
|
if old_parent != info.parent
|
||||||
|
&& let Some(old_parent) = old_parent
|
||||||
|
&& let Some(old_parent) = self.active.get_mut(&old_parent)
|
||||||
|
{
|
||||||
|
old_parent.children.retain(|child| *child != id);
|
||||||
|
}
|
||||||
settled
|
settled
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,7 +497,7 @@ impl UiRenderState {
|
|||||||
parent_move: MoveIdx,
|
parent_move: MoveIdx,
|
||||||
widgets: &Widgets,
|
widgets: &Widgets,
|
||||||
) -> Option<(Size, [Holds; 2])> {
|
) -> Option<(Size, [Holds; 2])> {
|
||||||
if widgets.needs_redraw.contains(&id) || self.dirty_size_under(id, widgets) {
|
if widgets.needs_redraw.contains(&id) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let active = self.active.get(&id)?;
|
let active = self.active.get(&id)?;
|
||||||
@@ -509,22 +522,7 @@ impl UiRenderState {
|
|||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let (size, holds) = active.answer;
|
active.answers_at(info.px).then_some(active.answer)
|
||||||
(holds[0].contains(info.px.x) && holds[1].contains(info.px.y)).then_some((size, holds))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether anything whose size this widget's own size was read from is
|
|
||||||
/// dirty, which makes what it would answer not yet known. It also keeps
|
|
||||||
/// a reader that asks first from laying out twice, which is all it was
|
|
||||||
/// here for while a changed size was thought to reach its reader in any
|
|
||||||
/// order; it does not, where the change settles inside the reader's own
|
|
||||||
/// draw.
|
|
||||||
fn dirty_size_under(&self, id: WidgetId, widgets: &Widgets) -> bool {
|
|
||||||
self.active.get(&id).is_some_and(|active| {
|
|
||||||
active.size_deps.iter().any(|child| {
|
|
||||||
widgets.needs_redraw.contains(child) || self.dirty_size_under(*child, widgets)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The pixel lengths of the box a widget was given and of the box it was
|
/// The pixel lengths of the box a widget was given and of the box it was
|
||||||
@@ -638,12 +636,12 @@ impl UiRenderState {
|
|||||||
self.remap_subtree(id, &remap, info.parent_move, rsc);
|
self.remap_subtree(id, &remap, info.parent_move, rsc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.redepth(id, info.depth);
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
active.region = region;
|
active.region = region;
|
||||||
active.given = region;
|
active.given = region;
|
||||||
active.given_len = info.given_len;
|
active.given_len = info.given_len;
|
||||||
active.offer_len = info.offer_len;
|
active.offer_len = info.offer_len;
|
||||||
active.depth = info.depth;
|
|
||||||
#[cfg(feature = "layout-diagnostics")]
|
#[cfg(feature = "layout-diagnostics")]
|
||||||
{
|
{
|
||||||
match (moved, has_region_node) {
|
match (moved, has_region_node) {
|
||||||
@@ -667,6 +665,24 @@ impl UiRenderState {
|
|||||||
Some(answer)
|
Some(answer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A reused subtree keeps its shape, so every widget in it moves by the
|
||||||
|
/// same amount -- and where the top of it did not move, none of it did,
|
||||||
|
/// which is what makes this free in the ordinary case.
|
||||||
|
fn redepth(&mut self, id: WidgetId, depth: usize) {
|
||||||
|
let Some(active) = self.active.get_mut(&id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if active.depth == depth {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
active.depth = depth;
|
||||||
|
let children = active.children.len();
|
||||||
|
for index in 0..children {
|
||||||
|
let child = self.active[&id].children[index];
|
||||||
|
self.redepth(child, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Re-expresses an ordinary retained subtree in a new parent region.
|
/// Re-expresses an ordinary retained subtree in a new parent region.
|
||||||
/// An independently movable descendant needs only its own region changed;
|
/// An independently movable descendant needs only its own region changed;
|
||||||
/// its contents stay in that region's coordinate space.
|
/// its contents stay in that region's coordinate space.
|
||||||
|
|||||||
+1
-1
@@ -251,7 +251,7 @@ impl<State: DefaultAppState> AppState for DefaultApp<State> {
|
|||||||
ui_state.renderer.draw();
|
ui_state.renderer.draw();
|
||||||
}
|
}
|
||||||
WindowEvent::Resized(size) => {
|
WindowEvent::Resized(size) => {
|
||||||
render.resize((size.width, size.height));
|
render.resize((size.width, size.height), rsc.widgets_mut());
|
||||||
ui_state.renderer.resize(size)
|
ui_state.renderer.resize(size)
|
||||||
}
|
}
|
||||||
WindowEvent::KeyboardInput { event, .. } => {
|
WindowEvent::KeyboardInput { event, .. } => {
|
||||||
|
|||||||
+3
-3
@@ -144,9 +144,9 @@ impl Harness {
|
|||||||
// bound that comes with `SyncSender` is far past anything a test
|
// bound that comes with `SyncSender` is far past anything a test
|
||||||
// leaves unread.
|
// leaves unread.
|
||||||
let (send, updates) = sync_channel(1024);
|
let (send, updates) = sync_channel(1024);
|
||||||
let rsc = DefaultRsc::init(Arc::new(Queue(send)));
|
let mut rsc = DefaultRsc::init(Arc::new(Queue(send)));
|
||||||
let mut render = UiRenderState::new();
|
let mut render = UiRenderState::new();
|
||||||
render.resize(size);
|
render.resize(size, rsc.widgets_mut());
|
||||||
Self {
|
Self {
|
||||||
rsc,
|
rsc,
|
||||||
render,
|
render,
|
||||||
@@ -161,7 +161,7 @@ impl Harness {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
pub fn resize(&mut self, size: impl Into<Vec2>) {
|
||||||
self.render.resize(size);
|
self.render.resize(size, self.rsc.widgets_mut());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes a length rule after the fact, the way `.width()` sets one.
|
/// Changes a length rule after the fact, the way `.width()` sets one.
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ impl Widget for Pad {
|
|||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||||
// The inner's own alignment, not the near edge. This reports the
|
// The inner's own alignment, not the near edge. This reports the
|
||||||
// inner's size plus the padding, so where the box is that answer the
|
// inner's size plus the padding, so where the box is that answer the
|
||||||
// inner is exactly what it asked for and alignment has no room to
|
// inset box is exactly the inner and alignment has no room to move
|
||||||
// move it; where the box is bigger -- a share of a row, a rule over
|
// it; where the box is bigger -- a share of a row, a rule over this
|
||||||
// this widget -- the slack is the inner's to sit in, and forcing the
|
// widget -- the slack is the inner's to sit in, and forcing the near
|
||||||
// near edge pinned it to a corner it had not asked for.
|
// edge pinned it to a corner it had not asked for.
|
||||||
let inner = painter
|
let inner = painter
|
||||||
.widget_within(&self.inner, self.padding.region())
|
.widget_within(&self.inner, self.padding.region())
|
||||||
.size();
|
.size();
|
||||||
@@ -29,45 +29,6 @@ impl Widget for Pad {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Room taken off the inside rather than added round the outside: the child
|
|
||||||
/// draws in what is left once both edges are gone, and this widget is
|
|
||||||
/// exactly as long as the box it was given.
|
|
||||||
///
|
|
||||||
/// So `rel(1.0)` under an [`Inset`] is the room inside it, where the same
|
|
||||||
/// rule under a [`Pad`] is the pad's whole box and overflows it by the
|
|
||||||
/// padding. Both are wanted; which one a layout means is which widget it
|
|
||||||
/// reaches for.
|
|
||||||
pub struct Inset {
|
|
||||||
pub padding: Padding,
|
|
||||||
pub inner: StrongWidget,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Widget for Inset {
|
|
||||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
|
||||||
let region = self.padding.inset_region();
|
|
||||||
let inner = painter.widget_within(&self.inner, region).size();
|
|
||||||
// What a fraction the child reported is a fraction of is this
|
|
||||||
// widget's to say, and it says the room inside: the child asked for
|
|
||||||
// a part of the box it drew in, and that box is shorter than this
|
|
||||||
// one by both edges. Then the edges go back on, so this widget is
|
|
||||||
// its child and the room taken off around it.
|
|
||||||
let (x, y) = (
|
|
||||||
inner.x.within_len(region.x.len()),
|
|
||||||
inner.y.within_len(region.y.len()),
|
|
||||||
);
|
|
||||||
Size {
|
|
||||||
x: LayoutLen {
|
|
||||||
px: x.px + self.padding.left + self.padding.right,
|
|
||||||
..x
|
|
||||||
},
|
|
||||||
y: LayoutLen {
|
|
||||||
px: y.px + self.padding.top + self.padding.bottom,
|
|
||||||
..y
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Padding {
|
pub struct Padding {
|
||||||
pub left: Px,
|
pub left: Px,
|
||||||
pub right: Px,
|
pub right: Px,
|
||||||
@@ -92,24 +53,7 @@ impl Padding {
|
|||||||
bottom: amt,
|
bottom: amt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// The box a [`Pad`] gives its child: as long as the pad's own, moved in
|
|
||||||
/// by the near edge. Padding is outside what it pads, so a fraction the
|
|
||||||
/// child asks for is a fraction of the same length whether a rule beside
|
|
||||||
/// it states one or it reports one, and its pixels are the same pixels.
|
|
||||||
/// Shrinking the box instead would make `rel` mean the inner box while
|
|
||||||
/// `px` meant the outer one. [`Inset`] is the widget that shrinks.
|
|
||||||
pub fn region(&self) -> UiRegion {
|
pub fn region(&self) -> UiRegion {
|
||||||
let mut region = UiRegion::FULL;
|
|
||||||
region.x.start.px += self.left;
|
|
||||||
region.y.start.px += self.top;
|
|
||||||
region.x.end.px += self.left;
|
|
||||||
region.y.end.px += self.top;
|
|
||||||
region
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The box an [`Inset`] gives its child: shorter than its own by both
|
|
||||||
/// edges, so what the child fills is the room left inside.
|
|
||||||
pub fn inset_region(&self) -> UiRegion {
|
|
||||||
let mut region = UiRegion::FULL;
|
let mut region = UiRegion::FULL;
|
||||||
region.x.start.px += self.left;
|
region.x.start.px += self.left;
|
||||||
region.y.start.px += self.top;
|
region.y.start.px += self.top;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ impl Widget for Scroll {
|
|||||||
let container_len = painter.px_len(self.axis);
|
let container_len = painter.px_len(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 draw it at the scrolled offset.
|
// not already known, then draw it at the scrolled offset.
|
||||||
let answer_len = match painter.known_len(&self.inner, self.axis, UiRegion::FULL) {
|
let whole = UiRegion::FULL;
|
||||||
|
let answer_len = match painter.known_len(&self.inner, self.axis, whole, whole.size()) {
|
||||||
Some(len) => len,
|
Some(len) => len,
|
||||||
None => painter.widget(&self.inner).size().axis(self.axis),
|
None => painter.widget(&self.inner).size().axis(self.axis),
|
||||||
};
|
};
|
||||||
@@ -63,7 +64,7 @@ impl Widget for Scroll {
|
|||||||
region = region.offset(offset);
|
region = region.offset(offset);
|
||||||
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);
|
||||||
}
|
}
|
||||||
painter.widget_at(&self.inner, region, [true; 2]);
|
painter.widget_at(&self.inner, region, region.size(), [true; 2]);
|
||||||
// What it occupies is its box, on both axes: it clips its content to
|
// What it occupies is its box, on both axes: it clips its content to
|
||||||
// that box, so it can neither take less of one nor honestly ask for
|
// that box, so it can neither take less of one nor honestly ask for
|
||||||
// more. The content's length is what it scrolls through, not what it
|
// more. The content's length is what it scrolls through, not what it
|
||||||
|
|||||||
+29
-40
@@ -21,12 +21,14 @@ impl Widget for Span {
|
|||||||
}
|
}
|
||||||
let region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
let region = UiRegion::from_axis(axis, span, UiSpan::FULL);
|
||||||
// Offered the room left from the cursor, because a text has to
|
// Offered the room left from the cursor, because a text has to
|
||||||
// wrap at the width actually there, while what it reports is a
|
// wrap at the width actually there, but reporting a fraction of
|
||||||
// fraction of the whole row: `rel(0.5)` is half the span
|
// the whole row: `rel(0.5)` is half the span whatever else is in
|
||||||
// whatever else is in it and wherever this child sits.
|
// it and wherever this child sits among them.
|
||||||
let len = match painter.known_len(child, axis, region) {
|
let len = match painter.known_len(child, axis, region, UiVec2::FULL_SIZE) {
|
||||||
Some(len) => len,
|
Some(len) => len,
|
||||||
None => painter.widget_at(child, region, [false; 2]).len(axis),
|
None => painter
|
||||||
|
.widget_at(child, region, UiVec2::FULL_SIZE, [false; 2])
|
||||||
|
.len(axis),
|
||||||
};
|
};
|
||||||
cursor.px += len.px + self.gap;
|
cursor.px += len.px + self.gap;
|
||||||
cursor.rel += len.rel;
|
cursor.rel += len.rel;
|
||||||
@@ -44,43 +46,26 @@ impl Widget for Span {
|
|||||||
|sum, len| sum + *len,
|
|sum, len| sum + *len,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// What is left for the shares to divide: the box less everything
|
||||||
|
// fixed, as a length of the box rather than a number of pixels.
|
||||||
|
let room = Len::rel_max() - Len::from_parts(total.rel, total.px);
|
||||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||||
// beside 300 px is full at 600 and overfull at 400. The room to
|
// beside 300 px is full at 600 and overfull at 400. Asked of `room`
|
||||||
// divide is `len * fixed - total.px`, and the length where it runs
|
// itself, and answered back through the same expression, so the
|
||||||
// out is exactly the box a parent sizing itself from this answer
|
// boundary is the drawing's own and not a second way of finding it:
|
||||||
// hands back -- which is why this used to need a margin either side
|
// the three cases a rounded division needed -- the fixed parts
|
||||||
// of the boundary, and why it does not now: that box and this sum are
|
// growing slower than the box, faster, or exactly with it -- are the
|
||||||
// whole counts of the same step, and both routes to it land on the
|
// sign of `room.rel`, which `through` already reads. What the
|
||||||
// same count. What the generated oracle checks is the consequence,
|
// generated oracle checks is the consequence, since which children
|
||||||
// since which children exist at all turns on this.
|
// exist at all turns on this.
|
||||||
let fixed = Rel::ONE - total.rel;
|
|
||||||
let mut shares = false;
|
let mut shares = false;
|
||||||
if total.leftover > Weight::ZERO {
|
if total.leftover > Weight::ZERO {
|
||||||
let current = painter.px_len(axis);
|
shares = room.to_px(painter.px_len(axis)) > Px::ZERO;
|
||||||
let holds = if fixed > Rel::ZERO {
|
let holds = match shares {
|
||||||
// The box length the fixed parts alone fill.
|
true => Holds::from(Px::STEP..=Px::MAX),
|
||||||
let full = total.px.div(fixed);
|
false => Holds::from(Px::MIN..=Px::ZERO),
|
||||||
shares = current > full;
|
|
||||||
match shares {
|
|
||||||
true => Holds::from(full.next_up()..=Px::MAX),
|
|
||||||
false => Holds::from(Px::MIN..=full),
|
|
||||||
}
|
|
||||||
} else if fixed < Rel::ZERO {
|
|
||||||
// The relative parts grow faster than the box does, so here
|
|
||||||
// a shorter box is the one that leaves room.
|
|
||||||
let full = total.px.div(fixed);
|
|
||||||
shares = current < full;
|
|
||||||
match shares {
|
|
||||||
true => Holds::from(Px::MIN..=full.next_down()),
|
|
||||||
false => Holds::from(full..=Px::MAX),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// The relative parts take exactly the box, whatever it is, so
|
|
||||||
// the only room is what negative pixels leave.
|
|
||||||
shares = total.px < Px::ZERO;
|
|
||||||
Holds::ANY
|
|
||||||
};
|
};
|
||||||
painter.holds(axis, holds);
|
painter.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
|
||||||
@@ -97,7 +82,6 @@ impl Widget for Span {
|
|||||||
// row.
|
// row.
|
||||||
let mut fixed = Len::rel_min();
|
let mut fixed = Len::rel_min();
|
||||||
let mut taken = Weight::ZERO;
|
let mut taken = Weight::ZERO;
|
||||||
let room = Len::rel_max() - Len::from_parts(total.rel, total.px);
|
|
||||||
let mut start = Len::rel_min();
|
let mut start = Len::rel_min();
|
||||||
let mut ortho = LayoutLen::ZERO;
|
let mut ortho = LayoutLen::ZERO;
|
||||||
for (child, len) in self.children.iter().zip(&lens) {
|
for (child, len) in self.children.iter().zip(&lens) {
|
||||||
@@ -126,7 +110,12 @@ impl Widget for Span {
|
|||||||
// Along the row this box is the child's own answer, so the answer
|
// Along the row this box is the child's own answer, so the answer
|
||||||
// is not placed in it again; across it the child sits where its
|
// is not placed in it again; across it the child sits where its
|
||||||
// alignment says.
|
// alignment says.
|
||||||
let placed = painter.widget_at(child, region, [axis == Axis::X, axis == Axis::Y]);
|
let placed = painter.widget_at(
|
||||||
|
child,
|
||||||
|
region,
|
||||||
|
UiVec2::FULL_SIZE,
|
||||||
|
[axis == Axis::X, axis == Axis::Y],
|
||||||
|
);
|
||||||
if shrinks {
|
if shrinks {
|
||||||
let used = placed.len(!axis);
|
let used = placed.len(!axis);
|
||||||
// Choosing between a fixed and a relative length from the
|
// Choosing between a fixed and a relative length from the
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl Widget for Stack {
|
|||||||
// child is handed a box that owes nothing to its own answer, and
|
// child is handed a box that owes nothing to its own answer, and
|
||||||
// where it sits in one bigger than itself is its own business.
|
// where it sits in one bigger than itself is its own business.
|
||||||
match sizing == Some(i) {
|
match sizing == Some(i) {
|
||||||
true => painter.widget_at(child, region, [true; 2]),
|
true => painter.widget_at(child, region, region.size(), [true; 2]),
|
||||||
false => painter.widget_within(child, region),
|
false => painter.widget_within(child, region),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,16 +12,6 @@ widget_trait! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inset(self, padding: impl Into<Padding>) -> impl WidgetFn<Rsc, Inset> {
|
|
||||||
// Room taken off the inside, where `pad` adds it round the outside:
|
|
||||||
// this is as long as the box it is given and the child fills what is
|
|
||||||
// left of it.
|
|
||||||
|state| Inset {
|
|
||||||
padding: padding.into(),
|
|
||||||
inner: self.add_strong(state),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn align(self, align: impl Into<Align>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
fn align(self, align: impl Into<Align>) -> impl WidgetIdFn<Rsc, WL::Widget> {
|
||||||
// An axis left out keeps whatever it had, which is centered unless
|
// An axis left out keeps whatever it had, which is centered unless
|
||||||
// something else set it.
|
// something else set it.
|
||||||
|
|||||||
+8
-25
@@ -82,13 +82,11 @@ fn a_text_in_a_span_wraps_at_the_room_left_rather_than_the_whole_row() {
|
|||||||
assert!(crowded > whole_row, "{crowded} against {whole_row}");
|
assert!(crowded > whole_row, "{crowded} against {whole_row}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Padding is outside what it pads, so a fraction under one is a fraction of
|
/// The same reading through a pad: its inset is the whole box less the
|
||||||
/// the box the padding is measured from: half of a 400 px row is 200, and
|
/// padding, so half of the inset plus the padding is half the box plus one
|
||||||
/// the pad is that plus both edges. Inset it instead and `rel` would mean the
|
/// padding, not two.
|
||||||
/// inner box while `px` meant the outer one, which is the one thing a length
|
|
||||||
/// may not do.
|
|
||||||
#[test]
|
#[test]
|
||||||
fn a_pad_is_outside_the_fraction_its_child_asked_for() {
|
fn a_pad_reports_a_fraction_of_its_inset_as_a_fraction_of_its_box() {
|
||||||
let mut h = Harness::new((400, 100));
|
let mut h = Harness::new((400, 100));
|
||||||
let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc);
|
let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc);
|
||||||
let padded = (inner,).span(Dir::RIGHT).pad(10).add(&mut h.rsc);
|
let padded = (inner,).span(Dir::RIGHT).pad(10).add(&mut h.rsc);
|
||||||
@@ -97,23 +95,8 @@ fn a_pad_is_outside_the_fraction_its_child_asked_for() {
|
|||||||
// placed inside it by its own alignment, which is not what is under test.
|
// placed inside it by its own alignment, which is not what is under test.
|
||||||
h.set_root((padded, tail).span(Dir::RIGHT).width(rel(1.0)));
|
h.set_root((padded, tail).span(Dir::RIGHT).width(rel(1.0)));
|
||||||
|
|
||||||
assert_corners!(h, padded, (0, 0), (220, 100));
|
assert_corners!(h, padded, (0, 0), (210, 100));
|
||||||
assert_corners!(h, tail, (220, 0), (320, 100));
|
assert_corners!(h, tail, (210, 0), (310, 100));
|
||||||
}
|
|
||||||
|
|
||||||
/// The other half of the pair: an inset takes its room off the inside, so it
|
|
||||||
/// is exactly as long as the box it was given and the fraction its child
|
|
||||||
/// asked for is a fraction of what is left inside. Half of the 380 left in a
|
|
||||||
/// 400 px row is 190, and the inset is the whole 400.
|
|
||||||
#[test]
|
|
||||||
fn an_inset_is_inside_the_fraction_its_child_asked_for() {
|
|
||||||
let mut h = Harness::new((400, 100));
|
|
||||||
let inner = rect(Color::GREEN).width(rel(0.5)).add(&mut h.rsc);
|
|
||||||
let inset = (inner,).span(Dir::RIGHT).inset(10).add(&mut h.rsc);
|
|
||||||
h.set_root((inset,).span(Dir::RIGHT).width(rel(1.0)));
|
|
||||||
|
|
||||||
assert_corners!(h, inset, (0, 0), (200, 100));
|
|
||||||
assert_corners!(h, inner, (10, 0), (200, 100));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -254,7 +237,7 @@ fn a_moved_subtree_takes_its_children_with_it() {
|
|||||||
let mut h = Harness::new((400, 400));
|
let mut h = Harness::new((400, 400));
|
||||||
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
let first = rect(Color::RED).height(40).add(&mut h.rsc);
|
||||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
let row = inner.inset(10).height(40).region_node().add(&mut h.rsc);
|
let row = inner.pad(10).height(40).region_node().add(&mut h.rsc);
|
||||||
// 80 of fixed rows in a 400 window, so the span takes 80 and sits in the
|
// 80 of fixed rows in a 400 window, so the span takes 80 and sits in the
|
||||||
// middle of what it was given.
|
// middle of what it was given.
|
||||||
h.set_root((first, row).span(Dir::DOWN));
|
h.set_root((first, row).span(Dir::DOWN));
|
||||||
@@ -297,7 +280,7 @@ fn a_box_with_a_fixed_length_can_be_stretched_on_its_other_axis() {
|
|||||||
// impossible to take out of: recovering a fraction of a box needs a
|
// impossible to take out of: recovering a fraction of a box needs a
|
||||||
// relative extent, and it has none on that axis.
|
// relative extent, and it has none on that axis.
|
||||||
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
let inner = rect(Color::BLUE).add(&mut h.rsc);
|
||||||
let row = inner.inset(10).height(40).add(&mut h.rsc);
|
let row = inner.pad(10).height(40).add(&mut h.rsc);
|
||||||
let filler = rect(Color::GREEN).add(&mut h.rsc);
|
let filler = rect(Color::GREEN).add(&mut h.rsc);
|
||||||
// This column is an item in a row, so it takes the width left for it
|
// This column is an item in a row, so it takes the width left for it
|
||||||
// rather than asking for a full row-width in addition to the bar.
|
// rather than asking for a full row-width in addition to the bar.
|
||||||
|
|||||||
+110
-1
@@ -462,7 +462,7 @@ fn a_change_two_levels_under_its_reader_still_reaches_it() {
|
|||||||
let (leaf, _) = counted(&mut h, Size::px((100, 100).into()), true);
|
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).inset(12));
|
h.set_root((padded, below).span(Dir::DOWN).pad(12));
|
||||||
assert_corners!(h, below, (12, 132), (388, 388));
|
assert_corners!(h, below, (12, 132), (388, 388));
|
||||||
|
|
||||||
h.rsc[leaf].size = Size::px((100, 200).into());
|
h.rsc[leaf].size = Size::px((100, 200).into());
|
||||||
@@ -628,3 +628,112 @@ fn a_masked_widget_redrawn_on_its_own_sets_its_mask_again() {
|
|||||||
h.frame();
|
h.frame();
|
||||||
assert_corners!(h, inner, (100, 0), (400, 200));
|
assert_corners!(h, inner, (100, 0), (400, 200));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The two spans a subtree changes hands between, and the branch that is not
|
||||||
|
/// in the tree yet -- kept alive by the test until it is.
|
||||||
|
struct Handover {
|
||||||
|
leaf: WidgetId,
|
||||||
|
first: WeakWidget<Span>,
|
||||||
|
second: WeakWidget<Span>,
|
||||||
|
root: WeakWidget<Span>,
|
||||||
|
spare: StrongWidget,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A subtree that changes hands while its box does not move, so nothing about
|
||||||
|
/// reusing its drawing says it changed parents. `deeper` puts a span between
|
||||||
|
/// the root and `second`, so it changes depth by changing hands as well.
|
||||||
|
fn plant_handover(h: &mut Harness, moved: bool, deeper: bool, width: f32) -> Handover {
|
||||||
|
let leaf = rect(Color::RED).add(&mut h.rsc);
|
||||||
|
let sized = leaf.width(width).add(&mut h.rsc);
|
||||||
|
let holder = (sized,).span(Dir::RIGHT).add(&mut h.rsc);
|
||||||
|
let first = Span {
|
||||||
|
children: match moved {
|
||||||
|
true => Vec::new(),
|
||||||
|
false => vec![holder.add_strong(&mut h.rsc)],
|
||||||
|
},
|
||||||
|
dir: Dir::RIGHT,
|
||||||
|
gap: Px::ZERO,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let second = Span {
|
||||||
|
children: match moved {
|
||||||
|
true => vec![holder.add_strong(&mut h.rsc)],
|
||||||
|
false => Vec::new(),
|
||||||
|
},
|
||||||
|
dir: Dir::RIGHT,
|
||||||
|
gap: Px::ZERO,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
let branch = match deeper {
|
||||||
|
true => (second,).span(Dir::RIGHT).add_strong(&mut h.rsc),
|
||||||
|
false => second.add_strong(&mut h.rsc),
|
||||||
|
};
|
||||||
|
let (in_tree, spare) = match moved {
|
||||||
|
true => (branch, first.add_strong(&mut h.rsc)),
|
||||||
|
false => (first.add_strong(&mut h.rsc), branch),
|
||||||
|
};
|
||||||
|
let root = Span {
|
||||||
|
children: vec![in_tree],
|
||||||
|
dir: Dir::RIGHT,
|
||||||
|
gap: Px::ZERO,
|
||||||
|
}
|
||||||
|
.add(&mut h.rsc);
|
||||||
|
h.state.root = Some(root.add_strong(&mut h.rsc));
|
||||||
|
Handover {
|
||||||
|
leaf: sized.id(),
|
||||||
|
first,
|
||||||
|
second,
|
||||||
|
root,
|
||||||
|
spare,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves the subtree and swaps the branch it sits in for the one it left.
|
||||||
|
fn hand_over(h: &mut Harness, tree: Handover) -> WidgetId {
|
||||||
|
let holder = h.rsc[tree.first].children.remove(0);
|
||||||
|
h.rsc[tree.second].children.push(holder);
|
||||||
|
h.rsc[tree.root].children.clear();
|
||||||
|
h.rsc[tree.root].children.push(tree.spare);
|
||||||
|
h.frame();
|
||||||
|
tree.leaf
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_subtree_that_changed_parents_is_not_undrawn_by_the_one_it_left() {
|
||||||
|
let mut warm = Harness::new((400, 200));
|
||||||
|
let tree = plant_handover(&mut warm, false, false, 40.0);
|
||||||
|
warm.frame();
|
||||||
|
let leaf = hand_over(&mut warm, tree);
|
||||||
|
|
||||||
|
let mut cold = Harness::new((400, 200));
|
||||||
|
let grown = plant_handover(&mut cold, true, false, 40.0);
|
||||||
|
cold.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
warm.region(&leaf),
|
||||||
|
cold.region(&grown.leaf),
|
||||||
|
"the span it left still listed it and undrew it"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_subtree_that_changed_parents_settles_at_the_depth_it_moved_to() {
|
||||||
|
let mut warm = Harness::new((400, 200));
|
||||||
|
let tree = plant_handover(&mut warm, false, true, 40.0);
|
||||||
|
warm.frame();
|
||||||
|
let leaf = hand_over(&mut warm, tree);
|
||||||
|
// After it has changed hands, so what has to reach the new parent is a
|
||||||
|
// change made under the subtree it now holds.
|
||||||
|
warm.set_len(leaf, Axis::X, LayoutLen::px(90.0));
|
||||||
|
warm.frame();
|
||||||
|
|
||||||
|
let mut cold = Harness::new((400, 200));
|
||||||
|
let grown = plant_handover(&mut cold, true, true, 90.0);
|
||||||
|
cold.frame();
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
warm.region(&leaf),
|
||||||
|
cold.region(&grown.leaf),
|
||||||
|
"the span it moved to is the one the change has to reach"
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user