InputState.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. from direct.directnotify import DirectNotifyGlobal
  2. from direct.showbase import DirectObject
  3. from direct.showbase.PythonUtil import SerialNumGen
  4. from direct.showbase.MessengerGlobal import messenger
  5. # internal class, don't create these on your own
  6. class InputStateToken:
  7. _SerialGen = SerialNumGen()
  8. Inval = 'invalidatedToken'
  9. def __init__(self, inputState):
  10. self._id = InputStateToken._SerialGen.next()
  11. self._hash = self._id
  12. self._inputState = inputState
  13. def release(self):
  14. # subclasses will override
  15. assert False
  16. def isValid(self):
  17. return self._id != InputStateToken.Inval
  18. def invalidate(self):
  19. self._id = InputStateToken.Inval
  20. def __hash__(self):
  21. return self._hash
  22. #snake_case alias:
  23. is_valid = isValid
  24. class InputStateWatchToken(InputStateToken, DirectObject.DirectObject):
  25. def release(self):
  26. self._inputState._ignore(self)
  27. self.ignoreAll()
  28. class InputStateForceToken(InputStateToken):
  29. def release(self):
  30. self._inputState._unforce(self)
  31. class InputStateTokenGroup:
  32. def __init__(self):
  33. self._tokens = []
  34. def addToken(self, token):
  35. self._tokens.append(token)
  36. def release(self):
  37. for token in self._tokens:
  38. token.release()
  39. self._tokens = []
  40. #snake_case alias:
  41. add_token = addToken
  42. class InputState(DirectObject.DirectObject):
  43. """
  44. InputState is for tracking the on/off state of some events.
  45. The initial usage is to watch some keyboard keys so that another
  46. task can poll the key states. By the way, in general polling is
  47. not a good idea, but it is useful in some situations. Know when
  48. to use it:) If in doubt, don't use this class and listen for
  49. events instead.
  50. """
  51. notify = DirectNotifyGlobal.directNotify.newCategory("InputState")
  52. # standard input sources
  53. WASD = 'WASD'
  54. QE = 'QE'
  55. ArrowKeys = 'ArrowKeys'
  56. Keyboard = 'Keyboard'
  57. Mouse = 'Mouse'
  58. def __init__(self):
  59. # stateName->set(SourceNames)
  60. self._state = {}
  61. # stateName->set(SourceNames)
  62. self._forcingOn = {}
  63. # stateName->set(SourceNames)
  64. self._forcingOff = {}
  65. # tables to look up the info needed to undo operations
  66. self._token2inputSource = {}
  67. self._token2forceInfo = {}
  68. # inputSource->token->(name, eventOn, eventOff)
  69. self._watching = {}
  70. assert self.debugPrint("InputState()")
  71. def delete(self):
  72. del self._watching
  73. del self._token2forceInfo
  74. del self._token2inputSource
  75. del self._forcingOff
  76. del self._forcingOn
  77. del self._state
  78. self.ignoreAll()
  79. def isSet(self, name, inputSource=None):
  80. """
  81. returns True/False
  82. """
  83. #assert self.debugPrint("isSet(name=%s)"%(name))
  84. if name in self._forcingOn:
  85. return True
  86. elif name in self._forcingOff:
  87. return False
  88. if inputSource:
  89. s = self._state.get(name)
  90. if s:
  91. return inputSource in s
  92. else:
  93. return False
  94. else:
  95. return name in self._state
  96. def getEventName(self, name):
  97. return "InputState-%s" % (name,)
  98. def set(self, name, isActive, inputSource=None):
  99. assert self.debugPrint("set(name=%s, isActive=%s, inputSource=%s)"%(name, isActive, inputSource))
  100. # inputSource is a string that identifies where this input change
  101. # is coming from (like 'WASD', 'ArrowKeys', etc.)
  102. # Each unique inputSource is allowed to influence this input item
  103. # once: it's either 'active' or 'not active'. If at least one source
  104. # activates this input item, the input item is considered to be active
  105. if inputSource is None:
  106. inputSource = 'anonymous'
  107. if isActive:
  108. self._state.setdefault(name, set())
  109. self._state[name].add(inputSource)
  110. else:
  111. if name in self._state:
  112. self._state[name].discard(inputSource)
  113. if len(self._state[name]) == 0:
  114. del self._state[name]
  115. # We change the name before sending it because this may
  116. # be the same name that messenger used to call InputState.set()
  117. # this avoids running in circles:
  118. messenger.send(self.getEventName(name), [self.isSet(name)])
  119. def releaseInputs(self, name):
  120. # call this to act as if all inputs affecting this state have been released
  121. del self._state[name]
  122. def watch(self, name, eventOn, eventOff, startState=False, inputSource=None):
  123. """
  124. This returns a token; hold onto the token and call token.release() when
  125. you no longer want to watch for these events.
  126. Example::
  127. # set up
  128. token = inputState.watch('forward', 'w', 'w-up', inputSource=inputState.WASD)
  129. ...
  130. # tear down
  131. token.release()
  132. """
  133. assert self.debugPrint(
  134. "watch(name=%s, eventOn=%s, eventOff=%s, startState=%s)"%(
  135. name, eventOn, eventOff, startState))
  136. if inputSource is None:
  137. inputSource = "eventPair('%s','%s')" % (eventOn, eventOff)
  138. # Do we really need to reset the input state just because
  139. # we're watching it? Remember, there may be multiple things
  140. # watching this input state.
  141. self.set(name, startState, inputSource)
  142. token = InputStateWatchToken(self)
  143. # make the token listen for the events, to allow multiple listeners for the same event
  144. token.accept(eventOn, self.set, [name, True, inputSource])
  145. token.accept(eventOff, self.set, [name, False, inputSource])
  146. self._token2inputSource[token] = inputSource
  147. self._watching.setdefault(inputSource, {})
  148. self._watching[inputSource][token] = (name, eventOn, eventOff)
  149. return token
  150. def watchWithModifiers(self, name, event, startState=False, inputSource=None):
  151. patterns = ('%s', 'control-%s', 'shift-control-%s', 'alt-%s',
  152. 'control-alt-%s', 'shift-%s', 'shift-alt-%s')
  153. tGroup = InputStateTokenGroup()
  154. for pattern in patterns:
  155. tGroup.addToken(self.watch(name, pattern % event, '%s-up' % event, startState=startState, inputSource=inputSource))
  156. return tGroup
  157. def _ignore(self, token):
  158. """
  159. Undo a watch(). Don't call this directly, call release() on the token that watch() returned.
  160. """
  161. inputSource = self._token2inputSource.pop(token)
  162. name, eventOn, eventOff = self._watching[inputSource].pop(token)
  163. token.invalidate()
  164. DirectObject.DirectObject.ignore(self, eventOn)
  165. DirectObject.DirectObject.ignore(self, eventOff)
  166. if len(self._watching[inputSource]) == 0:
  167. del self._watching[inputSource]
  168. # I commented this out because we shouldn't be modifying an
  169. # input state simply because we're not looking at it anymore.
  170. # self.set(name, False, inputSource)
  171. def force(self, name, value, inputSource):
  172. """
  173. Force isSet(name) to return 'value'.
  174. This returns a token; hold onto the token and call token.release() when
  175. you no longer want to force the state.
  176. Example::
  177. # set up
  178. token = inputState.force('forward', True, inputSource='myForwardForcer')
  179. ...
  180. # tear down
  181. token.release()
  182. """
  183. token = InputStateForceToken(self)
  184. self._token2forceInfo[token] = (name, inputSource)
  185. if value:
  186. if name in self._forcingOff:
  187. self.notify.error(
  188. "%s is trying to force '%s' to ON, but '%s' is already being forced OFF by %s" %
  189. (inputSource, name, name, self._forcingOff[name])
  190. )
  191. self._forcingOn.setdefault(name, set())
  192. self._forcingOn[name].add(inputSource)
  193. else:
  194. if name in self._forcingOn:
  195. self.notify.error(
  196. "%s is trying to force '%s' to OFF, but '%s' is already being forced ON by %s" %
  197. (inputSource, name, name, self._forcingOn[name])
  198. )
  199. self._forcingOff.setdefault(name, set())
  200. self._forcingOff[name].add(inputSource)
  201. return token
  202. def _unforce(self, token):
  203. """
  204. Stop forcing a value. Don't call this directly, call release() on your token.
  205. """
  206. name, inputSource = self._token2forceInfo[token]
  207. token.invalidate()
  208. if name in self._forcingOn:
  209. self._forcingOn[name].discard(inputSource)
  210. if len(self._forcingOn[name]) == 0:
  211. del self._forcingOn[name]
  212. if name in self._forcingOff:
  213. self._forcingOff[name].discard(inputSource)
  214. if len(self._forcingOff[name]) == 0:
  215. del self._forcingOff[name]
  216. def debugPrint(self, message):
  217. """for debugging"""
  218. return self.notify.debug(
  219. "%s (%s) %s"%(id(self), len(self._state), message))
  220. #snake_case alias:
  221. watch_with_modifiers = watchWithModifiers
  222. is_set = isSet
  223. get_event_name = getEventName
  224. debug_print = debugPrint
  225. release_inputs = releaseInputs