Skip to content

Instantly share code, notes, and snippets.

@jsbain jsbain/bezier_draw.py
Last active Jun 26, 2017

Embed
What would you like to do?
bezier_draw.py
# a simple bezier drawing program
# divide into interface (taps), drawing code, callbacks
# ui:
# top menu
#.
# drawing code:
# single tap, add a point
# tap + drag => addoint then create and drag locked handle
# dragging existing control point or point. move ''
#. tap an existing point:show handles
# todo:
# add pinch zoom
# add point to existing curve. delete points. lock handles
# callbacks
# "toolbar" with different tools ( shapes, pen, line, scribble, etc)
from gestures import Gestures
import ui
class State (object):
READY=0
ADDPOINT=1
MOVINGHANDLES=2
MOVINGPOINT=3
HIT_THRESHOLD=15
class BezierCanvas(ui.View):
def __init__(self):
self.points=[]
self.cps=[]
g=Gestures()
self.state=State.READY
self.active=-1
g.add_tap(self,self.handle_tap,number_of_taps_required=2)
g.add_long_press(self,self.handle_long_press,
allowable_movement=1000,
minimum_press_duration=0.001)
self.bg_color='white'
self.frame=[0,0,576,769]
self.g=g
def handle_tap(self,data):
if data.state == Gestures.ENDED:
if self.check_cp_hit(data.location):
self.cps[self.active][1-self.activecp] = 2*self.points[self.active] - self.cps[self.active][self.activecp]
self.set_needs_display()
def flash(self):
a=self.alpha
def dark():
self.alpha=0.9*a
def light():
self.alpha=a
ui.animate(dark,duration=0.01,completion=light)
def check_point_hit(self,location):
# FIND nearest point, or cp
for i,p in enumerate(self.points):
if abs(p-location)<=HIT_THRESHOLD:
self.active=i
return True
def check_cp_hit(self,location):
if not self.cps:
return
for j,cp in enumerate(self.cps[self.active]):
if abs(cp-location)<=HIT_THRESHOLD:
self.activecp=j
return True
def handle_long_press(self,data):
if data.state == Gestures.BEGAN:
#self.check_cp_hit
if self.check_cp_hit(data.location):
self.state=State.MOVINGHANDLES
return
if self.check_point_hit(data.location):
self.state=State.MOVINGPOINT
return
self.active=-1
self.points.append(data.location)
self.cps.append([data.location,data.location])
elif data.state == Gestures.CHANGED:
if self.state==State.MOVINGPOINT:
dp=data.location-self.points[self.active]
self.points[self.active]+=dp
self.cps[self.active][0]+=dp
self.cps[self.active][1]+=dp
elif self.state==State.MOVINGHANDLES:
self.cps[self.active][self.activecp]=data.location
else:
self.cps[self.active] = [2*self.points[self.active] - data.location, data.location]
else:
self.state=State.READY
self.set_needs_display()
def draw(self):
pth=ui.Path()
ui.set_color(self.tint_color)
for p in self.points:
ui.Path.oval(p.x-5,p.y-5,10,10).fill()
if len(self.points)>1:
pth=ui.Path()
pth.move_to(self.points[0].x,self.points[0].y)
for i in range(1,len(self.points)):
p1=self.points[i]
cp0=self.cps[i-1][1]
cp1=self.cps[i][0]
pth.add_curve(p1.x,p1.y,cp0.x,cp0.y,cp1.x,cp1.y)
pth.stroke()
lastcp=self.cps[self.active]
for p in lastcp:
ui.Path.oval(p.x-5,p.y-5,10,10).stroke()
pth=ui.Path()
pth.move_to(*lastcp[0])
pth.line_to(*self.points[self.active])
pth.line_to(*lastcp[1])
pth.set_line_dash([4, 3])
pth.stroke()
b=BezierCanvas()
b.name='Draw!'
b.present('sheet')
# coding: utf-8
#https://github.com/mikaelho/pythonista-gestures
import ui
from objc_util import *
import uuid
# https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/index.html#//apple_ref/occ/cl/UIGestureRecognizer
class Gestures():
POSSIBLE = 0
BEGAN = 1
RECOGNIZED = 1
CHANGED = 2
ENDED = 3
CANCELLED = 4
FAILED = 5
RIGHT = 1
LEFT = 2
UP = 4
DOWN = 8
EDGE_NONE = 0
EDGE_TOP = 1
EDGE_LEFT = 2
EDGE_BOTTOM = 4
EDGE_RIGHT = 8
EDGE_ALL = 15
def __init__(self, delegate=None, retain_global_reference = True):
self.buttons = {}
self.views = {}
self.recognizers = {}
self.actions = {}
self.delegate=delegate
if retain_global_reference:
retain_global(self)
def gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer_(
_self,_sel,gr,othergr):
try:
should_recog = self.delegate.recognizer_should_simultaneously_recognize
if callable(should_recog):
return should_recog(gr,othergr)
except AttributeError:
return False
self._delegate=create_objc_class('PythonistaGestureRecognizerDelegate',
superclass=NSObject,
methods=[gestureRecognizer_shouldRecognizeSimultaneouslyWithGestureRecognizer_],
classmethods=[],
protocols=['UIGestureRecognizerDelegate'],
debug=True).new()
@on_main_thread
def add_tap(self, view, action, number_of_taps_required = None, number_of_touches_required = None):
recog = self._get_recog('UITapGestureRecognizer', view, self._general_action, action)
if number_of_taps_required:
recog.numberOfTapsRequired = number_of_taps_required
if number_of_touches_required:
recog.numberOfTouchesRequired = number_of_touches_required
return recog
@on_main_thread
def add_long_press(self, view, action, number_of_taps_required = None, number_of_touches_required = None, minimum_press_duration = None, allowable_movement = None):
recog = self._get_recog('UILongPressGestureRecognizer', view, self._general_action, action)
if number_of_taps_required:
recog.numberOfTapsRequired = number_of_taps_required
if number_of_touches_required:
recog.numberOfTouchesRequired = number_of_touches_required
if minimum_press_duration:
recog.minimumPressDuration = minimum_press_duration
if allowable_movement:
recog.allowableMovement = allowable_movement
return recog
@on_main_thread
def add_pan(self, view, action, minimum_number_of_touches = None, maximum_number_of_touches = None, set_translation = None):
recog = self._get_recog('UIPanGestureRecognizer', view, self._pan_action, action)
if minimum_number_of_touches:
recog.minimumNumberOfTouches = minimum_number_of_touches
if maximum_number_of_touches:
recog.maximumNumberOfTouches = maximum_number_of_touches
if set_translation:
recog.set_translation_(CGPoint(set_translation.x, set_translation.y), ObjCInstance(view))
return recog
@on_main_thread
def add_screen_edge_pan(self, view, action, edges = None):
recog = self._get_recog('UIScreenEdgePanGestureRecognizer', view, self._pan_action, action)
if edges:
recog.edges = edges
return recog
@on_main_thread
def add_pinch(self, view, action):
recog = self._get_recog('UIPinchGestureRecognizer', view, self._pinch_action, action)
return recog
@on_main_thread
def add_rotation(self, view, action):
recog = self._get_recog('UIRotationGestureRecognizer', view, self._rotation_action, action)
return recog
@on_main_thread
def add_swipe(self, view, action, direction = None, number_of_touches_required = None):
recog = self._get_recog('UISwipeGestureRecognizer', view, self._general_action, action)
if direction:
combined_dir = direction
if isinstance(direction, list):
combined_dir = 0
for one_direction in direction:
combined_dir |= one_direction
recog.direction = combined_dir
if number_of_touches_required:
recog.numberOfTouchesRequired = number_of_touches_required
return recog
@on_main_thread
def remove(self, view, recognizer):
key = None
for id in self.recognizers:
if self.recognizers[id] == recognizer:
key = id
break
if key:
del self.buttons[key]
del self.views[key]
del self.recognizers[key]
del self.actions[key]
ObjCInstance(view).removeGestureRecognizer_(recognizer)
@on_main_thread
def enable(self, recognizer):
ObjCInstance(recognizer).enabled = True
@on_main_thread
def disable(self, recognizer):
ObjCInstance(recognizer).enabled = False
@on_main_thread
def remove_all_gestures(self, view):
gestures = ObjCInstance(view).gestureRecognizers()
for recog in gestures:
self.remove(view, recog)
def _get_recog(self, recog_name, view, internal_action, final_handler):
button = ui.Button()
key = str(uuid.uuid4())
button.name = key
button.action = internal_action
self.buttons[key] = button
self.views[key] = view
recognizer = ObjCClass(recog_name).alloc().initWithTarget_action_(button, sel('invokeAction:')).autorelease()
self.recognizers[key] = recognizer
self.actions[key] = final_handler
ObjCInstance(view).addGestureRecognizer_(recognizer)
recognizer.delegate=self._delegate
return recognizer
class Data():
def __init__(self):
self.recognizer = self.view = self.location = self.state = self.number_of_touches = self.scale = self.rotation = self.velocity = None
def _context(self, button):
key = button.name
(view, recog, action) = (self.views[key], self.recognizers[key], self.actions[key])
data = Gestures.Data()
data.recognizer = recog
data.view = view
data.location = self._location(view, recog)
data.state = recog.state()
data.number_of_touches = recog.numberOfTouches()
return (data, action)
def _location(self, view, recog):
loc = recog.locationInView_(ObjCInstance(view))
return ui.Point(loc.x, loc.y)
def _general_action(self, sender):
(data, action) = self._context(sender)
action(data)
def _pan_action(self, sender):
(data, action) = self._context(sender)
trans = data.recognizer.translationInView_(ObjCInstance(data.view))
vel = data.recognizer.velocityInView_(ObjCInstance(data.view))
data.translation = ui.Point(trans.x, trans.y)
data.velocity = ui.Point(vel.x, vel.y)
action(data)
def _pinch_action(self, sender):
(data, action) = self._context(sender)
data.scale = data.recognizer.scale()
data.velocity = data.recognizer.velocity()
action(data)
def _rotation_action(self, sender):
(data, action) = self._context(sender)
data.rotation = data.recognizer.rotation()
data.velocity = data.recognizer.velocity()
action(data)
# TESTING AND DEMONSTRATION
if __name__ == "__main__":
class EventDisplay(ui.View):
def __init__(self):
self.tv = ui.TextView(flex='WH')
self.tv.width=50
self.tv.height=50
self.add_subview(self.tv)
#self.tv.frame = (0, 0, self.width, self.height)
g = Gestures(delegate=self)
self.g=g
g.add_tap(self, self.general_handler)
g.add_long_press(self, self.long_handler)
# Pan disabled to test the function and to see swipe working
pan = g.add_pan(self, self.pan_handler)
#g.disable(pan)
#g.add_screen_edge_pan(self.tv, self.pan_handler, edges = Gestures.EDGE_LEFT)
#g.add_swipe(self.tv, self.general_handler, direction = [Gestures.DOWN])
g.add_pinch(self, self.pinch_handler)
#g.add_rotation(self.tv, self.rotation_handler)
def t(self, msg):
self.tv.text = self.tv.text + msg + '\n'
def general_handler(self, data):
self.t('General: ' + str(data.location) + ' - state: ' + str(data.state) + ' - touches: ' + str(data.number_of_touches))
def long_handler(self,data):
self.t('long')
def pan_handler(self, data):
self.t('Pan: ' + str(data.translation) + ' - state: ' + str(data.state))
def pinch_handler(self, data):
self.t('Pinch: ' + str(data.scale) + ' state: ' + str(data.state))
def rotation_handler(self, data):
self.t('Rotation: ' + str(data.rotation))
def recognizer_should_simultaneously_recognize(self,gr,ogr):
g=ObjCInstance(gr)
o=ObjCInstance(ogr)
ispinch=g._get_objc_classname()==b'UIPinchGestureRecognizer'
ispan=g._get_objc_classname()==b'UIPanGestureRecognizer'
if ispinch or ispan and g.view()==o.view():
return True
else:
return False
view = EventDisplay()
view.present('panel')
@cclauss

This comment has been minimized.

Copy link

commented Oct 24, 2016

self.t('General: ' + str(data.location) + ' - state: ' + str(data.state) + ' - touches: ' + str(data.number_of_touches))
# -->
self.t('General: {location} - state: {state} - touches: {number_of_touches}'.format(**data))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.