area.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. from functools import partial
  2. from geometry import Positionable
  3. from logger import Logger
  4. from trackers import create_tracker
  5. from abc import ABCMeta, abstractmethod
  6. class Area(Positionable, Logger):
  7. """
  8. Abstract class for area implementations. A area represents a 2D object on
  9. the screen in which gestures can occur. Handlers for specific gesture types
  10. can be bound to a area.
  11. """
  12. __metaclass__ = ABCMeta
  13. def __init__(self, x=None, y=None):
  14. Positionable.__init__(self, x, y)
  15. # List of all active trackers
  16. self.trackers = []
  17. # Map of gesture types to a list of handlers for that type
  18. self.handlers = {}
  19. # Area tree references
  20. self.parent = None
  21. self.children = []
  22. self.delegate_queue = {}
  23. self.update_handlers = []
  24. def get_root_area(self):
  25. """
  26. Traverse up in the area tree to find the root area.
  27. """
  28. if self.parent:
  29. return self.parent.get_root_area()
  30. return self
  31. def get_screen_offset(self):
  32. """
  33. Get the position relative to the screen.
  34. """
  35. if not self.parent:
  36. return self.get_position()
  37. ox, oy = self.parent.get_screen_offset()
  38. return ox + self.x, oy + self.y
  39. def get_root_offset(self):
  40. """
  41. Get the position relative to the root area.
  42. """
  43. if not self.parent:
  44. return 0, 0
  45. ox, oy = self.parent.get_root_offset()
  46. return ox + self.x, oy + self.y
  47. def get_offset(self, offset_area):
  48. """
  49. Get the position relative to an ancestor area.
  50. """
  51. if self == offset_area:
  52. return 0, 0
  53. ox, oy = self.parent.get_offset(offset_area)
  54. return ox + self.x, oy + self.y
  55. def add_area(self, area):
  56. """
  57. Add a new child area.
  58. """
  59. self.children.append(area)
  60. area.set_parent(self)
  61. def remove_area(self, area):
  62. """
  63. Remove a child area.
  64. """
  65. self.children.remove(area)
  66. area.set_parent(None)
  67. def set_parent(self, area):
  68. """
  69. Set a new parent area. If a parent area has already been assigned,
  70. remove the area from that parent first.
  71. """
  72. if area and self.parent:
  73. self.parent.remove_area(self)
  74. self.parent = area
  75. def unbind(self, gesture_type, handler=None):
  76. """
  77. Unbind a single handler, or all handlers bound to a gesture type.
  78. Remove the corresponding tracker if it is no longer needed.
  79. """
  80. if gesture_type not in self.handlers:
  81. raise KeyError('Gesture type "%s" is not bound.')
  82. if handler:
  83. # Remove a specific handler
  84. self.handlers[gesture_type].remove(handler)
  85. # Only remove the handler list and optionally the tracker if no
  86. # other handlers exist for the gesture type
  87. if self.handlers[gesture_type]:
  88. return
  89. else:
  90. # Remove handler list
  91. del self.handlers[gesture_type][:]
  92. tracker = self.find_tracker(gesture_type)
  93. # Check if any other handlers need the tracker
  94. for gtype in tracker.gesture_types:
  95. if gtype in self.handlers:
  96. return
  97. # No more handlers are bound, remove unused tracker and handlers
  98. self.trackers.remove(gesture_type)
  99. for gtype in tracker.gesture_types:
  100. del self.handlers[gtype]
  101. def find_tracker(self, gesture_type):
  102. """
  103. Find a tracker that is tracking some gesture type.
  104. """
  105. for tracker in self.trackers:
  106. if gesture_type in tracker.gesture_types:
  107. return tracker
  108. def bind(self, gesture_type, handler, remove_existing=False, **kwargs):
  109. """
  110. Bind a handler to the specified type of gesture. Create a tracker for
  111. the gesture type if it does not exists yet. Ik a new tracker is
  112. created, configure is with any keyword arguments that have been
  113. specified.
  114. """
  115. if gesture_type not in self.handlers:
  116. tracker = create_tracker(gesture_type, self)
  117. tracker.configure(**kwargs)
  118. self.trackers.append(tracker)
  119. self.handlers[gesture_type] = []
  120. # Create empty tracker lists for all supported gestures
  121. for gtype in tracker.gesture_types:
  122. self.handlers[gtype] = []
  123. elif remove_existing:
  124. del self.handlers[gesture_type][:]
  125. self.handlers[gesture_type].append(handler)
  126. def __getattr__(self, name):
  127. """
  128. Allow calls like:
  129. area.on_gesture(...)
  130. instead of:
  131. area.bind('gesture', ...)
  132. """
  133. if len(name) < 4 or name[:3] != 'on_':
  134. raise AttributeError("'%s' has no attribute '%s'"
  135. % (self.__class__.__name__, name))
  136. return partial(self.bind, name[3:])
  137. @abstractmethod
  138. def contains_event(self, event):
  139. """
  140. Check if the coordinates of an event are contained within this area.
  141. """
  142. raise NotImplementedError
  143. def delegate_event(self, event):
  144. """
  145. Delegate a triggered event to all child areas. If a child stops
  146. propagation, return so that its siblings and the parent area will not
  147. delegate the event to their trackers.
  148. """
  149. event.add_offset(*self.get_position())
  150. #print 'delegated in %s: %s' % (self, event)
  151. if self.children:
  152. # Delegate to children in reverse order because areas that are
  153. # added later, should be placed over previously added siblings
  154. delegate_to = [c for c in self.children
  155. if c.contains_event(event)]
  156. if delegate_to:
  157. self.delegate_queue[event] = delegate_to
  158. self.propagate_event(event)
  159. def propagate_event(self, event):
  160. """
  161. Delagate an event to all gesture trackers (immediate propagation), then
  162. propagate it to the parent area (if any). Propagation can be stopped by
  163. a tracker.
  164. """
  165. if event in self.delegate_queue:
  166. child = self.delegate_queue[event].pop()
  167. if not self.delegate_queue[event]:
  168. del self.delegate_queue[event]
  169. child.delegate_event(event)
  170. return
  171. self.handle_event(event)
  172. if self.parent and not event.is_propagation_stopped():
  173. event.add_offset(-self.x, -self.y)
  174. #print 'propagated to %s: %s' % (self.parent, event)
  175. self.parent.propagate_event(event)
  176. def handle_event(self, event):
  177. for tracker in self.trackers:
  178. tracker.handle_event(event)
  179. if event.is_immediate_propagation_stopped():
  180. return
  181. def handle_gesture(self, gesture):
  182. """
  183. Handle a gesture that is triggered by a gesture tracker. First, all
  184. handlers bound to the gesture type are called.
  185. """
  186. for handler in self.handlers.get(gesture.get_type(), ()):
  187. handler(gesture)
  188. def on_update(self, handler):
  189. self.update_handlers.append(handler)
  190. def update(self):
  191. """
  192. Call update handlers of this area and all children, so that the
  193. underlying subtree knows about changes in geometry and can redraw.
  194. """
  195. for handler in self.update_handlers:
  196. if handler():
  197. return True
  198. for child in self.children:
  199. if child.update():
  200. return True
  201. def set_position(self, x, y):
  202. Positionable.set_position(self, x, y)
  203. self.update()