News

( My personal website has gone to dust, any links thereto will fail. This site will be home for a while. )

Showing posts with label Events. Show all posts
Showing posts with label Events. Show all posts

Wednesday, 17 February 2010

Clutter Mouse Events

Well, it has been a fairly good week in-between the segfaults and other scary things. I have a basic system to tame mouse events so that I can respond to 'click' and 'double-click' and stuff like 'dragging' and 'drag-over'.
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

Saturday, 9 May 2009

Things! An easy animation API for Python.

Announcing "Things" - Vector animation in Python
I just got my Savannah project approved and have uploaded the code to CVS. I have never used a cvs before, so I don't really know what I am doing.

You can visit the project here:
http://savannah.nongnu.org/projects/things/

"Things" is a way to make quick visual metaphors and animate them. Things can contain other things -- in this way you can have one Thing moving left-right and then put that into another Thing moving up-down and you'd get an animation doing both.

There's more to it than that; you can tell individual things when to stop and start; what to do and when to do it; how to move and so on.

There is a primitive event-system going; so you can produce buttons and hit areas and so forth.

Inkscape and SVGs
Things was written with Inkscape in mind. You draw your graphics there, give stuff id's and then in your Things Python code, you can refer to those ids and use the SVG directly. The whole idea was to use Inkscape as the "IDE" and Things as the animation engine.

There are ways to create:
1. "Sprites" - Single frame drawings.
2. "Loops" - Drawings that will change over time -- like a walk cycle.
3. "Paths" - Simple paths for things like line drawing or following.
4. "Masks" - Also simple paths, but intended for clipping and hit-detection etc.

Each of these is drawn in a layer within Inkscape (and there are a few simple rules to follow) after which you simply pull them out in Python and use 'em!

Help code! Take it away!
So, come on over and give it a whirl. Perhaps you can improve it and code it into some fast C/assembler. It sure needs more love and skill than I can bring :)

\d