-
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
Add support for more mouse buttons and tracking holding. #843
Conversation
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.
A potential gotcha here is that MouseButton
itself is currently limited to representing five buttons, and on mac unknown buttons get mapped to Left
. This is wrong, and should at least be changed to something else; but I think we probably just need an Other
member in MouseButton
?
(otherwise this looks good)
Okay I added |
e6e0ad4
to
3b3b656
Compare
After some further testing and thinking I also changed mouse events to contain One slight downside of using an |
578a4a0
to
94c2f27
Compare
Would it make more sense to have |
An additional (probably useless?) thought: mouse buttons are really a mask, multiple can be down at a time etc. I'm probably over thinking this though, it definitely isn't what we would want to surface as the principle API (although we might want to surface it at some point, for advanced cases?) |
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.
A few little thoughts; this might be the best path forward but would like to consider the alternatives.
druid-shell/src/mouse.rs
Outdated
} | ||
|
||
impl MouseButton { | ||
/// Adds convenience methods to `MouseButton` and `Option<MouseButton>`. | ||
pub trait MouseButtonExt { |
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 get the motivation but this doesn't feel great to me. I do think I might prefer MouseButton::None
, and we could sort of redefine MouseButton
to be 'the mouse button that triggered a given event, if one exists.' really this is all about mouse moved. Down and up must be associated with a specific button, but moved can have no or all buttons, etc. I wonder if it's worth representing differently in the API?
I've also experimented with ways to build a higher-level API on top of this in runebender; I'm not totally happy with the results, but I think something like that could be useful?
3 => Some(MouseButton::Right), | ||
4 => Some(MouseButton::X1), | ||
5 => Some(MouseButton::X2), | ||
_ => Some(MouseButton::Unknown), |
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 might consider making this Unknown(u32)
? it doesn't matter too much either way, but if someone wants that info we might as well provide it?
But only if it's not a super big hassle..
I went with How about a custom struct impl MouseButtons {
fn has(&self, button: MouseButton) -> bool;
fn has_left(&self) -> bool;
fn has_right(&self) -> bool;
// ....
fn has_x2(&self) -> bool;
fn has_any(&self) -> bool;
} |
The problem is that these have different uses; so that would work, but would not replace on For all this, though, what's most important is a good API that covers the 99% case; to me this means that One option:
|
That sounds reasonable. During Event::MouseDown((MouseEvent, MouseButton)) // Something like this tuple? |
After thinking about this some more, especially in relation to #842 which is headed towards adding a new Having the single Event::MouseUp(e) => {
if e.button.is_left() { do stuff; }
} |
a few options I think: MouseDown { event: MouseEvent, button: MouseButton }
// or,
MouseClickEvent {
mouse: MouseEvent,
button: MouseButton,
count: u32,
}
// with
Event::MousDown(MouseClickEvent),
Event::MouseUp(MouseClickEvent),
Event::MouseMove(MouseEvent), (I realized in the course of writing this that the click count is also irrelevant for the purposes of I think maybe one reason for the current design is that I was thinking about mac, and NSEvent is a monolith; the same underlying 'type' can represent tons of different events, so there are lots of fields that exist that aren't actually meaningful for a given specific event. |
94c2f27
to
04b5950
Compare
I pushed a new iteration of this work. Holding down multiple mouse buttons is now nicely tracked. It works perfectly on macOS and Windows and mostly on GTK. GTK has all sorts of issues due to fragmentation, which will need solving separately. This PR at the very least only improves the situation in druid-shell and doesn't make it worse. For this iteration I went with // What used to be this ..
Event::MouseDown(e) | Event::MouseUp(e) | Event::MouseMove(e) => {
self.last_mouse_pos = Some(e.pos)
}
// is now this.
Event::MouseDown(ClickEvent { pos, .. })
| Event::MouseUp(ClickEvent { pos, .. })
| Event::MouseMove(MoveEvent { pos, .. }) => self.last_mouse_pos = Some(*pos), I'm not 100% convinced that splitting them is the way to go. Maybe the superior ergonomics in handling these three events together is worth the price of wasting those extra bytes of memory and having useless fields in the code editor autocomplete for |
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.
Sorry, didn't see your update. I have some thoughts; if I were put on the spot I would say let's just use a single event, but include the split between MouseButton
and MouseButtons
. Some other notes inline. Thanks for digging into this!
/// or the released button in the case of a mouse-up event. | ||
pub count: u8, | ||
/// The button that was pressed down in the case of mouse-down, | ||
/// or the button that was released in the case of mouse-up. | ||
pub button: MouseButton, |
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.
okay, thinking about the API:
I think that separating out MouseButtons
from MouseButton
solves most of our issue here, and shows a viable way to have a single type cover these three events without any clear inconsistency. There will be two fields, button
and count
, that are not used in MouseMove
, and I think that's fine.
count
is actually a funny one; the docstring says "will always be 0 on mouse-up", but I'm not sure I actually like that as an implementation; this is something we're going to have to revisit at some point, in that some platforms might provide their own double/triple click counting, and on some platforms we might need to do it ourselves. We probably do want to trust the platform, though, so that we are respecting whatever settings the user has on that platform.
My gut tells me that mouse-up should include count, though? (but this is unrelated to current PR).
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.
MouseMove
would also have a useless focused
that will be introduced in #842, however even so I'm also thinking a single event is probably the way to go.
As for count
, I have additional thoughts on it that I describe in #859. The short of it is that yes I think count
should be there for MouseUp
but it's not there right now on any platfrom besides macOS so I just wrote the docs to reflect that reality and made macOS compatible with that. This would be a short-lived change in light of #859.
druid-shell/src/mouse.rs
Outdated
/// First X button. | ||
X1, | ||
/// Second X button. | ||
X2, | ||
/// Some other unknown button. | ||
Other, |
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 if we're reworking this API we should include precise arbitrary buttons, which means Other
should be Other(u8)
. We will end up needing to do this eventually for something, so we might as well do it now.
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.
Having it be Other(u8)
would change the MouseButtons
implementation because it couldn't just be a bitmask and would need to store arbitrary u8
s in the set as well.
Also it doesn't really look like any of the platforms (Windows, macOS, GTK, web) support more specific buttons via the APIs that we're currently using. GTK is struggling with X1/X2 already. It's of course possible to tap into the mouse driver and get more of them, but I feel like that's a rather large project and probably not even that useful for general GUI apps.
So given the large scope of cross-platfrom support for more buttons, I think we can't reasonably prepare our API for them. When someone wants to take that task on and knows the demands of a practical implementation, then it would be much more reasonable to change the API.
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.
on macOS the mouse buttons are presented as a u32 bitmask. Windows seems to support up to five buttons. I haven't checked elsewhere.
Okay, so my last question would be, is Other
even a useful distinction? If the user can't get any more information I think it could be problematic, since two separate buttons could send Other
, and you can't tell them apart for the purposes of up & down.
In the very long term, I've been imagining we want to have some API to get the underlying event from the platform in these cases, but that's also another can of worms.
druid-shell/src/mouse.rs
Outdated
/// Adds all the [`MouseButton`]s in `other` to the set. | ||
/// | ||
/// [`MouseButton`]: enum.MouseButton.html | ||
pub fn union(&mut self, other: MouseButtons) { |
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 would expect this to return a new MouseButtons
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.
You shouldn't, because builder methods start with with
.
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 wouldn't be a builder method, it would just be a method that happens to return the union of two sets; similar to the union
method on collection types like HashSet
. (These actually return an iterator over the union, but since our type is Copy
I wouldn't worry about that).
If what you want to do is add all the items from one set to the other, the only std
api for this would be the Extend
trait.
Anyway not a blocker or anything, just something that jumps out at me.
|
||
/// Builder-style method for removing the `button` from the set. | ||
#[inline] | ||
pub fn without(mut self, button: MouseButton) -> MouseButtons { |
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'm getting some YAGNI vibes here; it isn't clear when or where these would be used? Are they just by us, or do we expect external users to need to synthesize mouse buttons?
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.
Well this specific without
method is used in the GTK platform code already.
More generally these functions can be used by external users. Yes they might synthesize MouseButtons
from some sort of hotkey configuration file and then do a check during a mouse event against that.
druid-shell/src/mouse.rs
Outdated
/// | ||
/// [`MouseButton::Other`]: enum.MouseButton.html#variant.Other | ||
#[inline] | ||
pub fn has_other(self) -> bool { |
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.
if we make Other
include a specific id, we should allow this to check for that value.
/// | ||
/// [`MouseButton`]: enum.MouseButton.html | ||
#[derive(PartialEq, Eq, Clone, Copy, Default)] | ||
pub struct MouseButtons(u8); |
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.
we probably have more than 8 bits of possible buttons; I would just make this a u32 now, that should be enough for anybody.
459cbef
to
158ad19
Compare
158ad19
to
f3ae994
Compare
A new iteration has been pushed. I went back to a single I also thought about your question of whether
I think druid should strive for a consistent API that is reliable across platforms. Given these issues I don't think In the long term, if we want to support more buttons, that can of course be done. However it should be done in a qualitative way and I don't think it can be achieved with the platform mouse APIs |
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.
Okay, I think this is the right direction.
This is basically good to go; only issue is that I think the combination of MouseButton::None
and repr(u8)
will cause problems as currently written.
There are a few other comments and thoughts inline, but it's mostly just me thinking out loud.
pub count: u32, | ||
/// The currently pressed button in the case of a move or click event, | ||
/// or the released button in the case of a mouse-up event. | ||
/// be `0` for a mouse-up and mouse-move events. |
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.
Just discussion, but I wonder if it makes more sense for, in the case of mouse-up, for count
to be the same as the count of the corresponding mouse-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.
Yes but it's not a trivial change and so will come with #859.
druid-shell/src/mouse.rs
Outdated
/// Add the `button` to the set. | ||
#[inline] | ||
pub fn add(&mut self, button: MouseButton) { | ||
self.0 |= 1 << button as u8; |
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 plays weird with MouseButton::None
; but if we make it the first member in the enum, we can just do something like self.0 |= 1.min(button as u8) << button as u8
?
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.
druid-shell
itself never adds or checks MouseButton::None
but you're right, some external user might do it. I updated the code to ignore MouseButton::None
. Hopefully the min
will be branchless.
#[derive(PartialEq, Eq, Clone, Copy, Default)] | ||
pub struct MouseButtons(u8); | ||
|
||
impl MouseButtons { |
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.
just sort of riffing on this type; if the idea is to construct it from MouseButton
s, one possible thing to do is implement FromIterator<MouseButton>
, and then you could write, say,
let buttons: MouseButtons = [MouseButton::Left, MouseButton::X1].iter().copied().collect();
That doesn't feel great really; probably simpler to have a constructor that just takes a &[MouseButton]
.
In any case I'm just daydreaming, this isn't necessary or anything.
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.
The following one liner isn't too bad either:
let buttons = MouseButtons::new().with(MouseButton::Left).with(MouseButton::Right);
There can be value in adding more methods, however I think I would leave these slice/iterator constructors out of this PR.
druid-shell/src/mouse.rs
Outdated
|
||
/// Returns `true` if any button is in the set. | ||
#[inline] | ||
pub fn has_any(self) -> bool { |
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 might prefer to use is_empty()
for this, if we're treating it like a set? Not totally sure, just a thought.
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 named as many as I could with has_
prefix because it seemed nicer to me. However it's probably more ergonomic to follow HashSet
more closely in naming, because that's what Rust programmers will know. I will rename all the methods that don't match but have equals in HashSet
.
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.
yea, this is my general thinking; people are more likely to try and use method names they know from elsewhere.
@@ -139,6 +141,8 @@ impl Application { | |||
button_release.event_x() as f64, | |||
button_release.event_y() as f64, | |||
), | |||
// TODO: Fill with held down buttons |
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.
let's open an issue for this when we merge
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.
Looks good, only thing is to update the changelog?
44ead59
to
4db547f
Compare
This PR adds mouse support for more than just two buttons.
Update: It also adds
MouseButtons
to theMouseEvent
to track which buttons are being held down.