-
Notifications
You must be signed in to change notification settings - Fork 567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make the cursor API stateful and widget-local. #1433
Conversation
If I am not mistaken, this only allows setting cursors for I guess this is sort of what I also imagined at first, except I hit a wall at having some widget state managed by druid. I guess this is exactly that except it only works for setting cursors within I took a quick look at the code and it seems very straight-forward to me, looks good. |
We definitely want to be able to set the cursor in Runebender to represent hovering over, eg, control points. Does this preclude that? |
self.handle.set_cursor(&cursor); | ||
} else if matches!(event, Event::MouseMove(..)) { | ||
self.handle.set_cursor(&Cursor::Arrow); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice if it would only set the cursor when it actually changed. I think it was part of the nice to haves.
You could probably just compare the Rc
since that should stay the same if the cursor doesn't change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there are cases where you have to set the cursor because it was invalidated some other way, for example when the window loses focus. My personal feeling is that it is possible to track those other state changes but simpler not to, so set it every time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, there's an issue open about this (#112) but I didn't feel like taking it on.
If you call |
I don't think so, no. You'll call |
I think it does? but maybe I'm interpreting the question wrong. Because if you set the cursor within a widget(pod) it will automatically set the cursor to that when it hovers over that widget(pod) right? |
Right, but if you want to do something fancier (like only show a cursor while the mouse is over some more complicated subset of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this on! have left my comments inline, nothing really major. I think this is definitely a more intuitive API.
druid/examples/cursor.rs
Outdated
data: &AppState, | ||
env: &Env, | ||
) { | ||
if !Rc::ptr_eq(&data.cursor, &old_data.cursor) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to just write this out as !data.cursor.same(&old_data.cursor)
, that should be more familiar to folks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This brings up an interesting question: which is better
first.same(&second)
Data::same(&first, &second)
I prefer the second one, the reason being that I can immediately grok that we are using the fact that both types impl Data
. I also find it easier to read. This is a subjective opinion though and I'd be interested in others' opinions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the first is more idiomatic, but I see your point. We have definitely been using the first version everywhere, though, so I'm inclined to be consistent; understanding that same
is the method on Data
is a pretty fundamental druid concept.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I make Cursor
implement PartialEq
, so now this is just ==
.
druid/examples/cursor.rs
Outdated
} | ||
|
||
fn ui_builder() -> impl Widget<AppState> { | ||
Button::new("Change cursor") | ||
.on_click(|ctx, data: &mut AppState, _env| { | ||
.on_click(|_ctx, data: &mut AppState, _env| { | ||
data.cursor = next_cursor(&data.cursor, data.custom.clone()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally unrelated to this but just noticing that it might make sense for this to just be a method on AppState
? then we wouldn't need to pass in anything.
druid/src/contexts.rs
Outdated
pub fn set_cursor(&mut self, cursor: &Cursor) { | ||
*self.cursor = Some(cursor.clone()); | ||
self.widget_state.cursor_setting = CursorSetting::Set(Rc::new(cursor.clone())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the motivation for using the Rc
? is this so that we play well with Data
? Or to avoid having cloning be expensive?
In either case I think it might be more ergonomic to solve this at the druid-shell
level; for instance Cursor::Custom
could have an Rc<platform::window::Cursor>
, and we could add a Data
impl for Cursor
in druid, if we wanted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was worried about the cost. Which is silly, because I only did the druid-shell cursor thing recently, and I already forgot that it does the shared pointer thing already (within the platform-specific code). I've added the Data
impl anyway.
druid/src/core.rs
Outdated
@@ -140,6 +145,18 @@ pub(crate) enum FocusChange { | |||
Previous, | |||
} | |||
|
|||
/// The possible cursor states for a widget. | |||
#[derive(Clone)] | |||
pub(crate) enum CursorSetting { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Feels weird to have use a gerund for the name here, although it's not a big deal. I might match the naming of the FocusChange
and go with CursorChange
? or SetCursor
? I don't think it's a big deal for an internal type but at least want to consider what some alternatives could be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went with CursorChange
.
self.cursor = child_cursor; | ||
} else if let CursorSetting::Set(cursor) = &child_state.cursor_setting { | ||
self.cursor = Some(Rc::clone(cursor)); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this logic is hurting my head a bit.
Is there a situation where the child_cursor.is_some()
ends up unintentionally overriding the child's cursor_setting
?
or... what if both a parent and a child have called set_cursor
, and the mouse moves over to the child, and then back to the parent, does the parent end up correctly resetting the cursor? This happens at the grandparent, in its own merge_up
?
I think I'm satisfied, but want to at least write that concern down.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've re-written the logic so that it's only merging self.cursor_change
and child.cursor
to make self.cursor
. (i.e. child.cursor_change
is no longer involved, so there's less multi-generational thinking involved)
I've also tried to make it clearer that cursor
gets reset on every pass through the tree, whereas cursor_change
is persistent.
Thanks for the comments! I think I've addressed them all, and I also realized that I never updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks again for taking this on!
Co-authored-by: Colin Rofls <[email protected]>
This is an attempt at #1368. The point is that instead of calling
set_cursor
on every mouse move, we call it once and then it will take effect whenever the calling widget is hot or active.Since this is a more "structured" API than the previous one, it's also a bit less powerful. This is because druid now takes care of bubbling and deciding between the various cursor requests, whereas in the previous API it was all under the caller's control. Having said that, I don't actually know of any realistic use-cases that the new API misses out on.