• dcl

    Having now seen the light by having tested the Gestures
    code with a Scene, I am convinced that this is the best way to implement what I am trying to do in my Scene applications / scripts. However I am curious if more advanced gesture recognition is possible.

    I read here about subclassing the objective C UIGestureRecognizer class to implement more advanced or customized gesture recognition.

    @mikael or @JonB is it possible to subclass UIGestureRecognizer inside Pythonista using objc_util? I read the Pythonista documentation and from my understanding it is possible to subclass Objective C classes in Pythonista, but since the Apple help page specifically mentions having to include this #import "UIGestureRecognizerSubclass.h" before doing the subclassing, I’m skeptical it can be done in Pythonista. And if it is possible, I am still yet unclear on how to properly subclass an Objective C class with Python syntax.

    posted in Pythonista read more
  • dcl

    @cvp the from objc_util import * is part of the Gestures.py file which was obtained as-is from GitHub and the call is at the top of the file, before any of the code. So unless the files are getting included weird as a part of the Scene setup, I am not sure what might cause that behavior

    @JonB Thanks for the tip! I tried instantiating the gestures object in the __init__() function of the Scene rather than setup() that seems to work. The key being that you MUST call the Scene.__init__() function inside the MyScene.__init__() function, else the script will throw exceptions of missing members.

    The revised code is below:

    from scene import *
    import Gestures
    
    class MyScene (Scene):
        def __init__(self, **kwargs):
            Scene.__init__(self, **kwargs)
            self.GO = Gestures.Gestures()
    
        def setup(self):
            self.ship = SpriteNode('spc:PlayerShip1Orange')
            self.ship.last_scale = self.ship.scale
            self.ship.position = self.size / 2
            self.add_child(self.ship)
            GO.add_pinch(self.view, self.handle_zoom)
    
        def handle_zoom(self, data):
            self.ship.scale = self.ship.last_scale * data.scale
                if data.state is self.Gest.ENDED:
                    self.ship.last_scale = self.ship.scale
    
    run(MyScene())
    

    posted in Pythonista read more
  • dcl

    @mikael, You’re great. Goes to show what I know... haha.

    I’m going to play with this some to see what all I can do with it, but knowing this earlier (and shame on me for missing the fact that Scene had a Scene.view that could that could be attached to) would have saved me a great deal of work on what I have made to solve this issue, although making that code also taught me a lot, so I guess that’s a plus.

    I’ll leave the code on GitHub if anyone is interested in looking at it. I feel like there are some good parts to it, and certainly some parts I am not happy with. Hopefully someone can find some use for it, and I may end up combining parts of it with Gestures to get a final result I am happy with using in a Scene

    @mikael Notably, for whatever reason, Pythonista is giving me continuous NameError: ObjCInstance is not defined when I run the scene script, but not when I run directly from Gestures (your demo code). The code still runs, Pythonista just complains about the NameError the whole time. I’m going to look into this, but any advice is welcome.

    Thanks for your input and your help!

    posted in Pythonista read more
  • dcl

    The touches are only available at the Scene level (technically the top “Node”) as far as I understand it, yes.
    I am not sure if there is currently a way to “attach” recognizers to a given Node at this time, at least not one I could find. And the reason that your Gestures library is incompatible is that the objC calls that it is based on are dependent on a UI object to attach them to, of which there are natively none in a Scene application.
    I thought about wrapping an entire scene application in a ui.sceneview object, but decided that I really didn’t want to do that, so I wrote this bit of code to try and streamline the analysis of the touches in the scene. My intention / ambition is not to attach recognizers to an individual Node (although that could be useful I suppose) as much as it is to actually categorize the touches and their interaction with the scene into recognizable gestures / a consistent set of metadata about the entire interaction and act on that accordingly. This is in contrast to the process today which is more like having to write custom code to pay attention to when and where a single touch began or when “each” touch began, what it / they did while they were on screen, and then when and where it / they ended and then decide if that interaction is something my scene should pay attention to and then code the interaction accordingly.

    A more summarized goal would be “to write code that is reusable between Scene applications that takes touches as an input and generates the user a way to easily react to the entire touch interaction as a single entity”.
    This could be accomplished (as I have started) as a system of gestures recognizers that when recognized they call a callback function, or by dumping the metadata (start location, duration, etc.) as an object and have the user handle that themselves.

    This may be born out of my ignorance of the best way to handle touch interactions given the current Scene API, but I personally would like to be able to write functions for my Scene application to handle certain gestures (call Func_A() if the user swipes across the screen, call Func_B() if the user pinches in/out, call Func_C() if the user taps, etc) and let some other bit of code tell me if that gesture occurred instead of writing a massive if...then block inside the touch_began(), touch_moved(), and touch_ended() functions to test if the touch did something I am interested in and then interact with it. Notably, my Scene application is a bit more complex than something like the included alien platformer, match-3, or bricks examples where the user interaction is fairly simple and well defined. The nodes in my scene react differently to different interactions (zoom in/out, move all nodes as a unit, move one, place / remove one, etc.)

    posted in Pythonista read more
  • dcl

    Hello all,

    About a year ago I started looking for a solution to gesture recognition for a Pythonista Scene module. At that time, I was able to find the wonderful Gestures module for UI Views by @mikael but I was unable to use this in the Scene module (game). I was also unable to find anything that fit the need at the time, so I began crafting my own bit of code inspired by Gestures but for the Scene module using only Python code and the scene.touches interface (not hooking back into the objC code that the Gestures module uses).

    Some of the history of this project can be found at this forum post:

    https://forum.omz-software.com/topic/4624/help-gestures-for-scene-pythonista-debugging

    But I am pleased to say that while the code hasn't progressed much since that post, I have now gotten the code broken into individual files (instead of the monolithic code blob posted previously) and put on GitHub here:

    https://github.com/dlazenby/PythonistaSceneGestures

    Because this continues to be developed completely in my free time, and is fairly low on my priority list at the moment, I am providing this on GitHub in hopes that someone with more free time will take an interest and help me bring this project to completion for others to freely use.

    The current status of the project:

    By running the code from the gesture_handler.py file, the user gets a Scene in which user touches can be visualized on-screen individually and data about the "gesture" (grouping and movement of the current touches) can be seen at the top of the screen.

    Gesture data available:
    State (of the recognizer internal state machine)
    Number of Touches present
    Duration of the "gesture" (seconds)
    Translation of the "gesture" (pixels x, pixels y)
    Rotation of the "gesture" (degrees)
    Scale of the "gesture" (multiplier 1.0 = 100%)
    Result of the Recognizer (was the "gesture" recognized, and if so what type)

    There is also logging capability built in. To enable it, uncomment the code block near the top of the gesture_handler.py file. The logger dumps quite a bit of data (at least one message per update loop).

    My intention for the project is to
    (1) Finalize the functionality, making it similar to the Gestures module in that you have an object that, using hook functions placed inside the Scene module's touch_began(), touch_updated(), and touch_ended(), will analyze the touches and when a gesture is recognized will call a user defined callback which was setup on object creation along with the type of gesture to be recognized.
    (2) Possibly move some of the recognizer's functionality onto another thread in order to improve performance (I have very little experience with multi-threaded programs in Python / Pythonista)

    Currently, I am happy with the math which is analyzing the touches, but the schema for recognizing the "gesture" based on that data is not reliable. If someone would like to help troubleshoot what I have or develop a new / better schema for analyzing the data generated about a grouping of touches to recognize it as a gesture, that would be great!

    posted in Pythonista read more
  • dcl

    @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).

    posted in Pythonista read more
  • dcl

    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)
    #<><><><><><><><><><><><><><><><><><><><><><><><><><>
    

    posted in Pythonista read more
  • dcl

    Would this work with the Scene module?

    My understanding of your code, from reading through the python file, leads me to believe that a UI object has to be included. I don’t know a whole lot about the ObjC classes for gesture handling, so beyond what is written in your python file I don’t know how the code operates.

    I’m trying to get some form of gesture control in a Scene module that I am writing. I took inspiration from your original Gestures code and some touch tracking code I found online to brew my own bit of gesture handling, but it isn’t currently functional. I am trying to gauge whether to trash it and try to adapt / use what you’ve written directly with the Scene module or whether to seek help to get what I have working.

    posted in Pythonista read more
  • dcl

    Figured out the 401 error. It is complaining about credentials.

    I created a new local repo on my iPad and tried to push it to an empty repo on Bitbucket. I added a remote for the Bitbucket repo using:

    git remote [name] [URL] 
    

    And then tried to push using:

    Git push [name] 
    Username: [user]
    Password: [pass]
    

    But I got the same error as above

    class 'dulwich.errors.GitProtocolError'>: unexpected http response 401
    

    Is there some reason that StaSh can't talk to Bitbucket? Can I use SSH keys? If so, how so I do that?

    Also, in the process I added a bad remote to my repo on the iPad. Is there some StaSh command to remove a remote? The help doesn't list one. I suppose I could dig through the source to find how it added one and reverse that... if I had to.


    EDIT:

    Trashed the repo on the iPad because there is no way to remove remotes.

    Went back to Bitbucket and turned the repo off of "Private"

    Then I went back to Pythonista -> StaSh window and tried clone repo again using:

    git clone [URL]  
    

    And successfully got the repo to clone on my iPad
    I then added some files to the repo from the iPad by moving files from another folder in Pythonista and then added to the repo using:

    git add *
    git commit
    Commit Message: [message]
    Author Name: [name]
    Save [y/n]y
    Author Email: [email]
    Save [y/n]y
    

    This worked okay too, but then when I tried to push back to the repo using:

    git push
    Username: [user]
    Password: [pass]
    

    I ALWAYS get the same error from before:

    class 'dulwich.errors.GitProtocolError'>: unexpected http response 401
    

    And I am certain my credentials are correct for the repo (I have logged into and out of it on the website many times today to verify this). I have tried using my email instead of username. Nothing I try works to allow me to push back to the Bitbucket repo.

    Would it be possible for someone to try this and let me know what I am doing wrong, or add Bitbucket capability to dulwich if necessary? Thanks!!

    posted in Pythonista read more
  • dcl

    Has any progress been made with interfacing StaSh with Bitbucket? I am trying to share a project between my iPad and iPhone on Pythonista and I would like to use Bitbucket + StaSh to do revision control and be able to push from one device and pull from another. Bitbucket (as mentioned above) allows me to keep the project private without paying monthly for the service, and as this is a non-revenue project I do not wish to pay for monthly hosting.
    I have found, however, that StaSh isn't playing nicely with Bitbucket at the moment. Any advice?

    I tried the command:
    git clone https://myusername@bitbucket.org/myusername/myproj.git localFolder

    And got the error:
    class 'dulwich.errors.GitProtocolError'>: unexpected http response 401

    posted in Pythonista read more

Internal error.

Oops! Looks like something went wrong!