def __init__(self, node, source = avg.TOUCH | avg.MOUSE, maxSize = None, minSize = None, onResize = lambda: None, onDrop = lambda: None, onStop = lambda: None, onAction = lambda: None, onMotion = lambda pos, size, angle, pivot: None, onResetMotion = lambda: None, onDragStart = lambda: None, initialDown = None, inertia = 0.95, torque = 0.95, moveNode = True): global g_Player self.__inertia = inertia self.__torque = torque self.__node = node self.__minSize = minSize self.__maxSize = maxSize self.__moveNode = moveNode self.__callback = { 'onResize': onResize, 'onDrop': onDrop, 'onStop': onStop, 'onAction': onAction, 'onMotion': onMotion, 'onResetMotion': onResetMotion, 'onDragStart': onDragStart, } self.__stopInertia() #self.__oldAngle = 0.0 self.__lastFixPoint = None self.__lastNumFingers = 0 self.__onFrameHandler = g_Player.setOnFrameHandler(self.__onFrame) self.__eventList = ClusteredEventList (self.__node, source = source, onMotion = self.__onMotion, onUp = self.__onUp, onDown = self.__onDown, resetMotion = self.__resetMotion ) if initialDown: self.__eventList.handleInitialDown (initialDown) self.__lastPos = None self.__stopped = True
class Grabbable: """Helper for multitouch object movement. Grabbable will add the well-known multitouch gestures to a node, i.e. moving with one finger, rotating, resizing and moving with 2 fingers. It also works with many more cursors/fingers, clustering them. """ def __init__(self, node, source = avg.TOUCH | avg.MOUSE, maxSize = None, minSize = None, onResize = lambda: None, onDrop = lambda: None, onStop = lambda: None, onAction = lambda: None, onMotion = lambda pos, size, angle, pivot: None, onResetMotion = lambda: None, onDragStart = lambda: None, initialDown = None, inertia = 0.95, torque = 0.95, moveNode = True): global g_Player self.__inertia = inertia self.__torque = torque self.__node = node self.__minSize = minSize self.__maxSize = maxSize self.__moveNode = moveNode self.__callback = { 'onResize': onResize, 'onDrop': onDrop, 'onStop': onStop, 'onAction': onAction, 'onMotion': onMotion, 'onResetMotion': onResetMotion, 'onDragStart': onDragStart, } self.__stopInertia() #self.__oldAngle = 0.0 self.__lastFixPoint = None self.__lastNumFingers = 0 self.__onFrameHandler = g_Player.setOnFrameHandler(self.__onFrame) self.__eventList = ClusteredEventList (self.__node, source = source, onMotion = self.__onMotion, onUp = self.__onUp, onDown = self.__onDown, resetMotion = self.__resetMotion ) if initialDown: self.__eventList.handleInitialDown (initialDown) self.__lastPos = None self.__stopped = True def isDragging(self): return len(self.__eventList) > 0 def getSpeed(self): return self.__speed def getAngle(self): return self.__angle def delete(self): global g_Player g_Player.clearInterval(self.__onFrameHandler) self.__eventList.delete() self.__node = None def __onFrame(self): global g_Player if len (self.__eventList) == 0: # inertia if self.__speed.getNorm() < 1: if not self.__stopped: pos = Point2D(round(self.__node.pos.x), round(self.__node.pos.y)) size = Point2D(round(self.__node.size.x), round(self.__node.size.y)) self.__callback['onMotion'](pos, size, self.__node.angle, self.__node.pivot) self.__stopped = True self.__callback['onStop']() self.__stopInertia() return pos = self.__node.pos + self.__speed angle = self.__node.angle + self.__rotSpeed size = self.__node.size pivot = self.__node.pivot if self.__moveNode: self.__node.pos = pos self.__node.size = size self.__node.angle = angle self.__node.pivot = pivot self.__callback['onMotion'](pos, size, angle, pivot) self.__speed *= self.__inertia self.__rotSpeed *= self.__torque else: self.__stopped = False # save current speed for inertia pos = self.__node.pos angle = self.__node.angle if self.__lastPos: speed = pos - self.__lastPos rotSpeed = angle - self.__lastAngle # use minimal angle to avoid extreme rotation: if rotSpeed < -math.pi/2: rotSpeed += math.pi*2 if rotSpeed > math.pi/2: rotSpeed -= math.pi*2 self.__speeds = (self.__speeds + [speed])[-NUM_SPEEDS:] self.__rotSpeeds = (self.__rotSpeeds + [rotSpeed])[-NUM_SPEEDS:] self.__lastPos = pos self.__lastAngle = angle def __stopInertia (self): self.__speed = Point2D(0,0) self.__rotSpeed = 0 self.__speeds = [Point2D(0,0)] self.__rotSpeeds = [0.0] self.__lastPos = None def __onDown(self): def __gotoTopLayer(): parent = self.__node.getParent() numChildren = parent.getNumChildren() parent.reorderChild(self.__node, numChildren - 1) self.__stopInertia() self.__reshape() if len(self.__eventList) == 1: # first finger down if self.__moveNode: __gotoTopLayer() self.__callback['onDragStart']() self.__callback['onAction']() def __onMotion(self): self.__reshape() self.__callback['onAction']() def __drop(self): self.__speed = sum(self.__speeds, Point2D(0,0)) / len(self.__speeds) self.__rotSpeed = sum(self.__rotSpeeds, 0.0) / len(self.__rotSpeeds) self.__callback['onDrop']() def __onUp(self): if len(self.__eventList) == 0: self.__drop() self.__callback['onAction']() def __reshape (self): def applyAffineTransformation (parameters, p): """apply transformation to point: M: v: P' = P * (m -n) + (v0) (n m) (v1) """ m, n, v0, v1 = parameters nx = p[0] * m - p[1] * n + v0 ny = p[0] * n + p[1] * m + v1 return Point2D(nx, ny) clusters = self.__eventList.getCursors() if len(clusters) == 1: cursor = self.__eventList.getCursors()[0] pos = self.__oldpos + cursor.getDelta() size = self.__node.size angle = self.__node.angle pivot = self.__node.pivot elif len(clusters) == 2: # calculate an affine transformation which describes cluster movement # TODO: explain used maths cursor1, cursor2 = clusters equationMatrix = ( ((cursor1.getStartPos().x, -cursor1.getStartPos().y, 1, 0), cursor1.getPos().x), ((cursor1.getStartPos().y, cursor1.getStartPos().x, 0, 1), cursor1.getPos().y), ((cursor2.getStartPos().x, -cursor2.getStartPos().y, 1, 0), cursor2.getPos().x), ((cursor2.getStartPos().y, cursor2.getStartPos().x, 0, 1), cursor2.getPos().y), ) try: param = solveEquationMatrix (equationMatrix) except (EquationSingular, EquationNotSolvable): # g_Log.trace(g_Log.APP, # "Grabbable: cannot solve equation, skipping movement") return False absTouchCenter = cursor2.getPos() + (cursor1.getPos() - cursor2.getPos()) / 2 n_tl = applyAffineTransformation (param, self.o_tl) n_bl = applyAffineTransformation (param, self.o_bl) n_tr = applyAffineTransformation (param, self.o_tr) pos = n_tl if pos.x > 1e6 and pos.x > self.o_tl.x: pos = self.o_tl if pos.y > 1e6 and pos.y > self.o_tl.y: pos = self.o_tl if pos.x < -1e6 and pos.x < self.o_tl.x: pos = self.o_tl if pos.y < -1e6 and pos.y < self.o_tl.y: pos = self.o_tl size = Point2D(getDistance (n_tl, n_tr), getDistance (n_tl, n_bl)) if size.x<=0.1: size.x=0.1 if size.y<=0.1: size.y=0.1 angle = getAngle(n_tl, n_tr) # pivot for this rotation is 0,0 (rel. to node) # get middle of touches relative to the node relTouchCenter = getRelPos(absTouchCenter, pos, angle, Point2D(0,0)) pos += getOffsetForMovedPivot( oldPivot = Point2D(0,0), newPivot = relTouchCenter, angle = angle) pivot = relTouchCenter # the absolute position of the pivot should change when # resizing, so we track and revert absolute pivot movement oldAbsPivot = getAbsPos(pivot, pos, angle, pivot) newSize = getScaledDim (size, max = self.__maxSize, min = self.__minSize) ratio = newSize.x / size.x newPivot = pivot * ratio pos += getOffsetForMovedPivot( oldPivot = pivot, newPivot = newPivot, angle = angle) oldPivot = pivot pivot = newPivot size = newSize newAbsPivot = getAbsPos(pivot, pos, angle, pivot) # move node to revert absolute pivot position pos -= (newAbsPivot - oldAbsPivot) else: # more than 2 clusters assert False if self.__moveNode: self.__node.angle = angle self.__node.size = size self.__node.pos = pos self.__node.pivot = pivot self.__callback['onResize']() self.__callback['onMotion'](pos, size, angle, pivot) def __resetMotion (self): """ sets a new movement start point """ # store absolute position at the beginning of the movement # for 2-finger-movements: def getPos(pos): return getAbsPos(pos, self.__node.pos, self.__node.angle, self.__node.pivot) self.o_tl = getPos((0, 0)) self.o_bl = getPos((0, self.__node.height)) self.o_tr = getPos((self.__node.width, 0)) # for 1-finger-movements: self.__oldpos = self.__node.pos self.__lastFixPoint = None self.__callback['onResetMotion']()