Commit c8706a27 authored by Taddeüs Kroes's avatar Taddeüs Kroes

Added various fixes for transformations in the test application.

parent f3801a8a
from logger import Logger from logger import Logger
from tracker import GestureTracker, Gesture
from drivers import create_driver from drivers import create_driver
from tracker import GestureTracker, Gesture
from geometry import Positionable
from areas import * from areas import *
...@@ -31,21 +31,7 @@ class RectangularArea(Area): ...@@ -31,21 +31,7 @@ class RectangularArea(Area):
and self.y <= y <= self.y + self.height and self.y <= y <= self.y + self.height
def contains_event(self, event): def contains_event(self, event):
if self.contains(*event.get_position()): return self.contains(*event.get_position())
return True
if isinstance(event, PointMoveEvent):
px, py = event.point.get_previous_position()
if self.parent:
x, y = self.parent.get_screen_offset()
else:
x = y = 0
if self.contains(px - x, py - y):
self.handle_event(PointUpEvent(event.point))
return False
class CircularArea(Area): class CircularArea(Area):
......
from __future__ import division from __future__ import division
from math import atan2, pi from math import atan2, pi, sin, cos
import time import time
...@@ -60,6 +60,14 @@ class Positionable(object): ...@@ -60,6 +60,14 @@ class Positionable(object):
def translate(self, x, y): def translate(self, x, y):
self.set_position(self.x + x, self.y + y) self.set_position(self.x + x, self.y + y)
def scale(self, scale):
self.set_position(self.x * scale, self.y * scale)
def rotate(self, angle):
c = cos(angle)
s = sin(angle)
self.set_position(c * self.x - s * self.y, s * self.x + c * self.y)
class MovingPositionable(Positionable): class MovingPositionable(Positionable):
""" """
......
...@@ -21,7 +21,7 @@ class RotationGesture(Gesture, Positionable): ...@@ -21,7 +21,7 @@ class RotationGesture(Gesture, Positionable):
% (self.x, self.y, self.angle) % (self.x, self.y, self.angle)
def get_angle(self): def get_angle(self):
return self.angle return -self.angle
class PinchGesture(Gesture, Positionable): class PinchGesture(Gesture, Positionable):
...@@ -84,6 +84,8 @@ class TransformationTracker(GestureTracker): ...@@ -84,6 +84,8 @@ class TransformationTracker(GestureTracker):
# Current and previous centroid of all touch points # Current and previous centroid of all touch points
self.centroid = None self.centroid = None
self.deleted = []
def update_centroid(self): def update_centroid(self):
if not self.points: if not self.points:
self.centroid = None self.centroid = None
...@@ -105,18 +107,28 @@ class TransformationTracker(GestureTracker): ...@@ -105,18 +107,28 @@ class TransformationTracker(GestureTracker):
def on_point_down(self, event): def on_point_down(self, event):
self.points.append(event.point) self.points.append(event.point)
self.update_centroid() self.update_centroid()
event.stop_propagation()
def on_point_move(self, event): def on_point_move(self, event):
point = event.point point = event.point
if point not in self.points: if point not in self.points:
pid = point.get_id()
if pid not in self.deleted:
return return
self.debug('recovered %s' % point)
self.deleted.remove(pid)
self.points.append(point)
self.update_centroid()
event.stop_propagation()
self.invalidate_points()
l = len(self.points) l = len(self.points)
if l > 1: if l > 1:
offset_centroid = self.centroid - self.area.get_screen_offset() offset_centroid = self.centroid - self.area.get_screen_offset()
print self.centroid, self.area, offset_centroid
# Rotation (around the previous centroid) # Rotation (around the previous centroid)
rotation = point.rotation_around(self.centroid) / l rotation = point.rotation_around(self.centroid) / l
...@@ -137,8 +149,32 @@ class TransformationTracker(GestureTracker): ...@@ -137,8 +149,32 @@ class TransformationTracker(GestureTracker):
self.centroid.translation(), l)) self.centroid.translation(), l))
def on_point_up(self, event): def on_point_up(self, event):
if event.point not in self.points: if event.point in self.points:
return
self.points.remove(event.point) self.points.remove(event.point)
self.update_centroid() self.update_centroid()
event.stop_propagation()
def invalidate_points(self):
"""
Check if all points are still in the corresponding area, and remove
those which are not.
"""
delete = []
if self.area.parent:
ox, oy = self.area.parent.get_screen_offset()
else:
ox = oy = 0
for i, p in enumerate(self.points):
x, y = p.get_position()
if not self.area.contains(x - ox, y - oy):
self.debug('deleted %s' % p)
delete.append(i)
self.deleted.append(p.get_id())
if delete:
self.points = [p for i, p in enumerate(self.points)
if i not in delete]
self.update_centroid()
#!/usr/bin/env python #!/usr/bin/env python
import os import os
src_path = os.path.realpath('src')
import sys import sys
sys.path.insert(0, src_path)
sys.path.insert(0, os.path.realpath('src'))
sys.path.insert(0, os.path.realpath('tests'))
del sys.argv[0] del sys.argv[0]
execfile(sys.argv[0]) execfile(sys.argv[0])
#!/usr/bin/env python lambda g: #!/usr/bin/env python
from __future__ import division from __future__ import division
import pygame import pygame
from threading import Thread from threading import Thread
from math import degrees from math import degrees
from tests.parse_arguments import create_parser, parse_args
from src import FullscreenArea, create_driver from src import FullscreenArea, create_driver
from src.screen import screen_size from src.screen import screen_size
from parse_arguments import create_parser, parse_args
# Parse arguments # Parse arguments
parser = create_parser() parser = create_parser()
......
import argparse import argparse
import logging import logging
from src.logger import Logger from src import Logger
# Parse arguments # Parse arguments
...@@ -18,6 +18,7 @@ def create_parser(): ...@@ -18,6 +18,7 @@ def create_parser():
def parse_args(parser): def parse_args(parser):
print 'here:', parser.format_usage()
args = parser.parse_args() args = parser.parse_args()
# Configure logger # Configure logger
......
#!/usr/bin/env python #!/usr/bin/env python
from __future__ import division from __future__ import division
import gtk import gtk
import cairo
from math import radians
from threading import Thread from threading import Thread
from math import pi, tan
import src as mt import src as mt
from utils import BoundingBoxArea
RED = 1, 0, 0
GREEN = 0, 1, 0
BLUE = 0, 0, 1
WHITE = 1, 1, 1
BLACK = 0, 0, 0
class Rectangle(mt.RectangularArea): class Rectangle(mt.RectangularArea):
def __init__(self, x, y, width, height, color=(1, 0, 0)): def __init__(self, x, y, width, height, color=(1, 0, 0)):
super(Rectangle, self).__init__(x, y, width, height) super(Rectangle, self).__init__(x, y, width, height)
self.w = width
self.h = height
self.scale = 1
self.angle = 0
self.color = color self.color = color
self.t = cairo.Matrix() self.on_drag(self.handle_drag)
self.t.translate(x, y)
self.on_drag(self.move) def handle_drag(self, g):
self.on_pinch(self.resize) tx, ty = g.get_translation()
#self.on_rotate(self.rotate) self.translate(tx, ty)
def move(self, g):
print 'move:', g
self.translate(*g.get_translation())
self.ttrans(*g.get_translation())
refresh() refresh()
def resize(self, g): def draw(self, cr):
print 'resize:', g cr.rectangle(self.x, self.y, self.width, self.height)
cr.set_source_rgb(*self.color)
cr.fill()
x, y = g.get_position()
scale = g.get_scale()
self.ttrans(x, y)
self.tscale(scale)
self.ttrans(-x, -y)
self.translate(x - x * scale, y - y * scale) class Polygon(BoundingBoxArea):
def __init__(self, x, y, points, color=BLUE, border_color=RED):
super(Polygon, self).__init__(x, y, points)
self.fill_color = color
self.border_color = border_color
self.width *= scale self.on_drag(self.handle_drag)
self.height *= scale self.on_pinch(self.handle_pinch)
self.on_rotate(self.handle_rotate)
def handle_drag(self, g):
tx, ty = g.get_translation()
self.translate(tx, ty)
refresh() refresh()
def rotate(self, g): def handle_pinch(self, g):
print 'rotate:', g cx, cy = g.get_position()
self.scale_points(g.get_scale(), cx, cy)
x, y = g.get_position() self.update_bounds()
self.ttrans(x, y) refresh()
self.trot(-g.get_angle())
self.ttrans(-x, -y)
def handle_rotate(self, g):
cx, cy = g.get_position()
self.rotate_points(g.get_angle(), cx, cy)
self.update_bounds()
refresh() refresh()
def ttrans(self, tx, ty): def draw(self, cr):
t = cairo.Matrix() # Draw bounding box
t.translate(tx, ty) if draw_bounding_boxes:
self.t = t * self.t cr.rectangle(self.x, self.y, self.width, self.height)
cr.set_source_rgb(*self.border_color)
cr.set_line_width(3)
cr.stroke()
def tscale(self, s): # Fill polygon
t = cairo.Matrix() cr.translate(self.x, self.y)
t.scale(s, s) cr.new_path()
self.t = t * self.t
def trot(self, a): for x, y in zip(*self.points):
t = cairo.Matrix() cr.line_to(x, y)
t.rotate(a)
self.t = t * self.t
def draw(self, cr): cr.set_source_rgb(*self.fill_color)
cr.transform(self.t)
cr.rectangle(0, 0, self.w, self.h)
cr.set_source_rgb(*self.color)
cr.fill() cr.fill()
fullscreen = False fullscreen = False
draw_bounding_boxes = draw_touch_points = True
W, H = mt.screen.screen_size W, H = mt.screen.screen_size
...@@ -92,10 +94,12 @@ def create_context_window(w, h, callback): ...@@ -92,10 +94,12 @@ def create_context_window(w, h, callback):
"""Synchronize root multi-touch area with GTK window.""" """Synchronize root multi-touch area with GTK window."""
root.set_position(*event.get_coords()) root.set_position(*event.get_coords())
root.set_size(event.width, event.height) root.set_size(event.width, event.height)
overlay.set_size(event.width, event.height)
draw() draw()
def handle_key(win, event): def handle_key(win, event):
"""Handle key event. 'f' toggles fullscreen, 'q' exits the program.""" """Handle key event. 'f' toggles fullscreen, 'q' exits the program, 'b'
toggles bounding boxes, 'p' toggles touch points."""
if event.keyval >= 256: if event.keyval >= 256:
return return
...@@ -107,10 +111,19 @@ def create_context_window(w, h, callback): ...@@ -107,10 +111,19 @@ def create_context_window(w, h, callback):
fullscreen = not fullscreen fullscreen = not fullscreen
elif key == 'q': elif key == 'q':
quit() quit()
elif key == 'b':
global draw_bounding_boxes
draw_bounding_boxes = not draw_bounding_boxes
refresh()
elif key == 'p':
global draw_touch_points
draw_touch_points = not draw_touch_points
refresh()
# Root area (will be synchronized with GTK window) # Root area (will be synchronized with GTK window)
global root global root, overlay
root = mt.RectangularArea(0, 0, w, h) root = mt.RectangularArea(0, 0, w, h)
overlay = mt.RectangularArea(0, 0, w, h)
# GTK window # GTK window
global window global window
...@@ -121,6 +134,9 @@ def create_context_window(w, h, callback): ...@@ -121,6 +134,9 @@ def create_context_window(w, h, callback):
window.connect('configure-event', move_window) window.connect('configure-event', move_window)
window.connect('show', callback) window.connect('show', callback)
if fullscreen:
window.fullscreen()
# Drawing area, needed by cairo context for drawing # Drawing area, needed by cairo context for drawing
area = gtk.DrawingArea() area = gtk.DrawingArea()
area.set_size_request(w, h) area.set_size_request(w, h)
...@@ -137,7 +153,7 @@ def draw(*args): ...@@ -137,7 +153,7 @@ def draw(*args):
# Background # Background
cr.rectangle(0, 0, *root.get_size()) cr.rectangle(0, 0, *root.get_size())
cr.set_source_rgb(0, 1, 0) cr.set_source_rgb(*BLACK)
cr.fill() cr.fill()
# Drawable objects (use save and restore to allow transformations) # Drawable objects (use save and restore to allow transformations)
...@@ -146,6 +162,24 @@ def draw(*args): ...@@ -146,6 +162,24 @@ def draw(*args):
obj.draw(cr) obj.draw(cr)
cr.restore() cr.restore()
if draw_touch_points:
ox, oy = root.get_position()
cr.set_source_rgb(*WHITE)
for x, y in touch_points.itervalues():
x -= ox
y -= oy
cr.set_line_width(3)
cr.arc(x, y, 20, 0, 2 * pi)
cr.stroke()
cr.set_line_width(1)
cr.move_to(x - 8, y)
cr.line_to(x + 8, y)
cr.move_to(x, y - 8)
cr.line_to(x, y + 8)
cr.stroke()
def refresh(): def refresh():
window.queue_draw() window.queue_draw()
...@@ -156,8 +190,13 @@ def quit(*args): ...@@ -156,8 +190,13 @@ def quit(*args):
# Initialization # Initialization
window = cr = root = None window = cr = root = overlay = None
draw_objects = [] draw_objects = []
touch_points = {}
def triangle_height(width):
return abs(.5 * width * tan(2 / 3 * pi))
def on_show(window): def on_show(window):
...@@ -165,18 +204,53 @@ def on_show(window): ...@@ -165,18 +204,53 @@ def on_show(window):
root.on_double_tap(root_dtap) root.on_double_tap(root_dtap)
# Create blue rectangle # Create blue rectangle
rect = Rectangle(300, 200, 250, 150, color=(0, 0, 1)) x, y, w, h = 0, 0, 250, 150
rect = Polygon(x, y, [(0, 0), (0, h), (w, h), (w, 0)])
draw_objects.append(rect) draw_objects.append(rect)
root.add_area(rect) root.add_area(rect)
def rect_tap(g): print 'tapped on rectangle' def rect_tap(g): print 'tapped on rectangle'
rect.on_tap(rect_tap, propagate_up_event=False) rect.on_tap(rect_tap, propagate_up_event=False)
# Create green triangle
x, y, w = 400, 400, 200
h = triangle_height(w)
triangle = Polygon(x, y, [(0, h), (w, h), (w / 2, 0)], color=GREEN)
draw_objects.append(triangle)
root.add_area(triangle)
# Overlay catches basic events
def handle_down(gesture):
point = gesture.get_event().get_touch_object()
touch_points[point.get_id()] = point.get_position()
if draw_touch_points:
refresh()
def handle_up(gesture):
point = gesture.get_event().get_touch_object()
del touch_points[point.get_id()]
if draw_touch_points:
refresh()
overlay.on_point_down(handle_down)
overlay.on_point_move(handle_down)
overlay.on_point_up(handle_up)
root.add_area(overlay)
if __name__ == '__main__': if __name__ == '__main__':
from parse_arguments import create_parser, parse_args
# Parse arguments # Parse arguments
from tests.parse_arguments import create_parser, parse_args parser = create_parser()
parse_args(create_parser()) parser.add_argument('-f', '--fullscreen', action='store_true', default=False,
help='run in fullscreen initially')
args = parse_args(parser)
fullscreen = args.fullscreen
# Create a window with a Cairo context in it and a multi-touch area # Create a window with a Cairo context in it and a multi-touch area
# syncronized with it # syncronized with it
......
from numpy import array, diag, dot, cos, sin
from src import RectangularArea
class BoundingBoxArea(RectangularArea):
def __init__(self, x, y, points):
super(BoundingBoxArea, self).__init__(x, y, 0, 0)
self.points = array(points).T
self.update_bounds()
def translate_points(self, tx, ty):
self.points += [[tx], [ty]]
def scale_points(self, scale, cx, cy):
self.translate_points(-cx, -cy)
self.points = dot(diag([scale, scale]), self.points)
self.translate_points(cx, cy)
def rotate_points(self, angle, cx, cy):
cosa = cos(angle)
sina = sin(angle)
mat = array([[cosa, -sina], [sina, cosa]])
self.translate_points(-cx, -cy)
self.points = dot(mat, self.points)
self.translate_points(cx, cy)
def update_bounds(self):
min_x, min_y = self.points.min(1)
max_x, max_y = self.points.max(1)
self.set_size(max_x - min_x, max_y - min_y)
if min_x or min_y:
self.translate(min_x, min_y)
self.translate_points(-min_x, -min_y)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment