Source code for engine.evt.inputs

"""Input classes, representing filtered subsets of Pygame events."""

import sys

import pygame as pg

class _Unfilterable (object):
    pass

#: A value that an :class:`Input` cannot filter for.
UNFILTERABLE = _Unfilterable()

#: ``{device: allowed_mod_devices}`` for :class:`ButtonInput` instances.  An
#: input for :attr:`device <Input.device>` ``device`` may only have modifiers
#: with :attr:`device <Input.device>` in ``allowed_mod_devices``.
mod_devices = {
    'kbd': ('kbd',),
    'mouse': ('kbd', 'mouse'),
    'pad': ('pad',),
    'evt': ('evt',)
}


def _init_pad (dev_id=True):
    # 'pad' device initialisation function
    done = []
    todo = xrange(pg.joystick.get_count()) if dev_id is True else (dev_id,)
    for i in todo:
        try:
            pg.joystick.Joystick(i).init()
        except pg.error:
            pass
        else:
            done.append(i)
    return done


#: ``{device: init_fn}`` giving initialisation functions to initialise devices
#: where possible.  These functions take the ``Input.device_id`` to initialise
#: for, or no argument to initialise for all devices of this type, and should
#: return a sequence of device IDs that have been successfully initialised.
device_init_handlers = {
    'pad': _init_pad
}

[docs]class mbtn: """Contains mouse button aliases.""" LEFT = 1 MIDDLE = 2 RIGHT = 3 UP = 4 DOWN = 5
def _pad_matches (device_id): # get the pygame.joystick.Joystick instances that match the given device_id if device_id is True: ids = xrange(pg.joystick.get_count()) elif device_id is None: ids = () else: ids = (device_id,) js = [] for i in ids: try: js.append(pg.joystick.Joystick(i)) except pg.error: print >> sys.stderr, 'warning: no such pad: {}'.format(i) return js
[docs]class Input (object): """Base class for handling input events. Does nothing by itself. Input(*pgevts) :arg pgevts: Pygame event IDs to listen for. If a subclass has a ``pgevts`` attribute, this is a list of events to add to the argument at initialisation. Comparing inputs for equality compares filters only (so inputs of different types may be equal). """ #: Number of components ('directions'/'button-likes') represented by this #: input. components = 0 #: The string device name that this input type corresponds to (see #: :data:`inputs_by_name <engine.evt.conffile.inputs_by_name>`). device = None #: A value that the device ID will never take (see :attr:`device_id`). invalid_device_id = -1 def __init__ (self, *pgevts): #: An ``{id: provided}`` dict of 'interfaces' this input provides, with #: keys ``'button'``, ``'axis'``, ``'relaxis'``. self.provides = {'button': False, 'axis': False, 'relaxis': False} #: Variable representing the current device ID; may be a string as a #: variable name, or ``None``. See also #: :meth:`EventHandler.assign_devices() #: <engine.evt.handler.EventHandler.assign_devices>`). self.device_var = None #: An :class:`Event <engine.evt.evts.Event>` instance that contains #: this input, or ``None``. self.evt = None pgevts = set(pgevts) if hasattr(self, 'pgevts'): pgevts.update(self.pgevts) #: A ``{pgevt_attr: val}`` dict that represents how events are filtered #: before being passed to this input (see :meth:`filter`). self.filters = {'type': pgevts or ('',)} self._device_id = True def _str_dev_id (self): # device id/var for printing dev_id = self._device_id if dev_id is True: dev_id = '(any)' elif dev_id is None and self.device_var is not None: dev_id = '<{0}>'.format(self.device_var) return dev_id def _str (self, arg): # string representation with some contained data return '{0}({1})'.format(type(self).__name__, arg) def __str__ (self): return self._str(self.filters) def __repr__ (self): return str(self) def __eq__ (self, other): return isinstance(other, Input) and other.filters == self.filters def __hash__ (self): # required in Python 3 since have __eq__ return id(self) @property def eh (self): """:class:`EventHandler <engine.evt.handler.EventHandler>` for :attr:`evt`, or ``None``.""" evt = self.evt while evt is not None: eh = evt.eh if eh: return eh evt = evt.evt
[docs] def filter (self, attr, *vals, **kw): """Filter events passed to this input. filter(attr, *vals, refilter = False) -> self :arg attr: Pygame event attribute to filter by. :arg vals: allowed values of the given attribute for filtered events. :arg refilter: if ``True``, replace previous filtering by ``attr`` with the given ``vals``, else add to the values already filtered by. """ refilter = kw.get('refilter', False) if not vals: if refilter: # refilter to nothing, ie. remove all filtering self.unfilter(attr) # else nothing to do return self eh = self.eh # wrap with removal from/readdition to handler if eh is not None: eh._rm_inputs(self) if UNFILTERABLE in vals: raise ValueError('cannot filter for {0}'.format(UNFILTERABLE)) if refilter: self.filters[attr] = set(vals) else: self.filters.setdefault(attr, set()).update(vals) if eh is not None: eh._add_inputs(self) return self
[docs] def unfilter (self, attr, *vals): """Remove filtering by the given attribute. :arg attr: Pygame event attribute to modify filtering for. :arg vals: values to remove filtering for. If none are given, all filtering by ``attr`` is removed. """ if attr not in self.filters: return self eh = self.eh # wrap with removal from/readdition to handler if eh is not None: eh._rm_inputs(self) got = self.filters[attr] if vals: # remove given values got.difference_update(vals) if not got: # no longer filtering by this attribute del self.filters[attr] else: # remove all del self.filters[attr] if eh is not None: eh._add_inputs(self) return self
@property def device_id (self): """The particular device that this input captures input for. May be ``True``, in which case all such devices work through this input. May be ``None``, in which case no input will be registered; this is done by filtering by :attr:`invalid_device_id`. Subclasses may set an attribute ``device_id_attr``, in which case setting this attribute filters using ``device_id_attr`` as the event attribute and the set value as the attribute value to filter by. If a subclass does not provide ``device_id_attr`` and does not override the setter, this operation raises ``TypeError``. """ return self._device_id @device_id.setter def device_id (self, device_id): if hasattr(self, 'device_id_attr'): if device_id is True: # sort by nothing to get all events ids = () elif device_id is None: # sort by an invalid ID to make sure we get no events ids = (self.invalid_device_id,) else: ids = (device_id,) self.filter(self.device_id_attr, *ids, refilter = True) self._device_id = device_id self._init() else: raise TypeError('this Input type doesn\'t support device IDs') def _init (self): # initialise the device/id associated with this input dev_id = self._device_id if dev_id is not None: init_fn = device_init_handlers.get(self.device) if init_fn is not None: eh = self.eh if dev_id is True: done = init_fn() else: key = (self.device, dev_id) if eh is not None and key in eh._init_data: # make sure every handler knows about this done = (dev_id,) else: done = init_fn(dev_id) keys = [(dev_id, dev_id) for dev_id in done] if eh is not None: eh._init_data.update(keys)
[docs] def handle (self, pgevt): """Called by :class:`EventHandler <engine.evt.handler.EventHandler>` with a ``pygame.event.Event``. The passed event matches :attr:`filters`. :return: whether anything in the input's state changed. """ return False
[docs] def normalise (self): """Determine and set the input's current state, if any. This implementation does nothing. """ pass
[docs]class BasicInput (Input): """An input that handles raw Pygame events. BasicInput(*pgevts) :arg pgevts: Pygame event IDs to listen for. """ def __init__ (self, *pgevts): #: Pygame event IDs as passed to the constructor. self.pgevts = pgevts Input.__init__(self, *pgevts) # stored Pygame events, used by Event self._pgevts = [] def __str__ (self): return self._str( ', '.join(map(pg.event.event_name, self.pgevts)).upper() ) def handle (self, pgevt): """:inherit:""" Input.handle(self, pgevt) self._pgevts.append(pgevt) return True
[docs] def reset (self): """Clear cached Pygame events. Called by the owning :class:`Event <engine.evt.evts.Event>`. """ self._pgevts = []
[docs]class ButtonInput (Input): """Abstract base class representing a button-like action (:class:`Input` subclass). ButtonInput([button], *mods) :arg button: button ID to listen for. To use this, subclasses must set a ``button_attr`` property to filter by that Pygame event attribute with this ID as the value. Otherwise, they must implement filtering themselves. :arg mods: inputs to use as modifiers. Each may be a :class:`ButtonInput`, a sequence of them, or ``(input, component)`` giving the component of the input to use (from ``0`` to ``input.components - 1``). Subclasses must have a :attr:`device <Input.device>` in :data:`mod_devices`, which restricts allowed devices of modifiers. """ components = 1 def __init__ (self, button = None, *mods): self._held = [False] * self.components #: Whether this input is acting as a modifier. self.is_mod = False #: ``{container: components}`` for each container (such as an #: :class:`Event <engine.evt.evts.Event>`, or another #: :class:`ButtonInput` as a modifier). ``components`` is a sequence #: of the components of this input that the container uses. self.used_components = {} Input.__init__(self) self.provides['button'] = True if hasattr(self, 'button_attr') and button is not None: self.filter(self.button_attr, button) #: The button ID this input represents, as taken by the constructor. self.button = button mods = list(mods) mods_parsed = [] for m in mods: # default to using component 0 of the modifier if isinstance(m, Input): m = (m, 0) elif len(m) == 1: m = (m[0], 0) # now we have a sequence if isinstance(m[1], Input): # sequence of mods mods.extend(m) else: # (mod, component) mods_parsed.append(m) if any(m.mods for m, c in mods_parsed): raise ValueError('modifiers cannot have modifiers') ds = mod_devices[self.device] for m, c in mods_parsed: if m.device not in ds: raise TypeError( 'the modifier {0} is for device {1}, which is not ' 'compatible with {2} instances' .format(m, m.device, type(self).__name__) ) #: List of modifiers (:class:`ButtonInput` instances) that affect this #: input. self.mods = mods = [] for m, c in mods_parsed: if c < 0 or c >= m.components: raise ValueError('{0} has no component {1}'.format(m, c)) if not m.provides['button']: raise TypeError('input {0} cannot be a modifier'.format(m)) # we're now the mod's container m.is_mod = True m.used_components[self] = (c,) mods.append(m) def __str__ (self): if hasattr(self, '_btn_name'): # make something like [mod1]...[modn]self to pass to Input._str # _btn_name should give form for displaying within type wrapper s = self._btn_name() for m in self.mods: if hasattr(m, '_mod_btn_name'): # _mod_btn_name should give form for displaying as a mod mod_s = m._mod_btn_name() else: mod_s = str(m) s = '[{0}]{1}'.format(mod_s, s) return self._str(s) else: return Input.__str__(self)
[docs] def mods_active (self): """Whether modifiers for this button are held.""" if self.is_mod: return True if self.eh is None: return False all_mods = self.eh._mods mods = self.mods for device in mod_devices[self.device]: for device_id in set((self.device_id, True)): for m in all_mods.get(device, {}).get(device_id, ()): # mod matches if it's the same button as the input itself if m == self: pass # or if it's held in exactly this input's components # 'm in this_mods' uses __eq__, but we need identity elif any(m is n for n in mods): # only have one component if not (m.held(self)[0] and m._held.count(True) == 1): return False elif any(m._held): return False return True
[docs] def held (self, container): """A list of the held state of this button for each component. Each item is a bool that corresponds to the component in the same position in :attr:`used_components` for the given container. """ return [self._held[c] for c in self.used_components[container]]
[docs] def down (self, component=0, evt=True): """Set the given component's button state to down. :arg evt: whether to let the containing event know about this. """ self._held[component] = True # mods don't have events if evt and not self.is_mod: evt = self.evt if evt is not None: if component in self.used_components[evt]: evt.inp_down(self, component) return True return False
[docs] def up (self, component=0, evt=True): """Set the given component's button state to up. :arg evt: whether to let the containing event know about this. """ # don't allow an up without a down if self._held[component]: self._held[component] = False # mods don't have events if evt and not self.is_mod: evt = self.evt if evt is not None: if component in self.used_components[evt]: evt.inp_up(self, component) return True return False
[docs] def set_held (self, held, evts=False, component=0): """Set the held state of the button on the given component. :arg evts: whether to trigger button down/up events if the held state changes. """ if held != self._held[component]: if evts: self.down() if held else self.up() else: self._held[component] = bool(held)
[docs] def handle (self, pgevt): """:meth:`Input.handle`. If a subclass has a ``down_pgevts`` attribute, this sets the button down on component ``0`` for Pygame events with IDs in this list, and up on component ``0`` for all other events. Otherwise, it does nothing. """ rtn = Input.handle(self, pgevt) if hasattr(self, 'down_pgevts'): if pgevt.type in self.down_pgevts: if self.mods_active(): rtn |= self.down() else: rtn |= self.up() return rtn
[docs]class KbdKey (ButtonInput): """Keyboard key. :arg key: the key code (required). """ device = 'kbd' name = 'key' pgevts = (pg.KEYDOWN, pg.KEYUP) button_attr = 'key' down_pgevts = (pg.KEYDOWN,) def __init__ (self, key, *mods): ButtonInput.__init__(self, key, *mods) def _btn_name (self): return pg.key.name(self.button).upper() _mod_btn_name = _btn_name def normalise (self): """:inherit:""" self.set_held(pg.key.get_pressed()[self.button])
class _SneakyMultiKbdKey (KbdKey): # KbdKey wrapper to handle multiple keys, for use as a modifier (held if # any key is held) - only for module.mod def __init__ (self, name, button, *buttons): # `button` isn't a real key KbdKey.__init__(self, buttons[0]) self._name = name self.filter(self.button_attr, *buttons[1:]) self._keys = buttons # track each key's held state self._held_multi = dict.fromkeys(buttons, False) def _btn_name (self): return self._name _mod_btn_name = _btn_name def _update_held (self): self._held[0] = any(self._held_multi.itervalues()) def handle (self, pgevt): self._held_multi[pgevt.key] = pgevt.type in self.down_pgevts self._update_held() return False def normalise (self): """:inherit:""" held = pg.key.get_pressed() for k in self._keys: self._held_multi[k] = held[k] self._update_held()
[docs]class MouseButton (ButtonInput): """Mouse button. The ``button`` argument is required, and is the mouse button ID. """ device = 'mouse' name = 'button' pgevts = (pg.MOUSEBUTTONDOWN, pg.MOUSEBUTTONUP) button_attr = 'button' down_pgevts = (pg.MOUSEBUTTONDOWN,) def __init__ (self, button, *mods): ButtonInput.__init__(self, button, *mods) def _btn_name (self): return '{0}'.format(self.button) def _mod_btn_name (self): return 'mouse button {0}'.format(self.button) def normalise (self): """:inherit:""" held = pg.mouse.get_pressed() b = self.button - 1 if b >= len(held): print >> sys.stderr, 'warning: cannot determine held state of ' \ '{0}'.format(self) # Pygame doesn't return states for some buttons, such as scroll wheels held = held[b] if b < len(held) else False self.set_held(held)
[docs]class PadButton (ButtonInput): """Gamepad button. PadButton(device_id, button, *mods) :arg device_id: the gamepad's device ID, either a variable (:attr:`device_var <Input.device_var>`) or a non-string ID (:attr:`device_id <Input.device_id>`). :arg button: as taken by :class:`ButtonInput`. :arg mods: as taken by :class:`ButtonInput`. """ device = 'pad' name = 'button' pgevts = (pg.JOYBUTTONDOWN, pg.JOYBUTTONUP) device_id_attr = 'joy' button_attr = 'button' down_pgevts = (pg.JOYBUTTONDOWN,) def __init__ (self, device_id, button, *mods): ButtonInput.__init__(self, button, *mods) if isinstance(device_id, basestring): self.device_id = None self.device_var = device_id else: self.device_id = device_id def _btn_name (self): return '{0}, {1}'.format(self._str_dev_id(), self.button) def _mod_btn_name (self): return 'pad {0} button {1}'.format(self._str_dev_id(), self.button) def normalise (self): """:inherit:""" for j in _pad_matches(self._device_id): try: held = j.get_button(self.button) except pg.error: print >> sys.stderr, \ 'warning: cannot determine held state of {0} (gamepad ' \ 'not initialised or no such button)'.format(self) else: self.set_held(held)
[docs]class AxisInput (ButtonInput): """Abstract base class representing 2-component axes. AxisInput([axis][, thresholds], *mods) :arg axis: axis ID to listen for. To use this, subclasses must set an ``axis_attr`` property to filter by that Pygame event attribute with this ID as the value, and with an attribute giving the axis's value. Otherwise, they must implement filtering themselves. :arg thresholds: required if the axis is to act as a button. For each axis (that is, for each pair of :attr:`Input.components`), this list has two elements: ``down`` followed by ``up``, positive numbers giving the magnitude of the value of the axis in either direction that triggers a button down or up event. For example, a 2-component axis might have ``(.6, .4)``. A subclass with more than 2 components may pass a length-2 sequence here, which is expanded by assuming the same thresholds for each axis. :arg mods: as taken by :class:`ButtonInput`. Only used if this axis is treated as a button. Subclasses must have an even number of components. """ components = 2 def __init__ (self, axis = None, thresholds = None, *mods): self._pos = [0] * self.components if mods and thresholds is None: raise TypeError('an AxisInput must have thresholds defined to ' 'have modifiers') ButtonInput.__init__(self, None, *mods) self.provides['axis'] = True if hasattr(self, 'axis_attr'): if axis is None: raise TypeError('expected axis argument') self.filter(self.axis_attr, axis) #: Axis ID, as passed to the constructor. self.axis = axis # same threshold for each axis if only given for one if thresholds is not None: if len(thresholds) == 2: thresholds *= (self.components // 2) if len(thresholds) != self.components: raise ValueError('invalid number of threshold arguments') else: # ButtonInput sets this to True self.provides['button'] = False #: As passed to the constructor. self.thresholds = thresholds self.deadzone = 0 @property def pos (self): """Sequence of positions for each axis.""" p = self._pos return [p[i + 1] - p[i] for i in xrange(self.components // 2)] @pos.setter def pos (self, pos): for axis, apos in enumerate(pos): if self.axis_motion(axis, apos): # HACK evt = self.evt if evt is not None: evt._changed = True @property def deadzone (self): """Axis value magnitude below which the value is mapped to ``0``; defaults to ``0``. Above this value, the mapped value increases linearly from ``0``. """ return self._deadzone @deadzone.setter def deadzone (self, dz): n = self.components // 2 if isinstance(dz, (int, float)): dz = (dz,) * n else: dz = tuple(dz) if len(dz) != n: raise ValueError('{0} deadzone must have {1} components' .format(type(self).__name__, n)) if any(x < 0 or x >= 1 for x in dz): raise ValueError('require 0 <= deadzone < 1') self._deadzone = dz
[docs] def axis_motion (self, axis, apos): """Signal a change in axis position. :arg axis: the index of the axis to modify (a 2-component :class:`AxisInput` has one axis, with index ``0``). :arg apos: the new axis position (``-1 <= apos <= 1``). """ # get magnitude in each direction pos = [0, 0] if apos > 0: pos[1] = apos else: pos[0] = -apos # apply deadzone (linear scale up from it) dz = self._deadzone for i in (0, 1): pos[i] = max(0, pos[i] - dz[axis]) / (1 - dz[axis]) # know dz != 1 imn = 2 * axis imx = 2 * (axis + 1) old_pos = self._pos if pos != old_pos[imn:imx]: if self.provides['button']: # act as button down, up = self.thresholds[imn:imx] l = list(zip(xrange(imn, imx), old_pos[imn:imx], pos)) # all up (towards 0/centre) first, then all down, to end up # held if move down for i, old, new in l: if self._held[i] and old > up and new <= up: self.up(i) if self.mods_active(): for i, old, new in l: if old < down and new >= down: self.down(i) for i, j in enumerate(xrange(imn, imx)): old_pos[j] = pos[i] return True else: # neither magnitude changed return False
[docs] def handle (self, pgevt): """:meth:`ButtonInput.handle`. If a subclass has an ``axis_val_attr`` attribute, the value of this attribute in ``pgevt`` is used as a list of axis positions (or just one, if a number). Otherwise, this method does nothing. """ rtn = Input.handle(self, pgevt) if hasattr(self, 'axis_val_attr'): apos = getattr(pgevt, self.axis_val_attr) if isinstance(apos, (int, float)): apos = (apos,) if len(apos) != self.components // 2: raise ValueError( 'the event attribute given by the axis_val_attr attribute' 'has the wrong number of components' ) for i, apos in enumerate(apos): rtn |= self.axis_motion(i, apos) return rtn
[docs]class PadAxis (AxisInput): """Gamepad axis. PadAxis(device_id, axis[, thresholds], *mods) :arg device_id: the gamepad's device ID, either a variable (:attr:`device_var <Input.device_var>`) or a non-string ID (:attr:`device_id <Input.device_id>`). :arg axis: as taken by :class:`AxisInput`. :arg thresholds: as taken by :class:`AxisInput`. :arg mods: as taken by :class:`ButtonInput`. """ device = 'pad' name = 'axis' device_id_attr = 'joy' pgevts = (pg.JOYAXISMOTION,) axis_attr = 'axis' axis_val_attr = 'value' def __init__ (self, device_id, axis, thresholds = None, *mods): AxisInput.__init__(self, axis, thresholds, *mods) if isinstance(device_id, basestring): self.device_id = None self.device_var = device_id else: self.device_id = device_id def _mod_btn_name (self): return 'pad {0} axis {1}'.format(self._str_dev_id(), self.axis) def __str__ (self): return self._str('{0}, {1}'.format(self._str_dev_id(), self.axis)) def normalise (self): """:inherit:""" for j in _pad_matches(self._device_id): try: apos = j.get_axis(self.axis) except pg.error: print >> sys.stderr, \ 'warning: cannot determine held state of {0} (gamepad ' \ 'not initialised or no such axis)'.format(self) else: self.pos = (apos,)
[docs]class PadHat (AxisInput): """Gamepad hat. PadHat(device_id, axis[, thresholds], *mods) :arg device_id: the gamepad's device ID, either a variable (:attr:`device_var <Input.device_var>`) or a non-string ID (:attr:`device_id <Input.device_id>`). :arg hat: the hat ID to listen for. :arg thresholds: as taken by :class:`AxisInput`. :arg mods: as taken by :class:`ButtonInput`. """ components = 4 device = 'pad' name = 'hat' device_id_attr = 'joy' pgevts = (pg.JOYHATMOTION,) axis_attr = 'hat' axis_val_attr = 'value' def __init__ (self, device_id, hat, thresholds = None, *mods): AxisInput.__init__(self, hat, thresholds, *mods) if isinstance(device_id, basestring): self.device_id = None self.device_var = device_id else: self.device_id = device_id def _mod_btn_name (self): return 'pad {0} hat {1}'.format(self._str_dev_id(), self.axis) def __str__ (self): return self._str('{0}, {1}'.format(self._str_dev_id(), self.axis)) def normalise (self): """:inherit:""" for j in _pad_matches(self._device_id): try: apos = j.get_hat(self.axis) except pg.error: print >> sys.stderr, \ 'warning: cannot determine held state of {0} (gamepad ' \ 'not initialised or no such hat)'.format(self) else: self.pos = (apos,)
[docs]class RelAxisInput (AxisInput): """Abstract base class representing 2-component relative axes. RelAxisInput([relaxis][, bdy][, thresholds], *mods) :arg relaxis: axis ID to listen for. To use this, subclasses must set a ``relaxis_attr`` property to filter by that Pygame event attribute with this ID as the value, and with an attribute giving the axis's value. Otherwise, they must implement filtering themselves. :arg bdy: required if the relative axis is to act as an axis or a button. For each axis (each 2 components), this sequence contains a positive number giving the maximum magnitude of the axis. The normalised axis position is then obtained by dividing by this value. May be a single number instead of a one-item sequence. :arg thresholds: as taken by :class:`AxisInput`. Only used if this relative axis is treated as an axis, and required if it is to act as a button. :arg mods: as taken by :class:`ButtonInput`. Only used if this relative axis is treated as a button. A relative axis is one where events convey a change in the axis's value, rather than its absolute position. Subclasses must have an even number of components. Note that using the same component of an instance of a subclass for two different events (or using the same component twice for a single :class:`MultiEvent <engine.evt.evts.MultiEvent>`) is not supported, and behaviour in this case is undefined. """ components = 2 def __init__ (self, relaxis = None, bdy = None, thresholds = None, *mods): #: The change in each component since last :meth:`reset`. self.rel = [0, 0] * (self.components // 2) AxisInput.__init__(self, None, thresholds, *mods) self.provides['relaxis'] = True if hasattr(self, 'relaxis_attr'): if relaxis is None: raise TypeError('expected relaxis argument') self.filter(self.relaxis_attr, relaxis) #: Axis ID, as passed to the constructor. self.relaxis = relaxis if bdy is not None: if isinstance(bdy, (int, float)): bdy = (bdy,) * (self.components // 2) if len(bdy) != self.components // 2: raise ValueError('invalid number of bdy arguments') if any(b <= 0 for b in bdy): raise ValueError('all bdy elements must be greater than zero') else: # AxisInput sets this to True self.provides['axis'] = False #: As taken by the constructor. self.bdy = bdy
[docs] def relaxis_motion (self, relaxis, rpos): # split relative axis motion into magnitudes in each direction if rpos > 0: self.rel[2 * relaxis + 1] += rpos else: self.rel[2 * relaxis] -= rpos if self.provides['axis']: # act as axis (add relative pos to current pos) # normalise and restrict magnitude to 1 apos = (self._pos[2 * relaxis + 1] - self._pos[2 * relaxis] + float(rpos) / self.bdy[relaxis]) sgn = 1 if apos > 0 else -1 apos = sgn * min(sgn * apos, 1) return self.axis_motion(relaxis, apos) else: return bool(rpos)
[docs] def handle (self, pgevt): """:meth:`AxisInput.handle`. If a subclass has an ``relaxis_val_attr`` attribute, the value of this attribute in ``pgevt`` is used as a list of axis changes (or just one, if a number). Otherwise, this method does nothing. """ rtn = Input.handle(self, pgevt) if hasattr(self, 'relaxis_val_attr'): rpos = getattr(pgevt, self.relaxis_val_attr) if isinstance(rpos, (int, float)): rpos = (rpos,) if len(rpos) != self.components // 2: raise ValueError( 'the event attribute given by the relaxis_val_attr ' 'attribute has the wrong number of components' ) for i, rpos in enumerate(rpos): rtn |= self.relaxis_motion(i, rpos) return rtn
[docs] def reset (self, *components): """Reset values in :attr:`rel` to ``0`` for the given components. Called by the owning :class:`Event <engine.evt.evts.Event>`. If no components are given, reset in all components. """ for c in components or xrange(self.components): self.rel[c] = 0
def normalise (self): """:inherit:""" self.reset() self.pos = (0,) * (self.components // 2) for c in xrange(self.components): self.set_held(False, component=c)
[docs]class MouseAxis (RelAxisInput): """Represents both mouse axes. MouseAxis([bdy][, thresholds], *mods) Arguments are as taken by :class:`RelAxisInput`. """ components = 4 device = 'mouse' name = 'axis' pgevts = (pg.MOUSEMOTION,) relaxis_val_attr = 'rel' def __init__ (self, bdy = None, thresholds = None, *mods): if isinstance(bdy, int): bdy = (bdy, bdy) RelAxisInput.__init__(self, None, bdy, thresholds, *mods) def _mod_btn_name (self): return 'mouse axis' def __str__ (self): return self._str('')
class _mod (object): @property def CTRL (self): return _SneakyMultiKbdKey('CTRL', pg.KMOD_CTRL, pg.K_LCTRL, pg.K_RCTRL) @property def SHIFT (self): return _SneakyMultiKbdKey('SHIFT', pg.KMOD_SHIFT, pg.K_LSHIFT, pg.K_RSHIFT) @property def ALT (self): return _SneakyMultiKbdKey('ALT', pg.KMOD_ALT, pg.K_LALT, pg.K_RALT) @property def META (self): return _SneakyMultiKbdKey('META', pg.KMOD_META, pg.K_LMETA, pg.K_RMETA) #: Contains objects that act as specific keyboard modifiers: CTRL, SHIFT, ALT, #: META. mod = _mod()