Repository: vispy/vispy

Issue:
Mouse event behaviour
```
    def on_mouse_press(self, event):    
        print(event.buttons)
```

The [docs](https://vispy.org/api/vispy.app.canvas.html?highlight=mouseevent#vispy.app.canvas.MouseEvent) says 

```
buttons[int, …]
The list of buttons pressed during this event.
```

But, pressing the left click returns an empty list and while holding down the left click and pressing other mouse buttons returns the mouse button key [1], [2] etc.

Is it a bug or I am getting something wrong?

I am using `glfw` as my backend.



Discussion:
Can you post a small example script that I can use to try to reproduce and debug this?
Also, you have the documentation wrong. It doesn't say "pressed" it says "depressed". You may want to play with letting go of the button (depressing) and see if that changes the results. However, I think that would make more sense to check on a `on_mouse_release` event handler. I haven't used mouse events heavily for a while so I could be wrong.
This is the script I was using, event.buttons return the buttons that were already pressed during that event excluding the mouse button pressed that called that event.

If, I pressed the left mouse button and held on to it and then pressed the right mouse button. `events.buttons` gives me the `1` as output.
```
import math
from vispy import app, gloo, use

use("glfw")

class Canvas(app.Canvas):

    def __init__(self, *args, **kwargs):
        app.Canvas.__init__(self, *args, **kwargs)
        
    def on_mouse_press(self, event):
        print(event.buttons)

if __name__ == '__main__':
    canvas = Canvas(keys='interactive', always_on_top=True)
    canvas.show()
    app.run()
```
Does "depressed" mean released or continued to be pressed?

I made some changes and printed out the `.button` and the `.buttons`:

```python
ifrom vispy import app, gloo, use

class Canvas(app.Canvas):
    def on_mouse_press(self, event):
        print(f"Press: {event.button=} | {event.buttons=}")

    def on_mouse_release(self, event):
        print(f"Release: {event.button=} | {event.buttons=}")

if __name__ == '__main__':
    canvas = Canvas(keys='interactive', always_on_top=True)
    canvas.show()
    app.run()
```

Here's what I get (with annotations):

```
# left press
Press: event.button=1 | event.buttons=[]
# left release
Release: event.button=1 | event.buttons=[1]
# right press
Press: event.button=2 | event.buttons=[]
# right release
Release: event.button=2 | event.buttons=[2]
# left press
Press: event.button=1 | event.buttons=[]
# right press
Press: event.button=2 | event.buttons=[1]
# right release
Release: event.button=2 | event.buttons=[1, 2]
# left release
Release: event.button=1 | event.buttons=[1]
```

To me, and let me know if this is the original bug you were mentioning, the last press/release cycle is a bug. When the right button is pressed the `1` is included in the "depressed" `.buttons` list, but it wasn't released. When the right button is then released we also see `1` in the `.buttons` list, I would consider this a bug. Unless the `.buttons` list is meant to be "other buttons being acted on including or in addition to the button that triggered the event", then the docs are wrong.
Ok, apparently as a native english speaker I don't know what "depressed" means. It means currently pressed here. See this code:

https://github.com/vispy/vispy/blob/fbf90ebadbdd1054f3bb3bba4cebc7e60e2082f5/vispy/app/base.py#L188

and the other "buttons" related code in that module. I think to get a list of "all buttons pressed right now" you need to do `.buttons` plus `.button`. I would be open to the idea of a pull request that moves the above linked code so `.buttons` always includes `.button` but I can imagine this causing bugs for some people.
> you need to do `.buttons` plus `.button` 

Yes, that is what I am doing right now. 

> but I can imagine this causing bugs for some people.

We could change the docs to reflect the current behaviour if that's the case, but that might get confusing. 

I would prefer adding the `button` currently pressed to the `buttons` list, that would fix and make the behaviour intuitive as well. 

Output like this is what a user(or I expected) would expect.

```
# left press
Press: event.button=1 | event.buttons=[1]
# left release
Release: event.button=1 | event.buttons=[1]
# right press
Press: event.button=2 | event.buttons=[2]
# right release
Release: event.button=2 | event.buttons=[2]
# left press
Press: event.button=1 | event.buttons=[1]
# right press
Press: event.button=2 | event.buttons=[1,2]
# right release
Release: event.button=2 | event.buttons=[1, 2]
# left release
Release: event.button=1 | event.buttons=[1]
```

> I don't know what "depressed" means

I am confused as well about this. I think it means released or "de-pressed" :p

It would be probably better to hear what others have to say on this to figure out the correct behaviour that one would expect. 
Yes, depressed means pressed (https://www.dictionary.com/browse/depressed; definition 2); it's not "de-pressed".
Thanks @QuLogic. @vispy/core what are your thoughts? Should the MouseEvent `.buttons` include the newly pressed button (the one causing the "press" event)? See my comment above.

...actually @tushar5526 I'm a little confused by your expected output. I would have thought that the "release" event would not include the released button in the `.buttons`. It is no longer pressed so it should no longer be in the list, right? That is, if we're using "depressed" to mean pressed.
Sorry for the confusion, I was wrong about what "depressed" means, hence my expected output.

> I would have thought that the "release" event would not include the released button in the .buttons. It is no longer pressed so it should no longer be in the list, right? That is, if we're using "depressed" to mean pressed.

Yes, this should be the correct behaviour. 

We can change "depressed" to "pressed" in the docs, to avoid confusion.
> We can change "depressed" to "pressed" in the docs, to avoid confusion.

Agreed. Let's see what others think (@almarklein, @kmuehlbauer, @rougier any experience with mouse events in other applications?)
I also learned what "depressed" means and I agree this brings confusion. "pressed" would be much better.
Is "depressed" a common term in the UX world? To be honest I think of a sad mouse button when I hear it.
Ok so I think we've settled on two changes:

1. "depressed" -> "pressed" in the docstring
2. Add the newly pressed button (the one triggering the press event) to the `.buttons` list.

@tushar5526 agreed? how would you like to make a pull request for these changes?
@djhoese Agreed. 

[https://github.com/vispy/vispy/blob/main/vispy/app/canvas.py/#L635](https://github.com/vispy/vispy/blob/main/vispy/app/canvas.py/#L635), we can simply add `button` to the `buttons` if the event type is mouse_pressed. 

I suppose this should work.

Resolving patch:
diff --git a/vispy/app/backends/_glfw.py b/vispy/app/backends/_glfw.py
--- a/vispy/app/backends/_glfw.py
+++ b/vispy/app/backends/_glfw.py
@@ -27,7 +27,7 @@
 try:
     import glfw
 except ImportError:
-    why_not = "Could not import glwf, you may need to `pip install glfw` first."
+    why_not = "Could not import glfw, you may need to `pip install glfw` first."
     available, testable, why_not, which = False, False, why_not, None
 except Exception as err:
     why_not = "Error importing glfw: " + str(err)
diff --git a/vispy/app/canvas.py b/vispy/app/canvas.py
--- a/vispy/app/canvas.py
+++ b/vispy/app/canvas.py
@@ -606,7 +606,7 @@ class MouseEvent(Event):
         will return the button that started the drag (same thing as
         ``event.press_event.button``).
     buttons : [int, ...]
-        The list of buttons depressed during this event.
+        The list of buttons pressed during this event.
     modifiers : tuple of Key instances
         Tuple that specifies which modifier keys were pressed down at the
         time of the event (shift, control, alt, meta).
@@ -632,7 +632,9 @@ def __init__(self, type, pos=None, button=None, buttons=None,
         Event.__init__(self, type, **kwargs)
         self._pos = np.array([0, 0]) if (pos is None) else np.array(pos)
         self._button = int(button) if (button is not None) else None
-        self._buttons = [] if (buttons is None) else buttons
+        # Explicitly add button to buttons if newly pressed, check #2344 for more reference
+        newly_pressed_buttons = [button] if button is not None and type == 'mouse_press' else []
+        self._buttons = [] if (buttons is None) else buttons + newly_pressed_buttons
         self._modifiers = tuple(modifiers or ())
         self._delta = np.zeros(2) if (delta is None) else np.array(delta)
         self._last_event = last_event


Test patch:
diff --git a/vispy/app/tests/test_canvas.py b/vispy/app/tests/test_canvas.py
--- a/vispy/app/tests/test_canvas.py
+++ b/vispy/app/tests/test_canvas.py
@@ -2,7 +2,7 @@
 # Copyright (c) Vispy Development Team. All Rights Reserved.
 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
 
-from vispy.app import Canvas
+from vispy.app import Canvas, MouseEvent
 from vispy.visuals import ImageVisual
 from vispy.testing import requires_application
 from vispy.visuals.transforms import STTransform
@@ -106,3 +106,17 @@ def on_draw(ev):
             
         rgba_result = c.render()
         assert not np.allclose(rgba_result[..., :3], 0)
+
+
+@requires_application()
+@pytest.mark.parametrize("mouse_event_type, button, buttons, expected_button, expected_buttons", [
+    ('mouse_press', 1, [], 1, [1]),
+    ('mouse_release', 1, [1], 1, [1]),
+    # left click pressed and held, followed by a right click
+    ('mouse_press', 2, [1], 2, [1, 2]),
+    ('mouse_release', 2, [1, 2], 2, [1, 2]),
+])
+def test_mouse_event(mouse_event_type, button, buttons, expected_button, expected_buttons):
+    mev = MouseEvent(type=mouse_event_type, button=button, buttons=buttons)
+    assert mev.buttons == expected_buttons
+    assert mev.button == expected_button