Say when a drawing cannot be taken out of its box, rather than guessing
`lerp_inv` returns `Option`. Inverting a lerp over a range of zero length has no one answer, and `div_or`'s fallback picked one: the start of the range. Measured, that is not a wrong number so much as a plausible one -- taking a part out of a box fixed at the top of the window hands back exactly what went in, and out of a box fixed at the middle hands back the parent's own `rel` of 0.5 as if it were the child's fraction. Either way the caller cannot tell that nothing was recovered, which is the defect; the previous commit's claim that it "returns a rel of 0" is right only for the first case. `UiScalar::outside` and `UiSpan::outside` follow it to `Option`, and `Remap` is the answer at the region level: `new` says whether a drawing in one box can be put in another and `apply` then cannot fail, so `try_reuse` asks once for a whole subtree and `reusable` goes back to reporting only what the widget claims. That replaces the predicate the last commit put in `reusable`, which stated the same rule in a second place. `DivOr` existed only for the fallback and is gone, along with `UiRegion::outside` and `UiVec2::outside`, which had no callers once `Remap` owned the operation. `UiRegion::axis` took `&mut self` to return a shared reference; `Remap::new` needs it on a shared one. This does not redraw less. `Remap::new` refuses exactly what the predicate refused; what it buys is one statement of the rule and a remap that cannot half-apply. Counted on a resize, with the old wipe-everything for comparison: 20 padded rows, 101 widgets 101 draws -> 0 the `tabs` example 73 draws -> 0 the `text` example 49 draws -> 48 So the saving is whole where a resize does not change any widget's size, and nil in `text`, where both paragraphs rewrap to a different height and the relayout that forces reaches the root. The last commit's message oversold that case. Checked: fmt, clippy and 35 tests. `tabs` (with the image replay), `view` and `minimal` still byte-identical to `upstream/main`, `text` unchanged, and the live sway resize round trip still matches a cold start at each size.
This commit is contained in:
1 parent
984f482a7f
commit
53e61289f5
5 files changed
+106
-77
No files matched your search
+54
-22
@@ -56,13 +56,6 @@ impl UiVec2 {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, region: &UiRegion) -> UiVec2 {
|
||||
UiVec2 {
|
||||
x: self.x.outside(®ion.x),
|
||||
y: self.y.outside(®ion.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn axis_mut(&mut self, axis: Axis) -> &mut UiScalar {
|
||||
match axis {
|
||||
Axis::X => &mut self.x,
|
||||
@@ -209,10 +202,12 @@ impl UiScalar {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, span: &UiSpan) -> Self {
|
||||
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel);
|
||||
/// Undoes `within`, and `None` where the span has a fixed length: every
|
||||
/// fraction of it lands on the same `rel`, so none can be told apart.
|
||||
pub fn outside(&self, span: &UiSpan) -> Option<Self> {
|
||||
let rel = self.rel.lerp_inv(span.start.rel, span.end.rel)?;
|
||||
let abs = self.abs - rel.lerp(span.start.abs, span.end.abs);
|
||||
Self { rel, abs }
|
||||
Some(Self { rel, abs })
|
||||
}
|
||||
|
||||
pub fn within_len(&self, len: UiScalar) -> Self {
|
||||
@@ -283,10 +278,10 @@ impl UiSpan {
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn outside(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
start: self.start.outside(parent),
|
||||
end: self.end.outside(parent),
|
||||
pub fn outside(&self, parent: &Self) -> Option<Self> {
|
||||
match (self.start.outside(parent), self.end.outside(parent)) {
|
||||
(Some(start), Some(end)) => Some(Self { start, end }),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,14 +319,7 @@ impl UiRegion {
|
||||
y: self.y.within(&parent.y),
|
||||
}
|
||||
}
|
||||
pub const fn outside(&self, parent: &Self) -> Self {
|
||||
Self {
|
||||
x: self.x.outside(&parent.x),
|
||||
y: self.y.outside(&parent.y),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn axis(&mut self, axis: Axis) -> &UiSpan {
|
||||
pub const fn axis(&self, axis: Axis) -> &UiSpan {
|
||||
match axis {
|
||||
Axis::X => &self.x,
|
||||
Axis::Y => &self.y,
|
||||
@@ -409,6 +397,50 @@ impl UiRegion {
|
||||
}
|
||||
}
|
||||
|
||||
/// Taking a drawing out of one box and putting it in another, checked once
|
||||
/// for a whole subtree so that applying it cannot fail.
|
||||
///
|
||||
/// A box of a fixed length holds each part as an offset from its start rather
|
||||
/// than as a fraction of it, so those parts can be carried to a box of the
|
||||
/// 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 {
|
||||
x: Self::span(region.x, self.from.x, self.to.x),
|
||||
y: Self::span(region.y, self.from.y, self.to.y),
|
||||
}
|
||||
}
|
||||
|
||||
fn span(span: UiSpan, from: UiSpan, to: UiSpan) -> UiSpan {
|
||||
match span.outside(&from) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for UiRegion {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
|
||||
+14
-23
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Size,
|
||||
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Remap, Size,
|
||||
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
|
||||
util::{HashMap, HashSet, Vec2, forget_ref},
|
||||
};
|
||||
@@ -183,38 +183,29 @@ impl UiRenderState {
|
||||
return Some(size);
|
||||
}
|
||||
// TODO: epsilon?
|
||||
if old.size() == region.size() {
|
||||
self.mov(id, old, region);
|
||||
return Some(size);
|
||||
if old.size() != region.size() && !self.reusable(id, region, rsc) {
|
||||
return None;
|
||||
}
|
||||
if self.reusable(id, region, rsc) {
|
||||
// Its drawing stands; the new box is remapped into the primitives.
|
||||
self.mov(id, old, region);
|
||||
return Some(size);
|
||||
}
|
||||
None
|
||||
// Its drawing stands, if the new box can be reached from the old one.
|
||||
self.mov(id, &Remap::new(old, region)?);
|
||||
Some(size)
|
||||
}
|
||||
|
||||
/// Whether the widget can keep the drawing it has and be given `region`
|
||||
/// instead, asked one axis at a time: a change on an axis it does not
|
||||
/// depend on costs nothing, whatever it depends on elsewhere.
|
||||
fn reusable(&self, id: WidgetId, mut region: UiRegion, rsc: &dyn UiRsc) -> bool {
|
||||
fn reusable(&self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> bool {
|
||||
let Some(active) = self.active.get(&id) else {
|
||||
return false;
|
||||
};
|
||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||
return false;
|
||||
};
|
||||
let mut old = active.region;
|
||||
[Axis::X, Axis::Y].into_iter().all(|axis| {
|
||||
let offered = region.axis_mut(axis).len();
|
||||
let had = old.axis_mut(axis).len();
|
||||
let offered = region.axis(axis).len();
|
||||
let had = active.region.axis(axis).len();
|
||||
match widget.on_resize(axis) {
|
||||
// Remapping out of the old box only scales while that box
|
||||
// had a relative extent; inside a fixed one every part became
|
||||
// an offset from its start, which `mov` carries but cannot
|
||||
// stretch.
|
||||
OnResize::Scale => offered == had || had.rel != 0.0,
|
||||
OnResize::Scale => true,
|
||||
// `Translate` is not acted on yet, and cannot be until a
|
||||
// drawing can sit somewhere other than its box. `region` is
|
||||
// both the box a widget was given and the box its primitives
|
||||
@@ -237,17 +228,17 @@ impl UiRenderState {
|
||||
})
|
||||
}
|
||||
|
||||
fn mov(&mut self, id: WidgetId, from: UiRegion, to: UiRegion) {
|
||||
fn mov(&mut self, id: WidgetId, remap: &Remap) {
|
||||
let active = self.active.get_mut(&id).unwrap();
|
||||
for h in &active.primitives {
|
||||
let region = self.layers[h.layer].region_mut(h);
|
||||
*region = region.outside(&from).within(&to);
|
||||
*region = remap.apply(*region);
|
||||
}
|
||||
active.region = active.region.outside(&from).within(&to);
|
||||
active.region = remap.apply(active.region);
|
||||
// SAFETY: children cannot be recursive
|
||||
let children = unsafe { forget_ref(&active.children) };
|
||||
for child in children {
|
||||
self.mov(*child, from, to);
|
||||
self.mov(*child, remap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+10
-22
@@ -1,33 +1,21 @@
|
||||
use std::ops::*;
|
||||
|
||||
pub const trait LerpUtil {
|
||||
pub const trait LerpUtil: Sized {
|
||||
fn lerp(self, from: Self, to: Self) -> Self;
|
||||
fn lerp_inv(self, from: Self, to: Self) -> Self;
|
||||
fn lerp_inv(self, from: Self, to: Self) -> Option<Self>;
|
||||
}
|
||||
|
||||
pub const trait DivOr {
|
||||
fn div_or(self, rhs: Self, other: Self) -> Self;
|
||||
}
|
||||
|
||||
const impl DivOr for f32 {
|
||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
||||
let res = self / rhs;
|
||||
if res.is_nan() { other } else { res }
|
||||
}
|
||||
}
|
||||
|
||||
const impl<
|
||||
T: const Add<Output = T> + const Sub<Output = T> + const Mul<Output = T> + const DivOr + Copy,
|
||||
> LerpUtil for T
|
||||
{
|
||||
const impl LerpUtil for f32 {
|
||||
/// linear interpolation
|
||||
/// from * (1.0 - self) + to * self
|
||||
fn lerp(self, from: Self, to: Self) -> Self {
|
||||
from + (to - from) * self
|
||||
}
|
||||
/// inverse of lerp
|
||||
fn lerp_inv(self, from: Self, to: Self) -> Self {
|
||||
(self - from).div_or(to - from, from)
|
||||
/// 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)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-10
@@ -1,4 +1,4 @@
|
||||
use crate::util::{DivOr, impl_op};
|
||||
use crate::util::impl_op;
|
||||
use std::{hash::Hash, ops::*};
|
||||
|
||||
#[repr(C)]
|
||||
@@ -67,15 +67,6 @@ impl_op!(Vec2 Sub sub; x y);
|
||||
impl_op!(Vec2 Mul mul; x y);
|
||||
impl_op!(Vec2 Div div; x y);
|
||||
|
||||
const impl DivOr for Vec2 {
|
||||
fn div_or(self, rhs: Self, other: Self) -> Self {
|
||||
Self {
|
||||
x: self.x.div_or(rhs.x, other.x),
|
||||
y: self.y.div_or(rhs.y, other.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vec2 {
|
||||
type Output = Self;
|
||||
|
||||
|
||||
Reference in new issue
Block a user