Return the size from draw, and fold placing back into drawing
Review response. `Painter::set_size` is gone: `Widget::draw` returns the `Size` instead, so a widget that does not say what it used cannot compile rather than panicking at the widget that forgot. That also settles setting it twice -- a branch that learns something late just returns a different value. `Painter::place` and `UiRenderState::place` are gone too. `draw_inner` already tried to reuse an active widget's drawing before redrawing it, so `place` was `widget_within` with its own bookkeeping bolted on; drawing a child a second time now *is* how a parent puts it where it belongs, and a child is deduplicated in `children` because listing one twice would move it twice. The unification also drops `place`'s use of `ActiveData::layer`, which is the layer a widget's own `child_layer()` left the painter on rather than the layer it was drawn into. What `place` did unconditionally and `widget_within` did not is record the size dependency, so `Painter::size_hint` now records one: reading a child's length to lay out around it is reading its size, whether it came from a draw or from a hint. `tests/retained.rs` has a parent that only ever reads the hint, which is the case no existing widget exercises. `()` sizes itself `Size::default()` -- rest -- rather than zero, so it is a gap that takes an even share of a span; `WidgetPtr` with nothing in it does the same, since it is the same situation. `Widget::on_resize`'s default body said `Translate` while the enum's `#[default]` said `Redraw`; it now defers to the enum. `was` is `old` throughout, the `OnResize` variant comments are gone, and so are two empty `impl` blocks. Checked: fmt, clippy and 27 tests across the workspace; `tabs` on each of its five tabs, and `tabs` with a replay that adds two images, all byte-identical to `upstream/main`; `view` and `minimal` likewise; and the `text` example rendered at 1920x1200 and 900x1200 to see the paragraph reflow and its container follow.
This commit is contained in:
1 parent
b108645240
commit
9520996623
20 files changed
+143
-151
No files matched your search
+18
-45
@@ -20,7 +20,6 @@ pub struct Painter<'a> {
|
||||
pub(super) children: Vec<WidgetId>,
|
||||
/// The children whose size this widget read while drawing.
|
||||
pub(super) size_deps: Vec<WidgetId>,
|
||||
pub(super) size: Option<Size>,
|
||||
pub layer: usize,
|
||||
pub(super) id: WidgetId,
|
||||
}
|
||||
@@ -70,24 +69,13 @@ impl<'a> Painter<'a> {
|
||||
self.mask = self.rsc.ui_mut().masks.push(Mask { region });
|
||||
}
|
||||
|
||||
/// States what this widget uses of the box it was handed, once per draw.
|
||||
/// This is the size a parent reads.
|
||||
pub fn set_size(&mut self, size: impl Into<Size>) {
|
||||
debug_assert!(
|
||||
self.size.is_none(),
|
||||
"{} set its size twice in one draw",
|
||||
self.label(),
|
||||
);
|
||||
self.size = Some(size.into());
|
||||
}
|
||||
|
||||
/// Draws a widget within this widget's region.
|
||||
pub fn widget<W: ?Sized>(&mut self, id: &StrongWidget<W>) -> DrawResult<'_, 'a> {
|
||||
self.widget_at(id, self.region)
|
||||
}
|
||||
|
||||
/// Draws a widget somewhere within this one.
|
||||
/// Useful for drawing child widgets in select areas.
|
||||
/// Draws a widget somewhere within this one. Drawing one a second time
|
||||
/// gives it a new box, keeping the drawing it already has where it can.
|
||||
pub fn widget_within<W: ?Sized>(
|
||||
&mut self,
|
||||
id: &StrongWidget<W>,
|
||||
@@ -102,10 +90,14 @@ impl<'a> Painter<'a> {
|
||||
id: &StrongWidget<W>,
|
||||
region: UiRegion,
|
||||
) -> DrawResult<'_, 'a> {
|
||||
self.children.push(id.id());
|
||||
let child = id.id();
|
||||
// A child listed twice would be moved twice.
|
||||
if !self.children.contains(&child) {
|
||||
self.children.push(child);
|
||||
}
|
||||
let size = self.state.draw_inner(
|
||||
self.layer,
|
||||
id.id(),
|
||||
child,
|
||||
region,
|
||||
Some(self.id),
|
||||
self.mask,
|
||||
@@ -113,43 +105,24 @@ impl<'a> Painter<'a> {
|
||||
self.rsc,
|
||||
);
|
||||
DrawResult {
|
||||
child: id.id(),
|
||||
child,
|
||||
painter: self,
|
||||
size,
|
||||
}
|
||||
}
|
||||
|
||||
/// What a child says its length is without being drawn, if it can say.
|
||||
pub fn size_hint<W: ?Sized>(&self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
||||
self.rsc.widgets().get_dyn(id.id())?.size_hint(axis)
|
||||
/// Asking counts as reading its size.
|
||||
pub fn size_hint<W: ?Sized>(&mut self, id: &StrongWidget<W>, axis: Axis) -> Option<Len> {
|
||||
let hint = self.rsc.widgets().get_dyn(id.id())?.size_hint(axis)?;
|
||||
self.depend_on_size(id.id());
|
||||
Some(hint)
|
||||
}
|
||||
|
||||
/// Gives a child its final box once this widget knows what that is,
|
||||
/// keeping the drawing it already has where it can.
|
||||
pub fn place<W: ?Sized>(&mut self, id: &StrongWidget<W>, region: UiRegion) -> Size {
|
||||
let region = region.within(&self.region);
|
||||
let id = id.id();
|
||||
// Choosing a child's box depends on its size, from a draw or a hint.
|
||||
// Claiming it is separate: one left out of `children` is removed.
|
||||
if !self.size_deps.contains(&id) {
|
||||
self.size_deps.push(id);
|
||||
fn depend_on_size(&mut self, child: WidgetId) {
|
||||
if !self.size_deps.contains(&child) {
|
||||
self.size_deps.push(child);
|
||||
}
|
||||
if !self.children.contains(&id) {
|
||||
self.children.push(id);
|
||||
}
|
||||
if self.state.active.contains_key(&id) {
|
||||
return self.state.place(id, region, self.rsc);
|
||||
}
|
||||
// Not drawn yet: its length came from a hint, so this is its one draw.
|
||||
self.state.draw_inner(
|
||||
self.layer,
|
||||
id,
|
||||
region,
|
||||
Some(self.id),
|
||||
self.mask,
|
||||
None,
|
||||
self.rsc,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn render_text(
|
||||
@@ -230,7 +203,7 @@ pub struct DrawResult<'p, 'a> {
|
||||
|
||||
impl DrawResult<'_, '_> {
|
||||
pub fn size(self) -> Size {
|
||||
self.painter.size_deps.push(self.child);
|
||||
self.painter.depend_on_size(self.child);
|
||||
self.size
|
||||
}
|
||||
|
||||
|
||||
@@ -106,12 +106,11 @@ impl UiRenderState {
|
||||
primitives: Vec::new(),
|
||||
children: Vec::new(),
|
||||
size_deps: Vec::new(),
|
||||
size: None,
|
||||
rsc,
|
||||
};
|
||||
|
||||
let mut widget = painter.rsc.widgets().get_dyn_dynamic(id);
|
||||
widget.draw(&mut painter);
|
||||
let size = widget.draw(&mut painter);
|
||||
drop(widget);
|
||||
|
||||
let Painter {
|
||||
@@ -123,17 +122,10 @@ impl UiRenderState {
|
||||
primitives,
|
||||
children,
|
||||
size_deps,
|
||||
size,
|
||||
layer,
|
||||
id,
|
||||
} = painter;
|
||||
|
||||
let size = size.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"'{}' ({id:?}) drew without a size; every widget calls Painter::set_size",
|
||||
rsc.widgets().label(id)
|
||||
)
|
||||
});
|
||||
debug_assert!(
|
||||
Self::hints_agree(id, size, rsc),
|
||||
"'{}' ({id:?}) drew a size its size_hint disagrees with",
|
||||
@@ -166,26 +158,6 @@ impl UiRenderState {
|
||||
size
|
||||
}
|
||||
|
||||
/// Gives an already drawn widget a new box, keeping its drawing if it can
|
||||
/// and drawing it again if it cannot.
|
||||
pub(super) fn place(&mut self, id: WidgetId, region: UiRegion, rsc: &mut dyn UiRsc) -> Size {
|
||||
if let Some(size) = self.try_reuse(id, region, rsc) {
|
||||
return size;
|
||||
}
|
||||
let Some(active) = self.remove(id, false, rsc) else {
|
||||
return Size::ZERO;
|
||||
};
|
||||
self.draw_inner(
|
||||
active.layer,
|
||||
id,
|
||||
region,
|
||||
active.parent,
|
||||
active.mask,
|
||||
Some(active.children),
|
||||
rsc,
|
||||
)
|
||||
}
|
||||
|
||||
/// The drawing a widget already has, kept for a new box if the box has not
|
||||
/// changed in a way it depends on.
|
||||
fn try_reuse(&mut self, id: WidgetId, region: UiRegion, rsc: &dyn UiRsc) -> Option<Size> {
|
||||
@@ -193,18 +165,18 @@ impl UiRenderState {
|
||||
return None;
|
||||
}
|
||||
let active = self.active.get(&id)?;
|
||||
let (size, was) = (active.size, active.region);
|
||||
if was == region {
|
||||
let (size, old) = (active.size, active.region);
|
||||
if old == region {
|
||||
return Some(size);
|
||||
}
|
||||
// TODO: epsilon?
|
||||
if was.size() == region.size() {
|
||||
self.mov(id, was, region);
|
||||
if old.size() == region.size() {
|
||||
self.mov(id, old, region);
|
||||
return Some(size);
|
||||
}
|
||||
if self.reusable(id, region, rsc) {
|
||||
// Its drawing stands; the new box is remapped into the primitives.
|
||||
self.mov(id, was, region);
|
||||
self.mov(id, old, region);
|
||||
return Some(size);
|
||||
}
|
||||
None
|
||||
@@ -220,10 +192,10 @@ impl UiRenderState {
|
||||
let Some(widget) = rsc.widgets().get_dyn(id) else {
|
||||
return false;
|
||||
};
|
||||
let mut was = active.region;
|
||||
let mut old = active.region;
|
||||
[Axis::X, Axis::Y].into_iter().all(|axis| {
|
||||
let offered = region.axis_mut(axis).len();
|
||||
let had = was.axis_mut(axis).len();
|
||||
let had = old.axis_mut(axis).len();
|
||||
match widget.on_resize(axis) {
|
||||
OnResize::Scale => true,
|
||||
// `Translate` is not acted on yet, and cannot be until a
|
||||
|
||||
+7
-16
@@ -20,25 +20,15 @@ pub use widgets::*;
|
||||
/// text reads the width it is offered and not the height.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
|
||||
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]
|
||||
Redraw,
|
||||
}
|
||||
|
||||
pub trait Widget: Any {
|
||||
/// Draws the widget, and states what it used with `Painter::set_size`.
|
||||
fn draw(&mut self, painter: &mut Painter);
|
||||
/// Draws the widget, and returns what it used of the box it was given.
|
||||
fn draw(&mut self, painter: &mut Painter) -> Size;
|
||||
|
||||
/// An exact length the widget can give without a painter or its children.
|
||||
/// Optional, and saves a draw rather than changing one: a hint that
|
||||
@@ -48,17 +38,18 @@ pub trait Widget: Any {
|
||||
}
|
||||
|
||||
fn on_resize(&self, _axis: Axis) -> OnResize {
|
||||
OnResize::Translate
|
||||
OnResize::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Widget for () {
|
||||
fn draw(&mut self, painter: &mut Painter) {
|
||||
painter.set_size(Size::ZERO);
|
||||
/// A gap: nothing drawn, at the default length, so a span gives it a share.
|
||||
fn draw(&mut self, _: &mut Painter) -> Size {
|
||||
Size::default()
|
||||
}
|
||||
|
||||
fn size_hint(&self, _axis: Axis) -> Option<Len> {
|
||||
Some(Len::ZERO)
|
||||
Some(Len::default())
|
||||
}
|
||||
|
||||
fn on_resize(&self, _axis: Axis) -> OnResize {
|
||||
|
||||
Reference in new issue
Block a user