testapp.py 9.2 KB

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