| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- from functools import partial
- from geometry import Positionable
- from logger import Logger
- from trackers import create_tracker
- from abc import ABCMeta, abstractmethod
- class Area(Positionable, Logger):
- """
- Abstract class for area implementations. A area represents a 2D object on
- the screen in which gestures can occur. Handlers for specific gesture types
- can be bound to a area.
- """
- __metaclass__ = ABCMeta
- def __init__(self, x=None, y=None):
- Positionable.__init__(self, x, y)
- # List of all active trackers
- self.trackers = []
- # Map of gesture types to a list of handlers for that type
- self.handlers = {}
- # Area tree references
- self.parent = None
- self.children = []
- self.delegate_queue = {}
- self.update_handlers = []
- def get_root_area(self):
- """
- Traverse up in the area tree to find the root area.
- """
- if self.parent:
- return self.parent.get_root_area()
- return self
- def get_screen_offset(self):
- """
- Get the position relative to the screen.
- """
- if not self.parent:
- return self.get_position()
- ox, oy = self.parent.get_screen_offset()
- return ox + self.x, oy + self.y
- def get_root_offset(self):
- """
- Get the position relative to the root area.
- """
- if not self.parent:
- return 0, 0
- ox, oy = self.parent.get_root_offset()
- return ox + self.x, oy + self.y
- def get_offset(self, offset_area):
- """
- Get the position relative to an ancestor area.
- """
- if self == offset_area:
- return 0, 0
- ox, oy = self.parent.get_offset(offset_area)
- return ox + self.x, oy + self.y
- def add_area(self, area):
- """
- Add a new child area.
- """
- self.children.append(area)
- area.set_parent(self)
- def remove_area(self, area):
- """
- Remove a child area.
- """
- self.children.remove(area)
- area.set_parent(None)
- def set_parent(self, area):
- """
- Set a new parent area. If a parent area has already been assigned,
- remove the area from that parent first.
- """
- if area and self.parent:
- self.parent.remove_area(self)
- self.parent = area
- def unbind(self, gesture_type, handler=None):
- """
- Unbind a single handler, or all handlers bound to a gesture type.
- Remove the corresponding tracker if it is no longer needed.
- """
- if gesture_type not in self.handlers:
- raise KeyError('Gesture type "%s" is not bound.')
- if handler:
- # Remove a specific handler
- self.handlers[gesture_type].remove(handler)
- # Only remove the handler list and optionally the tracker if no
- # other handlers exist for the gesture type
- if self.handlers[gesture_type]:
- return
- else:
- # Remove handler list
- del self.handlers[gesture_type][:]
- tracker = self.find_tracker(gesture_type)
- # Check if any other handlers need the tracker
- for gtype in tracker.gesture_types:
- if gtype in self.handlers:
- return
- # No more handlers are bound, remove unused tracker and handlers
- self.trackers.remove(gesture_type)
- for gtype in tracker.gesture_types:
- del self.handlers[gtype]
- def find_tracker(self, gesture_type):
- """
- Find a tracker that is tracking some gesture type.
- """
- for tracker in self.trackers:
- if gesture_type in tracker.gesture_types:
- return tracker
- def bind(self, gesture_type, handler, remove_existing=False, **kwargs):
- """
- Bind a handler to the specified type of gesture. Create a tracker for
- the gesture type if it does not exists yet. Ik a new tracker is
- created, configure is with any keyword arguments that have been
- specified.
- """
- if gesture_type not in self.handlers:
- tracker = create_tracker(gesture_type, self)
- tracker.configure(**kwargs)
- self.trackers.append(tracker)
- self.handlers[gesture_type] = []
- # Create empty tracker lists for all supported gestures
- for gtype in tracker.gesture_types:
- self.handlers[gtype] = []
- elif remove_existing:
- del self.handlers[gesture_type][:]
- self.handlers[gesture_type].append(handler)
- def __getattr__(self, name):
- """
- Allow calls like:
- area.on_gesture(...)
- instead of:
- area.bind('gesture', ...)
- """
- if len(name) < 4 or name[:3] != 'on_':
- raise AttributeError("'%s' has no attribute '%s'"
- % (self.__class__.__name__, name))
- return partial(self.bind, name[3:])
- @abstractmethod
- def contains_event(self, event):
- """
- Check if the coordinates of an event are contained within this area.
- """
- raise NotImplementedError
- def delegate_event(self, event):
- """
- Delegate a triggered event to all child areas. If a child stops
- propagation, return so that its siblings and the parent area will not
- delegate the event to their trackers.
- """
- event.add_offset(*self.get_position())
- #print 'delegated in %s: %s' % (self, event)
- if self.children:
- # Delegate to children in reverse order because areas that are
- # added later, should be placed over previously added siblings
- delegate_to = [c for c in self.children
- if c.contains_event(event)]
- if delegate_to:
- self.delegate_queue[event] = delegate_to
- self.propagate_event(event)
- def propagate_event(self, event):
- """
- Delagate an event to all gesture trackers (immediate propagation), then
- propagate it to the parent area (if any). Propagation can be stopped by
- a tracker.
- """
- if event in self.delegate_queue:
- child = self.delegate_queue[event].pop()
- if not self.delegate_queue[event]:
- del self.delegate_queue[event]
- child.delegate_event(event)
- return
- self.handle_event(event)
- if self.parent and not event.is_propagation_stopped():
- event.add_offset(-self.x, -self.y)
- #print 'propagated to %s: %s' % (self.parent, event)
- self.parent.propagate_event(event)
- def handle_event(self, event):
- for tracker in self.trackers:
- tracker.handle_event(event)
- if event.is_immediate_propagation_stopped():
- return
- def handle_gesture(self, gesture):
- """
- Handle a gesture that is triggered by a gesture tracker. First, all
- handlers bound to the gesture type are called.
- """
- for handler in self.handlers.get(gesture.get_type(), ()):
- handler(gesture)
- def on_update(self, handler):
- self.update_handlers.append(handler)
- def update(self):
- """
- Call update handlers of this area and all children, so that the
- underlying subtree knows about changes in geometry and can redraw.
- """
- for handler in self.update_handlers:
- if handler():
- return True
- for child in self.children:
- if child.update():
- return True
- def set_position(self, x, y):
- Positionable.set_position(self, x, y)
- self.update()
|