testapp.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. #!/usr/bin/env python
  2. from __future__ import division
  3. import gtk
  4. from threading import Thread
  5. from math import pi, tan
  6. import src as mt
  7. from utils import BoundingBoxArea, Flick, FlickThread, GtkEventWindow
  8. from hand import HandTracker
  9. mt.register_tracker(HandTracker)
  10. RED = 1, 0, 0
  11. GREEN = 0, 1, 0
  12. BLUE = 0, 0, 1
  13. WHITE = 1, 1, 1
  14. BLACK = 0, 0, 0
  15. class Rectangle(mt.RectangularArea):
  16. def __init__(self, x, y, width, height, color=(1, 0, 0)):
  17. super(Rectangle, self).__init__(x, y, width, height)
  18. self.color = color
  19. self.on_drag(self.handle_drag)
  20. def handle_drag(self, g):
  21. tx, ty = g.get_translation()
  22. self.translate(tx, ty)
  23. refresh()
  24. def draw(self, cr):
  25. cr.rectangle(self.x, self.y, self.width, self.height)
  26. cr.set_source_rgb(*self.color)
  27. cr.fill()
  28. class Polygon(BoundingBoxArea):
  29. def __init__(self, x, y, points, margin=0, color=BLUE, border_color=RED):
  30. super(Polygon, self).__init__(x, y, points)
  31. self.color = color
  32. self.border_color = border_color
  33. self.margin = margin
  34. self.on_drag(self.handle_drag)
  35. self.on_pinch(self.handle_pinch)
  36. self.on_rotate(self.handle_rotate)
  37. self.on_flick(self.handle_flick)
  38. self.on_double_tap(self.circulate_color)
  39. def circulate_color(self, g):
  40. self.color = self.color[2:] + self.color[:2]
  41. def flick_drag(self, amt):
  42. tx, ty = self.flick_direction
  43. self.translate(tx * amt, ty * amt)
  44. refresh()
  45. def handle_flick(self, g):
  46. trans = g.get_translation()
  47. if trans.distance_to((0, 0)) > 10:
  48. self.flick_direction = trans
  49. flicks.add(Flick(self.flick_drag, 0.7, 0.4))
  50. def margin_contains(self, x, y):
  51. m = self.margin
  52. return self.x - m <= x < self.x + self.width + m \
  53. and self.y - m <= y < self.y + self.height + m
  54. def contains(self, x, y):
  55. if draw_bounding_boxes:
  56. return self.margin_contains(self, x, y)
  57. return BoundingBoxArea.contains(self, x, y)
  58. def handle_drag(self, g):
  59. tx, ty = g.get_translation()
  60. self.translate(tx, ty)
  61. refresh()
  62. def handle_pinch(self, g):
  63. cx, cy = g.get_position()
  64. self.scale_points(g.get_scale(), cx, cy)
  65. self.update_bounds()
  66. refresh()
  67. def handle_rotate(self, g):
  68. cx, cy = g.get_position()
  69. self.rotate_points(g.get_angle(), cx, cy)
  70. self.update_bounds()
  71. refresh()
  72. def draw(self, cr):
  73. # Draw bounding box
  74. if draw_bounding_boxes:
  75. m = self.margin
  76. cr.rectangle(self.x - m, self.y - m,
  77. self.width + 2 * m, self.height + 2 * m)
  78. cr.set_source_rgb(*self.border_color)
  79. cr.set_line_width(3)
  80. cr.stroke()
  81. # Fill polygon
  82. rx, ry = self.get_offset(root)
  83. cr.translate(rx, ry)
  84. cr.new_path()
  85. for x, y in zip(*self.points):
  86. cr.line_to(x, y)
  87. cr.set_source_rgb(*self.color)
  88. cr.fill()
  89. fullscreen = False
  90. draw_bounding_boxes = False
  91. draw_touch_objects = True
  92. W, H = mt.screen.screen_size
  93. def create_context_window(w, h, callback):
  94. def create_context(area, event):
  95. """Add Cairo context to GTK window and draw state."""
  96. global cr
  97. cr = area.window.cairo_create()
  98. draw()
  99. def handle_key(win, event):
  100. """Handle key event. 'f' toggles fullscreen, 'b' toggles bounding
  101. boxes, 'i' toggles input points, 'q' exits the program."""
  102. if event.keyval >= 256:
  103. return
  104. key = chr(event.keyval)
  105. if key == 'f':
  106. global fullscreen
  107. (win.unfullscreen if fullscreen else win.fullscreen)()
  108. fullscreen = not fullscreen
  109. elif key == 'b':
  110. global draw_bounding_boxes
  111. draw_bounding_boxes = not draw_bounding_boxes
  112. refresh()
  113. elif key == 'i':
  114. global draw_touch_objects
  115. draw_touch_objects = not draw_touch_objects
  116. refresh()
  117. elif key == 'q':
  118. quit()
  119. def update_window():
  120. refresh()
  121. return True
  122. # GTK window
  123. global window, root
  124. window = GtkEventWindow()
  125. window.set_title('Cairo test')
  126. window.connect('destroy', quit)
  127. window.connect('key-press-event', handle_key)
  128. window.connect('show', callback)
  129. window.on_update(update_window)
  130. root = window.get_area()
  131. if fullscreen:
  132. window.fullscreen()
  133. # Drawing area, needed by cairo context for drawing
  134. area = gtk.DrawingArea()
  135. area.set_size_request(w, h)
  136. area.connect('expose-event', create_context)
  137. window.add(area)
  138. area.show()
  139. window.show()
  140. def draw():
  141. if not cr:
  142. return
  143. # Background
  144. cr.rectangle(0, 0, *root.get_size())
  145. cr.set_source_rgb(*BLACK)
  146. cr.fill()
  147. # Drawable objects (use save and restore to allow transformations)
  148. for obj in draw_objects:
  149. cr.save()
  150. obj.draw(cr)
  151. cr.restore()
  152. if draw_touch_objects:
  153. ox, oy = root.get_position()
  154. cr.set_source_rgb(*WHITE)
  155. for hand in touch_hands:
  156. cx, cy = hand.get_centroid()
  157. # Filled centroid circle
  158. if len(hand) > 1:
  159. cr.arc(cx - ox, cy - oy, 20, 0, 2 * pi)
  160. cr.fill()
  161. for x, y in hand:
  162. x -= ox
  163. y -= oy
  164. # Circle outline
  165. cr.set_line_width(3)
  166. cr.arc(x, y, 20, 0, 2 * pi)
  167. cr.stroke()
  168. # Line to centroid
  169. if len(hand) > 1:
  170. cr.move_to(x, y)
  171. cr.line_to(cx - ox, cy - oy)
  172. cr.set_line_width(2)
  173. cr.stroke()
  174. # Cross
  175. cr.set_line_width(1)
  176. cr.move_to(x - 8, y)
  177. cr.line_to(x + 8, y)
  178. cr.move_to(x, y - 8)
  179. cr.line_to(x, y + 8)
  180. cr.stroke()
  181. def refresh(*args):
  182. window.queue_draw()
  183. def quit(*args):
  184. gtk.main_quit()
  185. # Global variables
  186. window = cr = root = overlay = flicks = None
  187. draw_objects = []
  188. touch_hands = []
  189. def triangle_height(width):
  190. return abs(.5 * width * tan(2 / 3 * pi))
  191. def on_show(window):
  192. def root_dtap(g): print 'double tapped on root'
  193. root.on_double_tap(root_dtap)
  194. # Create blue rectangle
  195. x, y, w, h = 0, 0, 250, 150
  196. rect = Polygon(x, y, [(0, 0), (0, h), (w, h), (w, 0)], margin=20)
  197. draw_objects.append(rect)
  198. root.add_area(rect)
  199. def rect_tap(g): print 'tapped on rectangle'
  200. rect.on_tap(rect_tap, propagate_up_event=False)
  201. # Create green triangle
  202. x, y, w = 400, 400, 200
  203. h = triangle_height(w)
  204. triangle = Polygon(x, y, [(0, h), (w, h), (w / 2, 0)],
  205. margin=20, color=GREEN)
  206. draw_objects.append(triangle)
  207. root.add_area(triangle)
  208. # Overlay catches finger events to be able to draw touch points
  209. def handle_down(gesture):
  210. if gesture.is_first():
  211. touch_hands.append(gesture.get_hand())
  212. if draw_touch_objects:
  213. refresh()
  214. def handle_up(gesture):
  215. if gesture.is_last():
  216. touch_hands.remove(gesture.get_hand())
  217. if draw_touch_objects:
  218. refresh()
  219. global screen, overlay
  220. screen = mt.FullscreenArea()
  221. overlay = mt.FullscreenArea()
  222. screen.add_area(root)
  223. screen.add_area(overlay)
  224. overlay.on_finger_down(handle_down)
  225. overlay.on_finger_move(lambda g: draw_touch_objects and refresh())
  226. overlay.on_finger_up(handle_up)
  227. # Root area rotation leads to rotation of all polygons around the centroid
  228. def move_to_polygons(g):
  229. gx, gy = g.get_position()
  230. for obj in draw_objects:
  231. x, y = obj.get_position()
  232. g.set_position(gx - x, gy - y)
  233. yield obj, g
  234. def rotate_root(g):
  235. for obj, gesture in move_to_polygons(g):
  236. obj.handle_rotate(gesture)
  237. def scale_root(g):
  238. for obj, gesture in move_to_polygons(g):
  239. obj.handle_pinch(gesture)
  240. def drag_root(g):
  241. for obj, gesture in move_to_polygons(g):
  242. obj.handle_drag(gesture)
  243. root.on_rotate(rotate_root)
  244. root.on_pinch(scale_root)
  245. root.on_drag(drag_root)
  246. if __name__ == '__main__':
  247. from parse_arguments import create_parser, parse_args
  248. # Parse arguments
  249. parser = create_parser()
  250. parser.add_argument('-f', '--fullscreen', action='store_true',
  251. default=False, help='run in fullscreen initially')
  252. args = parse_args(parser)
  253. fullscreen = args.fullscreen
  254. # Create a window with a Cairo context in it and a multi-touch area
  255. # syncronized with it
  256. create_context_window(800, 600, on_show)
  257. # Run multi-touch gesture server in separate thread
  258. driver = mt.create_driver(screen)
  259. mt_thread = Thread(target=driver.start)
  260. mt_thread.daemon = True
  261. mt_thread.start()
  262. # Flick movement is also handled in a separate thread
  263. flicks = FlickThread()
  264. flicks.daemon = True
  265. flicks.start()
  266. # Initialize threads in GTK so that the thread started above will work
  267. gtk.gdk.threads_init()
  268. # Start main loop in current thread
  269. gtk.main()