testapp.py 8.4 KB

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