| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- #!/usr/bin/env python
- from __future__ import division
- import gtk
- from threading import Thread
- from math import pi, tan
- import src as mt
- from utils import BoundingBoxArea, Flick, FlickThread, GtkEventWindow
- from hand import HandTracker
- mt.register_tracker(HandTracker)
- RED = 1, 0, 0
- GREEN = 0, 1, 0
- BLUE = 0, 0, 1
- WHITE = 1, 1, 1
- BLACK = 0, 0, 0
- class Rectangle(mt.RectangularArea):
- def __init__(self, x, y, width, height, color=(1, 0, 0)):
- super(Rectangle, self).__init__(x, y, width, height)
- self.color = color
- self.on_drag(self.handle_drag)
- def handle_drag(self, g):
- tx, ty = g.get_translation()
- self.translate(tx, ty)
- refresh()
- def draw(self, cr):
- cr.rectangle(self.x, self.y, self.width, self.height)
- cr.set_source_rgb(*self.color)
- cr.fill()
- class Polygon(BoundingBoxArea):
- def __init__(self, x, y, points, margin=0, color=BLUE, border_color=RED):
- super(Polygon, self).__init__(x, y, points)
- self.color = color
- self.border_color = border_color
- self.margin = margin
- self.on_drag(self.handle_drag)
- self.on_pinch(self.handle_pinch)
- self.on_rotate(self.handle_rotate)
- self.on_flick(self.handle_flick)
- self.on_tap(self.circulate_color)
- def circulate_color(self, g):
- self.color = self.color[2:] + self.color[:2]
- refresh()
- def flick_drag(self, amt):
- tx, ty = self.flick_direction
- self.translate(tx * amt, ty * amt)
- refresh()
- def handle_flick(self, g):
- trans = g.get_translation()
- if trans.distance_to((0, 0)) > 10:
- self.flick_direction = trans
- flicks.add(Flick(self.flick_drag, 0.7, 0.4))
- def margin_contains(self, x, y):
- m = self.margin
- return self.x - m <= x < self.x + self.width + m \
- and self.y - m <= y < self.y + self.height + m
- def contains(self, x, y):
- if draw_bounding_boxes:
- return self.margin_contains(self, x, y)
- return BoundingBoxArea.contains(self, x, y)
- def handle_drag(self, g):
- tx, ty = g.get_translation()
- self.translate(tx, ty)
- refresh()
- def handle_pinch(self, g):
- cx, cy = g.get_position()
- self.scale_points(g.get_scale(), cx, cy)
- self.update_bounds()
- refresh()
- def handle_rotate(self, g):
- cx, cy = g.get_position()
- self.rotate_points(g.get_angle(), cx, cy)
- self.update_bounds()
- refresh()
- def draw(self, cr):
- # Draw bounding box
- if draw_bounding_boxes:
- m = self.margin
- cr.rectangle(self.x - m, self.y - m,
- self.width + 2 * m, self.height + 2 * m)
- cr.set_source_rgb(*self.border_color)
- cr.set_line_width(3)
- cr.stroke()
- # Fill polygon
- rx, ry = self.get_offset(root)
- cr.translate(rx, ry)
- cr.new_path()
- for x, y in zip(*self.points):
- cr.line_to(x, y)
- cr.set_source_rgb(*self.color)
- cr.fill()
- fullscreen = False
- draw_bounding_boxes = False
- draw_touch_objects = True
- W, H = mt.screen.screen_size
- def toggle_fullscreen():
- global fullscreen
- (window.unfullscreen if fullscreen else window.fullscreen)()
- fullscreen = not fullscreen
- refresh()
- def create_context_window(w, h, callback):
- def create_context(area, event):
- """Add Cairo context to GTK window and draw state."""
- global cr
- cr = area.window.cairo_create()
- draw()
- def handle_key(win, event):
- """Handle key event. 'f' toggles fullscreen, 'b' toggles bounding
- boxes, 'i' toggles input points, 'q' exits the program."""
- if event.keyval >= 256:
- return
- key = chr(event.keyval)
- if key == 'f':
- toggle_fullscreen()
- elif key == 'b':
- global draw_bounding_boxes
- draw_bounding_boxes = not draw_bounding_boxes
- refresh()
- elif key == 'i':
- global draw_touch_objects
- draw_touch_objects = not draw_touch_objects
- refresh()
- elif key == 'q':
- quit()
- def redraw_updated_window():
- refresh()
- return True
- # GTK window
- global window, root
- window = GtkEventWindow()
- window.set_title('Cairo test')
- window.connect('destroy', quit)
- window.connect('key-press-event', handle_key)
- window.connect('show', callback)
- window.on_update(redraw_updated_window)
- root = window.get_area()
- if fullscreen:
- window.fullscreen()
- # Drawing area, needed by cairo context for drawing
- area = gtk.DrawingArea()
- area.set_size_request(w, h)
- area.connect('expose-event', create_context)
- window.add(area)
- area.show()
- window.show()
- def draw():
- if not cr:
- return
- # Background
- cr.rectangle(0, 0, *root.get_size())
- cr.set_source_rgb(*BLACK)
- cr.fill()
- # Drawable objects (use save and restore to allow transformations)
- for obj in draw_objects:
- cr.save()
- obj.draw(cr)
- cr.restore()
- if draw_touch_objects:
- ox, oy = root.get_position()
- cr.set_source_rgb(*WHITE)
- for hand in touch_hands:
- centroid = hand.get_centroid()
- # Check if all fingers have not been removed already
- if not centroid:
- continue
- cx, cy = centroid
- # Filled centroid circle
- if len(hand) > 1:
- cr.arc(cx - ox, cy - oy, 20, 0, 2 * pi)
- cr.fill()
- for x, y in hand:
- x -= ox
- y -= oy
- # Circle outline
- cr.set_line_width(3)
- cr.arc(x, y, 20, 0, 2 * pi)
- cr.stroke()
- # Line to centroid
- if len(hand) > 1:
- cr.move_to(x, y)
- cr.line_to(cx - ox, cy - oy)
- cr.set_line_width(2)
- cr.stroke()
- # Cross
- cr.set_line_width(1)
- cr.move_to(x - 8, y)
- cr.line_to(x + 8, y)
- cr.move_to(x, y - 8)
- cr.line_to(x, y + 8)
- cr.stroke()
- def refresh(*args):
- window.queue_draw()
- def quit(*args):
- gtk.main_quit()
- # Global variables
- window = cr = root = overlay = flicks = None
- draw_objects = []
- touch_hands = []
- def triangle_height(width):
- return abs(.5 * width * tan(2 / 3 * pi))
- def on_show(window):
- # Toggle fullscreen on double tap
- root.on_double_tap(lambda g: toggle_fullscreen())
- # Create blue rectangle
- x, y, w, h = 0, 0, 250, 150
- rect = Polygon(x, y, [(0, 0), (0, h), (w, h), (w, 0)], margin=20)
- draw_objects.append(rect)
- root.add_area(rect)
- # Create green triangle
- x, y, w = 400, 400, 200
- h = triangle_height(w)
- triangle = Polygon(x, y, [(0, h), (w, h), (w / 2, 0)],
- margin=20, color=GREEN)
- draw_objects.append(triangle)
- root.add_area(triangle)
- # Overlay catches finger events to be able to draw touch points
- def handle_down(gesture):
- touch_hands.append(gesture.get_hand())
- if draw_touch_objects:
- refresh()
- def handle_up(gesture):
- touch_hands.remove(gesture.get_hand())
- if draw_touch_objects:
- refresh()
- global screen, overlay
- screen = mt.FullscreenArea()
- overlay = mt.FullscreenArea()
- screen.add_area(root)
- screen.add_area(overlay)
- overlay.on_hand_down(handle_down)
- overlay.on_finger_move(lambda g: draw_touch_objects and refresh())
- overlay.on_hand_up(handle_up)
- # Root area rotation leads to rotation of all polygons around the centroid
- def move_to_polygons(g):
- gx, gy = g.get_position()
- for obj in draw_objects:
- x, y = obj.get_position()
- g.set_position(gx - x, gy - y)
- yield obj, g
- def rotate_root(g):
- for obj, gesture in move_to_polygons(g):
- obj.handle_rotate(gesture)
- def scale_root(g):
- for obj, gesture in move_to_polygons(g):
- obj.handle_pinch(gesture)
- def drag_root(g):
- for obj, gesture in move_to_polygons(g):
- obj.handle_drag(gesture)
- root.on_rotate(rotate_root)
- root.on_pinch(scale_root)
- root.on_drag(drag_root)
- if __name__ == '__main__':
- from parse_arguments import create_parser, parse_args
- # Parse arguments
- parser = create_parser()
- parser.add_argument('-f', '--fullscreen', action='store_true',
- default=False, help='run in fullscreen initially')
- args = parse_args(parser)
- fullscreen = args.fullscreen
- # Create a window with a Cairo context in it and a multi-touch area
- # syncronized with it
- create_context_window(800, 600, on_show)
- # Run multi-touch gesture server in separate thread
- mt.create_driver(screen).start(threaded=True)
- # Flick movement is also handled in a separate thread
- flicks = FlickThread()
- flicks.daemon = True
- flicks.start()
- # Initialize threads in GTK so that the thread started above will work
- gtk.gdk.threads_init()
- # Start main loop in current thread
- gtk.main()
|