Created
July 10, 2022 10:15
-
-
Save yhyu13/9f18f9248064d8551b1b71c20a029962 to your computer and use it in GitHub Desktop.
Dearpygui v1.6.2 InputManager
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Handle User inputs and manage input states | |
Example: | |
Main loop: | |
inputManager.init(args) | |
while dpg.is_dearpygui_running(): | |
inputManager.run() | |
Events: check Crtl+Shift+S is toggled | |
if inputManager.keyStateMatchAll([dpg.mvKey_S], [EButtonState.toggled]) \ | |
and inputManager.modStateMatchExact(Modifier.MOD_SHIFT | Modifier.MOD_CRTL) | |
""" | |
import enum | |
import dearpygui.dearpygui as dpg | |
from Source.Logger.logger import spdLogger | |
# It's a class that defines the state of a button | |
class EButtonState(enum.Enum): | |
released = 0 | |
pressed = 1 | |
downed = 2 # to handle period pressed events, e.g. holding a key for half a second to toggle a new window | |
toggled = 3 # change from pressed/downed to release in current frame is toggled | |
doubleClicked = 4 # mouse only | |
dragged = 5 # mouse only | |
wheeled = 6 # mouse only | |
# It stores the state of a button | |
class _InputState: | |
def __init__(self, _id, _name, _state): | |
self.id = _id | |
self.name = _name | |
self.state = _state | |
# stores previous state | |
self.prevState = EButtonState.released | |
# DPG sometimes invoke downed event right after pressed event, we use a flag to prevent that | |
self.isPressedCurrentFrame = False | |
# It stores the state of a button | |
class _MouseInputState(_InputState): | |
def __init__(self, _id, _name, _state): | |
super().__init__(_id, _name, _state) | |
self.isDoubleClickedCurrentFrame = False | |
class Modifier: | |
MOD_SHIFT = 1 << 0 | |
MOD_CTRL = 1 << 1 | |
MOD_ALT = 1 << 2 | |
# MOD_CAPSLOCK = 1 << 3 # we do not handle lock modifier | |
# MOD_NUMLOCK = 1 << 4 # we do not handle lock modifier | |
MOD_WINDOWS = 1 << 5 | |
# MOD_COMMAND = 1 << 6 # equivalent to windows by dpg | |
# MOD_OPTION = 1 << 7 # not supported by dpg | |
# MOD_SCROLLLOCK = 1 << 8 # we do not handle lock modifier | |
# MOD_FUNCTION = 1 << 9 # not supported by dpg | |
Key2Mod = { | |
dpg.mvKey_Shift : MOD_SHIFT, | |
dpg.mvKey_LShift : MOD_SHIFT, | |
dpg.mvKey_RShift : MOD_SHIFT, | |
dpg.mvKey_LControl: MOD_CTRL, | |
dpg.mvKey_Control : MOD_CTRL, | |
dpg.mvKey_RControl: MOD_CTRL, | |
dpg.mvKey_Alt : MOD_ALT, | |
dpg.mvKey_LWin : MOD_WINDOWS, | |
dpg.mvKey_RWin : MOD_WINDOWS, | |
} | |
Mod2Keys = { | |
MOD_SHIFT : [dpg.mvKey_Shift, dpg.mvKey_LShift, dpg.mvKey_RShift], | |
MOD_CTRL : [dpg.mvKey_Control, dpg.mvKey_LControl, dpg.mvKey_RControl], | |
MOD_ALT : [dpg.mvKey_Alt], | |
MOD_WINDOWS: [dpg.mvKey_LWin, dpg.mvKey_RWin], | |
} | |
@classmethod | |
def modif2Str(cls, modifierState) -> str: | |
ret = '' | |
if modifierState & cls.MOD_SHIFT: | |
ret += 'MOD_SHIFT | ' | |
if modifierState & cls.MOD_CTRL: | |
ret += 'MOD_CTRL | ' | |
if modifierState & cls.MOD_ALT: | |
ret += 'MOD_ALT | ' | |
if modifierState & cls.MOD_WINDOWS: | |
ret += 'MOD_WINDOWS | ' | |
# if modifierState & cls.MOD_FUNCTION: | |
# ret += 'MOD_FUNCTION | ' | |
# if modifierState & cls.MOD_COMMAND: | |
# ret += 'MOD_COMMAND | ' | |
# if modifierState & cls.MOD_OPTION: | |
# ret += 'MOD_OPTION | ' | |
return ret[:-3] if len(ret) > 3 else ret | |
class _InputManager: | |
_keyMap = {} | |
_mouseMap = {} | |
_modifierState = 0 # bit masked state for modifiers such as SHIFT, ALT, CAPSLOCK, etc | |
def __init__(self): | |
for const in dir(dpg): | |
if const.startswith('mvKey_'): | |
c = getattr(dpg, const) | |
self._keyMap[c] = _InputState(c, const, EButtonState.released) | |
elif const.startswith('mvMouseButton_'): | |
c = getattr(dpg, const) | |
self._mouseMap[c] = _InputState(c, const, EButtonState.released) | |
def init(self, args): | |
# https://dearpygui.readthedocs.io/en/latest/tutorials/item-usage.html?highlight=current%20widget#using-item-handlers | |
with dpg.handler_registry(): | |
dpg.add_key_press_handler(callback = self._key_pressed_event) | |
dpg.add_key_down_handler(callback = self._key_down_event) | |
dpg.add_key_release_handler(callback = self._key_release_event) | |
for key in Modifier.Key2Mod.keys(): | |
dpg.add_key_press_handler(key = key, callback = self._mod_pressed_event) | |
# dpg.add_key_down_handler(key = key, callback = self._mod_down_event) | |
dpg.add_key_release_handler(key = key, callback = self._mod_release_event) | |
dpg.add_mouse_click_handler(callback = self._mouse_pressed_event) | |
dpg.add_mouse_down_handler(callback = self._mouse_down_event) | |
dpg.add_mouse_release_handler(callback = self._mouse_release_event) | |
dpg.add_mouse_drag_handler(callback = self._mouse_drag_event) | |
dpg.add_mouse_double_click_handler(callback = self._mouse_doubleclicked_event) | |
spdLogger.info('InputManager initialized') | |
def run(self): | |
self._clearState() | |
def finalize(self): | |
pass | |
# Clear state beginning of every frame | |
def _clearState(self): | |
for value in self._keyMap.values(): | |
value.prevState = value.state | |
value.state = EButtonState.released | |
value.isPressedCurrentFrame = False | |
for value in self._mouseMap.values(): | |
value.prevState = value.state | |
value.state = EButtonState.released | |
value.isPressedCurrentFrame = False | |
value.isDoubleClickedCurrentFrame = False | |
def key2Str(self, key) -> str: | |
return self._keyMap[key].name | |
def mouse2Str(self, mouse) -> str: | |
return self._mouseMap[mouse].name | |
def modif2Str(self) -> str: | |
return Modifier.modif2Str(self._modifierState) | |
def modStateMatchExact(self, modstates) -> bool: | |
""" | |
`modStateMatchExact` returns `True` if the `modstates` argument is equal to the `_modifierState` attribute of the | |
`Key` object | |
:param modstates: A list of modifier states | |
:return: A boolean value. | |
""" | |
return modstates == self._modifierState | |
def mouseStateMatchAll(self, keys, states) -> bool: | |
""" | |
Returns true if all of the mouse keys in the list `keys` are in the list of states `states` | |
:param keys: A list of mouse keys to check | |
:param states: a list of states to check for | |
:return: A boolean value. | |
""" | |
assert len(keys) > 0 and len(states) > 0 | |
for key in keys: | |
if not self._mouseMap[key].state in states: | |
return False | |
return True | |
def keyStateMatchAll(self, keys, states) -> bool: | |
""" | |
Returns true if all of the keys in the keys list are in the states list | |
:param keys: A list of keys to check | |
:param states: a list of states to check for | |
:return: A boolean value. | |
""" | |
assert len(keys) > 0 and len(states) > 0 | |
for key in keys: | |
if not self._keyMap[key].state in states: | |
return False | |
return True | |
# def keyStateMatchNone(self, keys, states) -> bool: | |
# """ | |
# Either supply multiple keys with array of single state, or supply matching sizes of keys and states | |
# (e.g. KeyStateMatchNone([KeyA, KeyB], [Down]) or KeyStateMatchNone([KeyA, KeyB], [Down, Release])) | |
# """ | |
# assert len(keys) > 0 and len(states) > 0 | |
# if len(states) == 1: | |
# for i, key in enumerate(keys): | |
# if self._keyMap[key].state == states[0]: | |
# return False | |
# else: | |
# assert len(states) == len(keys) | |
# for i, (key, state) in enumerate(zip(keys, states)): | |
# if self._keyMap[key].state == state: | |
# return False | |
# return True | |
# def keyStateMatchAtLeastOne(self, keys, states) -> bool: | |
# """ | |
# Either supply multiple keys with array of single state, or supply matching sizes of keys and states | |
# (e.g. KeyStateMatchAtLeastOne([KeyA, KeyB], [Down]) or KeyStateMatchAtLeastOne([KeyA, KeyB], [Down, Release])) | |
# """ | |
# assert len(keys) > 0 and len(states) > 0 | |
# if len(states) == 1: | |
# for i, key in enumerate(keys): | |
# if self._keyMap[key].state == states[0]: | |
# return True | |
# else: | |
# assert len(states) == len(keys) | |
# for i, (key, state) in enumerate(zip(keys, states)): | |
# if self._keyMap[key].state == state: | |
# return True | |
# return False | |
# Generic key events -------------------------------------------------------------- | |
def _key_pressed_event(self, sender, app_data): | |
key = app_data | |
keystate = self._keyMap[key] | |
keystate.state = EButtonState.pressed | |
keystate.isPressedCurrentFrame = True | |
spdLogger.trace(f'Key pressed : {self.key2Str(key)}') | |
def _key_down_event(self, sender, app_data): | |
key = app_data[0] | |
keystate = self._keyMap[key] | |
if not keystate.isPressedCurrentFrame: | |
keystate.state = EButtonState.downed | |
spdLogger.trace(f'Key down : {self.key2Str(key)}') | |
def _key_release_event(self, sender, app_data): | |
key = app_data | |
keystate = self._keyMap[key] | |
if keystate.prevState == EButtonState.pressed or keystate.prevState == EButtonState.downed: | |
keystate.state = EButtonState.toggled | |
spdLogger.trace(f'Key toggled : {self.key2Str(key)}') | |
else: | |
keystate.state = EButtonState.released | |
spdLogger.trace(f'Key release : {self.key2Str(key)}') | |
# Generic mouse events -------------------------------------------------------------- | |
def _mouse_pressed_event(self, sender, app_data): | |
key = app_data | |
keystate = self._mouseMap[key] | |
keystate.state = EButtonState.pressed | |
keystate.isPressedCurrentFrame = True | |
spdLogger.trace(f'Mouse pressed : {self.mouse2Str(key)}') | |
def _mouse_down_event(self, sender, app_data): | |
key = app_data[0] | |
keystate = self._mouseMap[key] | |
if not keystate.isPressedCurrentFrame and not keystate.isDoubleClickedCurrentFrame: | |
keystate.state = EButtonState.downed | |
spdLogger.trace(f'Mouse down : {self.mouse2Str(key)}') | |
def _mouse_release_event(self, sender, app_data): | |
key = app_data | |
keystate = self._mouseMap[key] | |
if keystate.prevState == EButtonState.pressed or keystate.prevState == EButtonState.downed: | |
keystate.state = EButtonState.toggled | |
spdLogger.trace(f'Mouse toggled : {self.mouse2Str(key)}') | |
else: | |
keystate.state = EButtonState.released | |
spdLogger.trace(f'Mouse release : {self.mouse2Str(key)}') | |
def _mouse_drag_event(self, sender, app_data): | |
# print(app_data) | |
key = app_data[0] | |
keystate = self._mouseMap[key] | |
keystate.state = EButtonState.dragged | |
spdLogger.trace(f'Mouse dragged : {self.mouse2Str(key)}') | |
def _mouse_doubleclicked_event(self, sender, app_data): | |
key = app_data | |
keystate = self._mouseMap[key] | |
keystate.state = EButtonState.doubleClicked | |
keystate.isDoubleClickedCurrentFrame = True | |
spdLogger.trace(f'Mouse double clicked : {self.mouse2Str(key)}') | |
# Modifier events -------------------------------------------------------------- | |
def _mod_pressed_event(self, sender, app_data): | |
key = app_data | |
mod = Modifier.Key2Mod[key] | |
self._modifierState |= mod | |
spdLogger.trace(f'Mod pressed : {self.key2Str(key)}, mod state {self.modif2Str()}') | |
# def _mod_down_event(self, sender, app_data): | |
# # no need to handle modifier down as it would not change modifier state | |
# pass | |
def _mod_release_event(self, sender, app_data): | |
key = app_data | |
mod = Modifier.Key2Mod[key] | |
keys = Modifier.Mod2Keys[mod] | |
# only clear modifier state on all this modifier keys are released (e.g. both L/R Shift are released) | |
if self.keyStateMatchAll(keys, [EButtonState.released, EButtonState.toggled]): | |
self._modifierState &= ~mod | |
spdLogger.trace(f'Mod released : {self.key2Str(key)}, mod state {self.modif2Str()}') | |
inputManager = _InputManager() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment