Skip to content

Conversation

@not-my-profile
Copy link
Contributor

Finding widgets by their id in the RenderRoot::edit_root_widget callback makes the code more maintainable because you can rearrange the widgets without having to update the callback. And with the unsafe tree arena find_mut from the root is O(1).

To make this pattern convenient this commit also introduces a Default impl for WidgetId that just returns WidgetId::next().

Finding widgets by their id in the RenderRoot::edit_root_widget callback
makes the code more maintainable because you can rearrange the widgets
without having to update the callback. And with the unsafe tree arena
find_mut from the root is O(1).

To make this pattern convenient this commit also introduces
a Default impl for WidgetId that just returns WidgetId::next().
Copy link
Contributor

@PoignardAzur PoignardAzur left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the idea behind this, and it's something we'll eventually support first-class, but right now it doesn't really work.

Comment on lines +260 to +266
/// Return a [`WidgetMut`] to a child widget by its id.
pub fn find_mut(&mut self, child_id: WidgetId) -> WidgetMut<'_, dyn Widget> {
let child_state_mut = self
.widget_state_children
.reborrow_mut()
.find_mut(child_id)
.expect("find_mut: child not found");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, correct me if I'm missing something, but the problem with that implementation is that WidgetMut will not correctly update the flags of any intermediary nodes between the current widget and the descendant widget.

Like, say you have a A -> B -> C -> D -> E chain and you do A.ctx.find_mut(E) and then update some stuff; the flags for A and E will be updated, but not the flags for B, C and D.

You need something like #1011 to make this method possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right yeah. Forgot about that.

Comment on lines +53 to +58
impl Default for WidgetId {
fn default() -> Self {
Self::next()
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Big no to this. Default implementations that return different things if you call them twice are a huge code smell.

Comment on lines 286 to 290
ctx.render_root().edit_root_widget(|mut root| {
let mut root = root.downcast::<RootWidget>();
let mut flex = RootWidget::child_mut(&mut root);
let mut flex = flex.downcast();
let mut label = Flex::child_mut(&mut flex, 1).unwrap();
let mut label = root.ctx.find_mut(self.ids.display);
let mut label = label.downcast::<Label>();
Label::set_text(&mut label, &*self.value);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that if you already know the id of the widget you're trying to mutate, you don't need a new method:

         ctx.render_root().edit_widget(self.ids.display, |mut label| {
            let mut label = label.downcast::<Label>();
            Label::set_text(&mut label, &*self.value);
         };

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By the way, my medium-term plan for this kind of use-case is to create a new kind of string id (something like WidgetKey) that could be assigned to a widget at construction and let you identify them, basically an equivalent of the DOM id attribute.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need a new method

Oh right yeah. I'll create a new PR to use that in the examples.

my medium-term plan for this kind of use-case is to create a new kind of string id

I really don't see how that would be better than just using WidgetIds. If we have sth else I don't think it should be strings because of type-safety.

Comment on lines +348 to +351
#[derive(Default, Clone)]
struct WidgetIds {
display: WidgetId,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to implement Default. You could add a new() or create() method.

@not-my-profile
Copy link
Contributor Author

not-my-profile commented Jun 5, 2025

Closing this for now since it needs sth like #1011. I'll open a new PR to make the examples use edit_widget.

@not-my-profile not-my-profile reopened this Jun 5, 2025
@not-my-profile not-my-profile marked this pull request as draft June 5, 2025 10:52
@not-my-profile
Copy link
Contributor Author

Meh calling edit_widget multiple times is not that great because then you have multiple rewrite passes.

@PoignardAzur
Copy link
Contributor

PoignardAzur commented Jun 6, 2025

Honestly, that doesn't matter. We're talking about doing two O(Depth) operations for the mutate passes per user input. Even if the widget tree were huge (it very much isn't), that would be acceptable on principle.

Edit: Although I guess we're running all the other passes too, so I see your point. On the other hand, those passes are fairly cheap as long as we don't change layout.

@PoignardAzur
Copy link
Contributor

Okay, so having re-read the code:

  • Mutate passes are O(Depth) and are okay to re-run.
  • Most update passes will just be skipped since we're not doing anything to change their values.
  • Layout passes currently basically recompute everything, so they're O(N).

The examples do change layout (adding buttons or changing text counts), so doubling rewrite passes isn't great for performance. Of course they're small enough that performance doesn't matter, but I get that this isn't the pattern we want to encourage.

I'm thinking this definitely needs to be documented somewhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants