Say what may be done to a drawing, and default to nothing

`SizeDependence::{None, Internal, External}` becomes
`OnResize::{Scale, Translate, Redraw}`, which says what the retained path
may do rather than leaving the reader to work it out from a dependency.

`Redraw` is now the default, and that is the substance of this rather
than the naming. `Translate` was, and nothing opted into it: `SetSize`
reports one size and hands its child the whole box, so its pixels change
with the box and carrying them stretched a 100x100 rect across half the
window. A default that is only right for widgets that happen to qualify
is the same fault as an unchecked reuse flag.

`Translate` still does nothing, and now for the reason rather than the
one I gave before: `mov` translates perfectly well, but `ActiveData`'s
`region` is both the box a widget was given and the box its primitives
occupy, and `mov` remaps out of it. Keeping a drawing at its old size
while the box grows leaves those two disagreeing, and the next move
stretches it. Separating them is what the offset chain does.

Caught by rendering `tabs` against `main` rather than by a test, which is
the argument for keeping that check in the loop.
This commit is contained in:
iris committed 2026-09-13 23:47:12 -04:00
1 parent ec012c4552
commit b108645240
8 files changed
+63 -53

No files matched your search

+19 -10
View File
@@ -1,5 +1,5 @@
use crate::{
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, Painter, PixelRegion, Size, SizeDependence,
ActiveData, Axis, DrawLayers, IdLike, MaskIdx, OnResize, Painter, PixelRegion, Size,
StrongWidget, UiRegion, UiRsc, WidgetId, Widgets,
util::{HashMap, HashSet, Vec2, forget_ref},
};
@@ -93,6 +93,7 @@ impl UiRenderState {
}
// draw widget
rsc.widgets_mut().needs_redraw.remove(&id);
self.draw_started.insert(id);
let mut painter = Painter {
@@ -202,7 +203,7 @@ impl UiRenderState {
return Some(size);
}
if self.reusable(id, region, rsc) {
// Its drawing stands; the box is written into the primitives.
// Its drawing stands; the new box is remapped into the primitives.
self.mov(id, was, region);
return Some(size);
}
@@ -223,12 +224,15 @@ impl UiRenderState {
[Axis::X, Axis::Y].into_iter().all(|axis| {
let offered = region.axis_mut(axis).len();
let had = was.axis_mut(axis).len();
match widget.size_dependence(axis) {
SizeDependence::None => true,
// `Internal` could also keep its drawing when only the room
// around it changed, but that is a translation rather than a
// remap, so it waits for the move chain.
SizeDependence::Internal | SizeDependence::External => offered == had,
match widget.on_resize(axis) {
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
// are in, and `mov` remaps from it -- so carrying a drawing at
// its old size while the box grows makes the next move stretch
// it. The offset chain is what separates the two.
OnResize::Translate | OnResize::Redraw => offered == had,
}
})
}
@@ -350,14 +354,19 @@ impl UiRenderState {
/// redraws a widget that's currently active (drawn)
pub fn redraw(&mut self, id: WidgetId, rsc: &mut dyn UiRsc) {
rsc.widgets_mut().needs_redraw.remove(&id);
self.draw_started.remove(&id);
// Whoever read this widget's size may be a different size now, so the
// highest reader is what draws; it reaches this one on the way down.
// highest reader is what draws; it reaches this one on the way down,
// where the mark is what stops it being reused as it stands. Clearing
// it here would hide the change from the draw that came to apply it.
if let Some(top) = self.highest_reader(id) {
self.redraw(top, rsc);
// Cleared by that draw if it reached here; if it did not, this is
// no longer drawn and asking again would not end.
rsc.widgets_mut().needs_redraw.remove(&id);
return;
}
rsc.widgets_mut().needs_redraw.remove(&id);
if self.draw_started.contains(&id) {
return;
+21 -16
View File
@@ -15,20 +15,25 @@ pub use tag::*;
pub use view::*;
pub use widgets::*;
/// How much of the box a widget was handed its drawing depends on, and so
/// what has to change before it must be drawn again. Asked per axis, because
/// wrapped text depends on the width it is offered and not on the height.
/// What may be done to a widget's drawing when the box it was given changes
/// on this axis, instead of drawing it again. Asked per axis, because wrapped
/// text reads the width it is offered and not the height.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum SizeDependence {
/// None of it: the box only says where the primitives go, so a new one is
/// written into them instead of drawn.
None,
/// Its own extent, whatever box that sits in. Reusable in any box that
/// leaves that extent unchanged, including a larger one it does not fill.
pub enum OnResize {
/// Stretched to the new box, which is all its drawing ever was.
Scale,
/// Carried to the new box at the size it drew, which it keeps.
Translate,
/// Nothing doing: it draws again.
///
/// The default, because it is the only answer that is right without
/// knowing anything about the widget. The other two are claims that the
/// drawing does not change when the box does, and a widget that inherited
/// such a claim by accident would be quietly wrong -- `SetSize` reports
/// one size and hands its child the whole box, so its pixels very much do
/// change.
#[default]
Internal,
/// The extent of the box itself, used or not.
External,
Redraw,
}
pub trait Widget: Any {
@@ -42,8 +47,8 @@ pub trait Widget: Any {
None
}
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
SizeDependence::Internal
fn on_resize(&self, _axis: Axis) -> OnResize {
OnResize::Translate
}
}
@@ -56,8 +61,8 @@ impl Widget for () {
Some(Len::ZERO)
}
fn size_dependence(&self, _axis: Axis) -> SizeDependence {
SizeDependence::None
fn on_resize(&self, _axis: Axis) -> OnResize {
OnResize::Scale
}
}