Retire Remap: a translation shifts, and only a stretch needs a fraction
`Remap` existed to invert a composition, and a translation never needed one: shifting a box shifts everything composed into it by the same amount, because `lerp(s + d, e + d, t) == lerp(s, e, t) + d` on both channels. That holds whether or not the box has a relative extent, so the carry branch was answering a question it did not have to ask. So the decision is made once, before the walk, and neither relocation method branches. A translation is already one slot write. A change of length calls `UiRegion::stretch`, which re-expresses each part at its own fraction of the new box and needs `stretchable` -- a fixed length holds its parts as offsets from its start and keeps no fraction to stretch by. `Remap`, `UiScalar::outside`, `UiSpan::outside` and `LerpUtil::lerp_inv` are all gone with it. Nothing inverts a lerp any more: the one division is done against a denominator `stretchable` already established is not zero. What it gives up is the per-axis carry, so a box that changed length on one axis and not the other is redrawn where it used to be remapped. Counted: six of `tabs`'s fourteen relocations and five of `text`'s sixteen, and one extra redraw per frame on `replace_cost`'s 200 rows -- 354,310,889 instructions against 354,272,387, which is noise. Checked: fmt, clippy and 42 tests. `tabs` (with the image replay), `view`, `minimal` and `text` all still render byte-identical to `upstream/main`.
This commit is contained in:
1 parent
78a53b6bf6
commit
db1751fdfd
5 files changed
+86
-100
No files matched your search
+28
-52
@@ -202,12 +202,15 @@ impl UiScalar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Undoes `within`, and `None` where the span has a fixed length: every
|
/// `within` undone against `from` and redone against `to`, for one axis.
|
||||||
/// fraction of it lands on the same `rel`, so none can be told apart.
|
/// `from`'s relative extent is the denominator, so it must not be zero.
|
||||||
pub fn outside(&self, span: &UiSpan) -> Option<Self> {
|
fn stretch(&self, from: &UiSpan, to: &UiSpan) -> Self {
|
||||||
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel)?;
|
let frac = (self.rel - from.start.rel) / (from.end.rel - from.start.rel);
|
||||||
let abs = self.abs - rel.lerp(span.start.abs, span.end.abs);
|
Self {
|
||||||
Some(Self { rel, abs })
|
rel: frac.lerp(to.start.rel, to.end.rel),
|
||||||
|
abs: self.abs - frac.lerp(from.start.abs, from.end.abs)
|
||||||
|
+ frac.lerp(to.start.abs, to.end.abs),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn within_len(&self, len: UiScalar) -> Self {
|
pub fn within_len(&self, len: UiScalar) -> Self {
|
||||||
@@ -278,13 +281,6 @@ impl UiSpan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn outside(&self, parent: &Self) -> Option<Self> {
|
|
||||||
Some(Self {
|
|
||||||
start: self.start.outside(parent)?,
|
|
||||||
end: self.end.outside(parent)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const fn len(&self) -> UiScalar {
|
pub const fn len(&self) -> UiScalar {
|
||||||
self.end - self.start
|
self.end - self.start
|
||||||
}
|
}
|
||||||
@@ -395,48 +391,28 @@ impl UiRegion {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether a stretch out of this box can be expressed. Each part inside a
|
||||||
|
/// box is held as a fraction of it, and a fixed length has no fraction to
|
||||||
|
/// hold one by -- every part of it is just an offset from its start.
|
||||||
|
pub fn stretchable(&self) -> bool {
|
||||||
|
self.x.start.rel != self.x.end.rel && self.y.start.rel != self.y.end.rel
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Taking a drawing out of one box and putting it in another, checked once
|
/// Re-expresses a region inside `from` as the same fractions of `to`.
|
||||||
/// for a whole subtree so that applying it cannot fail.
|
/// `from` must be `stretchable`; a translation is `shift` instead, which
|
||||||
///
|
/// needs no fractions and works out of any box.
|
||||||
/// A box of a fixed length holds each part as an offset from its start rather
|
pub fn stretch(&self, from: &UiRegion, to: &UiRegion) -> UiRegion {
|
||||||
/// than as a fraction of it, so those parts can be carried to a box of the
|
debug_assert!(from.stretchable(), "a fixed length has no fraction");
|
||||||
/// same length but never stretched to a different one.
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
|
||||||
pub struct Remap {
|
|
||||||
from: UiRegion,
|
|
||||||
to: UiRegion,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Remap {
|
|
||||||
pub fn new(from: UiRegion, to: UiRegion) -> Option<Self> {
|
|
||||||
[Axis::X, Axis::Y]
|
|
||||||
.into_iter()
|
|
||||||
.all(|axis| {
|
|
||||||
let (from, to) = (from.axis(axis), to.axis(axis));
|
|
||||||
from.start.rel != from.end.rel || from.len() == to.len()
|
|
||||||
})
|
|
||||||
.then_some(Self { from, to })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, region: UiRegion) -> UiRegion {
|
|
||||||
UiRegion {
|
UiRegion {
|
||||||
x: Self::span(region.x, self.from.x, self.to.x),
|
x: UiSpan {
|
||||||
y: Self::span(region.y, self.from.y, self.to.y),
|
start: self.x.start.stretch(&from.x, &to.x),
|
||||||
}
|
end: self.x.end.stretch(&from.x, &to.x),
|
||||||
}
|
},
|
||||||
|
y: UiSpan {
|
||||||
fn span(span: UiSpan, from: UiSpan, to: UiSpan) -> UiSpan {
|
start: self.y.start.stretch(&from.y, &to.y),
|
||||||
match span.outside(&from) {
|
end: self.y.end.stretch(&from.y, &to.y),
|
||||||
Some(out) => out.within(&to),
|
},
|
||||||
// `new` admits this only where the two are the same length, so
|
|
||||||
// the difference between their starts is the whole move.
|
|
||||||
None => {
|
|
||||||
let mut span = span;
|
|
||||||
span.shift(to.start - from.start);
|
|
||||||
span
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, Moves, OnResize, Painter, PixelRegion,
|
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, MoveIdx, Moves, OnResize, Painter, PixelRegion,
|
||||||
Remap, Size, StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
Size, StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -214,11 +214,11 @@ impl UiRenderState {
|
|||||||
self.moves.set(slot, moved);
|
self.moves.set(slot, moved);
|
||||||
return Some(size);
|
return Some(size);
|
||||||
}
|
}
|
||||||
if !self.reusable(id, region, rsc) {
|
if !self.reusable(id, region, rsc) || !old.stretchable() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Its drawing stands, if the new box can be reached from the old one.
|
// Its drawing stands, re-expressed as the same fractions of the box.
|
||||||
self.mov(id, &Remap::new(old, region)?);
|
self.stretch(id, old, region);
|
||||||
Some(size)
|
Some(size)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,21 +259,21 @@ impl UiRenderState {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rewrites a subtree's regions into a new box, for a change a slot
|
/// Rewrites a subtree's regions as the same fractions of a new box, for a
|
||||||
/// cannot express. Every region it rewrites is a region some slot was a
|
/// change of length that a slot cannot express. Every region it rewrites
|
||||||
/// delta from, so those go back to zero.
|
/// is a region some slot was a delta from, so those go back to zero.
|
||||||
fn mov(&mut self, id: WidgetId, remap: &Remap) {
|
fn stretch(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
||||||
let active = self.active.get_mut(&id).unwrap();
|
let active = self.active.get_mut(&id).unwrap();
|
||||||
for h in &active.primitives {
|
for h in &active.primitives {
|
||||||
let region = self.layers[h.layer].region_mut(h);
|
let region = self.layers[h.layer].region_mut(h);
|
||||||
*region = remap.apply(*region);
|
*region = region.stretch(&from, &to);
|
||||||
}
|
}
|
||||||
active.region = remap.apply(active.region);
|
active.region = active.region.stretch(&from, &to);
|
||||||
self.moves.set(active.move_idx, Vec2::ZERO);
|
self.moves.set(active.move_idx, Vec2::ZERO);
|
||||||
// SAFETY: children cannot be recursive
|
// SAFETY: children cannot be recursive
|
||||||
let children = unsafe { forget_ref(&active.children) };
|
let children = unsafe { forget_ref(&active.children) };
|
||||||
for child in children {
|
for child in children {
|
||||||
self.mov(*child, remap);
|
self.stretch(*child, from, to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-10
@@ -1,6 +1,5 @@
|
|||||||
pub const trait LerpUtil: Sized {
|
pub const trait LerpUtil {
|
||||||
fn lerp(self, from: Self, to: Self) -> Self;
|
fn lerp(self, from: Self, to: Self) -> Self;
|
||||||
fn lerp_inv(self, from: Self, to: Self) -> Option<Self>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl LerpUtil for f32 {
|
const impl LerpUtil for f32 {
|
||||||
@@ -9,14 +8,6 @@ const impl LerpUtil for f32 {
|
|||||||
fn lerp(self, from: Self, to: Self) -> Self {
|
fn lerp(self, from: Self, to: Self) -> Self {
|
||||||
from + (to - from) * self
|
from + (to - from) * self
|
||||||
}
|
}
|
||||||
/// inverse of lerp, and `None` where `from` and `to` are the same point:
|
|
||||||
/// every input lerps to it, so there is no one answer to come back to.
|
|
||||||
fn lerp_inv(self, from: Self, to: Self) -> Option<Self> {
|
|
||||||
match to == from {
|
|
||||||
true => None,
|
|
||||||
false => Some((self - from) / (to - from)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_op {
|
macro_rules! impl_op {
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
//! What a drawing can be taken out of, and what it cannot.
|
|
||||||
|
|
||||||
use iris::core::{Remap, UiRegion, UiScalar, UiSpan};
|
|
||||||
|
|
||||||
/// A box `size` tall whose top is `rel` of the way down the window.
|
|
||||||
fn fixed(rel: f32, size: f32) -> UiRegion {
|
|
||||||
UiRegion::new(
|
|
||||||
UiSpan::FULL,
|
|
||||||
UiSpan::new(UiScalar { rel, abs: 0.0 }, UiScalar { rel, abs: size }),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_fixed_box_can_be_carried_but_not_stretched() {
|
|
||||||
let from = fixed(0.0, 164.0);
|
|
||||||
assert!(Remap::new(from, UiRegion::FULL).is_none());
|
|
||||||
assert!(Remap::new(from, fixed(0.5, 164.0)).is_some());
|
|
||||||
assert!(Remap::new(from, fixed(0.0, 98.0)).is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn a_relative_box_can_be_stretched_to_any_other() {
|
|
||||||
let remap = Remap::new(UiRegion::FULL, fixed(0.0, 98.0)).expect("relative boxes remap");
|
|
||||||
// A part that filled the window keeps filling what replaced it, which is
|
|
||||||
// exactly what `outside` could not say for a box of a fixed length.
|
|
||||||
assert_eq!(remap.apply(UiRegion::FULL), fixed(0.0, 98.0));
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
//! What a drawing can be taken out of, and what it cannot.
|
||||||
|
|
||||||
|
use iris::core::{UiRegion, UiScalar, UiSpan};
|
||||||
|
|
||||||
|
/// A box `size` tall whose top is `rel` of the way down the window.
|
||||||
|
fn fixed(rel: f32, size: f32) -> UiRegion {
|
||||||
|
UiRegion::new(
|
||||||
|
UiSpan::FULL,
|
||||||
|
UiSpan::new(UiScalar { rel, abs: 0.0 }, UiScalar { rel, abs: size }),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_fixed_length_cannot_be_stretched_out_of() {
|
||||||
|
assert!(!fixed(0.0, 164.0).stretchable());
|
||||||
|
assert!(!fixed(0.5, 164.0).stretchable());
|
||||||
|
assert!(UiRegion::FULL.stretchable());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn a_stretch_keeps_each_part_at_its_fraction() {
|
||||||
|
let to = fixed(0.0, 98.0);
|
||||||
|
// A part filling the window fills what replaced it.
|
||||||
|
assert_eq!(UiRegion::FULL.stretch(&UiRegion::FULL, &to), to);
|
||||||
|
// And the middle half of it stays the middle half.
|
||||||
|
let half = UiRegion::new(
|
||||||
|
UiSpan::FULL,
|
||||||
|
UiSpan::new(UiScalar::rel(0.25), UiScalar::rel(0.75)),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
half.stretch(&UiRegion::FULL, &to),
|
||||||
|
UiRegion::new(
|
||||||
|
UiSpan::FULL,
|
||||||
|
UiSpan::new(
|
||||||
|
UiScalar {
|
||||||
|
rel: 0.0,
|
||||||
|
abs: 24.5
|
||||||
|
},
|
||||||
|
UiScalar {
|
||||||
|
rel: 0.0,
|
||||||
|
abs: 73.5
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user