Track retained layout validity explicitly
This commit is contained in:
1 parent
691e3eb23c
commit
29c7881c8a
25 files changed
+836
-795
No files matched your search
@@ -14,10 +14,6 @@ impl Widget for Image {
|
||||
fn size_hint(&self, axis: Axis) -> Option<Len> {
|
||||
Some(Len::px(self.handle.size().axis(axis)))
|
||||
}
|
||||
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
|
||||
pub fn image<State: UiRsc>(image: impl LoadableImage) -> impl WidgetFn<State, Image> {
|
||||
|
||||
@@ -9,9 +9,4 @@ impl Widget for Masked {
|
||||
painter.set_mask(painter.region());
|
||||
painter.widget(&self.inner).size()
|
||||
}
|
||||
|
||||
/// It clips to the box it was given, not to the part its child used.
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Redraw
|
||||
}
|
||||
}
|
||||
@@ -39,10 +39,4 @@ impl Widget for Aligned {
|
||||
let placed = painter.place(&self.inner, region).size();
|
||||
if had_size { placed } else { size }
|
||||
}
|
||||
|
||||
/// The aligned box is a fraction of its own, so the child keeps its
|
||||
/// length and stays against the edge it was aligned to.
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
@@ -12,8 +12,4 @@ impl Widget for LayerOffset {
|
||||
}
|
||||
painter.widget(&self.inner).size()
|
||||
}
|
||||
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,17 @@ pub struct MaxSize {
|
||||
impl Widget for MaxSize {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let child = painter.widget(&self.inner).size();
|
||||
let output = painter.output_size();
|
||||
let own = painter.px_size();
|
||||
Size {
|
||||
x: capped(child.x, self.x, output.x),
|
||||
y: capped(child.y, self.y, output.y),
|
||||
x: capped(child.x, self.x, own.x),
|
||||
y: capped(child.y, self.y, own.y),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn capped(len: Len, max: Option<Len>, output: f32) -> Len {
|
||||
fn capped(len: Len, max: Option<Len>, own: f32) -> Len {
|
||||
match max {
|
||||
Some(max) if len.apply_leftover().to_px(output) > max.apply_leftover().to_px(output) => max,
|
||||
Some(max) if len.apply_leftover().to_px(own) > max.apply_leftover().to_px(own) => max,
|
||||
_ => len,
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,4 @@ impl Widget for Offset {
|
||||
let region = UiRegion::FULL.offset(self.amt);
|
||||
painter.widget_within(&self.inner, region).size()
|
||||
}
|
||||
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,6 @@ impl Widget for Pad {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// The padding is an offset from each edge, so a longer box pads the same
|
||||
/// amount and the child takes what is left over.
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Padding {
|
||||
|
||||
@@ -11,31 +11,40 @@ pub struct Scroll {
|
||||
|
||||
impl Widget for Scroll {
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size {
|
||||
let output_len = painter.output_len(self.axis);
|
||||
// Its size is its content's, whatever box that is scrolled within.
|
||||
let container_len = UiScalar::px(painter.px_len_for_draw(self.axis));
|
||||
let container_len = painter.px_len(self.axis);
|
||||
// Draw in the whole container only when its scrolling-axis length is
|
||||
// not already known, then place it at the scrolled offset.
|
||||
let known_len = painter.known_len(&self.inner, self.axis, UiRegion::FULL);
|
||||
let measured = known_len.is_none();
|
||||
let child = measured.then(|| painter.place(&self.inner, UiRegion::FULL).size());
|
||||
let content_len = known_len
|
||||
.unwrap_or_else(|| child.unwrap().axis(self.axis))
|
||||
.apply_leftover()
|
||||
.within_len(container_len)
|
||||
.to_px(output_len);
|
||||
self.container_len = container_len.to_px(output_len);
|
||||
self.content_len = content_len;
|
||||
let (answer_len, measured) = match painter.known_len(&self.inner, self.axis, UiRegion::FULL)
|
||||
{
|
||||
Some(len) => (len, None),
|
||||
None => {
|
||||
let size = painter.place(&self.inner, UiRegion::FULL).size();
|
||||
(size.axis(self.axis), Some(size))
|
||||
}
|
||||
};
|
||||
let content = answer_len.apply_leftover();
|
||||
self.container_len = container_len;
|
||||
self.content_len = content.to_px(container_len);
|
||||
|
||||
if self.snap_end {
|
||||
self.amt = self.content_len - self.container_len;
|
||||
}
|
||||
self.update_amt();
|
||||
// Content of a fixed length that fits sits at the start of any box
|
||||
// it fits in; one scrolled part way sits where it is until the box
|
||||
// shrinks past what is left of it. Kept to the end, it moves with
|
||||
// every length.
|
||||
if content.rel == 0.0 && self.content_len <= self.container_len {
|
||||
painter.holds(self.axis, self.content_len..=f32::INFINITY);
|
||||
} else if content.rel == 0.0 && !self.snap_end {
|
||||
let left = self.content_len - self.amt;
|
||||
painter.holds(self.axis, f32::NEG_INFINITY..=left);
|
||||
}
|
||||
|
||||
let mut region = UiRegion::FULL.offset(Vec2::from_axis(self.axis, -self.amt, 0.0));
|
||||
region.axis_mut(self.axis).end = region.axis(self.axis).start.offset(self.content_len);
|
||||
let placed = painter.place(&self.inner, region).size();
|
||||
child.unwrap_or(placed)
|
||||
measured.unwrap_or_else(|| Size::from_axis(self.axis, answer_len, placed.axis(!self.axis)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,8 +27,4 @@ impl Widget for SetSize {
|
||||
Axis::Y => self.y,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
@@ -32,12 +32,44 @@ impl Widget for Span {
|
||||
let gap = self.gap * self.children.len().saturating_sub(1) as f32;
|
||||
let total = lens.iter().fold(Len::px(gap), |sum, len| sum + *len);
|
||||
|
||||
// Whether anything is left over is a question in pixels: `rel(0.5)`
|
||||
// beside 300 px is full at 600 and overfull at 400. The answer is
|
||||
// the same on either side of the length the fixed parts alone fill.
|
||||
let fixed = 1.0 - total.rel;
|
||||
let shares = total.leftover > 0.0 && fixed * painter.px_len(axis) > total.px;
|
||||
if total.leftover > 0.0 {
|
||||
let range = if fixed > 0.0 {
|
||||
let full = total.px / fixed;
|
||||
match shares {
|
||||
true => full..=f32::INFINITY,
|
||||
false => f32::NEG_INFINITY..=full,
|
||||
}
|
||||
} else if fixed < 0.0 {
|
||||
let full = total.px / fixed;
|
||||
match shares {
|
||||
true => f32::NEG_INFINITY..=full,
|
||||
false => full..=f32::INFINITY,
|
||||
}
|
||||
} else {
|
||||
f32::NEG_INFINITY..=f32::INFINITY
|
||||
};
|
||||
painter.holds(axis, range);
|
||||
}
|
||||
|
||||
let mut start = UiScalar::rel_min();
|
||||
let mut ortho = Len::ZERO;
|
||||
for (child, len) in self.children.iter().zip(&lens) {
|
||||
// A child asking for nothing but a part of what is left over,
|
||||
// when nothing is, is not drawn at all. One that also asked for
|
||||
// pixels or a fraction keeps those and overflows.
|
||||
if len.leftover > 0.0 && len.px == 0.0 && len.rel == 0.0 && !shares {
|
||||
painter.undraw(child);
|
||||
start.px += self.gap;
|
||||
continue;
|
||||
}
|
||||
let mut span = UiSpan::FULL;
|
||||
span.start = start;
|
||||
if len.leftover > 0.0 {
|
||||
if len.leftover > 0.0 && shares {
|
||||
let offset = UiScalar::new(total.rel, total.px);
|
||||
let rel_end = UiScalar::rel(len.leftover / total.leftover);
|
||||
let end = (UiScalar::rel_max() + start) - offset;
|
||||
@@ -70,12 +102,6 @@ impl Widget for Span {
|
||||
let along = total;
|
||||
Size::from_axis(axis, along, ortho)
|
||||
}
|
||||
|
||||
/// Every child is placed in fractions and offsets of the span's own box,
|
||||
/// so a longer box holds the same layout and the children follow it.
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
|
||||
@@ -28,10 +28,6 @@ impl Widget for Stack {
|
||||
}
|
||||
size
|
||||
}
|
||||
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
|
||||
@@ -41,11 +41,6 @@ impl Widget for Rect {
|
||||
fn size_hint(&self, _: Axis) -> Option<Len> {
|
||||
Some(Len::LEFTOVER)
|
||||
}
|
||||
|
||||
/// Its box is its primitive's own region, so a new one is written there.
|
||||
fn on_resize(&self, _: Axis) -> OnResize {
|
||||
OnResize::Scale
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rect(color: UiColor) -> Rect {
|
||||
|
||||
@@ -93,10 +93,6 @@ impl Widget for TextEdit {
|
||||
);
|
||||
size
|
||||
}
|
||||
|
||||
fn on_resize(&self, axis: Axis) -> OnResize {
|
||||
self.view.on_resize(axis)
|
||||
}
|
||||
}
|
||||
|
||||
const CARET_WIDTH: f32 = 1.0;
|
||||
|
||||
+10
-26
@@ -47,12 +47,16 @@ impl TextView {
|
||||
/// answers under the attrs too, so changing those asks a new question
|
||||
/// rather than invalidating anything.
|
||||
fn render(&mut self, painter: &mut Painter) -> &RenderedText {
|
||||
let width = if self.attrs.wrap {
|
||||
Some(painter.px_len(Axis::X))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
painter.render_text(&mut self.buf, &self.attrs, width)
|
||||
let width = self.attrs.wrap.then(|| painter.px_len(Axis::X));
|
||||
let text = painter.render_text(&mut self.buf, &self.attrs, width);
|
||||
// A greedy break is the same break at every width from its longest
|
||||
// line up to the one it was made at: each line still fits, and none
|
||||
// could take a word that did not fit in the wider box. A line too
|
||||
// long to fit at all says nothing about narrower boxes.
|
||||
if let Some(width) = width {
|
||||
painter.holds(Axis::X, text.size.x.min(width)..=width);
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
pub fn tex(&self) -> Option<&RenderedText> {
|
||||
@@ -78,22 +82,6 @@ impl TextView {
|
||||
(region, size)
|
||||
}
|
||||
|
||||
/// Wrapping reads the width it is offered, so a wider box reshapes it and
|
||||
/// a taller one does not. Alignment matters too, and separately: glyphs
|
||||
/// anchored to the start of an axis stay put when that extent changes,
|
||||
/// but centred or end-aligned ones move even though the shaping stands.
|
||||
pub fn on_resize(&self, axis: Axis) -> OnResize {
|
||||
let reshapes = axis == Axis::X && self.attrs.wrap;
|
||||
let anchored = match axis {
|
||||
Axis::X => self.align.x,
|
||||
Axis::Y => self.align.y,
|
||||
} == AxisAlign::Neg;
|
||||
match reshapes || !anchored {
|
||||
true => OnResize::Redraw,
|
||||
false => OnResize::Translate,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(&self) -> String {
|
||||
self.buf.text().to_string()
|
||||
}
|
||||
@@ -120,10 +108,6 @@ impl Widget for Text {
|
||||
self.update_buf();
|
||||
self.view.draw(painter).1
|
||||
}
|
||||
|
||||
fn on_resize(&self, axis: Axis) -> OnResize {
|
||||
self.view.on_resize(axis)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Text {
|
||||
|
||||
Reference in new issue
Block a user