Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
[Help] Gestures for Scene: Pythonista Debugging
-
Background:
I am writing a Scene application (a somewhat complex card game) and to do that I need gesture control for. I looked at the Gestures code which can be found on the pythonista forum and github but it seems to be geared toward UI gesture handling and not Scene gesture handling.I suppose I could stick my Scene module inside a sceneview UI module, that’s an option.
I could try to subclass or adapt the Gestures code to work with the Scene module.
But as an exercise in learning python better and playing around, I took inspiration from the Gestures code and tried to write my own version of gesture handling for use with the Scene module which leverages the touches objects which are available in the Scene module.
The trouble is that I haven’t gotten the hang of debugging inside the pythonista app. For simple things I get it to work okay, but for things which require interaction with the Scene at the same time as debugging, or which require conditional breakpoints. I’m currently having trouble figuring out why, given my state machine working (I think) as expected, why my calculations for touch translation and rotation are not functioning correctly.
I have included the code below for reference (it should be in a working state as posted). And if anyone has suggestions on how to accomplish conditional breakpoints it has fixes or suggestions for the code please chime in!
import scene import console import math from collections import OrderedDict #from touch_plus import TouchPlus from colorsys import hsv_to_rgb from random import random '''<><><><><><><><><><><><><><><><><><><><><><><><> touch_plus.py file: <><><><><><><><><><><><><><><><><><><><><><><><>''' #import scene #import console #from colorsys import hsv_to_rgb #from random import random class TouchPlus(): def __init__(self, parent, **kwargs): if not isinstance(parent, scene.Scene): #raise TypeError('parent must be a Scene object!') self.visible = False self.parentScene = None else: self.parentScene = parent # Extra kwargs handling if 'position' in kwargs: self.startPosition = kwargs.get('position') else: self.startPosition = scene.Point(0,0) if 'startTime' in kwargs: self.startTime = kwargs.get('startTime') else: self.startTime = 0 if 'visible' in kwargs: self.visible = kwargs.get('visible') else: self.visible = False if 'touch_id' in kwargs: self.touch_id = kwargs.get('touch_id') else: self.touch_id = None # Other self.A = scene.Action self.lastPosition = self.position = self.startPosition self.location = self.position self.lastTime = self.time = self.startTime self.totalMovement = self.lastMovement = self.movement = scene.Vector2(0,0) self.totalVelocity = self.lastVelocity = self.velocity = scene.Vector2(0,0) self.area = scene.Rect(self.position.x, self.position.y, 1, 1) self.sprite = None if self.visible: self.show() else: self.hide() def duration(self, time=0): if time > 0: self.lastTime = self.time self.time = time return self.time - self.startTime def hide(self): if self.sprite is not None: self.sprite.remove_from_parent() del self.sprite self.sprite = None self.visble = False def show(self, parent=None): if parent is not None: if self.parentScene is not None and self.sprite is not None: self.sprite.remove_from_parent() self.parentScene = parent if self.parentScene is not None: self.visible = True if self.sprite is None: self.sprite = scene.SpriteNode( texture='shp:wavering', position=self.position, color=hsv_to_rgb(random(), 1, 1)) self.parentScene.add_child(self.sprite) else: self.visble = False self.sprite = None def update(self, pos, time=0): self.duration(time) self.lastPosition = self.position self.lastMovement = self.movement self.lastVelocity = self.velocity self.position = pos self.location = self.position self.movement = self.position - self.lastPosition self.totalMovement = self.position - self.startPosition wh = self.totalMovement self.area = scene.Rect(self.startPosition.x, self.startPosition.y, wh.x, wh.y) if self.time == self.lastTime: self.totalVelocity = self.velocity = scene.Vector2(0,0) else: self.velocity = self.movement / (self.time - self.lastTime) self.totalVelocity = self.totalMovement / (self.time - self.startTime) if self.sprite is not None: self.sprite.run_action(self.A.move_to(pos.x, pos.y, 0.01)) '''<><><><><><><><><><><><><><><><><><><><><><><><> touch_plus.py file END <><><><><><><><><><><><><><><><><><><><><><><><>''' '''<><><><><><><><><><><><><><><><><><><><><><><><> Free Functions BEGIN <><><><><><><><><><><><><><><><><><><><><><><><>''' def angle(point0, point1): diff = point1 - point0 return math.atan2(diff.y, diff.x) def area(touches): min_x = min_y = 50000 max_x = max_y = 0 for touch in touches.values(): min_x = min(min_x, touch.location.x) min_y = min(min_y, touch.location.y) max_x = max(max_x, touch.location.x) max_y = max(max_y, touch.location.y) return scene.Rect(min_x, min_y, (max_x-min_x),(max_y-min_y)) def centroid(touches): x_cent = y_cent = 0 for touch in touches.values(): x_cent += touch.location.x y_cent += touch.location.y if len(touches) > 0: n = len(touches) x_cent /= n y_cent /= n return scene.Point(x_cent, y_cent) def direction(point0, point1): const = Consts() diff = point1 - point0 if diff.x == 0: diff.x = 0.0001 gradient = diff.y / diff.x if diff.x > 0 and diff.y > 0 and gradient > 1: return const.UP elif diff.x > 0 and diff.y > 0 and gradient < 1: return const.RIGHT elif diff.x > 0 and diff.y < 0 and gradient < 1: return const.RIGHT elif diff.x > 0 and diff.y < 0 and gradient > 1: return const.DOWN elif diff.x < 0 and diff.y < 0 and gradient > 1: return const.DOWN elif diff.x < 0 and diff.y < 0 and gradient < 1: return const.LEFT elif diff.x < 0 and diff.y > 0 and gradient < 1: return const.LEFT elif diff.x < 0 and diff.y > 0 and gradient > 1: return const.UP else: return None def rotation(touches0, touches1): cent0 = centroid(touches0) cent1 = centroid(touches1) angles0 = {} angles1 = {} for touch in touches0.values(): angles0[touch.touch_id] = angle(cent0, touch.location) for touch in touches1.values(): angles1[touch.touch_id] = angle(cent1, touch.location) ang_sum = ang_num = 0 for key in angles0.keys(): if key in angles1: ang_num += 1 ang_sum += (angles1[key] - angles0[key]) if ang_num == 0: return 0 else: return (ang_sum / ang_num) def scale(touches0, touches1): area0 = area(touches0) area1 = area(touches1) size0 = area0.width * area0.height size1 = area1.width * area1.height if size0 == 0 or size1 == 0: return 0 else: return (size1 / size0) def translation(touches0, touches1): cent0 = centroid(touches0) cent1 = centroid(touches1) return (cent1 - cent0) '''<><><><><><><><><><><><><><><><><><><><><><><><> Free Functions END <><><><><><><><><><><><><><><><><><><><><><><><>''' '''<><><><><><><><><><><><><><><><><><><><><><><><> timer.py file BEGIN <><><><><><><><><><><><><><><><><><><><><><><><>''' class Timer(): def __init__(self): self.reset() def tick(self, time, caller=None): if caller != self.last_called_by: self.stopwatch = 0 if time > 0: if self.start_time == 0: self.last_time = self.start_time = time dt = time - self.last_time self.last_time = time self.stopwatch += dt def tock(self): return self.stopwatch def reset(self): self.start_time = 0 self.last_time = 0 self.stopwatch = 0 self.last_called_by = None '''<><><><><><><><><><><><><><><><><><><><><><><><> timer.py file END <><><><><><><><><><><><><><><><><><><><><><><><>''' '''<><><><><><><><><><><><><><><><><><><><><><><><> gesture_handler.py file BEGIN <><><><><><><><><><><><><><><><><><><><><><><><>''' class Consts(): IDLE = 'State: Idle' ADDING = 'State: Adding' BEGAN = 'State: Began' CHANGED = 'State: Changed' REMOVING = 'State: Removing' ENDED = 'State: Ended' CANCELLED = 'State: Cancelled' HANDLED = 'State: Handled' NO_GESTURE = 'Gesture: None' TAP = 'Gesture: Tap' LONG_PRESS = 'Gesture: Long Press' LONG_PRESS_N_DRAG = 'Gesture: Long Press N Drag' SWIPE = 'Gesture: Swipe' PAN = 'Gesture: Pan' PINCH = 'Gesture: Pinch' ROTATION = 'Gesture: Rotation' SCREEN_EDGE_PAN = 'Gesture: Screen Edge Pan' LEFT = 'Left' RIGHT = 'Right' UP = 'Up' DOWN = 'Down' EDGE_RIGHT = 'Edge Right' EDGE_LEFT = 'Edge Left' EDGE_TOP = 'Edge Top' EDGE_BOTTOM = 'Edge Bottom' LONG_TOUCH_TIME = 0.4 #seconds ADD_REMOVE_TIME = 0.05 #seconds TAP_MOVEMENT = 10 #pixels class State(Consts): def __init__(self, **kwargs): #Defaults self.touches = OrderedDict() self.state = None self.time = 0 #Handle KWARGS: if 'touches' in kwargs: self.touches = kwargs.get('touches') if 'state' in kwargs: self.state = kwargs.get('state') if 'time' in kwargs: self.time = kwargs.get('time') class Data(Consts): def __init__(self, state_0, state_1): self.state = state_1.state self.gesture = None self.number_of_touches = len(state_1.touches) self.duration = state_1.time - state_0.time self.translation = translation(state_0.touches, state_1.touches) self.area = area(state_1.touches) self.starting_location = centroid(state_0.touches) self.location = centroid(state_1.touches) self.direction = direction(self.starting_location, self.location) self.scale = scale(state_0.touches, state_1.touches) self.rotation = rotation(state_0.touches, state_1.touches) if self.duration != 0: self.velocity_t = self.translation / self.duration self.velocity_r = self.rotation / self.duration else: self.velocity_t = None self.velocity_r = None class Recognizer(Consts): def __init__(self, action, **kwargs): self.minimum_press_duration = self.maximum_press_duration = self.minimum_number_of_touches = self.maximum_number_of_touches = self.minimum_movement = self.maximum_movement = self.direction = self.gesture = self.notify_on = None self.callback = action if 'minimum_press_duration' in kwargs: self.minimum_press_duration = kwargs.get('minimum_press_duration') if 'maximum_press_duration' in kwargs: self.maximum_press_duration = kwargs.get('maximum_press_duration') if 'minimum_number_of_touches' in kwargs: self.minimum_number_of_touches = kwargs.get('minimum_number_of_touches') if 'maximum_number_of_touches' in kwargs: self.maximum_number_of_touches = kwargs.get('maximum_number_of_touches') if 'minimum_movement' in kwargs: self.minimum_movement = kwargs.get('minimum_movement') if 'maximum_movement' in kwargs: self.maximum_movement = kwargs.get('maximum_movement') if 'direction' in kwargs: self.direction = kwargs.get('direction') if 'notify_on' in kwargs: self.notify_on = kwargs.get('notify_on') if 'gesture' in kwargs: self.gesture = kwargs.get('gesture') def is_recognized(self, data): recognized = True attributes = 0 if self.notify_on is not None: attributes += 1 if data.state == self.IDLE or data.state == self.CANCELLED: return False elif (self.notify_on == self.ENDED or self.notify_on == self.BEGAN) and data.state != self.notify_on: return False if self.maximum_number_of_touches is not None: attributes += 1 recognized &= (data.number_of_touches <= self.maximum_number_of_touches) if self.minimum_number_of_touches is not None: attributes += 1 recognized &= (data.number_of_touches >= self.minimum_number_of_touches) if self.maximum_movement is not None: attributes += 1 recognized &= (abs(data.translation) <= self.maximum_movement) if self.minimum_movement is not None: attributes += 1 recognized &= (abs(data.number_of_touches) >= self.minimum_movement) if self.maximum_press_duration is not None: attributes += 1 recognized &= (data.duration <= self.maximum_press_duration) if self.minimum_press_duration is not None: attributes += 1 recognized &= (data.duration >= self.minimum_press_duration) if self.direction is not None: attributes += 1 recognized &= (data.direction == self.direction) if attributes < 1: return False else: return recognized def run_action(self, data): self.callback(data) class GestureHandler(Consts): INIT_STATE = 0 LAST_STATE = 1 THIS_STATE = 2 FINAL_STATE = 3 def __init__(self, parent, **kwargs): self.parentScene = parent if not isinstance(parent, scene.Scene): raise TypeError('parent must be a Scene object!') self.A = scene.Action if 'visible' in kwargs: self.visible = kwargs.get('visible') else: self.visible = True #False self.timer = Timer() self.recognizers = [] self.reset() #Debugging if self.visible: self.setup_messages() def reset(self): # <><><><><><><><><><><><><><><><><><><><><><><><> self.touches = OrderedDict() self.timer.reset() t = self.parentScene.t self.states = [ State(time=t), State(time=t), State(time=t), State(time=t)] self.gesture_area = None # <><><><><><><><><><><><><><><><><><><><><><><><> def add_touch(self, touch, time=0): # <><><><><><><><><><><><><><><><><><><><><><><><> #Add a new touch object to the collection self.touches[touch.touch_id] = TouchPlus(self.parentScene, position=(touch.location), color=hsv_to_rgb(random(), 1, 1), startTime=time, touch_id = touch.touch_id, visible = self.visible) # <><><><><><><><><><><><><><><><><><><><><><><><> #Handle the gesture state change - The first touch indicates a tap gesture new_state = None self.timer.tick(time, 'add_touch') if (len(self.touches) == 1): new_state = self.BEGAN else: if (self.timer.tock() > self.ADD_REMOVE_TIME): new_state = self.CANCELLED else: new_state = self.ADDING self.change_state(new_state) # <><><><><><><><><><><><><><><><><><><><><><><><> def move_touch(self, touch, time=0): #Update the touch with the move if touch.touch_id in self.touches: self.touches[touch.touch_id].update(touch.location, time) self.change_state(self.CHANGED) # <><><><><><><><><><><><><><><><><><><><><><><>< def update_touches(self, touches, time=0): #return early because touches should only legitimately change on movement, add, or remove return updated = False for touch_key in touches.keys(): if touch_key in self.touches: updated = True self.touches[touch_key].update(touches[touch_key].location, time) if updated: self.change_state(self.CHANGED) # <><><><><><><><><><><><><><><><><><><><><><><><> def remove_touch(self, touch, time=0): #Handle the gesture state change - The last touch indicates a gesture is complete #new_state = self.states[self.THIS_STATE].state new_state=None self.timer.tick(time,'remove_touch') if (len(self.touches) == 0): new_state = self.ENDED else: if (self.timer.tock() > self.ADD_REMOVE_TIME): new_state = self.CANCELLED else: new_state = self.REMOVING #Remove a touch object from the collection if touch.touch_id in self.touches: self.touches[touch.touch_id].hide() del self.touches[touch.touch_id] self.change_state(new_state) # <><><><><><><><><><><><><><><><><><><><><><><><> def change_state(self, new_state=None): #The main state machine for detecting and handling gestures, this is where all the smarts (should) happen. if new_state is None: new_state = self.IDLE # ......................................... self.states[self.LAST_STATE] = self.states[self.THIS_STATE] self.states[self.THIS_STATE] = State(touches=self.touches, state=new_state, time=self.parentScene.t) self.msg = [new_state] clean_on_exit = False if new_state == self.BEGAN or new_state == self.ADDING: self.states[self.INIT_STATE] = self.states[self.THIS_STATE] elif new_state == self.CHANGED: clean_on_exit = self.check_recognizers(self.states[self.INIT_STATE],self.states[self.THIS_STATE]) elif new_state == self.REMOVING: if self.states[self.LAST_STATE].state != self.REMOVING: self.states[self.FINAL_STATE] = self.states[self.THIS_STATE] elif new_state == self.ENDED: clean_on_exit = self.check_recognizers(self.states[self.INIT_STATE],self.states[self.FINAL_STATE]) elif new_state == self.CANCELLED: clean_on_exit = True self.print_messages(self.msg) if clean_on_exit: self.cleanup() ''' #old state machine: if new_state == self.BEGAN: pass elif new_state == self.CHANGED: if len(self.touches) > 0: first_touch = next(iter(self.touches.values())) if this_gesture == self.TAP and abs(first_touch.totalMovement) <= self.TAP_MOVEMENT and first_touch.duration() > self.LONG_TOUCH_TIME: self.change_state(self.CHANGED, self.LONG_PRESS) elif this_gesture == self.LONG_PRESS and abs(first_touch.totalMovement) > self.TAP_MOVEMENT: self.highlight(first_touch.startPosition, first_touch.position) self.change_state(self.CHANGED, self.LONG_PRESS_N_DRAG) elif this_gesture == self.LONG_PRESS_N_DRAG: self.highlight(first_touch.startPosition, first_touch.position) elif new_state == self.ENDED: #Call handlers here! new_state = self.HANDLED ''' #<><><><><><><><><><><><><><><><><><><><><><><><> def check_recognizers(self, state0, state1): recognized = False if len(state0.touches) > 0 and len(state1.touches) > 0: gesture_data = Data(state0, state1) self.msg.append(gesture_data.state + ' Touches={0}'.format(gesture_data.number_of_touches)) self.msg.append('Rotation={0} Translation={1}'.format(gesture_data.rotation,gesture_data.translation)) self.msg.append('Duration={0:.2f} Scale={1}'.format(gesture_data.duration,gesture_data.scale)) for recog in self.recognizers: if recog.is_recognized(gesture_data): gesture_data.gesture = recog.gesture self.msg.append(recog.gesture) recog.run_action(gesture_data) recognized = True # If the new_state indicates that the gesture was cancelled or handled, reset everything and wait for something meaningful to happen return recognized def cleanup(self): #Cleanup any touch tracking objects on screen, highlights, and call the reset function for all other relevant resetting for touch in self.touches.values(): touch.hide() self.remove_highlight() self.reset() #<><><><><><><><><><><><><><><><><><><><><><><><> def register_tap(self, callback, number_of_touches_required = None): ''' Call `callback` when a tap gesture is recognized. Additional parameters: * `number_of_touches_required` - Set if more than one finger is required for the gesture to be recognized. ''' recog = Recognizer(callback, minimum_press_duration=None, maximum_press_duration=self.LONG_TOUCH_TIME, minimum_number_of_touches=number_of_touches_required, maximum_number_of_touches=number_of_touches_required, minimum_movement=None, maximum_movement=self.TAP_MOVEMENT, direction=None, gesture=self.TAP, notify_on=self.ENDED) self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def register_long_press(self, callback, number_of_touches_required = None, minimum_press_duration = None, allowable_movement = None): ''' Call `callback` when a long press gesture is recognized. Note that this is a continuous gesture; you might want to check for `data.state == Gestures.CHANGED` or `ENDED` to get the desired results. Additional parameters: * `number_of_touches_required` - Set if more than one finger is required for the gesture to be recognized. * `minimum_press_duration` - Set to change the default 0.5 second recognition treshold. * `allowable_movement` - Set to change the default 10 point maximum distance allowed for the gesture to be recognized. ''' if minimum_press_duration is None: minimum_press_duration=self.LONG_TOUCH_TIME if allowable_movement is None: allowable_movement=self.TAP_MOVEMENT recog = Recognizer(callback, minimum_press_duration=minimum_press_duration, maximum_press_duration=None, minimum_number_of_touches=number_of_touches_required, maximum_number_of_touches=number_of_touches_required, minimum_movement=None, maximum_movement=allowable_movement, direction=None, gesture=self.LONG_PRESS, notify_on=self.ENDED) self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def register_long_press_n_drag(self, callback, number_of_touches_required = None, minimum_press_duration = None, allowable_movement = None): ''' Call `callback` when a long press gesture is recognized. Note that this is a continuous gesture; you might want to check for `data.state == Gestures.CHANGED` or `ENDED` to get the desired results. Additional parameters: * `number_of_touches_required` - Set if more than one finger is required for the gesture to be recognized. * `minimum_press_duration` - Set to change the default 0.5 second recognition treshold. * `allowable_movement` - Set to change the default 10 point maximum distance allowed for the gesture to be recognized. ''' recog = Recognizer(callback, minimum_press_duration=minimum_press_duration, maximum_press_duration=None, minimum_number_of_touches=number_of_touches_required, maximum_number_of_touches=number_of_touches_required, minimum_movement=None, maximum_movement=None, direction=None, gesture=self.LONG_PRESS_N_DRAG, notify_on=self.CHANGED) #self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def register_pan(self, callback, minimum_number_of_touches = None, maximum_number_of_touches = None): ''' Call `callback` when a pan gesture is recognized. This is a continuous gesture. Additional parameters: * `minimum_number_of_touches` - Set to control the gesture recognition. * `maximum_number_of_touches` - Set to control the gesture recognition. Handler `action` receives the following gesture-specific attributes in the `data` argument: * `translation` - Translation from the starting point of the gesture as a `ui.Point` with `x` and `y` attributes. * `velocity` - Current velocity of the pan gesture as points per second (a `ui.Point` with `x` and `y` attributes). ''' recog = Recognizer(callback, minimum_press_duration=None, maximum_press_duration=None, minimum_number_of_touches=minimum_number_of_touches, maximum_number_of_touches=minimum_number_of_touches, minimum_movement=None, maximum_movement=None, direction=None, gesture=self.PAN, notify_on=self.CHANGED) self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def register_screen_edge_pan(self, callback, edges): ''' Call `callback` when a pan gesture starting from the edge is recogized. This is a continuous gesture. `edges` must be set to one of `Gestures.EDGE_NONE/EDGE_TOP/EDGE_LEFT/EDGE_BOTTOM/EDGE_RIGHT/EDGE_ALL`. If you want to recognize pans from different edges, you have to set up separate recognizers with separate calls to this method. Handler `action` receives the same gesture-specific attributes in the `data` argument as pan gestures, see `add_pan`. ''' recog = Recognizer(callback, minimum_press_duration=None, maximum_press_duration=None, minimum_number_of_touches=None, maximum_number_of_touches=None, minimum_movement=None, maximum_movement=None, direction=None, gesture=None, notify_on=self.ENDED) #self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def register_pinch(self, callback): ''' Call `callback` when a pinch gesture is recognized. This is a continuous gesture. Handler `action` receives the following gesture-specific attributes in the `data` argument: * `scale` - Relative to the distance of the fingers as opposed to when the touch first started. * `velocity_t` - Current velocity of the pinch gesture as scale per second. ''' recog = Recognizer(callback, minimum_press_duration=None, maximum_press_duration=None, minimum_number_of_touches=None, maximum_number_of_touches=None, minimum_movement=None, maximum_movement=None, direction=None, gesture=self.PINCH, notify_on=self.CHANGED) self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def register_rotation(self, callback): ''' Call `callback` when a rotation gesture is recognized. This is a continuous gesture. Handler `action` receives the following gesture-specific attributes in the `data` argument: * `rotation` - Rotation in radians, relative to the position of the fingers when the touch first started. * `velocity_r` - Current velocity of the rotation gesture as radians per second. ''' recog = Recognizer(callback, minimum_press_duration=None, maximum_press_duration=None, minimum_number_of_touches=None, maximum_number_of_touches=None, minimum_movement=None, maximum_movement=None, direction=None, gesture=self.ROTATION, notify_on=self.CHANGED) self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def register_swipe(self, callback, direction = None, number_of_touches_required = None): ''' Call `callback` when a swipe gesture is recognized Additional parameters: * `direction` - Direction of the swipe to be recognized. Either one of `Gestures.RIGHT/LEFT/UP/DOWN`, or a list of multiple directions. * `number_of_touches_required` - Set if you need to change the minimum number of touches required. If swipes to multiple directions are to be recognized, the handler does not receive any indication of the direction of the swipe. Add multiple recognizers if you need to differentiate between the directions. ''' recog = Recognizer(callback, minimum_press_duration=None, maximum_press_duration=self.LONG_TOUCH_TIME, minimum_number_of_touches=number_of_touches_required, maximum_number_of_touches=number_of_touches_required, minimum_movement=None, maximum_movement=None, direction=direction, gesture=self.SWIPE, notify_on=self.ENDED) self.recognizers.append(recog) #<><><><><><><><><><><><><><><><><><><><><><><><> def setup_messages(self): #For testing purposes, replaces console HUD with labels on screen. print using the print_messages() function w, h = scene.get_screen_size() self.labels = [] for i in range(8): d = 15 + (15 * i) self.labels.append(scene.LabelNode('', font=('Helvetica',12) ,position=(w/2, (h-d)))) for lbl in self.labels: self.parentScene.add_child(lbl) #<><><><><><><><><><><><><><><><><><><><><><><><> def print_messages(self, strings): #Replacement for Console.hud_alert, to allow for more formatting of the print area if len(self.labels) > 0: for lbl in self.labels: lbl.text = '' i = 0 for txt in strings: self.labels[i].text = txt i += 1 #<><><><><><><><><><><><><><><><><><><><><><><><> def hide(self): self.visible = False # <><><><><><><><><><><><><><><><><><><><><><><><> def highlight(self, start, stop): #Show a highlighted area of the gesture (intended for long_press_and_drag) to show the selection area of the gesture new = False if self.gesture_area is None: self.gesture_area = scene.SpriteNode(color='#4db9ff', alpha=0.2) new = True self.gesture_area.position=((start + stop) / 2) delta = stop - start self.gesture_area.size=(abs(delta.x), abs(delta.y)) if self.visible and new: self.parentScene.add_child(self.gesture_area) # <><><><><><><><><><><><><><><><><><><><><><><><> def remove_highlight(self): if self.gesture_area is not None: self.gesture_area.remove_from_parent() del self.gesture_area self.gesture_area = None # <><><><><><><><><><><><><><><><><><><><><><><><> def show(self): self.visible = True # <><><><><><><><><><><><><><><><><><><><><><><><> '''<><><><><><><><><><><><><><><><><><><><><><><><> gesture_handler.py file END <><><><><><><><><><><><><><><><><><><><><><><><>''' '''<><><><><><><><><><><><><><><><><><><><><><><><> touch_tracker.py file BEGIN <><><><><><><><><><><><><><><><><><><><><><><><>''' class TouchTracker(): def __init__(self, parent, **kwargs): self.parentScene = parent if not isinstance(parent, scene.Scene): raise TypeError('parent must be a Scene object!') self.A = scene.Action if 'visible' in kwargs: self.visible = kwargs.get('visible') else: self.visible = True #False self.touches = {} self.touch_keys = [] def add_touch(self, touch, time=0): self.touches[touch.touch_id] = TouchPlus(self.parentScene, position=(touch.location), color=hsv_to_rgb(random(), 1, 1), startTime=time, touch_id = touch.touch_id, visible = self.visible) self.touch_keys.append(touch.touch_id) console.hud_alert('{0} touches'.format(len(self.touches), duration=0.25)) def move_touch(self, touch, time=0): if touch.touch_id in self.touches: self.touches[touch.touch_id].update(touch.location, time) def update_touches(self, touches, time=0): for touch_key in touches.keys(): if touch_key in self.touches: self.touches[touch_key].update(touches[touch_key].location, time) def remove_touch(self, touch, time=0): if touch.touch_id in self.touches: self.touches[touch.touch_id].hide() del self.touches[touch.touch_id] self.touch_keys.remove(touch.touch_id) '''<><><><><><><><><><><><><><><><><><><><><><><><> touch_tracker.py file END <><><><><><><><><><><><><><><><><><><><><><><><>''' '''<><><><><><><><><><><><><><><><><><><><><><><><> test code: <><><><><><><><><><><><><><><><><><><><><><><><>''' # Variation of the 'Basic Scene' template that shows every # touch in a different (random) color that stays the same # for the duration of the touch. #import scene #from gesture_handler import GestureHandler #from touch_tracker import TouchTracker #<><><><><><><><><><><><><><><><><><><><><><><><><><> #This example is the update - object based Scene class ExampleScene (scene.Scene): def setup(self): self.background_color = '#000000' self.gesture = GestureHandler(self) #self.gesture = TouchTracker(self) def update(self): self.gesture.update_touches(self.touches, self.t) def touch_began(self, touch): self.gesture.add_touch(touch, self.t) def touch_moved(self, touch): self.gesture.move_touch(touch, self.t) def touch_ended(self, touch): self.gesture.remove_touch(touch, self.t) scene.run(ExampleScene(), show_fps=True) #<><><><><><><><><><><><><><><><><><><><><><><><><><>
-
What a nice piece of work -- can't wait until it is done!
This is a good use case for
logging
-- sometimes you need to be able to just dump events to a file, and sort through it later. If you get really fancy, you could record a sequence, and to back and replay it single stepping. pdb doesn't play nice with scene, since scene things want to happen in realtime.I have a snippet that populates whenever I type importlogging:
import logging logger = logging.getLogger('myapp') while logger.handlers: h=logger.handlers.pop() if isinstance(h,logging.FileHandler): h.close() hdlr = logging.FileHandler('myapp.txt',mode='w') formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') hdlr.setFormatter(formatter if not logger.handlers: logger.addHandler(hdlr) logger.setLevel(logging.DEBUG)
( idea: a custom logging handler which acts like a transparent scrolling console overlay in scene, so you can log to file and scene at the same time.. but i digress)
It is helpful to add useful
__repr__
methods to your various classes to ease dumping.I added some logging to Data, and noticed that the touchPlus is the same for both states.
state1:(177500780) State: Adding@1.2541460990905762 Touches:<TouchPlus 0x1d5c6440 p=(570.50, 609.00) totMov=(-28.50, -17.00)> <TouchPlus 0x19589440 p=(494.50, 340.00) totMov=(0.00, 0.00)> state2:(177501868) State: Changed@1.3780840635299683 Touches:<TouchPlus 0x1d5c6440 p=(570.50, 609.00) totMov=(-28.50, -17.00)> <TouchPlus 0x19589440 p=(494.50, 340.00) totMov=(0.00, 0.00)>
Since rotation, etc essentially compares location/angles/etc for different states, you maybe want TouchPlus to store location per state key, or else you would use location & lastLocation, or location and startLocation, etc.
-
@JonB Thanks for the tip! Being new to Python (and coming from C++, where everything is “roll your own”, or import someone else’s) I am still yet not aware of all the included tools Python has to offer.
I will look into the logging functionality Python has to offer and see what benefits it yields.
Also, your brief look into the code using logging already helped some, confirming my suspicion that the states were somehow not getting populated with appropriate touch data (touch data seems to be getting duplicated). I will look into this and post something hopefully within a few days. (This is a side project and the day job takes precedence unfortunately).
-
as I understand your code, it is not that data is getting duplicated, but you are using the same touch instance, since the touch Id is the same -- i.e you are updating the touch object, but not creating a new one. really, I think you sort of want to compute the rotation, etc parameters for each state, when the state object gets created, rather than manipulating the touch object in Data. Dunno.
btw, the python built in debugger is called pdb, and does let you single step code, look at variables in real time, etc. But it is not really useful within scene, since it is console based. I am not sure if scene is compatible with the long press to set breakpoints in the python ista debugger.