import pygame from pygame.locals import * from itertools import count from weakref import WeakValueDictionary ## Collection of all created sensors sensor_dict = WeakValueDictionary() ## Collection of sensors, over which the cursor was positioned last frame last_over_dict = WeakValueDictionary() def get(uid): return sensor_dict.get(uid) class Sensor(object): id_counter = count(1) dragging = None def __init__(self, parent=None, name="", rect=None, active=True, target=None, offset=(0, 0), callbacks=None): self.active = active self.children = [] self.callbacks = callbacks or {} self.target = target self.name = name if rect: self._rect = rect self.offset = offset self.uid = self.id_counter.next() sensor_dict[self.uid] = self if isinstance(parent, Sensor) and not parent is self: parent.add(self) def __len__(self): return len(self.children) def __repr__(self): return "" % (self.name, len(self)) def set_target(self, target=None): "Defines the object containing the rect attribute or get_rect method" self.target = target if target is not None: self.activate() return self def activate(self): "Activate the sensor, only active sensors are evaluated" if not self.active: self.active = True def deactivate(self): if self.active: self.active = False def switch(self, sensor=None): if sensor is None: sensor = self sensor.activate() if self.parent: for child in self.parent.children: if not child is sensor: child.deactivate() def _add(self, sensor, below=None): """Add another sensor as child""" if below == "bottom": self.children.insert(0, sensor) elif below in self.children: index = self.children.index(below) self.children.insert(index, sensor) else: self.children.append(sensor) def add(self, sensor, below=None): """Add another sensor as child tells where to insert the child: - 'bottom' -> insert at the bottom - -> insert below child - None (default) -> insert at top """ self._add(sensor, below=below) sensor.parent = self def detach(self): """Detach the sensor from its parent""" if self.parent: self.parent._remove(sensor=self) self.parent = None def _remove(self, sensor=None, uid=0): "Remove from the list of children" if uid: for sensor in self.children: if uid == sensor.uid: self.children.remove(sensor) return sensor elif sensor: self.children.remove(sensor) return True def remove(self, sensor=None, uid=0): """Remove sensor or with a) remove the sensor from the children b) set the removed sensor's parent to None""" sensor = self._remove(sensor, uid) if sensor: sensor.parent = None def attach(self, parent): "Attach the sensor to parent sensor " if isinstance(parent, Sensor): if not self in parent.children: self.detach() parent.children.append(self) self.parent = parent else: raise TypeError, "Sensor expected, %s received" % type(parent) def lift(self): if self.parent and self in self.parent.children: self.parent.children.remove(self) self.parent.children.append(self) def lower(self): if self.parent and self in self.parent.children: self.parent.children.remove(self) self.parent.children.insert(0, self) def get_rect(self): if hasattr(self, "_rect"): return getattr(self, "_rect") elif hasattr(self.target, "rect"): return getattr(self.target, "rect") elif hasattr(self.target, "get_rect"): return self.target.get_rect() else: raise AttributeError def __get_rect(self): return self.get_rect().move(self.offset) def __set_rect(self, rect): self._rect = rect def __del_rect(self): del self._rect rect = property(__get_rect, __set_rect, __del_rect) def collect_stack(self, point=()): """Collect the active sensors on position """ stack = [] if self.active: if not point or self.rect and self.rect.collidepoint(point): stack.append(self) for child in self.children: stack.extend(child.collect_stack(point)) return stack def _get_root(self): if self.parent: return self.parent.root else: return self root = property(_get_root) def mouse_button_down(self, sensor, event): if self.target: try: callback = self.target.mouse_button_down #getattr(self.target, "mouse_button_down") except AttributeError: return return callback(event) def mouse_button_up(self, sensor, event): #print "button up", event if self.target: try: callback = getattr(self.target, "mouse_button_up") except AttributeError: return return callback(event) def mouse_motion(self, sensor, event): if self.target: try: callback = self.target.mouse_motion except AttributeError: return return callback(event) def mouse_drag(self, sensor, event): if self.target: try: callback = self.target.mouse_drag except AttributeError: return return callback(event) def mouse_drop_receive(self, sensor, dropped, event): #print "drop receive:", sensor, dropped if self.target: try: callback = self.target.mouse_drop except AttributeError: return return callback(event) def mouse_drop_at(self, sensor, stack, event): #print "drop at:", sensor, stack if self.target: try: callback = self.target.mouse_drop except AttributeError: return return callback(event) def mouse_enter(self, sensor): print "cursor entered", sensor def mouse_leave(self, sensor): print "cursor left", sensor def mouse_hover(self, sensor): pass def move_offset(self, rel): dx, dy = rel x, y = self.offset self.offset = (x+dx, y+dy) def get_stack(self, pos): "Only for root: get the stack of sensors on , uppermost first" stack = self.collect_stack(pos) stack.reverse() return stack def check(self, events): root = self.root if self is root: for event in events: if event.type == MOUSEBUTTONDOWN: stack = self.get_stack(event.pos) self.dragging = stack[0] for sensor in stack: propagate = sensor.mouse_button_down(sensor, event) if not propagate: break elif event.type == MOUSEBUTTONUP: stack = self.get_stack(event.pos) ## end dragging mode, if it was activated if not self.dragging is None: try: mouse_drop_at = self.dragging.mouse_drop_at except AttributeError: pass if mouse_drop_at: filtered_stack = [sensor for sensor in stack if not sensor is self.dragging] mouse_drop_at(self.dragging, stack=filtered_stack, event=event) for sensor in stack: if sensor is self.dragging: continue propagate = sensor.mouse_drop_receive(sensor, dropped=self.dragging, event=event) if not propagate: break self.dragging = None for sensor in stack: propagate = sensor.mouse_button_up(sensor, event) if not propagate: break elif event.type == MOUSEMOTION: if self.dragging: sensor = self.dragging sensor.mouse_drag(sensor, event) else: for sensor in self.get_stack(event.pos): propagate = sensor.mouse_motion(sensor, event) if not propagate: break ## check for mouse_enter and mouse_leave events last_over = last_over_dict.values() for sensor in self.get_stack(event.pos): if sensor in last_over: sensor.mouse_hover(sensor) last_over.remove(sensor) else: sensor.mouse_enter(sensor) last_over_dict[sensor.uid] = sensor for sensor in last_over: sensor.mouse_leave(sensor) del last_over_dict[sensor.uid] else: root.check(events) def _get_root(self): try: return self.parent._get_root() except AttributeError: return self root = property(_get_root) def __nonzero__(self): return True class Test(object): def __init__(self): self._rect = Rect(10, 10, 20, 30) def get_rect(self): return self._rect if __name__ == "__main__": test = Test() desktop = Sensor(name="Desktop") button = Sensor(parent=desktop, name="button", target=test, offset=(5, 5)) print button.rect