Source code for psychopy.visual.custommouse

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Class for more control over the mouse,
including the pointer graphic and bounding box."""

# Part of the PsychoPy library
# Copyright (C) 2002-2018 Jonathan Peirce (C) 2019-2024 Open Science Tools Ltd.
# Distributed under the terms of the GNU General Public License (GPL).




import os

import psychopy  # so we can get the __path__
from psychopy import event, logging, visual, layout

from psychopy.visual.basevisual import MinimalStim

import numpy


[docs]class CustomMouse(MinimalStim): """Class for more control over the mouse, including the pointer graphic and bounding box. This is a lazy-imported class, therefore import using full path `from psychopy.visual.custommouse import CustomMouse` when inheriting from it. Seems to work with pyglet or pygame. Not completely tested. Known limitations: * only norm units are working * getRel() always returns [0,0] * mouseMoved() is always False; maybe due to `self.mouse.visible == False` -> held at [0,0] * no idea if clickReset() works Author: Jeremy Gray, 2011 """ def __init__(self, win, newPos=None, visible=True, leftLimit=None, topLimit=None, rightLimit=None, bottomLimit=None, showLimitBox=False, clickOnUp=False, pointer=None, name=None, autoLog=None): """Class for customizing the appearance and behavior of the mouse. Use a custom mouse for extra control over the pointer appearance and function. It's probably slower to render than the regular system mouse. Create your `visual.Window` before creating a CustomMouse. Parameters ---------- win : required, `visual.Window` the window to which this mouse is attached visible : **True** or False makes the mouse invisible if necessary newPos : **None** or [x,y] gives the mouse a particular starting position leftLimit : left edge of a virtual box within which the mouse can move topLimit : top edge of virtual box rightLimit : right edge of virtual box bottomLimit : lower edge of virtual box showLimitBox : default is False display the boundary within which the mouse can move. pointer : The visual display item to use as the pointer; must have .draw() and setPos() methods. If your item has .setOpacity(), you can alter the mouse's opacity. clickOnUp : when to count a mouse click as having occurred default is False, record a click when the mouse is first pressed down. True means record a click when the mouse button is released. """ # what local vars are defined (these are the init params) for use by # __repr__ self._initParams = dir() self._initParams.remove('self') super(CustomMouse, self).__init__(name=name, autoLog=False) self.autoLog = False # set properly at end of init self.win = win self.mouse = event.Mouse(win=self.win) # maybe inheriting from Mouse would be easier? its not that simple self.getRel = self.mouse.getRel self.getWheelRel = self.mouse.getWheelRel self.mouseMoved = self.mouse.mouseMoved # FAILS self.mouseMoveTime = self.mouse.mouseMoveTime self.getPressed = self.mouse.getPressed self.clickReset = self.mouse.clickReset # ??? self._pix2windowUnits = self.mouse._pix2windowUnits self._windowUnits2pix = self.mouse._windowUnits2pix # the graphic to use as the 'mouse' icon (pointer) if pointer: self.pointer = pointer else: self.pointer = vm = visual.ShapeStim( win, vertices=[ [-0.5, 0.5], [-0.5, -0.35], [-0.3, -0.15], [-0.1, -0.5], [0.025, -0.425], [-0.175, -0.1], [0.1, -0.1], [-0.5, 0.5], ], fillColor="white", lineColor="black", lineWidth=1, anchor="top left", size=layout.Size((20, 20), 'pix', win), ) self.mouse.setVisible(False) # hide the actual (system) mouse self.visible = visible # the custom (virtual) mouse self.leftLimit = self.rightLimit = None self.topLimit = self.bottomLimit = None self.setLimit(leftLimit=leftLimit, topLimit=topLimit, rightLimit=rightLimit, bottomLimit=bottomLimit) self.showLimitBox = showLimitBox self.lastPos = None self.prevPos = None if newPos is not None: self.lastPos = newPos else: self.lastPos = self.mouse.getPos() # for counting clicks: self.clickOnUp = clickOnUp self.wasDown = False # state of mouse 1 frame prior to current frame self.clicks = 0 # how many mouse clicks since last reset self.clickButton = 0 # which button to count clicks for; 0 = left # set autoLog now that params have been initialised wantLog = autoLog is None and self.win.autoLog self.__dict__['autoLog'] = autoLog or wantLog if self.autoLog: logging.exp("Created %s = %s" % (self.name, str(self))) def _setPos(self, pos=None): """internal mouse position management. setting a position here leads to the virtual mouse being out of alignment with the hardware mouse, which leads to an 'invisible wall' effect for the mouse. """ if pos is None: pos = self.getPos() else: self.lastPos = pos self.pointer.setPos(pos) def setPos(self, pos): """Not implemented yet. Place the mouse at a specific position. """ raise NotImplementedError('setPos is not available for custom mouse') def getPos(self): """Returns the mouse's current position. Influenced by changes in .getRel(), constrained to be in its virtual box. """ dx, dy = self.getRel() x = min(max(self.lastPos[0] + dx, self.leftLimit), self.rightLimit) y = min(max(self.lastPos[1] + dy, self.bottomLimit), self.topLimit) self.lastPos = numpy.array([x, y]) return self.lastPos def draw(self): """Draw mouse (if it's visible), show the limit box, update the click count. """ self._setPos() if self.showLimitBox: self.box.draw() if self.visible: self.pointer.draw() isDownNow = self.getPressed()[self.clickButton] if self.clickOnUp: if self.wasDown and not isDownNow: # newly up self.clicks += 1 else: if not self.wasDown and isDownNow: # newly down self.clicks += 1 self.wasDown = isDownNow def getClicks(self): """Return the number of clicks since the last reset""" return self.clicks def resetClicks(self): """Set click count to zero""" self.clicks = 0 def getVisible(self): """Return the mouse's visibility state""" return self.visible def setVisible(self, visible): """Make the mouse visible or not (pyglet or pygame).""" self.visible = visible def setPointer(self, pointer): """Set the visual item to be drawn as the mouse pointer.""" if hasattr(pointer, 'draw') and hasattr(pointer, 'setPos'): self.pointer = pointer else: raise AttributeError("need .draw() and .setPos() methods" " in pointer") def setLimit(self, leftLimit=None, topLimit=None, rightLimit=None, bottomLimit=None): """Set the mouse's bounding box by specifying the edges. """ if type(leftLimit) in (int, float): self.leftLimit = leftLimit elif self.leftLimit is None: self.leftLimit = -1 if self.win.units == 'pix': self.leftLimit = self.win.size[0] / -2. if type(rightLimit) in (int, float): self.rightLimit = rightLimit elif self.rightLimit is None: self.rightLimit = .99 if self.win.units == 'pix': self.rightLimit = self.win.size[0] / 2.0 - 5 if type(topLimit) in (int, float): self.topLimit = topLimit elif self.topLimit is None: self.topLimit = 1 if self.win.units == 'pix': self.topLimit = self.win.size[1] / 2.0 if type(bottomLimit) in (int, float): self.bottomLimit = bottomLimit elif self.bottomLimit is None: self.bottomLimit = -0.98 if self.win.units == 'pix': self.bottomLimit = self.win.size[1] / -2.0 + 10 self.box = psychopy.visual.ShapeStim( self.win, vertices=[[self.leftLimit, self.topLimit], [self.rightLimit, self.topLimit], [self.rightLimit, self.bottomLimit], [self.leftLimit, self.bottomLimit], [self.leftLimit, self.topLimit]], opacity=0.35, autoLog=False) # avoid accumulated relative-offsets producing a different effective # limit: self.mouse.setVisible(True) self.lastPos = self.mouse.getPos() # hardware mouse's position self.mouse.setVisible(False)

Back to top