This is all written in Python. The licence is GPLv3.
Save them both (states.py in a directory called helpers), then run two_squares.py -- you should be able to drag them around (with different mouse buttons. They dump info to the console.)
Module 1: helpers.states.py
import clutter import gobject class DragEnvelope(object): """Helps deal with drags.""" def __init__(self, obj, event): self.event = event self.diffx = event.x - obj.get_x() self.diffy = event.y - obj.get_y() def calcPos(self, event): """Where to draw the Actor relative to cursor within it.""" self.newx = event.x - self.diffx self.newy = event.y - self.diffy class Mouserizer(object): ## User vars to form ui mask HITABLE = 1<<0 DRAGABLE = 1<<1 CLICKABLE = 1<<2 SCROLLABLE = 1<<3 PICK_UNDER = 1<<4 LEFT = 1<<0 MIDDLE = 1<<1 RIGHT = 1<<2 ## Private class vars __FLAG_ENTERED = 1<<0 # Not used in this version. __FLAG_PRESSING = 1<<1 __FLAG_RELEASING = 1<<2 __FLAG_MOVING = 1<<3 __FLAG_DRAGGING = 1<<4 __PATTERN_CLICK = __FLAG_PRESSING | __FLAG_RELEASING __PATTERN_DRAG_START = __FLAG_PRESSING| __FLAG_MOVING __PATTERN_DRAG = __PATTERN_DRAG_START | __FLAG_DRAGGING __PATTERN_DROP = __FLAG_PRESSING| __FLAG_DRAGGING | __FLAG_RELEASING YES_CONTINUE_EMITTING = False NO_STOP_EMITTING = True __clutter_mouse_event_types=[ clutter.MOTION, clutter.ENTER, clutter.LEAVE, clutter.BUTTON_PRESS, clutter.BUTTON_RELEASE, clutter.SCROLL ] def __init__(self, ui=None, buttons=None): if ui is None: return # Not going to watch for any mouse events, so bug out. self.buttons = buttons ## If we want HIT kind of events, then just connect the usual suspects. if (ui & Mouserizer.HITABLE) !=0: # test bit self.connect('enter-event', self.on_enter_event) self.connect('leave-event', self.on_leave_event) self.__PICK_UNDER = False if (ui & Mouserizer.PICK_UNDER) !=0: self.__PICK_UNDER = True ## Keep a record of what we are going to listen to. self.ui = ui ## Enable the actor (self) to receive events. self.set_reactive(True) ## This is the state of our situation -- it will be masked bitwise. self.ui_state = 0 ## Route all events (for this Actor) through one function: self.connect('captured-event', self.event_central) def event_central(self, obj, event): ## This routine runs many times. Once for every kind ## of event the actor is getting. ## filter out only the mouse events. if event.type not in Mouserizer.__clutter_mouse_event_types: return Mouserizer.YES_CONTINUE_EMITTING ## filter out buttons we are NOT going to deal with if hasattr(event, "button"): b = 1 << (event.button - 1) #print bin(b)," vs ", bin(self.buttons) if not(b & self.buttons !=0 ): return Mouserizer.NO_STOP_EMITTING # is this wise? ## event_central ONLY runs when cursor is ## over the actor -- thus ENTER is implied. ## Make a note of PRESS/RELEASE if event.type==clutter.BUTTON_PRESS: self.ui_state = self.ui_state | Mouserizer.__FLAG_PRESSING # set bit if event.type==clutter.BUTTON_RELEASE: self.ui_state = self.ui_state | Mouserizer.__FLAG_RELEASING # set bit ## Make a note of MOTION ## First, clear it. self.ui_state = self.ui_state & ~Mouserizer.__FLAG_MOVING # clear bit if event.type==clutter.MOTION: self.ui_state = self.ui_state | Mouserizer.__FLAG_MOVING # set bit ## Now, what kinds of stuff is this actor interested in? ## DO META EVENTS - "More than" events. e.g. 'Click' is press, then release. if (self.ui & Mouserizer.CLICKABLE) != 0: # test bit if self.ui_state == Mouserizer.__PATTERN_CLICK: if event.click_count > 1: self.emit('double-click', event) else: ## A single click is fired just before double-click...! self.emit('single-click', event) if (self.ui & Mouserizer.DRAGABLE) !=0: # test bit if self.ui_state == Mouserizer.__PATTERN_DRAG_START: self.ui_state=self.ui_state | Mouserizer.__FLAG_DRAGGING # set bit self.draglet = DragEnvelope( self, event ) ## Phew! I thought I was fcuked! In order to get dragging to ## work when the pointer is NOT ON the Actor, I had to revert ## to grab_pointer* -- and that needs connecting. I connected the ## two appropriate event to *this* same function! And it works :D ## ## * grab_pointer causes the entire window (stage?) to focus on the ## Actor passed -- so I get all motion and release events even where ## the Actor aint. ## ! Not sure what kind of recursive issues this may throw at me :( clutter.grab_pointer( self ) self.connect('motion-event', self.event_central) self.connect('button-release-event', self.event_central) self.emit('drag-start', self.draglet ) elif self.ui_state == Mouserizer.__PATTERN_DRAG: self.draglet.calcPos( event ) # A 'draglet' is a little wrapper containing the event and some tricks. ## Who is under me? Only do if PICK_UNDER flag is set. if self.__PICK_UNDER: self.hide() a = self.stage.get_actor_at_pos(clutter.PICK_REACTIVE, int(event.x),int(event.y)) self.show() ## a is! ## Only emit if a has a drag-over signal: if gobject.signal_lookup('drag-over', a ): print a, " under me" a.emit('drag-over', self.draglet) self.emit('dragging', self.draglet) elif self.ui_state == Mouserizer.__PATTERN_DROP: self.draglet.calcPos( event ) self.ui_state= self.ui_state & ~Mouserizer.__FLAG_DRAGGING # clear bit clutter.ungrab_pointer() self.emit("drop", self.draglet) del(self.draglet) ## META EVENTS are done. ## Flip opposites off. if event.type==clutter.BUTTON_PRESS: self.ui_state = self.ui_state & ~Mouserizer.__FLAG_RELEASING # clear bit if event.type==clutter.BUTTON_RELEASE: self.ui_state = self.ui_state & ~Mouserizer.__FLAG_PRESSING # clear bit self.ui_state = self.ui_state & ~Mouserizer.__FLAG_RELEASING # clear bit self.ui_state = self.ui_state & ~Mouserizer.__FLAG_DRAGGING # clear bit return Mouserizer.YES_CONTINUE_EMITTING(Always with the rectangles eh? :D)
import clutter from helpers.states import Mouserizer as M import gobject meta_signals = { 'single-click' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ), 'double-click' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ), 'drag-start' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ), 'dragging' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ), 'drop' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ), 'drag-over' : ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,) ), } class DragHandle(clutter.Rectangle, M): ## Make this class a GObject type __gtype_name__ = 'DragHandle' ## Specify which signals it can receieve. __gsignals__ = meta_signals def __init__(self, stage, col, ui=None, buttons=None): """ You don't have to connect: enter-event, leave-event, single-click or double-click """ if ui is None: ui = M.HITABLE if buttons is None: buttons = M.LEFT clutter.Actor.__init__(self) M.__init__(self, ui=ui, buttons=buttons) self.stage = stage self.set_color( clutter.color_from_string(col)) self.set_size(50,50) def do_drag_over(self, draglet): print "DRAG OVER ", draglet return M.NO_STOP_EMITTING def do_single_click(self, *args): print "HANDLER CLICK" return M.NO_STOP_EMITTING def do_double_click(self, *args): print "HANDLER DCLICK" clutter.main_quit() def do_drag_start(self, draglet): print "DRAG STARTS:" return True def do_dragging(self, draglet): self.set_position(draglet.newx, draglet.newy) return True def do_drop(self, *args): print "DROP!" return True def on_enter_event(self, obj, evt): print "ENTERING ",obj return True def on_leave_event(self,obj,evt): print "LEAVING ", obj return True if __name__ == '__main__': stage = clutter.Stage() stage.set_size(640, 480) stage.set_color(clutter.color_from_string('Black')) stage.connect('destroy', clutter.main_quit) test=DragHandle( stage, "Green", ui=M.HITABLE | M.CLICKABLE | M.DRAGABLE, buttons= M.RIGHT ) stage.add(test) test.set_position(100,100) test.show() test2=DragHandle(stage, "Red", ui=M.HITABLE | M.DRAGABLE | M.PICK_UNDER , buttons= M.LEFT ) stage.add(test2) test2.show() stage.show() clutter.main()
Well, I hope that helps someone.
\d