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 Python. Show all posts
Showing posts with label Python. 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

Tuesday, 9 February 2010

PyClutter recipes.

Here are a few scripts using pyClutter 1.0 that I have recently finished. They are only for demonstrating some of the abilities of Clutter -- which I must say is just awesome!

Clipping actors (and groups of groups of... etc.) into a path shape
So, you have a shape and you want to force all the children in that Group (groups are Actors) to show within that path? Here you go:
import clutter
from clutter import cogl

"""
pyClutter 1.0
Demonstration of how to clip groups to a path.
"""

zoom = 1
def zoomify(obj,evt):
global zoom
if evt.direction == clutter.SCROLL_UP:
zoom += .1
else:
zoom -= .1
# When the zoom gets small, the cogl clip goes all wonky...
obj.set_scale(zoom, zoom)

class ClipGroup(clutter.Group):
""" Custom Group to perform clipping from a path."""
# Vital line.
# Registers the specified Python class as a PyGTK type.
# Also enables the do_paint method. I am not sure why...
__gtype_name__ = 'ClipGroup'

def __init__(self,w,h):
clutter.Group.__init__(self)#,*args)
self.width, self.height = w, h

def do_paint(self):
# Draw a triangle.
cogl.path_move_to(self.width / 2, 0)
cogl.path_line_to(self.width, self.height)
cogl.path_line_to(0, self.height)
cogl.path_line_to(self.width / 2, 0)
cogl.path_close()
# Start the clip
cogl.clip_push_from_path()

# errr.. the idea is to have the other stuff in the group
# get painted -- I assume Group knows what to do.
clutter.Group.do_paint(self)

# Finish the clip
cogl.clip_pop()

def main():
stage = clutter.Stage()
stage.set_size(500, 500)
stage.set_color(clutter.color_from_string("#000"))

rect_red, rect_green, rect_blue, rect_bounce = \
clutter.Rectangle(), clutter.Rectangle(), clutter.Rectangle(), clutter.Rectangle()

# We make two of our special groups.
clipped_group = ClipGroup(300,300)
inner_group = ClipGroup(100,100)

rect_red.set_position(0, 0)
rect_red.set_size(550, 550) #Big to be backdrop.
rect_red.set_color(clutter.color_from_string("#FF0000FF"))

rect_green.set_position(120, 10)
rect_green.set_size(50, 50)
rect_green.set_color(clutter.color_from_string("#00FF0090"))

rect_bounce.set_position(0,0)
rect_bounce.set_size(500,20)
rect_bounce.set_color(clutter.color_from_string("#AABBCCFF"))

clipped_group.add(rect_red, rect_green)
clipped_group.add(inner_group) # Add entire inner_group into clipped_group

rect_blue.set_color(clutter.color_from_string("#0000FF90"))
rect_blue.set_size(50, 50)
rect_blue.set_position(50,50) #relative to inner_group...
inner_group.add(rect_blue) # even though this comes after set_position...

clipped_group.set_position(100, 100)
inner_group.set_position(100,50)

stage.add(rect_bounce)
stage.add(clipped_group)

# Make rect_blue move around somewhat.
path = clutter.Path('M 0 0 L 40 0 L 40 40 L 0 40 Z')
timeline = clutter.Timeline(4000)
timeline.set_loop(True)
alpha = clutter.Alpha(timeline,clutter.EASE_OUT_SINE)
p_behaviour = clutter.BehaviourPath(alpha, path)
p_behaviour.apply(rect_blue)
timeline.start()

# Make rect_bounce go up and down
animation = rect_bounce.animate(clutter.EASE_IN_OUT_BOUNCE, 2000, "y", 500)
animation.set_loop(True)

# Start the main clipped_group rotating around the Y axis. For giggles.
timeline = clutter.Timeline(15000)
timeline.set_loop(True)
alpha = clutter.Alpha(timeline, clutter.LINEAR)
r_behave = clutter.BehaviourRotate(clutter.Y_AXIS, 0.0, 360.0, alpha=alpha)
r_behave.set_center(150, 0, 0)
r_behave.apply(clipped_group)
timeline.start()

stage.show_all()

stage.connect('key-press-event', clutter.main_quit)
stage.connect('destroy', clutter.main_quit)
stage.connect('scroll-event', zoomify)

clutter.main()

if __name__ == '__main__':
main()
Adventures in Time(line)
I wanted to see for myself how Timelines could be controlled from a Score. This is very cool. Here, three rectangles fire in sequence given markers in their timelines. It sounds complicated, but the code should be easy to follow:
import clutter
from clutter import cogl

def main():
stage = clutter.Stage()
stage.set_size(500, 500)
stage.set_color(clutter.color_from_string("#000"))

rect_red, rect_green, rect_blue = \
clutter.Rectangle(), clutter.Rectangle(), clutter.Rectangle()

rect_red.set_position(30, 30)
rect_red.set_size(20, 20)
rect_red.set_color(clutter.color_from_string("#FF0000FF"))

rect_green.set_position(100, 100)
rect_green.set_size(50, 50)
rect_green.set_color(clutter.color_from_string("#00FF0090"))

rect_blue.set_color(clutter.color_from_string("#0000FF90"))
rect_blue.set_size(80, 80)
rect_blue.set_position(200,200)

stage.add(rect_red, rect_green, rect_blue)

t_red=clutter.Timeline(10000)
t_green=clutter.Timeline(5000)
t_blue=clutter.Timeline(5000)

a_blue=clutter.Alpha(t_blue,clutter.EASE_OUT_SINE)
a_green=clutter.Alpha(t_green,clutter.EASE_OUT_SINE)
a_red=clutter.Alpha(t_red,clutter.EASE_OUT_SINE)

b_red = clutter.BehaviourRotate(clutter.Z_AXIS, 0.0, 360.0, alpha=a_red)
b_green = clutter.BehaviourRotate(clutter.Z_AXIS, 0.0, 360.0, alpha=a_green)
b_blue = clutter.BehaviourRotate(clutter.Z_AXIS, 0.0, 360.0, alpha=a_blue)

b_blue.apply(rect_blue)
b_green.apply(rect_green)
b_red.apply(rect_red)

s=clutter.Score()

t_red.set_loop(True)
s.append(t_red)
t_red.add_marker_at_time("go_green",2000)
s.append_at_marker(t_red,"go_green",t_green)
t_green.add_marker_at_time("go_blue",5000)
s.append_at_marker(t_green,"go_blue", t_blue)

stage.show_all()
s.start()

stage.connect('key-press-event', clutter.main_quit)
stage.connect('destroy', clutter.main_quit)

clutter.main()

if __name__ == '__main__':
main()

Tuesday, 19 May 2009

Things gets a tut.

I just finished writing a short tutorial for the Things API. You can get it on my wiki:
otherwise.relics.co.za or from the CVS on the official website.

The tarball contains a PDF, an SVG file and a bunch of python files. You will still need to fetch the actual API and make a link to it where you want to run the tutorial samples.

It should all make a kind of sense. If it does not, then you need to panic! Run around yelling! Or something.

Have fun with Things.

\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

Sunday, 1 February 2009

My new website!

I have put together a Django-powered website of my own -- come visit it at otherwise.relics.co.za -- I have a bunch of comics, tutorials and some writing there.

\d

Sunday, 20 January 2008

Cairoglyphics version 2.0



Saved here, is an overview/tutorial/summary of the Cairo drawing library from a Python perspective done in a diagram form. If you want an at-a-glance overview which is followed-up by many details that fill-in the surrounding aspects, then this might do the job.

It's done as a single SVG file and I suggest you use Inkscape to open and view it. Zoom-in on the page and read the instructions about what fonts you'll need (It requires two faces, both free and widely available.)

Please download it, share it and put it on your own website!

\d

Saturday, 5 January 2008

Fonty Python 0.3.2 update released


Fonty gets a tiny upgrade - version 0.3.2
I have changed the way the help files are stored so that they can now be localized. The setup.py file had to change a little too, but there are no radical changes to the way the app works. This is a 'help file only' update.

Dowload from here
\d

Friday, 4 January 2008

Python setup.py distutils tutorial

How to use distutils
I started this a year ago and just updated it a moment ago. There is a dearth of distutils (setup.py) information on the web and the Python docs are as clear as mud.

Here's the (new) link


I hope that helps someone out there :)

\d

Thursday, 3 January 2008

Cairo Python Summary (tutorial in diagram form)



I have created an SVG file with illustrations that explain some of the basics of using Cairo to draw vector graphics. It’s a great library and well worth using. I am developing an animation engine (for Python) that relies on Cairo and I’ll blog about it soon.

You can fetch the file from here: Cairoglyphics

Please let me know what you think.

\d

Fonty Python 0.3.0 released



Fonty Python now supports TTF, OTF, Type1 and TTC fonts.


I don’t have a lot of time to develop and Fonty languished for most of 2007 before I could get a solid week to spend upgrading the code.
It now supports a wider range of fonts and has been localized. There are some teething problems with the language thing, and I am not sure that all the new fonts types will work properly - but it’s out as version 0.3.0.

You can go to: https://savannah.nongnu.org/projects/fontypython/ to get the app.

We also have a wiki on: webfactional

I have some questions (posted on the list) regarding i18n and how to manage the admin surrounding po/mo files as well as how best to bundle it all up into a release. I’m hoping for some input either on this blog or on the the mailing list: fontypython@googlegroups.com

\d