A ninth sweep, over the part no earlier round named -- the widget vocabulary and the builder methods, `Widgets`, the examples, the `util` additions and the manifests -- and once more over `77ed7a2`, the eighth sweep's own commit and so itself unreviewed. A hint overrode a rule. `declared_lens` asked `rules[axis].declared()` first and fell through to the widget's own `size_hint` whenever that answered `None` -- which it does for a share, since a share is not a declaration. So a widget carrying `width(leftover(1))` and hinting a pixel length of its own was handed a box of the hint, against the rule and against the comment inside the function: "a hint still narrows the box where no rule does". `Painter::size_hint` spells the same rule-else-hint step three hundred lines up and gets it right, with the reason written on it; both read `Widgets::exact_len` now, and `declared_lens` is the part of its answer that needs nobody to divide it. `Image` is the only widget here whose hint is a declared length, and neither the tests nor the generator builds one, so nothing in this repository could reach the difference -- which is why the dump is unchanged and why the test builds a widget of its own. It records the box it was asked in: 400 with the rule and 50 without, and 50 either way before this. Marking a widget for redraw had no name. Twenty-one sites under `tests/` said it as `widgets_mut().get_dyn_mut(id);` with the widget thrown away, five with a `let _ =` in front, one with a comment explaining what the line was for, and one wrapped in a local function called `mark`. `Widgets::mark_for_redraw` says it. `revision_cost.rs` keeps the long spelling and now says why in place: it is deliberately in the API subset an old worktree also has. `assert_same_regions` could not see the defect the eighth sweep had just fixed. It zips the warm and cold id lists, so a list naming one widget twice -- which is what `width`, `sized` and `align` giving back their own argument produces -- compares fewer boxes than it lists and says nothing about it. It now rejects a repeated id and two lists of different lengths, which also checks the nine fixtures that round left alone: all eighteen cases pass. Bare pairs where the framework has named ones. `random.rs`'s `Lens` and `Aligns` were `[Option<LayoutLen>; 2]` and `[Option<AxisAlign>; 2]`, read as `[0]`/`[1]` and zipped against a hand-written `[Axis::X, Axis::Y]`. They are `SizeRules` and `Align`; `Align` took the `Index<Axis>` every other per-axis pair on this branch has, and `RegionAlign::from` does the "an axis left out is centred" step two rigs were spelling per axis. The three sites that wrote the axis pair out say `Axis::BOTH`, which is what the rest of the layout code says. `BothAxis<T>`, `AxisT`, `XAxis` and `YAxis` -- 45 lines with a const trait, two marker types and three accessors -- have no user anywhere in the workspace. They are the mechanism `impl_axis_index!` replaced, in the file this branch took `Vec2::axis`/`axis_mut` out of. Deleted, which is a drive-by in a block the branch was already rewriting; drop it if the scope matters more. Smaller things, each in its own place: `Wrapper` arrived beside core's `WidgetWrapper`, one word for a widget that wraps a child and for a dynamic borrow guard, so the alias is gone and its two uses name `DynBorrower` -- which is what they are. `Wrapper::new`, `Wrapper::empty` and its `Default` were three names for one value, two of them unused. `Arena::get_mut` was the only `pub(crate)` among `pub` siblings on a public type. `Selector` rounded the pointer onto the pixel grid to do arithmetic on two values already there, losing the precision the platform gave it for nothing; the step between the regions is taken on the grid instead. And the two `debug` profile settings carry their reason where the next reader looks rather than only in the commit that made them, one of which was about renaming `rest`. Format, clippy with and without layout-diagnostics, and the suite (132 + 19 + 13 + 4) are clean. The cold dump over 400 depth-5 trees is byte-identical to `77ed7a2` across all 34,488 boxes, and all three seed scans pass: 400 at depth 5 in 63.27s, 1,000 at depth 6 in 160.45s, 2,000 at depth 4 in 302.52s. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
218 lines
6.4 KiB
Rust
218 lines
6.4 KiB
Rust
use crate::util::impl_axis_index;
|
|
use crate::{Px, Rel};
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct Align {
|
|
pub x: Option<AxisAlign>,
|
|
pub y: Option<AxisAlign>,
|
|
}
|
|
|
|
impl Align {
|
|
pub const TOP_LEFT: RegionAlign = RegionAlign::TOP_LEFT;
|
|
pub const TOP_CENTER: RegionAlign = RegionAlign::TOP_CENTER;
|
|
pub const TOP_RIGHT: RegionAlign = RegionAlign::TOP_RIGHT;
|
|
pub const CENTER_LEFT: RegionAlign = RegionAlign::CENTER_LEFT;
|
|
pub const CENTER: RegionAlign = RegionAlign::CENTER;
|
|
pub const CENTER_RIGHT: RegionAlign = RegionAlign::CENTER_RIGHT;
|
|
pub const BOT_LEFT: RegionAlign = RegionAlign::BOT_LEFT;
|
|
pub const BOT_CENTER: RegionAlign = RegionAlign::BOT_CENTER;
|
|
pub const BOT_RIGHT: RegionAlign = RegionAlign::BOT_RIGHT;
|
|
pub const LEFT: CardinalAlign = CardinalAlign::LEFT;
|
|
pub const H_CENTER: CardinalAlign = CardinalAlign::H_CENTER;
|
|
pub const RIGHT: CardinalAlign = CardinalAlign::RIGHT;
|
|
pub const TOP: CardinalAlign = CardinalAlign::TOP;
|
|
pub const V_CENTER: CardinalAlign = CardinalAlign::V_CENTER;
|
|
pub const BOT: CardinalAlign = CardinalAlign::BOT;
|
|
|
|
pub fn tuple(&self) -> (Option<AxisAlign>, Option<AxisAlign>) {
|
|
(self.x, self.y)
|
|
}
|
|
}
|
|
|
|
/// Where a widget sits in a box longer than it is. The default is the middle,
|
|
/// because the two edges are the ones that assume a direction: which of them
|
|
/// is the near one depends on the writing system and on which way a container
|
|
/// runs, and the middle is the same either way.
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub struct AxisAlign(Rel);
|
|
|
|
impl AxisAlign {
|
|
pub const NEG: Self = Self::new(0.0);
|
|
pub const CENTER: Self = Self::new(0.5);
|
|
pub const POS: Self = Self::new(1.0);
|
|
|
|
pub const fn new(rel: f32) -> Self {
|
|
Self(Rel::from_f32(rel))
|
|
}
|
|
|
|
/// A fraction of the room left over, which is what the layout reads: the
|
|
/// three constants are the familiar places along it, not the only ones.
|
|
pub const fn rel(&self) -> Rel {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
impl Default for AxisAlign {
|
|
fn default() -> Self {
|
|
Self::CENTER
|
|
}
|
|
}
|
|
|
|
pub struct CardinalAlign {
|
|
axis: Axis,
|
|
align: AxisAlign,
|
|
}
|
|
|
|
impl CardinalAlign {
|
|
pub const LEFT: Self = Self::new(Axis::X, AxisAlign::NEG);
|
|
pub const H_CENTER: Self = Self::new(Axis::X, AxisAlign::CENTER);
|
|
pub const RIGHT: Self = Self::new(Axis::X, AxisAlign::POS);
|
|
pub const TOP: Self = Self::new(Axis::Y, AxisAlign::NEG);
|
|
pub const V_CENTER: Self = Self::new(Axis::Y, AxisAlign::CENTER);
|
|
pub const BOT: Self = Self::new(Axis::Y, AxisAlign::POS);
|
|
|
|
pub const fn new(axis: Axis, align: AxisAlign) -> Self {
|
|
Self { axis, align }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
|
pub struct RegionAlign {
|
|
pub x: AxisAlign,
|
|
pub y: AxisAlign,
|
|
}
|
|
|
|
impl RegionAlign {
|
|
pub const TOP_LEFT: Self = Self::new(AxisAlign::NEG, AxisAlign::NEG);
|
|
pub const TOP_CENTER: Self = Self::new(AxisAlign::CENTER, AxisAlign::NEG);
|
|
pub const TOP_RIGHT: Self = Self::new(AxisAlign::POS, AxisAlign::NEG);
|
|
pub const CENTER_LEFT: Self = Self::new(AxisAlign::NEG, AxisAlign::CENTER);
|
|
pub const CENTER: Self = Self::new(AxisAlign::CENTER, AxisAlign::CENTER);
|
|
pub const CENTER_RIGHT: Self = Self::new(AxisAlign::POS, AxisAlign::CENTER);
|
|
pub const BOT_LEFT: Self = Self::new(AxisAlign::NEG, AxisAlign::POS);
|
|
pub const BOT_CENTER: Self = Self::new(AxisAlign::CENTER, AxisAlign::POS);
|
|
pub const BOT_RIGHT: Self = Self::new(AxisAlign::POS, AxisAlign::POS);
|
|
|
|
pub const fn new(x: AxisAlign, y: AxisAlign) -> Self {
|
|
Self { x, y }
|
|
}
|
|
}
|
|
|
|
impl UiVec2 {
|
|
pub fn partial_align(&self, align: Align) -> UiRegion {
|
|
UiRegion {
|
|
x: if let Some(align) = align.x {
|
|
self.x.align(align)
|
|
} else {
|
|
UiSpan::FULL
|
|
},
|
|
y: if let Some(align) = align.y {
|
|
self.y.align(align)
|
|
} else {
|
|
UiSpan::FULL
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn align(&self, align: RegionAlign) -> UiRegion {
|
|
UiRegion {
|
|
x: self.x.align(align.x),
|
|
y: self.y.align(align.y),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Vec2 {
|
|
pub fn partial_align(&self, align: Align) -> UiRegion {
|
|
let s = UiVec2::from(*self);
|
|
UiRegion {
|
|
x: if let Some(align) = align.x {
|
|
s.x.align(align)
|
|
} else {
|
|
UiSpan::FULL
|
|
},
|
|
y: if let Some(align) = align.y {
|
|
s.y.align(align)
|
|
} else {
|
|
UiSpan::FULL
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn align(&self, align: RegionAlign) -> UiRegion {
|
|
let s = UiVec2::from(*self);
|
|
UiRegion {
|
|
x: s.x.align(align.x),
|
|
y: s.y.align(align.y),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Len {
|
|
/// This length placed in the box it is measured in: the alignment names a
|
|
/// point along that box, and the two ends are that point less the part of
|
|
/// the length falling before it and plus the part falling after.
|
|
pub const fn align(&self, align: AxisAlign) -> UiSpan {
|
|
let rel = align.rel();
|
|
let at = Len::from_parts(rel, Px::ZERO);
|
|
UiSpan {
|
|
start: at - self.scale(rel),
|
|
end: at + self.scale(Rel::ONE.sub(rel)),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RegionAlign> for Align {
|
|
fn from(region: RegionAlign) -> Self {
|
|
Self {
|
|
x: Some(region.x),
|
|
y: Some(region.y),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<Align> for RegionAlign {
|
|
fn from(align: Align) -> Self {
|
|
Self {
|
|
x: align.x.unwrap_or(AxisAlign::CENTER),
|
|
y: align.y.unwrap_or(AxisAlign::CENTER),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<CardinalAlign> for RegionAlign {
|
|
fn from(align: CardinalAlign) -> Self {
|
|
Align::from(align).into()
|
|
}
|
|
}
|
|
|
|
impl From<CardinalAlign> for Align {
|
|
fn from(cardinal: CardinalAlign) -> Self {
|
|
let align = Some(cardinal.align);
|
|
match cardinal.axis {
|
|
Axis::X => Self { x: align, y: None },
|
|
Axis::Y => Self { x: None, y: align },
|
|
}
|
|
}
|
|
}
|
|
|
|
const impl From<RegionAlign> for UiVec2 {
|
|
fn from(align: RegionAlign) -> Self {
|
|
Self::new(
|
|
Len::from_parts(align.x.rel(), Px::ZERO),
|
|
Len::from_parts(align.y.rel(), Px::ZERO),
|
|
)
|
|
}
|
|
}
|
|
|
|
impl RegionAlign {
|
|
pub const fn pos(self) -> UiVec2 {
|
|
UiVec2::from(self)
|
|
}
|
|
}
|
|
|
|
impl_axis_index!(RegionAlign => AxisAlign);
|
|
impl_axis_index!(Align => Option<AxisAlign>);
|