key polling

Here is a class that gives functions that allow you to poll whether a key is pressed or not with a function instead of using the event que. It is a singleton class meant to be imported anywhere that these functions are needed without worry of duplication.
base.accept() now will work with this…you can both poll and accept events!!!

Functions:
I.getButton(‘button’)
I.getButtonHit(‘button’)
I.buttonsPressed()
I.getMouse(‘button’)
I.getMouseHit(‘button’)
I.mousePressed()
I.getMousePos()
I.getMousePixel()
I.getMouseAspect()
I.getMouseSpeed()

examples:

import direct.directbase.DirectStart
from basePolling import I
while(True):
    taskMgr.step()
    l = I.buttonsPressed()
    if len(l)>0: print l
import direct.directbase.DirectStart
from basePolling import I
while(True):
    taskMgr.step()
    if I.getButton('g'): print 'g pressed'
    if I.getButtonHit('g'): print 'g hit'
import direct.directbase.DirectStart
from basePolling import I
while(True):
    taskMgr.step()
    print I.getMousePos(),I.getMousePixel(),I.getMouseAspect(),I.getMouseSpeed()

basePolling.py:

from pandac.PandaModules import ButtonThrower

class Interface(object):
	def getButton(self,key):
		'''returns true if the button is pressed, key is same as the key events'''
		return (key in self._butList)
	def getButtonHit(self,key):
		'''reuturns true if the button was pushed this frame, key is same as the key events'''
		return (key in self._butHitList)
	def buttonsPressed(self):
		'''returns a list of all buttons being pressed'''
		return self._butList
	def getMouse(self,key):
		'''same as getButton but for mouse buttons. key is one of left,middle,right,wheel'''
		if key=='wheel': return self._wheel #wheel special...
		return (key in self._mButList)
	def getMouseHit(self,key):
		'''same as getButtonhit but for mouse.  key is one of left,middle,right,wheel'''
		return (key in self._mButHitList)
	def mousePressed(self):
		'''same as buttonsPressed- but does not include the wheel...'''
		return self._mButList
	def getMousePos(self):
		'''the x,y mouse position according to render2d'''
		return [self._mPos[0],self._mPos[1]]
	def getMousePixel(self):
		'''the x,y pixel under mouse...0-screenWidth,0-screenHeight from upper left corner'''
		return [self._mPos[2],self._mPos[3]]
	def getMouseAspect(self):
		'''the x,y mouse position according to aspect2d'''
		return [self._mPos[4],self._mPos[1]]
	def getMouseSpeed(self):
		'''the delta x,delta y of the mouse pos in pixel coords...(everything else
			has both positive and negative which messes up speed and direction)'''
		return [self._mPos[2]-self._mPosOld[2],self._mPos[3]-self._mPosOld[3]]     
   
	def __init__(self):       
		#base.disableMouse()

		#make sure we don't override the normal events...
		mw = base.buttonThrowers[0].getParent()
		self.buttonThrower = mw.attachNewNode(ButtonThrower('Polling Buttons'))
		self.buttonThrower.node().setPrefix('poll-')
			   
		#mouse buttons
		self._mPos = [0,0,0,0,0]
		self._mPosOld = [0,0,0,0,0]
		self._mButList = []
		self._mButHitList = []

		base.accept('poll-mouse1',self._setmBut,['left',1])
		base.accept('poll-mouse3',self._setmBut,['right',1])
		base.accept('poll-mouse2',self._setmBut,['middle',1])

		base.accept('poll-mouse1-up',self._setmBut,['left',0])
		base.accept('poll-mouse3-up',self._setmBut,['right',0])
		base.accept('poll-mouse2-up',self._setmBut,['middle',0])

		#wheel is special...
		self._wheel = 0
		base.accept('poll-wheel_up',self.__setattr__,['_wheel',-1])
		base.accept('poll-wheel_down',self.__setattr__,['_wheel',1])

		#keyboard buttons
		self._but =['a','b','c','d','e','f','g','h','i','j','k',
					'l','m','n','o','p','q','r','s','t','u','v',
					'w','x','y','n','z',
					'`','0','1','2','3','4','5','6','7','8','9','-','=',
					'[',']','\\',';',"'",',','.','/',              #note that \\ is the \ key...stupid python     
					'f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11','f12',
					'escape','scroll_lock',
					'backspace','insert','home','page_up','num_lock',
					'tab','delete','end','page_down',
					'caps_lock','enter','arrow_left','arrow_up','arrow_down','arrow_right',
					'shift','lshift','rshift',
					'control','lcontrol','rcontrol','alt','lalt','ralt','space'
				   ]
		for key in self._but:
			base.accept('poll-'+key,self._setBut,[key,1])
			base.accept('poll-'+key+'-up',self._setBut,[key,0])
		self._butList = []
		self._butHitList = []
		#print screen is different, only has up...put it into hit list when released
		base.accept('poll-print_screen-up',self._butHitList.append,['print_screen'])

		taskMgr.add(self._updateMouse,'update Mouse Coordinates',priority=-49)    #right after dataloop
		taskMgr.add(self._updateKeys,'update Key Hits',priority=-60)    #before everything else


	def _setBut(self,key,val):
		if val==1:
			self._butList.append(key)
			self._butHitList.append(key)
		else:
			try: self._butList.remove(key)
			except ValueError: pass    #missed the keyDown event, happens if program hiccups when key is pushed
			 
	def _setmBut(self,key,val):
		if val==1:
			self._mButList.append(key)
			self._mButHitList.append(key)
		else:
			try: self._mButList.remove(key)
			except ValueError: pass
				   
	def _updateMouse(self,task):
		if base.mouseWatcherNode.hasMouse():
			self._mPosOld = self._mPos
			self._mPos = [0,0,0,0,0]
			self._mPos[0] = base.mouseWatcherNode.getMouseX()
			self._mPos[1] = base.mouseWatcherNode.getMouseY()
			#pixel coordinates
			self._mPos[2]=base.win.getPointer(0).getX()
			self._mPos[3]=base.win.getPointer(0).getY()
			#aspect2d coordinates (mPos[4],mPos[1]
			self._mPos[4]=self._mPos[0]*base.getAspectRatio()
		else: self._mPosOld = self._mPos    #out of screen, dx,dy=0
	   
		return task.cont
   
	def _updateKeys(self,task):
		self._butHitList = []
		self._mButHitList = []
		self._wheel = 0
		return task.cont
   
	#allows only 1 copy (singleton)   
	def __new__(cls,*args, **kwargs):
		if '_inst' not in vars(cls):
			cls._inst = super(Interface,cls).__new__(cls,*args,**kwargs)
		return cls._inst

I = Interface() 

EDIT: it might be better, depending on what you are doing (see below) to use:

  def getButton(key):
    if len(key)==1:
       kb=KeyboardButton.asciiKey(key)
    else:
       if key=='del': key='_del'
       if hasattr(KeyboardButton,key): # keyboard button
          kb=getattr(KeyboardButton,key)()
       else:                           # mouse button
          kb=getattr(MouseButton,key)()
    return base.mouseWatcherNode.isButtonDown(kb)

(code curtasy of ynjh_jo)

interesting!

There is always polling vs events debates. I think events win out in the long run but polling is still fun and easier some times.

Small update:

it is now possible to use base.accept() in your code with the buttonpolling activated! Use both for the best solution to each individual problem!

Thanks for this neat bit of code mindstormss. This makes ctrl/shift combinations much easier than through event catching. I’d vote to make this part of panda’s base systems really.

slight typo:

    def mousePressed(self): 
        '''same as anyButton - but does not include the wheel...''' 
        return self._mButLis

the “t” on self._mButList is missing should be :

    def mousePressed(self): 
        '''same as anyButton - but does not include the wheel...''' 
        return self._mButList

My question is how would implement dragging a square, or drag and drop, or button in and button out, with this polling implementation?

“capsLock” should be “caps_lock”

Then, “I.getMousePixel()” returns incorrect coordinates:

mousePos1 = I.getMousePixel()
print "I.getMousePixel()", mousePos1
mousePos2 = base.win.getPointer(0)
print "base.win.getPointer(0)", mousePos2

returns:

I.getMousePixel() [400.0, 300.0]
base.win.getPointer(0) MouseData: (512, 320)

To make it work properly you need to edit the basePolling.py. Replace:

self._mPos[2]=(self._mPos[0]+1)*self.WINSIZEX/2
self._mPos[3]=self.WINSIZEY-(self._mPos[1]+1)*self.WINSIZEY/2

with:

self._mPos[2]=base.win.getPointer(0).getX()
self._mPos[3]=base.win.getPointer(0).getY()

fixed the typos…

birukoff: I didn’t realize that you could get the pixel data that way, it is much better than mine, but when I run your code (and put it into a while(True): loop with taskMgr.step()) I get the same exact things from both my code and the base.win.getPointer(0)…
A gotcha with the “old” code is if you change the screen resolution you have to change WINSIZEX,WINSIZEY and maybe SCREENRATIO for that to work.

HOWEVER, I changed the code above and that is no longer true. Thanks for your guy’s help!

What is the advantage over the built-in class “InputState”?

from direct.showbase.InputStateGlobal import inputState
...
inputState.watchWithModifiers( 'forward', 'w' )
...
print inputState.isSet( 'forward' )

enn0x

I didn’t realize there was a class like that, but looking at it there are two differences. Both the keys basically do the same thing, but mine will record all of the keys without being told to. You don’t have to tell it to watch a specific key because it is already. Mine also watches the mouse coords and speed, and understanding the source is alot easier to understand :slight_smile:. On the other hand, inputState allows the forcing of a value to on or off and allows, once you get it set up, to use only one function for polling. I, a couple of weeks ago, added this functionality to my class out of necessity, and simply never got around to posting it. Speed wise they should be about the same, although I think that if the inputState was told to watch all of the keys, it would be slightly slower to poll because it checks the wanted value against two lists. On the other hand, whenever a key is pressed with mine a python function gets called (all it does is adds the key to a list, though), making it slightly slower if you were looking for only a couple of keys and many extraneous ones were being pressed.

My original application used this in a editable text field, and mine works (as it was designed to) out of the box for this purpose while the majority of what happens in the init code of mine would have to be replicated in the Direct version.

I guess it is best to use the right one for the right situation.

I see. Thanks for explaining. Looks good, and more comfortable :slight_smile:
enn0x

MouseWatcher already supports polling, and it uses all keys by default.

print base.mouseWatcherNode.isButtonDown(KeyboardButton.asciiKey(‘z’))

You know, I looked for that combination in mouseWatcherNode and could just not get it to work…I wonder what the speed of that is compared to the other two implementations.

(Mine’s alot shorter to poll!)

Oh, and mine records key hits too, not just the presses, which neither of the other options do (I think?).

Can’t believe I missed that earlier…

The speed difference :
isButtonDown queries the down status from _current_buttons_down BitArray, so it requires no Python operations at all.

Did you mean shorter in function/method name to call ?
Surely I can cut it down this way :

def down(key):
    if len(key)==1:
       kb=KeyboardButton.asciiKey(key)
    else:
       if key=='del': key='_del'
       if hasattr(KeyboardButton,key): # keyboard button
          kb=getattr(KeyboardButton,key)()
       else:                           # mouse button
          kb=getattr(MouseButton,key)()
    return base.mouseWatcherNode.isButtonDown(kb)

Usage example :

print 'KEYBD :',down('z'),down('1'),down('del'),down('f5'),down('capsLock')
print 'MOUSE :',down('one'),down('two'),down('three'),'\n'

What’s the difference of your “press” and “hit” status actually ?

The shorter was just a little humor, it means nothing in terms of comparison…as you so easily pointed out.

In my code, buttonHits are true for each button only the first frame that button is pressed. pressed is what you have there. I can’t think of an easy implementation unless there is another array for buttons that changed state between last frame and this frame…

What I am wondering, is if there is this method of getting keys, what was the purpose of the InputState (other than the creator also, like me, missed this function in mouseWatcherNode)?

I have to guess, since I am just a “user” like most others here, but InputState is in the directory direct/src/controllers, and there are several different implementations of “utility” classes for controlling a character in different situations (walk, swim, battle - like in toontown???, roller - bike???, pilot …).

I guess there controllers have been used in Toontown or an early version of toontown, and InputState has just been an utility for not having to write the same functionality in every controller again. I doubt that these classes are still maintined actively.

Controlling a characters movement is probably best done using polling: An arrow key gets pressed down, hold down for some time, and then gets released again. Character actions - like jumping, picking up something, throwing something - are probably better done using events.

To be honest I didn’t know this too - after two or three years of messing around with Panda3D on evenings and weekends. When reading the API reference I assumed “mouseWatcher” and “buttons” means “mouse buttons only”, silly me.

There are so many classes in Panda3D, and only those that get introduced in the tutorials or demos are well known. And Panda3D classes are not grouped by topics on the Python level, like “scene graph”, “input”, “rendering”, “gui”. Maybe C++ namespaces/Python packages would be a nice feature (I do not volunteer to refactor Panda3D source code and interrogate - I know this would be several weeks full of work :slight_smile:

I think this forum is a good place for spreading news about those classes which are rarely seen. And what they can be used for and what not.

enn0x

A little correction to the sample from ynjh_jo: delete key is now called simply “delete”. Even the reference documentation is not updated regarding this change…

under a (swiss)german keyboard (macbook pro) the base.mouseWatcherNode.isButtonDown(key) produces a ‘bad’ report for one of the keys. it’s the § or ° key that reports as ‘\r’ and ‘\x0b’ and ‘\x0c’. these characters are in the string.printable of python, so they might should be included in panda’s keyboard characters as well (by a name that is readable) for example?

maybe someone else wants to check if all available keys can be found?:

#!/usr/bin/env python

from panda3d.core import *


ALLKEYS = [
  'left', 'right', 'up', 'down',
  'meta',
  'alt', 'lalt', 'ralt',
  'shift', 'lshift', 'rshift',
  'control', 'lcontrol', 'rcontrol', 
  'shiftLock', 'scrollLock', 'capsLock', 'numLock', 
  
  'space', 'enter', 'escape', 'backspace', 'tab',
  
  'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9',
  'f10', 'f11', 'f12', 'f13', 'f14', 'f15', 'f16',
  
  'del', 'insert', 'pause', 'printScreen', 'help', 
  'home', 'end', 'pageDown', 'pageUp', 
  
  'delete', 
  
  '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/',
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  ':', ';', '<', '=', '>', '?', '@',
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
  'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  '[', '\\', ']', '^', '_', '`',
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
  'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
  '{', '|', '}', '~',
  '\n', '\t', '\r',
  '\x0c', '\x0b',
]

#import string
#ALLKEYS.extend(string.printable)
# includes another one that cant be printed (results in 3 keys pressed (ord = 10,11,12))
# \t\n\r\x0b\x0c

# i cant find the corresponding key for the one left of the '1' 

# single character key list generated by
#for i in xrange(128):
#  key = str(KeyboardButton.asciiKey(i))
#  if key is not None:
#    #print key
#    if len(key) == 1:
#      ALLKEYS.extend(key)
#print ALLKEYS

def isKeyDown(key):
  ''' returns if a key has been pressed
  '''
  if len(key)==1:
    kb=KeyboardButton.asciiKey(key)
  else:
    if key=='del': key='_del'
    if hasattr(KeyboardButton, key): # keyboard button
      kb=getattr(KeyboardButton, key)()
    else:                           # mouse button
      try:
        kb=getattr(MouseButton, key)()
      except:
        if key != 'delete':
          print("unknown key '%s'" % key)
        return False
  return base.mouseWatcherNode.isButtonDown(kb)


if __name__ == '__main__':
  from direct.directbase import DirectStart
  
  def keyTask(task):
    for c in ALLKEYS:
      if isKeyDown(c):
        print "pressed", c,
        if len(c) == 1:
          print ord(c)
        else:
          print
    return task.cont
  taskMgr.add(keyTask, 'keyTask')
  
  print "press any key"
  run()

Very helpful topic) Maybe my example of application of approaches described here may also be of some interest? ^^

from panda3d.core import *

class InputManager(object):
	# Allow only 1 copy
	def __new__(cls, *args, **kwargs):
		if '_inst' not in vars(cls):
			cls._inst = super(InputManager,cls).__new__(cls, *args, **kwargs)
		return cls._inst
	
	def __init__(self, noCompoundEvents=True, dontDriveCamera=True):
		if noCompoundEvents:
			# Turn off modifier compound events.
			# If it is left on, pressing modifier+KEY won't send KEY!
			base.mouseWatcherNode.setModifierButtons(ModifierButtons())
			base.buttonThrowers[0].node().setModifierButtons(ModifierButtons())
		
		if dontDriveCamera:
			# Disable default mouse control of main camera
			base.disableMouse()
		
		self._keys_prev = 0
		self._keys_changed = 0
		self._keys_held = 0
		self._keys_down = 0
		self._keys_up = 0
		self._printscreen = 0
		self._wheelAccum = Vec2()
		self._wheel = Vec2()
		self._lockMouse = None
		self._lastUnlockedPos = None
		self._limitToScreen = True
		self._wasInWindow = False
		self._ignoreNextDelta = True
		self._mouseSpeed = Vec2()
		self._mousePos = Point2()
		
		self._allKeys = {}
		self._pollKeys = []
		
		self.setMouseLocked(False, True)
		
		# KeyboardButton.asciiKey() in range 0-127 won't raise an error
		# 178 is current maximal key code (wheel_right key)
		for i in xrange(178+1):
			bh = KeyboardButton.asciiKey(i) if i < 128 else ButtonHandle(i)
			if bh.getIndex() == 0: continue # Ignore 'none' button
			self._addKey(bh)
		
		base.accept('wheel_up', self._setWheel, [Vec2(0, -1)])
		base.accept('wheel_down', self._setWheel, [Vec2(0, 1)])
		base.accept('wheel_left', self._setWheel, [Vec2(-1, 0)])
		base.accept('wheel_right', self._setWheel, [Vec2(1, 0)])
		
		# Update keys before everything else
		taskMgr.add(self._updateKeys, 'inputManager.Keys', sort=-60)
		
		# Update mouse right after dataloop
		try:
			# See manual about sort and priority
			task = taskMgr.getTasksNamed("dataLoop")[0]
			taskMgr.add(self._updateMouse, 'inputManager.Mouse',
				sort=task.getSort(), priority=task.getPriority()-1)
		except IndexError:
			taskMgr.add(self._updateMouse, 'inputManager.Mouse', sort=-49)
	
	def _addKey(self, bh):
		name = bh.getName()
		if name.startswith("wheel"): return
		
		mask = 1 << len(self._allKeys)
		self._allKeys[name] = mask
		if name == 'print_screen':
			# See manual about keyboard support
			base.accept('print_screen-up', self._setPrintScreen, [mask])
		else:
			self._pollKeys.append((bh, mask))
	
	def _setWheel(self, delta):
		self._wheelAccum += delta
	
	def _setPrintScreen(self, mask):
		self._printscreen = mask
	
	def _updateMouse(self, task):
		md = base.win.getPointer(0)
		mpos = Point2(md.getX(), md.getY())
		
		isInWindow = md.getInWindow()
		
		if not self._lockMouse:
			if self._wasInWindow and isInWindow:
				self._mouseSpeed = mpos - self._mousePos
			else:
				self._mouseSpeed = Vec2()
			self._mousePos = mpos
		else:
			sizeX = base.win.getXSize()
			sizeY = base.win.getYSize()
			halfX = sizeX * 0.5
			halfY = sizeY * 0.5
			if isInWindow:
				if self._ignoreNextDelta:
					self._mouseSpeed = Vec2()
					self._ignoreNextDelta = False
				else:
					self._mouseSpeed = mpos - Point2(halfX, halfY)
				self._mousePos += self._mouseSpeed
				if self._limitToScreen:
					x, y = self._mousePos
					x = min(max(x, 0), sizeX)
					y = min(max(y, 0), sizeY)
					self._mousePos = Point2(x, y)
			base.win.movePointer(0, int(halfX), int(halfY))
		
		self._wasInWindow = isInWindow
		
		return task.cont
	
	def _updateKeys(self, task):
		self._wheel = self._wheelAccum
		self._wheelAccum = Vec2()
		
		mask = 0
		# Store function handle in local variable for faster access
		isPressed = base.mouseWatcherNode.isButtonDown
		for c in self._pollKeys:
			if isPressed(c[0]): mask |= c[1]
		
		self._keys_held = mask
		self._keys_changed = self._keys_held ^ self._keys_prev
		self._keys_down = self._keys_held & self._keys_changed
		self._keys_up = self._keys_prev & self._keys_changed
		self._keys_prev = self._keys_held
		
		self._keys_up |= self._printscreen
		self._printscreen = 0
		
		return task.cont
	
	def getAllKeynames(self):
		return sorted(self._allKeys.iterkeys(),
			key=lambda (x) : (len(x)>1, x))
	
	def isPressed(self, key=None):
		if key is None: return bool(self._keys_held)
		return bool(self._keys_held & self._allKeys[key])
	
	def isChanged(self, key=None):
		if key is None: return bool(self._keys_changed)
		return bool(self._keys_changed & self._allKeys[key])
	
	def isHit(self, key=None):
		if key is None: return bool(self._keys_down)
		return bool(self._keys_down & self._allKeys[key])
	
	def isReleased(self, key=None):
		if key is None: return bool(self._keys_up)
		return bool(self._keys_up & self._allKeys[key])
	
	def getWheel(self):
		return self._wheel
	
	def getMouseLocked(self):
		return self._lockMouse
	
	def setMouseLocked(self, locked, unhideOnUnlock=True, useLastPosition=False):
		if self._lockMouse == locked: return
		
		props = WindowProperties()
		if locked:
			props.setCursorHidden(True)
			props.setMouseMode(WindowProperties.MRelative)
			props.setRawMice(True)
		else:
			if unhideOnUnlock: props.setCursorHidden(False)
			props.setMouseMode(WindowProperties.MAbsolute)
			props.setRawMice(False)
		base.win.requestProperties(props)
		
		self._ignoreNextDelta = False
		if locked:
			md = base.win.getPointer(0)
			halfX = base.win.getXSize() * 0.5
			halfY = base.win.getYSize() * 0.5
			if md.getInWindow():
				self._mousePos = Point2(md.getX(), md.getY())
			else:
				self._mousePos = Point2(halfX, halfY)
			self._lastUnlockedPos = self._mousePos
			self._ignoreNextDelta = True
		else:
			if useLastPosition:
				x, y = self._lastUnlockedPos
			else:
				x, y = self._mousePos
			if not (self._lockMouse is None):
				base.win.movePointer(0, int(x), int(y))
		
		self._lockMouse = locked
	
	def getMouseLimited(self):
		return self._limitToScreen
	
	def setMouseLimited(self, limit):
		self._limitToScreen = limit
	
	def getMouseSpeed(self, relative=False, aspect=False):
		if not (relative or aspect): return self._mouseSpeed
		halfX = base.win.getXSize() * 0.5
		halfY = base.win.getYSize() * 0.5
		x = self._mouseSpeed[0] / halfX
		y = -self._mouseSpeed[1] / halfY
		if aspect: x *= base.getAspectRatio()
		return Vec2(x, y)
	
	def setMouseSpeed(self, speed, relative=False, aspect=False):
		x, y = speed
		if relative or aspect:
			halfX = base.win.getXSize() * 0.5
			halfY = base.win.getYSize() * 0.5
			if aspect: x /= base.getAspectRatio()
			x = x * halfX
			y = -y * halfY
		self._mouseSpeed = Vec2(x, y)
	
	def getMousePos(self, relative=False, aspect=False):
		if not (relative or aspect): return self._mousePos
		halfX = base.win.getXSize() * 0.5
		halfY = base.win.getYSize() * 0.5
		x = (self._mousePos[0] - halfX) / halfX
		y = -(self._mousePos[1] - halfY) / halfY
		if aspect: x *= base.getAspectRatio()
		return Point2(x, y)
	
	def setMousePos(self, pos, relative=False, aspect=False):
		x, y = pos
		if relative or aspect:
			halfX = base.win.getXSize() * 0.5
			halfY = base.win.getYSize() * 0.5
			if aspect: x /= base.getAspectRatio()
			x = x * halfX + halfX
			y = -y * halfY + halfY
		self._mousePos = Point2(x, y)
		
		if not self._lockMouse:
			base.win.movePointer(0, int(x), int(y))