testapp.py 7.8 KB

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