From d21a21524f758f63b3949dbd2e2d6e6aaa0ca493 Mon Sep 17 00:00:00 2001 From: iris-ai <4+iris-ai@noreply.localhost> Date: Wed, 16 Sep 2026 20:48:07 -0400 Subject: [PATCH] Rename WidgetPtr to Wrapper and give it a builder Bryan's call, 2026-09-16: a length and an alignment are properties of one widget, so a widget cannot both be 100 wide and take two shares of a row -- that needs two widgets, and the second one should do as little as possible. `WidgetPtr` already was that widget: it draws its child in the whole of its box and reports what the child said. It only lacked a name that says so and a way to make one around an existing widget. `Wrapper` rather than `Wrap` so it cannot be read as the text setting, and `.wrapper()` rather than `.wrapped()` for the same reason. Its child stays optional, since being a swappable slot is what it was written for and what the tab bar still uses it as. `set_ptr` is deleted rather than renamed. It had no caller, and putting a widget into an existing wrapper is what `Wrapper::set` already does. `tabs` draws its centred square again: `.sized((100, 100)).center() .wrapper().width(leftover(2))` is two widgets where the chain without `.wrapper()` was one, and `.width` was overwriting what `.sized` set. That was the last of the three ways `tabs` had drifted from canonical `main` unnoticed; what is left between them is the truncated multiply's antialiased edges and the widget count itself. `widget_trait!` takes no attributes, so `.wrapper()` carries an ordinary comment and the explanation lives on `Wrapper`. Checked: fmt, clippy, 83 suite tests, 17 core unit tests, the release oracle at 100 seeds, and `tabs` rendered at 1920x1200 against `main`'s own. Co-Authored-By: Claude Opus 5 --- examples/tabs/main.rs | 6 +++++- src/widget/mod.rs | 4 ++-- src/widget/trait_fns.rs | 10 +++++++--- src/widget/{ptr.rs => wrapper.rs} | 16 ++++++++++++---- 4 files changed, 26 insertions(+), 10 deletions(-) rename src/widget/{ptr.rs => wrapper.rs} (57%) diff --git a/examples/tabs/main.rs b/examples/tabs/main.rs index 3861dc3..6de911d 100644 --- a/examples/tabs/main.rs +++ b/examples/tabs/main.rs @@ -20,10 +20,14 @@ impl DefaultAppState for Client { let pad_test = ( rrect.color(Color::BLUE), ( + // The square is one widget and the two shares of the row it + // sits centred in are another: a length is a property of a + // widget, so `.width` here would overwrite the `.sized`. rrect .color(Color::RED) .sized((100, 100)) .center() + .wrapper() .width(leftover(2)), ( rrect.color(Color::ORANGE), @@ -143,7 +147,7 @@ impl DefaultAppState for Client { .span(Dir::DOWN) .add(rsc); - let main = WidgetPtr::new().add(rsc); + let main = Wrapper::new().add(rsc); let vals = Rc::new(RefCell::new((0, Vec::new()))); let mut switch_button = |color, to: WeakWidget, label| { diff --git a/src/widget/mod.rs b/src/widget/mod.rs index 016fbe8..75617a2 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -1,15 +1,15 @@ mod image; mod mask; mod position; -mod ptr; mod rect; mod text; mod trait_fns; +mod wrapper; pub use image::*; pub use mask::*; pub use position::*; -pub use ptr::*; pub use rect::*; pub use text::*; pub use trait_fns::*; +pub use wrapper::*; diff --git a/src/widget/trait_fns.rs b/src/widget/trait_fns.rs index 8bd912d..bf7efad 100644 --- a/src/widget/trait_fns.rs +++ b/src/widget/trait_fns.rs @@ -134,9 +134,13 @@ widget_trait! { |state| self.add(state) } - fn set_ptr(self, ptr: WeakWidget, state: &mut Rsc) { - let id = self.add_strong(state); - state.ui_mut().widgets[ptr].inner = Some(id); + // Named for the type it makes rather than as `wrapped`, which would read + // as the text setting. `widget_trait!` takes no attributes, so what it is + // for is on `Wrapper` itself. + fn wrapper(self) -> impl WidgetFn { + |state| Wrapper { + inner: Some(self.add_strong(state)), + } } } diff --git a/src/widget/ptr.rs b/src/widget/wrapper.rs similarity index 57% rename from src/widget/ptr.rs rename to src/widget/wrapper.rs index d25312f..9ed3cfa 100644 --- a/src/widget/ptr.rs +++ b/src/widget/wrapper.rs @@ -1,11 +1,19 @@ use crate::prelude::*; use std::marker::Unsize; -pub struct WidgetPtr { +/// One widget in a box of its own, doing as little as possible on the way: +/// it draws its child in the whole of its box and reports back what the child +/// said. It exists because a length and an alignment are properties of one +/// widget, so a widget cannot both be 100 wide and take two shares of a row +/// -- the two lengths need two widgets, and this is the smaller one. +/// +/// Its child is optional so it can also be the swappable slot a tab bar +/// needs, which is what it was written for. +pub struct Wrapper { pub inner: Option, } -impl Widget for WidgetPtr { +impl Widget for Wrapper { fn draw(&mut self, painter: &mut Painter) -> Size { match &self.inner { Some(id) => painter.widget(id).size(), @@ -14,7 +22,7 @@ impl Widget for WidgetPtr { } } -impl WidgetPtr { +impl Wrapper { pub fn new() -> Self { Self::default() } @@ -35,7 +43,7 @@ impl WidgetPtr { } } -impl Default for WidgetPtr { +impl Default for Wrapper { fn default() -> Self { Self::empty() }