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 tracker import GestureTracker, Gesture
from drivers import create_driver
from tracker import GestureTracker, Gesture
from geometry import Positionable
from areas import *
......@@ -31,21 +31,7 @@ class RectangularArea(Area):
and self.y <= y <= self.y + self.height
def contains_event(self, event):
if 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
return self.contains(*event.get_position())
class CircularArea(Area):
......
from __future__ import division
from math import atan2, pi
from math import atan2, pi, sin, cos
import time
......@@ -60,6 +60,14 @@ class Positionable(object):
def translate(self, x, 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):
"""
......
......@@ -21,7 +21,7 @@ class RotationGesture(Gesture, Positionable):
% (self.x, self.y, self.angle)
def get_angle(self):
return self.angle
return -self.angle
class PinchGesture(Gesture, Positionable):
......@@ -84,6 +84,8 @@ class TransformationTracker(GestureTracker):
# Current and previous centroid of all touch points
self.centroid = None
self.deleted = []
def update_centroid(self):
if not self.points:
self.centroid = None
......@@ -105,18 +107,28 @@ class TransformationTracker(GestureTracker):
def on_point_down(self, event):
self.points.append(event.point)
self.update_centroid()
event.stop_propagation()
def on_point_move(self, event):
point = event.point
if point not in self.points:
return
pid = point.get_id()
if pid not in self.deleted:
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)
if l > 1:
offset_centroid = self.centroid - self.area.get_screen_offset()
print self.centroid, self.area, offset_centroid
# Rotation (around the previous centroid)
rotation = point.rotation_around(self.centroid) / l
......@@ -137,8 +149,32 @@ class TransformationTracker(GestureTracker):
self.centroid.translation(), l))
def on_point_up(self, event):
if event.point not in self.points:
return
if event.point in self.points:
self.points.remove(event.point)
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
self.points.remove(event.point)
self.update_centroid()
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
import os
src_path = os.path.realpath('src')
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]
execfile(sys.argv[0])
#!/usr/bin/env python
lambda g: #!/usr/bin/env python
from __future__ import division
import pygame
from threading import Thread
from math import degrees
from tests.parse_arguments import create_parser, parse_args
from src import FullscreenArea, create_driver
from src.screen import screen_size
from parse_arguments import create_parser, parse_args
# Parse arguments
parser = create_parser()
......
import argparse
import logging
from src.logger import Logger
from src import Logger
# Parse arguments
......@@ -18,6 +18,7 @@ def create_parser():
def parse_args(parser):
print 'here:', parser.format_usage()
args = parser.parse_args()
# Configure logger
......
#!/usr/bin/env python
from __future__ import division
import gtk
import cairo
from math import radians
from threading import Thread
from math import pi, tan
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):
def __init__(self, x, y, width, height, color=(1, 0, 0)):
super(Rectangle, self).__init__(x, y, width, height)
self.w = width
self.h = height
self.scale = 1
self.angle = 0
self.color = color
self.t = cairo.Matrix()
self.t.translate(x, y)
self.on_drag(self.handle_drag)
self.on_drag(self.move)
self.on_pinch(self.resize)
#self.on_rotate(self.rotate)
def move(self, g):
print 'move:', g
self.translate(*g.get_translation())
self.ttrans(*g.get_translation())
def handle_drag(self, g):
tx, ty = g.get_translation()
self.translate(tx, ty)
refresh()
def resize(self, g):
print 'resize:', g
def draw(self, cr):
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.height *= scale
self.on_drag(self.handle_drag)
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()
def rotate(self, g):
print 'rotate:', g
x, y = g.get_position()
self.ttrans(x, y)
self.trot(-g.get_angle())
self.ttrans(-x, -y)
def handle_pinch(self, g):
cx, cy = g.get_position()
self.scale_points(g.get_scale(), cx, cy)
self.update_bounds()
refresh()
def handle_rotate(self, g):
cx, cy = g.get_position()
self.rotate_points(g.get_angle(), cx, cy)
self.update_bounds()
refresh()
def ttrans(self, tx, ty):
t = cairo.Matrix()
t.translate(tx, ty)
self.t = t * self.t
def draw(self, cr):
# Draw bounding box
if draw_bounding_boxes:
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):
t = cairo.Matrix()
t.scale(s, s)
self.t = t * self.t
# Fill polygon
cr.translate(self.x, self.y)
cr.new_path()
def trot(self, a):
t = cairo.Matrix()
t.rotate(a)
self.t = t * self.t
for x, y in zip(*self.points):
cr.line_to(x, y)
def draw(self, cr):
cr.transform(self.t)
cr.rectangle(0, 0, self.w, self.h)
cr.set_source_rgb(*self.color)
cr.set_source_rgb(*self.fill_color)
cr.fill()
fullscreen = False
draw_bounding_boxes = draw_touch_points = True
W, H = mt.screen.screen_size
......@@ -92,10 +94,12 @@ def create_context_window(w, h, callback):
"""Synchronize root multi-touch area with GTK window."""
root.set_position(*event.get_coords())
root.set_size(event.width, event.height)
overlay.set_size(event.width, event.height)
draw()
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:
return
......@@ -107,10 +111,19 @@ def create_context_window(w, h, callback):
fullscreen = not fullscreen
elif key == 'q':
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)
global root
global root, overlay
root = mt.RectangularArea(0, 0, w, h)
overlay = mt.RectangularArea(0, 0, w, h)
# GTK window
global window
......@@ -121,6 +134,9 @@ def create_context_window(w, h, callback):
window.connect('configure-event', move_window)
window.connect('show', callback)
if fullscreen:
window.fullscreen()
# Drawing area, needed by cairo context for drawing
area = gtk.DrawingArea()
area.set_size_request(w, h)
......@@ -137,7 +153,7 @@ def draw(*args):
# Background
cr.rectangle(0, 0, *root.get_size())
cr.set_source_rgb(0, 1, 0)
cr.set_source_rgb(*BLACK)
cr.fill()
# Drawable objects (use save and restore to allow transformations)
......@@ -146,6 +162,24 @@ def draw(*args):
obj.draw(cr)
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():
window.queue_draw()
......@@ -156,8 +190,13 @@ def quit(*args):
# Initialization
window = cr = root = None
window = cr = root = overlay = None
draw_objects = []
touch_points = {}
def triangle_height(width):
return abs(.5 * width * tan(2 / 3 * pi))
def on_show(window):
......@@ -165,18 +204,53 @@ def on_show(window):
root.on_double_tap(root_dtap)
# 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)
root.add_area(rect)
def rect_tap(g): print 'tapped on rectangle'
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__':
from parse_arguments import create_parser, parse_args
# Parse arguments
from tests.parse_arguments import create_parser, parse_args
parse_args(create_parser())
parser = 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
# 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