File callbacks.py
File List > pyspacemouse > callbacks.py
Go to the documentation of this file
"""Callback types and configuration for PySpaceMouse.
This module contains callback definitions and configuration classes
for handling SpaceMouse events.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING, Callable, List, Optional, Sequence, Union
if TYPE_CHECKING:
from .types import SpaceMouseState
# Type aliases for callback signatures
StateCallback = Callable[["SpaceMouseState"], None]
ButtonChangeCallback = Callable[["SpaceMouseState", List[int]], None]
ButtonPressCallback = Callable[["SpaceMouseState", List[int], Union[int, List[int]]], None]
DofValueCallback = Callable[["SpaceMouseState", float], None]
@dataclass(slots=True)
class ButtonCallback:
"""Callback triggered when specific button(s) are pressed.
Attributes:
buttons: Single button index or list of button indices to watch
callback: Function called with (state, buttons, pressed_buttons)
"""
buttons: Union[int, List[int]]
callback: ButtonPressCallback
def __post_init__(self) -> None:
"""Validate the callback configuration."""
if not callable(self.callbackcallback):
raise TypeError("callback must be callable")
if isinstance(self.buttonsbuttons, list):
if not all(isinstance(b, int) for b in self.buttonsbuttons):
raise TypeError("buttons must be int or list of int")
elif not isinstance(self.buttonsbuttons, int):
raise TypeError("buttons must be int or list of int")
@dataclass(slots=True)
class DofCallback:
"""Callback triggered when a specific axis changes.
Attributes:
axis: Name of axis to monitor ('x', 'y', 'z', 'roll', 'pitch', 'yaw')
callback: Function called with (state, axis_value) for positive values
sleep: Minimum time between callback invocations
callback_minus: Optional function for negative axis values
filter: Minimum absolute value to trigger callback (deadzone)
"""
axis: str
callback: DofValueCallback
sleep: float = 0.0
callback_minus: Optional[DofValueCallback] = None
filter: float = 0.0
def __post_init__(self) -> None:
"""Validate the callback configuration."""
valid_axes = ("x", "y", "z", "roll", "pitch", "yaw")
if self.axis not in valid_axes:
raise ValueError(f"axis must be one of {valid_axes}, got '{self.axis}'")
if not callable(self.callbackcallback):
raise TypeError("callback must be callable")
if self.callback_minuscallback_minus is not None and not callable(self.callback_minuscallback_minus):
raise TypeError("callback_minus must be callable or None")
@dataclass(slots=True)
class Config:
"""Configuration container for all callback types.
Attributes:
callback: Called on every state change
dof_callback: Called on DoF (axis) state changes
dof_callbacks: List of axis-specific callbacks
button_callback: Called on any button state change
button_callbacks: List of button-specific callbacks
"""
callback: Optional[StateCallback] = None
dof_callback: Optional[StateCallback] = None
dof_callbacks: Optional[Sequence[DofCallback]] = None
button_callback: Optional[ButtonChangeCallback] = None
button_callbacks: Optional[Sequence[ButtonCallback]] = None
def __post_init__(self) -> None:
"""Validate the configuration."""
if self.dof_callbacksdof_callbacks:
for i, dc in enumerate(self.dof_callbacksdof_callbacks):
if not isinstance(dc, DofCallback):
raise TypeError(f"dof_callbacks[{i}] must be DofCallback instance")
if self.button_callbacksbutton_callbacks:
for i, bc in enumerate(self.button_callbacksbutton_callbacks):
if not isinstance(bc, ButtonCallback):
raise TypeError(f"button_callbacks[{i}] must be ButtonCallback instance")
# Legacy property names for backward compatibility
@property
def dof_callback_arr(self) -> Optional[Sequence[DofCallback]]:
"""Legacy alias for dof_callbacks."""
return self.dof_callbacksdof_callbacks
@property
def button_callback_arr(self) -> Optional[Sequence[ButtonCallback]]:
"""Legacy alias for button_callbacks."""
return self.button_callbacksbutton_callbacks