testapp.py 9.1 KB

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