Source code for vantagekit.kit.input

"""carb.input adapter: feeds a BindingState from keyboard, mouse and gamepad.

Kit 110 contract (carb/input/InputTypes.h, OnInputEventFn): the callback returns whether
the event should be processed by subsequent subscribers, so True passes it on and False
consumes it. The router passes everything on by default and leaves native navigation to
NativeNavigation; with consume=True it consumes the keys and mouse inputs that an active
layer binds.

The gamepad is polled once per frame (carb gamepads do not repeat, and polling gives the
analog levels); gamepad input events are ignored so nothing is counted twice.
"""

from __future__ import annotations

import carb
import carb.input

from ..core.bindings import BindingState, Frame

_STICK_AXES = {
    "LEFT_STICK_X": ("LEFT_STICK_RIGHT", "LEFT_STICK_LEFT"),
    "LEFT_STICK_Y": ("LEFT_STICK_UP", "LEFT_STICK_DOWN"),
    "RIGHT_STICK_X": ("RIGHT_STICK_RIGHT", "RIGHT_STICK_LEFT"),
    "RIGHT_STICK_Y": ("RIGHT_STICK_UP", "RIGHT_STICK_DOWN"),
}


def _mods(flags: int) -> list:
    out = []
    ci = carb.input
    if flags & int(getattr(ci, "KEYBOARD_MODIFIER_FLAG_CONTROL", 0)):
        out.append("ctrl")
    if flags & int(getattr(ci, "KEYBOARD_MODIFIER_FLAG_SHIFT", 0)):
        out.append("shift")
    if flags & int(getattr(ci, "KEYBOARD_MODIFIER_FLAG_ALT", 0)):
        out.append("alt")
    return out


[docs] class InputRouter: def __init__(self, state: BindingState, order: int = 0, consume: bool = False, name: str = "vantagekit"): self.state = state self.name = name self._consume = consume self._input = carb.input.acquire_input_interface() self._keys = {} for inp in state.inputs("key"): enum = getattr(carb.input.KeyboardInput, inp.split(":", 1)[1], None) if enum is None: carb.log_warn(f"[{name}] unknown key in bindings: {inp}") else: self._keys[enum] = inp self._pad_inputs = {} for inp in state.inputs("pad"): key = inp.split(":", 1)[1] if key in _STICK_AXES: pos, neg = _STICK_AXES[key] self._pad_inputs[inp] = ( getattr(carb.input.GamepadInput, pos), getattr(carb.input.GamepadInput, neg), ) else: enum = getattr(carb.input.GamepadInput, key, None) if enum is None: carb.log_warn(f"[{name}] unknown gamepad input in bindings: {inp}") else: self._pad_inputs[inp] = (enum, None) self._pads = [] self._drag_from = None # Optional callable: mouse events count only while it returns True. carb input is # app-wide, so a viewer with its own pop-up (a second view with its own orbit) sets # this to ignore drags and the wheel while the pointer is over that pop-up. self.mouse_gate = None self._sub = self._input.subscribe_to_input_events(self._on_event, order=order) self._pad_sub = self._input.subscribe_to_gamepad_connection_events(self._on_pad) # events ------------------------------------------------------------------- def _on_pad(self, e) -> None: if e.type == carb.input.GamepadConnectionEventType.CREATED: if e.gamepad not in self._pads: self._pads.append(e.gamepad) carb.log_info(f"[{self.name}] gamepad connected: {self._input.get_gamepad_name(e.gamepad)}") elif e.type == carb.input.GamepadConnectionEventType.DESTROYED: if e.gamepad in self._pads: self._pads.remove(e.gamepad) self.state.release_all() carb.log_info(f"[{self.name}] gamepad disconnected") def _on_event(self, event) -> bool: dt = event.deviceType if dt == carb.input.DeviceType.KEYBOARD: return self._on_key(event.event) if dt == carb.input.DeviceType.MOUSE: return self._on_mouse(event.event) return True # gamepad: polled in poll() def _on_key(self, e) -> bool: inp = self._keys.get(e.input) if inp is None: return True t = e.type if t == carb.input.KeyboardEventType.KEY_PRESS: self.state.key_down(inp, _mods(int(e.modifiers))) elif t == carb.input.KeyboardEventType.KEY_RELEASE: self.state.key_up(inp) # KEY_REPEAT and CHAR: ignored; a held key fires its press once return not (self._consume and self.state.claims(inp)) def _on_mouse(self, e) -> bool: if self.mouse_gate is not None and not self.mouse_gate(): self._drag_from = None # a drag that wanders into the pop-up ends there return True t = e.type mt = carb.input.MouseEventType claimed = False if t == mt.LEFT_BUTTON_DOWN: self._drag_from = (e.normalized_coords.x, e.normalized_coords.y) claimed = self.state.claims("mouse:DRAG_X") or self.state.claims("mouse:DRAG_Y") elif t == mt.LEFT_BUTTON_UP: self._drag_from = None elif t == mt.MOVE and self._drag_from is not None: x, y = e.normalized_coords.x, e.normalized_coords.y self.state.add_delta("mouse:DRAG_X", x - self._drag_from[0]) self.state.add_delta("mouse:DRAG_Y", y - self._drag_from[1]) self._drag_from = (x, y) claimed = self.state.claims("mouse:DRAG_X") or self.state.claims("mouse:DRAG_Y") elif t == mt.SCROLL: self.state.add_delta("mouse:WHEEL", e.scrollDelta.y) claimed = self.state.claims("mouse:WHEEL") return not (self._consume and claimed) # per frame ----------------------------------------------------------------
[docs] def poll(self, dt: float) -> Frame: if self._pads: gp = self._pads[0] get = self._input.get_gamepad_value for inp, (pos, neg) in self._pad_inputs.items(): v = float(get(gp, pos)) if neg is not None: v -= float(get(gp, neg)) self.state.set_level(inp, v) return self.state.step(dt)
[docs] def dispose(self) -> None: if self._sub is not None: self._input.unsubscribe_to_input_events(self._sub) self._sub = None if self._pad_sub is not None: self._input.unsubscribe_to_gamepad_connection_events(self._pad_sub) self._pad_sub = None self._pads = [] self.state.release_all()