[cairo-commit] [cairo-www] src/hittestpython.mdwn
Carl Worth
cworth at freedesktop.org
Fri Nov 9 07:41:31 PST 2007
src/hittestpython.mdwn | 137 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 137 insertions(+)
New commits:
commit a206df31f4eceab03c920d868986f7a373482126
Author: Donn <donn.ingle at gmail.com>
Date: Fri Nov 9 07:41:31 2007 -0800
hit again!
diff --git a/src/hittestpython.mdwn b/src/hittestpython.mdwn
new file mode 100644
index 0000000..3c39c6a
--- /dev/null
+++ b/src/hittestpython.mdwn
@@ -0,0 +1,137 @@
+## A quick recipe for testing a hit on an area.
+
+Once you've drawn something and **before** you cr.fill() or cr.stroke(), you can record the path to a list of points for later use. This recipe includes an algorithm to tell whether a point is within that path's area or not. It prints to the console, so it's not exactly bling, but it's pretty nifty for all that. Go see <http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/> for the theory behind the voodoo :)
+
+
+ #! /usr/bin/env python
+ import pygtk
+ pygtk.require('2.0')
+ import gtk, gobject, cairo
+ from gtk import gdk
+
+ # Create a GTK+ widget on which we will draw using Cairo
+ class Screen(gtk.DrawingArea):
+
+ # Draw in response to an expose-event
+ __gsignals__ = { "expose-event": "override" }
+
+ def __init__(self):
+ super(Screen,self).__init__()
+ # gtk.Widget signals
+ self.connect("button_press_event", self.button_press)
+ self.connect("button_release_event", self.button_release)
+ self.connect("motion_notify_event", self.motion_notify)
+ # More GTK voodoo : unmask events
+ self.add_events(gdk.BUTTON_PRESS_MASK |
+ gdk.BUTTON_RELEASE_MASK |
+ gdk.POINTER_MOTION_MASK)
+
+ # Handle the expose-event by drawing
+ def do_expose_event(self, event):
+
+ # Create the cairo context
+ cr = self.window.cairo_create()
+ self.hitpath = None #Is set later
+
+ # Restrict Cairo to the exposed area; avoid extra work
+ cr.rectangle(event.area.x, event.area.y,
+ event.area.width, event.area.height)
+ cr.clip()
+
+ self.draw(cr, *self.window.get_size())
+
+ def makeHitPath(self,cairopath):
+ ## Make a simpler list of tuples
+
+ ## Internally, a cairo path looks like this:
+ ## (0, (10.0, 10.0))
+ ## (1, (60.0, 10.0))
+ ## (1, (60.0, 60.0))
+ ## (1, (35.0, 60.0))
+ ## (1, (35.0, 35.0))
+ ## (1, (10.0, 35.0))
+ ## (1, (10.0, 60.0))
+ ## (1, (-40.0, 60.0))
+ ## (3, ()) #want to ignore this one
+ ## (0, (10.0, 10.0))
+
+ self.hitpath = []
+ for sub in cairopath:
+ if sub[1]: #kick out the close path () empty tuple
+ self.hitpath.append(sub[1]) #list of tuples
+
+ def draw(self, cr, width, height):
+ # Fill the background with gray
+ cr.set_source_rgb(0.5, 0.5, 0.5)
+ cr.rectangle(0, 0, width, height)
+ cr.fill()
+
+ def hitTest(self,*p):
+ ## Code lifted from http://local.wasp.uwa.edu.au/~pbourke/geometry/insidepoly/
+ ## converted to Python. I won't pretend I grok it at all, just glad it works!
+ ## Not sure how well it works yet, it might have edge flaws.
+ px = p[0]
+ py = p[1]
+ counter = i = xinters = 0
+ p1 = p2 = ()
+
+ p1 = self.hitpath[0]
+ N = len(self.hitpath)
+
+ # Mathemagic loop-de-loop
+ for i in range(0,N):
+ p2 = self.hitpath[i % N]
+ if py > min( p1[1] , p2[1] ):
+ if py <= max( p1[1], p2[1] ):
+ if px <= max( p1[0], p2[0] ):
+ if p1[1] != p2[1]:
+ xinters = ( py - p1[1] ) * ( p2[0] - p1[0] ) / ( p2[1] - p1[1] ) + p1[0]
+ if p1[0] == p2[0] or px <= xinters: counter += 1
+ p1 = p2
+
+ if counter % 2 == 0:
+ return "outside"
+ return "inside"
+
+ def button_press(self,widget,event):
+ pass
+ def button_release(self,widget,event):
+ pass
+ def motion_notify(self,widget,event):
+ pass
+
+ # GTK mumbo-jumbo to show the widget in a window and quit when it's closed
+ def run(Widget):
+ window = gtk.Window()
+ window.connect("delete-event", gtk.main_quit)
+ widget = Widget()
+ widget.show()
+ window.add(widget)
+ window.present()
+ gtk.main()
+
+ class Shapes(Screen):
+ #Override the press event
+ def button_press(self,widget,event):
+ print self.hitTest(event.x, event.y)
+
+ def draw(self, cr, width, height):
+ x = y = 10
+ sx = sy = 50
+ cr.move_to(x,y)
+ cr.line_to(x+sx,y)
+ cr.line_to(x+sx,y+sy)
+ cr.line_to(x+(sx/2),y+sy)
+ cr.line_to(x+(sx/2),y+(sy/2))
+ cr.line_to(x,y+(sy/2))
+ cr.line_to(x,y+sy)
+ cr.line_to(x-sx,y+sy)
+ cr.close_path()
+ cr.set_source_rgb(1,0,0)
+
+ self.makeHitPath(cr.copy_path_flat()) #record the path to use as a hit area.
+
+ cr.fill() #consumes the path, so get it before the fill
+
+
+ run(Shapes)
More information about the cairo-commit
mailing list