diff --git a/core/src/orientation/mod.rs b/core/src/orientation/mod.rs index 65040ec..894de8b 100644 --- a/core/src/orientation/mod.rs +++ b/core/src/orientation/mod.rs @@ -2,6 +2,7 @@ mod align; mod axis; mod len; mod pos; +mod wide; use crate::util::Vec2; @@ -9,3 +10,4 @@ pub use align::*; pub use axis::*; pub use len::*; pub use pos::*; +pub use wide::*; diff --git a/core/src/orientation/wide.rs b/core/src/orientation/wide.rs new file mode 100644 index 0000000..288c064 --- /dev/null +++ b/core/src/orientation/wide.rs @@ -0,0 +1,167 @@ +use crate::{PX_SHIFT, PixelRegion, Px, PxVec2, REL_SHIFT, UiRegion, UiSpan}; + +use super::Len; + +/// How many bits of a box a [`WideLen`] keeps, and how many of a pixel. Both +/// are the grid's own shift plus the room an `i64` has left over: a `Rel` is +/// a fraction of a box and needs eight bits above the point, a `Px` counts up +/// to two million of them and needs twenty-two. +const WIDE_REL: u32 = 48; +const WIDE_PX: u32 = 32; +const REL_GAIN: u32 = WIDE_REL - REL_SHIFT; +const PX_GAIN: u32 = WIDE_PX - PX_SHIFT; + +/// A length part-way through a composition, on a finer grid than the [`Len`] +/// it came from and will go back to. +/// +/// Composing a box through its ancestors is four multiplies a level, and in +/// `Len` every one of them lands back on the grid before the next starts, so +/// the error grows with the depth of the tree. Stepping through this instead +/// rounds once, at the end -- which matters because the same box is reached +/// two ways, composed down the chain and measured against the window, and a +/// structural layout decision turns on the two being one number. +/// +/// Twenty-four extra bits of a box and twenty-two of a pixel are far more +/// than a chain can spend: the residue is `2^-48` of a box a level, against +/// `2^-24` before. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct WideLen { + /// Counts `1/2^WIDE_REL` of the box this length is a part of. + rel: i64, + /// Counts `1/2^WIDE_PX` of a pixel. + px: i64, +} + +impl WideLen { + pub const fn of(len: Len) -> Self { + Self { + rel: (len.rel.raw() as i64) << REL_GAIN, + px: (len.px.raw() as i64) << PX_GAIN, + } + } + + /// This length composed through the box it sits in, which is the same + /// expression [`Len::within`] uses with the rounding left out. The parent + /// is a stored box and so is on the ordinary grid; only the part being + /// carried needs the room. + pub const fn within(self, parent: &UiSpan) -> Self { + let rel_span = (parent.end.rel.raw() - parent.start.rel.raw()) as i128; + let px_span = (parent.end.px.raw() - parent.start.px.raw()) as i128; + let rel = ((parent.start.rel.raw() as i64) << REL_GAIN) + + ((rel_span * self.rel as i128) >> REL_SHIFT) as i64; + // A pixel span of the parent, taken at this length's fraction of it: + // the product is `WIDE_REL + PX_SHIFT` bits under the point and the + // answer is `WIDE_PX`, so what comes off is the difference. + let px = self.px + + ((parent.start.px.raw() as i64) << PX_GAIN) + + ((px_span * self.rel as i128) >> (WIDE_REL + PX_SHIFT - WIDE_PX)) as i64; + Self { rel, px } + } + + /// Back onto the grid against a box of `len` pixels, which is the one + /// rounding a whole composition gets. Both parts are put over + /// `WIDE_REL + PX_SHIFT` first so that it is one and not two. + pub const fn to_px(self, len: Px) -> Px { + let px = (self.px as i128) << (WIDE_REL + PX_SHIFT - WIDE_PX); + let rel = self.rel as i128 * len.raw() as i128; + Px::from_raw(((px + rel) >> WIDE_REL) as i32) + } + + /// A *length* composed through the box it sits in, which is half of what + /// the two ends of that box cost: where the parent sits falls out of the + /// difference, so a length carries the parent's extent and its pixel span + /// and nothing else. Two multiplies a level rather than four. + pub const fn len_within(self, parent: &UiSpan) -> Self { + let rel_span = (parent.end.rel.raw() - parent.start.rel.raw()) as i128; + let px_span = (parent.end.px.raw() - parent.start.px.raw()) as i64; + Self { + rel: ((rel_span * self.rel as i128) >> REL_SHIFT) as i64, + // The pixel term takes the fraction on the ordinary grid, which + // keeps it inside an `i64`. Only the fraction itself compounds + // multiplicatively and so needs the room: an error of a `Rel` + // step here is that step times a pixel span, which is a ten + // thousandth of a pixel over a whole window. + px: self.px + ((px_span * (self.rel >> REL_GAIN)) >> (PX_SHIFT + REL_SHIFT - WIDE_PX)), + } + } +} + +/// The two ends of a box, part-way through a composition. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct WideSpan { + pub start: WideLen, + pub end: WideLen, +} + +impl WideSpan { + pub const fn of(span: UiSpan) -> Self { + Self { + start: WideLen::of(span.start), + end: WideLen::of(span.end), + } + } + + pub const fn within(self, parent: &UiSpan) -> Self { + Self { + start: self.start.within(parent), + end: self.end.within(parent), + } + } +} + +/// How long a box is on each axis, part-way through a composition. What +/// reads a box in pixels almost always wants this and not where it sits. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct WideSize { + pub x: WideLen, + pub y: WideLen, +} + +impl WideSize { + pub const fn of(region: UiRegion) -> Self { + Self { + x: WideLen::of(region.x.len()), + y: WideLen::of(region.y.len()), + } + } + + pub const fn within(self, parent: &UiRegion) -> Self { + Self { + x: self.x.len_within(&parent.x), + y: self.y.len_within(&parent.y), + } + } + + pub fn to_px(self, window: PxVec2) -> PxVec2 { + PxVec2::new(self.x.to_px(window.x), self.y.to_px(window.y)) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct WideRegion { + pub x: WideSpan, + pub y: WideSpan, +} + +impl WideRegion { + pub const fn of(region: UiRegion) -> Self { + Self { + x: WideSpan::of(region.x), + y: WideSpan::of(region.y), + } + } + + pub const fn within(self, parent: &UiRegion) -> Self { + Self { + x: self.x.within(&parent.x), + y: self.y.within(&parent.y), + } + } + + pub fn to_px(self, window: PxVec2) -> PixelRegion { + PixelRegion { + top_left: PxVec2::new(self.x.start.to_px(window.x), self.y.start.to_px(window.y)), + bot_right: PxVec2::new(self.x.end.to_px(window.x), self.y.end.to_px(window.y)), + } + } +} diff --git a/core/src/ui/holds.rs b/core/src/ui/holds.rs index 244aea9..cd763db 100644 --- a/core/src/ui/holds.rs +++ b/core/src/ui/holds.rs @@ -55,28 +55,40 @@ impl Holds { if rel == 0 { return Self::ANY; } - // In half steps, and no more than the arithmetic needs: too wide a - // range admits reusing a drawing where it does not hold, and too - // narrow a one leaves out the box a drawing was made in, which the - // `Holds` assertion in `draw_at` catches. `ROUTES` is at that floor - // -- two half steps fires it -- and tightening both ends moved not - // one of the rig's work counters, so the slack is not buying reuse. + // In half steps, and both at the floor: one less on either and the + // `Holds` assertion in `draw_at` fires, because the range stops + // containing the box a drawing was made in. Wider is the unsound + // side -- it admits reusing a drawing where it does not hold -- and + // neither end buys any reuse, since tightening them moves none of + // the rig's work counters. // // `ROUTES` covers a box composed down the chain against the same box - // measured against the window: two routes to one number, each - // rounding where the other does not. `way_in` covers the multiply - // this inverts, which drops a step and only ever downward, so it - // belongs at the top of the range alone -- and the whole of a box has - // no multiply in it, however many pixels were added to it, since - // multiplying by one is exact and taking the pixels off again is too. - // Allowing for it there anyway compounded, a step a level down a - // chain of widgets each taking the whole of its parent. + // measured against the window. It was three half steps while + // composing rounded four multiplies a level; `Moves::compose` now + // rounds the whole walk once, which took one off. One more is + // arithmetically available -- the `Holds` assertion is quiet at one + // half step, and the whole-of-a-box case maps back to itself exactly + // -- and it is **not** taken, because a range that tight makes + // shrinker seed 220 lay out differently warm than cold. Too narrow + // is supposed to cost only a redraw; there it re-breaks a wrapping + // text, whose reported width then moves a `Branch` onto its other + // subtree. That is the unsettled-text family rather than a rounding + // question, and closing it is what would let this go lower. + // + // `way_in` covers the multiply this inverts, which drops a step and + // only ever downward, so it belongs at the top of the range alone. + // It is irreducible for the same reason a floor is not invertible: + // many boxes give one length. The whole of a box has no multiply in + // it, however many pixels were added to it, since multiplying by one + // is exact and taking the pixels off again is too -- allowing for it + // there anyway compounded, a step a level down a chain of widgets + // each taking the whole of its parent. // // Shifted by half of what a `Rel` counts in, to divide by the // fraction: exact until the division takes it back to the grid. - const ROUTES: i64 = 3; let px = len.px.raw() as i64; let half_rel = REL_SHIFT - 1; + const ROUTES: i64 = 2; let way_in = match rel == Rel::ONE.raw() as i64 { true => 0, false => 2, @@ -136,11 +148,10 @@ mod tests { } /// A widget handed the whole of its parent's box, with or without pixels - /// taken off it, brings no multiply of its own: only the two routes to - /// the same length are left to allow for, and not a rounding that did - /// not happen. Widening for it as well grew the interval a level at a - /// time down a chain of them. Three half steps come back as one whole - /// one, since dividing by a whole box is dividing by one. + /// taken off it, brings no multiply of its own, so it allows for the two + /// routes and nothing else -- one step, where it was one a level of + /// nesting before `Moves::compose`. The identity is what the arithmetic + /// would allow; see `through` for why it is not taken. #[test] fn the_whole_of_a_box_widens_by_the_routes_alone() { let at = Px::from_int(956); diff --git a/core/src/ui/mod.rs b/core/src/ui/mod.rs index baccbcb..20dbaff 100644 --- a/core/src/ui/mod.rs +++ b/core/src/ui/mod.rs @@ -1,6 +1,6 @@ use crate::{ Mask, MoveIdx, MoveOffset, PrimitiveRegistry, TextData, Textures, UiRegion, WeakWidget, - WidgetId, Widgets, + WideRegion, WideSize, WidgetId, Widgets, util::{Arena, Id, TrackedArena}, }; @@ -68,17 +68,46 @@ impl Moves { } } - /// Composes a region held in `idx`'s coordinates down the chain, which is - /// the same walk the vertex shader does. + /// Composes a region held in `idx`'s coordinates down the chain, on a + /// grid fine enough that the whole walk rounds once. Composing in `Len` + /// rounds four multiplies a level, so the residue grew with the depth of + /// the tree -- and it is the box this produces that layout takes a + /// structural decision on. + pub fn compose(&self, idx: MoveIdx, local: UiRegion) -> WideRegion { + let mut region = WideRegion::of(local); + self.walk(idx, |entry| region = region.within(entry)); + region + } + + /// How long a region held in `idx`'s coordinates is, composed down the + /// chain on the same fine grid as [`Self::compose`] and for the same + /// reason. Half the multiplies of composing the box, since a length does + /// not need to know where the box sits -- and what reads a box in pixels + /// nearly always wants only this. + pub fn size_of(&self, idx: MoveIdx, local: UiRegion) -> WideSize { + let mut size = WideSize::of(local); + self.walk(idx, |entry| size = size.within(entry)); + size + } + + /// The same walk the vertex shader does, in the same `Len` the shader is + /// handed. What layout decides on is [`Self::compose`]; this is for + /// asking where the drawing will actually land, which the shader works + /// out again in floats from these same entries. pub fn resolve(&self, idx: MoveIdx, local: UiRegion) -> UiRegion { let mut region = local; + self.walk(idx, |entry| region = region.within(entry)); + region + } + + fn walk(&self, idx: MoveIdx, mut step: impl FnMut(&UiRegion)) { let mut at = idx; for _ in 0..CHAIN_LIMIT { if at == MoveIdx::NONE { - return region; + return; } - let entry = self.arena[at.idx()]; - region = region.within(&entry.region); + let entry = &self.arena[at.idx()]; + step(&entry.region); at = entry.parent; } debug_assert!( @@ -86,7 +115,6 @@ impl Moves { "a move chain longer than {CHAIN_LIMIT} resolves to the wrong place, \ and the shader stops at the same depth" ); - region } /// How many slots a region in `idx` is composed through, which is what diff --git a/core/src/ui/render_state.rs b/core/src/ui/render_state.rs index fa79584..8b08ff9 100644 --- a/core/src/ui/render_state.rs +++ b/core/src/ui/render_state.rs @@ -462,16 +462,14 @@ impl UiRenderState { /// The pixel size of a region held in `slot`'s coordinates. pub(super) fn px_of(&self, slot: MoveIdx, region: UiRegion) -> PxVec2 { - self.moves - .resolve(slot, region) - .size() - .to_px(self.output_size) + self.moves.size_of(slot, region).to_px(self.output_size) } - /// Where a region held in `slot`'s coordinates lands on screen, which is - /// the walk the vertex shader does. + /// Where a region held in `slot`'s coordinates lands on screen, to + /// compare one box against another. Both ends of it, where + /// [`Self::px_of`] wants only the length between them. fn px_region(&self, slot: MoveIdx, region: UiRegion) -> PixelRegion { - self.moves.resolve(slot, region).to_px(self.output_size) + self.moves.compose(slot, region).to_px(self.output_size) } /// A clean widget's retained answer, if that answer holds for a box of