Last active
June 26, 2017 21:07
-
-
Save jsbain/2d9e24f31f471f629d56912ead624538 to your computer and use it in GitHub Desktop.
bezier_draw.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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
commented
Oct 24, 2016
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment