Lock mouse inside window using Python's moderngl_window

28 Views Asked by At

I'm trying to make an interactive fragment shader using moderngl_window, featuring full 3D camera control. I want to do what pretty much every 3D game does - constrain the cursor to the active window, making it impossible for the cursor to leave the window or even reach a border. I'd like to find a solution that uses Python. I'd be ok with switching from moderngl_window to some other similar alternative if that turned out to be the simplest option (that also lets me hide the cursor, like moderngl_window does). I'd rather not use something like pyautogui to constrain the mouse by force. Too error prone and clunky. In case it is useful info, I'm using Arch Linux and would be ok with a OS specific solution.

I've tried looking both online and in the moderngl_window documentation, and found nothing useful. I did see pygame has a built in solution, but I'd rather ask first to see if there's some better and simpler solution.

Here's some boilerplate code I'm using for moderngl_window, in case it's useful in any way.

import moderngl_window as mgl

class App(mgl.WindowConfig):
    window_size = 1336, 768
    resource_dir = "src"
    cursor = False
    fullscreen = True

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.quad = mgl.geometry.quad_fs()
        self.prog = self.load_program(
            vertex_shader = "vertex.glsl", 
            fragment_shader = "frag.glsl")
        self.prog["resolution"] = self.window_size

    def render(self, time, frame_time):
        self.ctx.clear()
        try:
            self.prog["time"] = time
        except:
            pass
        self.quad.render(self.prog)

if __name__ == "__main__":
    mgl.run_window_config(App)

Thank you!

1

There are 1 best solutions below

0
vallentin On

You're looking for Window.mouse_exclusivity set it to True and your cursor is captured and hidden.

As an example, let's say we want to toggle the mouse being captured when pressing C. Then we can do that by adding the key_event() method, like this:

def key_event(self, key, action, modifiers):
    keys = self.wnd.keys

    if key == keys.C and action == keys.ACTION_PRESS:
        self.wnd.mouse_exclusivity = not self.wnd.mouse_exclusivity

Then after that, you can use e.g. mouse_position_event() to receive cursor movement events:

def mouse_position_event(self, x, y, dx, dy):
    pass

However, you have to remember, that while the mouse is hidden, then x and y will not change. You'll only receive delta changes through dx and dy.

Here's a simple and complete example:

import moderngl_window as mgl

class App(mgl.WindowConfig):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def key_event(self, key, action, modifiers):
        keys = self.wnd.keys
        if key == keys.C and action == keys.ACTION_PRESS:
            self.wnd.mouse_exclusivity = not self.wnd.mouse_exclusivity
            print("Mouse Locked:", self.wnd.mouse_exclusivity)

    def mouse_position_event(self, x, y, dx, dy):
        print(f"{dx} {dy}")

    def render(self, time, frame_time):
        pass

if __name__ == "__main__":
    print("Press C to toggle mouse lock")

    mgl.run_window_config(App)