diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index 8006e4d..664e0e3 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -3,7 +3,7 @@ use crate::layout_diagnostics::{self as diag, Counter, ReuseOutcome, TimerKind}; use crate::ui::painter::{declared_box, declared_lens, placed_box}; use crate::{ ActiveData, Axis, DrawLayers, Holds, IdLike, LayoutLen, Len, MaskIdx, MoveIdx, Moves, Painter, - PixelRegion, PxVec2, RegionAlign, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, Weight, + PixelRegion, Px, PxVec2, RegionAlign, Rel, Size, StrongWidget, UiRegion, UiRsc, UiSpan, Weight, WidgetId, Widgets, util::{HashMap, Vec2}, }; @@ -632,7 +632,7 @@ impl UiRenderState { self.moves.set(slot, region); } else { let remap = RegionRemap::new(old_region, region)?; - self.remap_subtree(id, remap, info.parent_move, mask, rsc); + self.remap_subtree(id, &remap, info.parent_move, mask, rsc); } } let active = self.active.get_mut(&id).unwrap(); @@ -668,7 +668,7 @@ impl UiRenderState { fn remap_subtree( &mut self, id: WidgetId, - remap: RegionRemap, + remap: &RegionRemap, parent_move: MoveIdx, inherited_mask: MaskIdx, rsc: &mut dyn UiRsc, @@ -1045,55 +1045,102 @@ fn same_pixel_region(a: PixelRegion, b: PixelRegion) -> bool { /// source extent can be translated but cannot recover fractions for a resize. #[derive(Clone, Copy)] struct RegionRemap { - from: UiRegion, - to: UiRegion, + axes: [AxisRemap; 2], +} + +/// Moving one axis of a box into another, worked out once for the whole +/// subtree that moves with it. Every part of that subtree is divided by the +/// same extent and placed between the same two ends, so the ends and the +/// divisor belong here rather than in each part's arithmetic. +#[derive(Clone, Copy)] +enum AxisRemap { + /// A box that kept its length carries its parts by moving them, which is + /// exact. Dividing to find the fraction each sits at and multiplying to + /// place it again are two roundings, and they land a step from where + /// growing the tree that way does. + Translate(Len), + /// A box that changed length has to re-express each part as a fraction of + /// the new one, which is what a part of a box means. + Scale(AxisScale), +} + +#[derive(Clone, Copy)] +struct AxisScale { + /// What the fraction is measured from, and what divides it. `whole` is + /// the common case of a box spanning the whole of its parent's, where + /// dividing by one is the expensive way to write a subtraction. + start_rel: Rel, + extent: Rel, + whole: bool, + /// `lerp` is `a + (b - a) * fraction`, and both ends are the same for + /// every part, so each is kept as its near end and its span. + from_px: Px, + from_px_span: Px, + to_rel: Rel, + to_rel_span: Rel, + to_px: Px, + to_px_span: Px, } impl RegionRemap { fn new(from: UiRegion, to: UiRegion) -> Option { - AXES.into_iter() - .all(|axis| { - let from = from.axis(axis); - from.start.rel != from.end.rel || from.len() == to.axis(axis).len() - }) - .then_some(Self { from, to }) + Some(Self { + axes: [AxisRemap::new(from.x, to.x)?, AxisRemap::new(from.y, to.y)?], + }) } - fn apply(self, region: UiRegion) -> UiRegion { + fn apply(&self, region: UiRegion) -> UiRegion { UiRegion { - x: self.apply_span(region.x, self.from.x, self.to.x), - y: self.apply_span(region.y, self.from.y, self.to.y), + x: self.axes[0].apply_span(region.x), + y: self.axes[1].apply_span(region.y), } } +} - fn apply_span(self, span: UiSpan, from: UiSpan, to: UiSpan) -> UiSpan { - UiSpan { - start: self.apply_scalar(span.start, from, to), - end: self.apply_scalar(span.end, from, to), +impl AxisRemap { + fn new(from: UiSpan, to: UiSpan) -> Option { + if from.len() == to.len() { + return Some(Self::Translate(to.start - from.start)); } - } - - fn apply_scalar(self, scalar: Len, from: UiSpan, to: UiSpan) -> Len { let extent = from.end.rel - from.start.rel; - // A box that only moved, or that has no relative extent to divide, - // carries its parts by moving them, which is exact. Dividing to find - // the fraction each sits at and multiplying to place it again are two - // roundings, and they land a step from where growing the tree that - // way does. Where the box changed length there is nothing else to do, - // and the fraction is what a part means. - if from.len() == to.len() || extent == Rel::ZERO { - return scalar + to.start - from.start; + // Without a relative extent there is no fraction to re-express: a box + // of fixed length cannot say where its parts sit in a different one. + if extent == Rel::ZERO { + return None; } - // A box that spans the whole of its parent's is the common one, and - // dividing by one is the expensive way to write a subtraction. - let offset = scalar.rel - from.start.rel; - let fraction = match extent == Rel::ONE { - true => offset, - false => offset / extent, + Some(Self::Scale(AxisScale { + start_rel: from.start.rel, + extent, + whole: extent == Rel::ONE, + from_px: from.start.px, + from_px_span: from.end.px - from.start.px, + to_rel: to.start.rel, + to_rel_span: to.end.rel - to.start.rel, + to_px: to.start.px, + to_px_span: to.end.px - to.start.px, + })) + } + + fn apply_span(&self, span: UiSpan) -> UiSpan { + UiSpan { + start: self.apply_scalar(span.start), + end: self.apply_scalar(span.end), + } + } + + fn apply_scalar(&self, scalar: Len) -> Len { + let scale = match self { + Self::Translate(by) => return scalar + *by, + Self::Scale(scale) => scale, }; - let from_px = fraction.lerp(from.start.px, from.end.px); - let to_rel = fraction.lerp(to.start.rel, to.end.rel); - let to_px = fraction.lerp(to.start.px, to.end.px); + let offset = scalar.rel - scale.start_rel; + let fraction = match scale.whole { + true => offset, + false => offset / scale.extent, + }; + let from_px = scale.from_px + scale.from_px_span.mul(fraction); + let to_rel = scale.to_rel + scale.to_rel_span.mul(fraction); + let to_px = scale.to_px + scale.to_px_span.mul(fraction); Len::from_parts(to_rel, scalar.px - from_px + to_px) } }