Skip to content

VTK interactor doesn't register mouse press/release events when another button is held #803

@sudomakeinstall

Description

@sudomakeinstall

Describe the bug

In pure VTK, mouse press/release events are triggered regardless of whether another mouse button is currently pressed. For example, a "LeftButtonPressEvent", "RightButtonPressEvent", "LeftButtonReleaseEvent", and "RightButtonReleaseEvent" can all be recorded in sequence. However, when using a VTK render window within Trame, button press/release events are not triggered if another mouse button is currently pressed. In the above example, the "RightButtonPressEvent" is not triggered (since the left button is pressed), the "LeftButtonReleaseEvent" is not triggered (since the right button is pressed), and the "RightButtonReleaseEvent" is not triggered (presumably since the right press was never registered).

To Reproduce

  1. I'm using a freshly created uv environment; version bounds from my pyproject.toml as follows:
dependencies = [
    "trame-vtk>=2.9.1",
    "trame-vuetify>=3.0.3",
    "trame>=3.12.0",
    "vtk>=9.5.1",
]
  1. Run the script copied below passing "trame" as the argument:
./test_multibutton_comparison.py trame
  1. In the VTK render window that opens within the browser, press the left button, drag, press the right button, drag, release the left button, drag, release the right button, drag. You will get an output similar to the following, with the mouse left in the "drag" state, though no buttons are depressed:
LEFT BUTTON PRESS
DRAG: L=True R=False delta=[0,0]
DRAG: L=True R=False delta=[113,237]
DRAG: L=True R=False delta=[0,0]
DRAG: L=True R=False delta=[-1,0]
DRAG: L=True R=False delta=[-2,0]
DRAG: L=True R=False delta=[-1,-1]
DRAG: L=True R=False delta=[0,0]
DRAG: L=True R=False delta=[-1,0]
DRAG: L=True R=False delta=[-3,-1]
DRAG: L=True R=False delta=[-9,-8]
DRAG: L=True R=False delta=[-1,-2]
DRAG: L=True R=False delta=[-2,-4]
DRAG: L=True R=False delta=[-2,-3]

In order to "release" the mouse, you need to press and release the left mouse button again.

Code

#!/usr/bin/env python3
"""
Multi-button test comparison: VTK vs Trame
Usage:
    ./test_multibutton_comparison.py vtk    # Native VTK (multi-button works)
    ./test_multibutton_comparison.py trame  # Trame/web (multi-button blocked)
"""

import sys
import vtk


class MultiButtonInteractor(vtk.vtkInteractorStyle):
    def __init__(self):
        super().__init__()
        self.left_button_down = False
        self.right_button_down = False
        self.last_mouse_pos = [0, 0]

        self.AddObserver("LeftButtonPressEvent", self.on_left_press)
        self.AddObserver("LeftButtonReleaseEvent", self.on_left_release)
        self.AddObserver("RightButtonPressEvent", self.on_right_press)
        self.AddObserver("RightButtonReleaseEvent", self.on_right_release)
        self.AddObserver("MouseMoveEvent", self.on_mouse_move)

    def on_left_press(self, obj, event):
        print("LEFT BUTTON PRESS")
        self.left_button_down = True
        interactor = self.GetInteractor()
        if interactor:
            self.last_mouse_pos = list(interactor.GetEventPosition())

    def on_left_release(self, obj, event):
        print("LEFT BUTTON RELEASE")
        self.left_button_down = False

    def on_right_press(self, obj, event):
        print("RIGHT BUTTON PRESS")
        self.right_button_down = True
        interactor = self.GetInteractor()
        if interactor:
            self.last_mouse_pos = list(interactor.GetEventPosition())

    def on_right_release(self, obj, event):
        print("RIGHT BUTTON RELEASE")
        self.right_button_down = False

    def on_mouse_move(self, obj, event):
        if self.left_button_down or self.right_button_down:
            interactor = self.GetInteractor()
            if interactor:
                current_pos = list(interactor.GetEventPosition())
                dx = current_pos[0] - self.last_mouse_pos[0]
                dy = current_pos[1] - self.last_mouse_pos[1]
                self.last_mouse_pos = current_pos

                print(
                    f"DRAG: L={self.left_button_down} R={self.right_button_down} delta=[{dx},{dy}]"
                )

                if self.left_button_down and self.right_button_down:
                    print("  *** LEFT+RIGHT COMBINATION DETECTED! ***")


def create_vtk_scene():
    """Shared VTK scene setup"""
    renderer = vtk.vtkRenderer()
    render_window = vtk.vtkRenderWindow()
    render_window.AddRenderer(renderer)
    
    interactor = vtk.vtkRenderWindowInteractor()
    interactor.SetRenderWindow(render_window)
    style = MultiButtonInteractor()
    interactor.SetInteractorStyle(style)
    
    return renderer, render_window, interactor


def run_mode(mode):
    if mode == "vtk":
        print("=== VTK MODE (Native) ===")
        print("Multi-button combinations SHOULD work")
        print("Try: Left-drag, then add Right button while dragging")
        print("")
        
        renderer, render_window, interactor = create_vtk_scene()
        render_window.SetWindowName("VTK Mode - Multi-button should work")
        
        interactor.Initialize()
        render_window.Render()
        interactor.Start()
        
    elif mode == "trame":
        print("=== TRAME MODE (Web) ===")
        print("Multi-button combinations should be BLOCKED")
        print("Try: Left-drag, then add Right button while dragging")
        print("Opening browser...")
        print("")
        
        from trame.app import get_server
        from trame.ui.vuetify3 import SinglePageLayout
        from trame.widgets import vtk as vtk_widgets
        
        server = get_server()
        
        renderer, render_window, interactor = create_vtk_scene()
        render_window.SetOffScreenRendering(True)
        
        with SinglePageLayout(server) as layout:
            layout.title.set_text("Trame Mode - Multi-button should be blocked")
            with layout.content:
                vtk_widgets.VtkRemoteView(render_window)
        
        server.start()


def main():
    if len(sys.argv) != 2 or sys.argv[1] not in ['vtk', 'trame']:
        print("Usage:")
        print("  python test_multibutton_comparison.py vtk    # Native VTK")
        print("  python test_multibutton_comparison.py trame  # Trame/web")
        print("")
        print("This demonstrates the difference in multi-button behavior:")
        print("  VTK:   Multi-button combinations work")
        print("  Trame: Multi-button combinations blocked by browser")
        sys.exit(1)
    
    mode = sys.argv[1]
    run_mode(mode)


if __name__ == "__main__":
    main()

Expected behavior

  1. To see the expected behavior, run the pure VTK version of the same script by passing the "vtk" argument:
./test_multibutton_comparison.py vtk
  1. In the VTK render window that opens, follow the same steps: press the left button, drag, press the right button, drag, release the left button, drag, release the right button, drag. You will get the expected output similar to the following:
LEFT BUTTON PRESS
DRAG: L=True R=False delta=[0,0]
DRAG: L=True R=False delta=[0,-1]
RIGHT BUTTON PRESS
DRAG: L=True R=True delta=[0,-1]
  *** LEFT+RIGHT COMBINATION DETECTED! ***
DRAG: L=True R=True delta=[0,-1]
  *** LEFT+RIGHT COMBINATION DETECTED! ***
LEFT BUTTON RELEASE
DRAG: L=False R=True delta=[0,0]
DRAG: L=False R=True delta=[0,-1]
RIGHT BUTTON RELEASE

Screenshots

N/A

Platform:

Device:

  • Desktop
  • Mobile => Not tested

OS:

  • Windows => Not tested
  • MacOS
  • Linux
  • Android => Not tested
  • iOS => Not tested

Browsers Affected:

  • Chrome
  • Firefox
  • Microsoft Edge => Not tested
  • Safari
  • Opera => Not tested
  • Brave
  • IE 11 => Not tested

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions