testapp.py 8.5 KB

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