renpy / pygame_sdl2

Reimplementation of portions of the pygame API using SDL2.
GNU Lesser General Public License v2.1
325 stars 63 forks source link

Support virtual input mode #148

Closed mal closed 7 months ago

mal commented 9 months ago

Between SDL1.2 and SDL2 the method for switching to relative mouse mode changed, breaking pygame's virtual input mode. This patch is based on a change in pygame proper from 2019 made to address this issue.

Current behaviour

Despite using pygame.event.set_grab(True) and pygame.mouse.set_visible(False) at the same time, the (hidden) cursor is limited to the confines of the window and the relative mouse movement values stop progressing at that point.

Expected behaviour / with this patch

Using pygame.event.set_grab(True) and pygame.mouse.set_visible(False) at the same time acitvates "virtual input" mode as documented by pygame near the top of this page. The (hidden) cursor and its reported position are still (correctly) limited to the confines of the window as before, however the relative mouse movement values no longer stop progressing at the window edge.

Benefits

This allows advanced CDDs to enter virtual input mode in order to facilitate seamless mouse-controlled panning, dragging, etc. It also becomes possible to simulate mouse sensitivity and acceleration changes by adjusting the relative movement values before accumulating them.

Test game (click and hold to switch into virtual input mode) ```rpy label main_menu: $ _confirm_quit = False return label start: scene black call screen test() return screen test(): grid 2 3: align (.5, .2) spacing 10 xsize .4 text 'real pos' xalign 1. text '[data["pos"]]' text 'most recent delta' xalign 1. text '[data["mov"]]' text 'virtual pos' xalign 1. if 'vir' in data: text '[data["vir"]]' color '7fa' else: text 'off' add MousePosReporter() init python: import collections import pygame v = collections.namedtuple('vector', 'x y') v.__iadd__ = lambda self, o: v(self[0] + o[0], self[1] + o[1]) data = _dict(pos=None, mov=None) def virtual_input(toggle): # per https://www.pygame.org/docs/ref/mouse.html pygame.mouse.set_visible(not toggle) pygame.event.set_grab(toggle) class MousePosReporter(Null): def event(self, ev, x, y, st): if ev.type == pygame.MOUSEMOTION: data['pos'] = x, y data['mov'] = ev.rel # same value as from pygame.mouse.get_rel() if 'vir' in data: data['vir'] += ev.rel renpy.restart_interaction() elif ev.type == pygame.MOUSEBUTTONDOWN and ev.button == 1: virtual_input(True) data['end'] = data['vir'] = v(x, y) elif ev.type == pygame.MOUSEBUTTONUP and ev.button == 1: virtual_input(False) del data['vir'] renpy.set_mouse_pos(*data['end']) else: return None renpy.restart_interaction() raise renpy.display.core.IgnoreEvent ```

Tested successfully on Ubuntu 22.04 using fix branches plus this change.

renpytom commented 9 months ago

Is there an application for this?

Since pygame was ported to SDL2, pygame_sdl2 only exists to support Ren'Py. And I don't think it makes sense to allow CDDs to trap the mouse like this.

mal commented 9 months ago

Trapping the mouse in this way is already possible. This just fixes the relative mouse movement values so that it can be made good use of in such cases as I highlighted in the "benefits" section above.

I arrived here after trying to make a CDD-based mini game where some items are heavier than others, so I wanted to adjust mouse sensitivity within the game while dragging the different items around based on their weight.

Another good, though advanced, case for this is facilitating player navigation around an equirectangular panoramic.