<s> '''<STR_LIT>''' <EOL> __all__ = ( '<STR_LIT>' , '<STR_LIT>' ) <EOL> from random import random <EOL> from kivy . event import EventDispatcher <EOL> from kivy . clock import Clock <EOL> from kivy . vector import Vector <EOL> from kivy . uix . floatlayout import FloatLayout <EOL> from kivy . graphics import Color , Line , Rectangle <EOL> from kivy . properties import ( NumericProperty , BooleanProperty , <EOL> DictProperty , ListProperty ) <EOL> from colorsys import hsv_to_rgb <EOL> UNDERSHOOT_MARGIN = <NUM_LIT:0.1> <EOL> class GestureContainer ( EventDispatcher ) : <EOL> '''<STR_LIT>''' <EOL> active = BooleanProperty ( True ) <EOL> active_strokes = NumericProperty ( <NUM_LIT:0> ) <EOL> max_strokes = NumericProperty ( <NUM_LIT:0> ) <EOL> was_merged = BooleanProperty ( False ) <EOL> bbox = DictProperty ( { '<STR_LIT>' : float ( '<STR_LIT>' ) , '<STR_LIT>' : float ( '<STR_LIT>' ) , <EOL> '<STR_LIT>' : float ( '<STR_LIT>' ) , '<STR_LIT>' : float ( '<STR_LIT>' ) } ) <EOL> width = NumericProperty ( <NUM_LIT:0> ) <EOL> height = NumericProperty ( <NUM_LIT:0> ) <EOL> def __init__ ( self , touch , ** kwargs ) : <EOL> self . color = kwargs . pop ( '<STR_LIT>' , [ <NUM_LIT:1.> , <NUM_LIT:1.> , <NUM_LIT:1.> ] ) <EOL> super ( GestureContainer , self ) . __init__ ( ** kwargs ) <EOL> self . id = str ( touch . uid ) <EOL> self . _create_time = Clock . get_time ( ) <EOL> self . _update_time = None <EOL> self . _cleanup_time = None <EOL> self . _cache_time = <NUM_LIT:0> <EOL> self . _vectors = None <EOL> self . _strokes = { } <EOL> self . update_bbox ( touch ) <EOL> def get_vectors ( self , ** kwargs ) : <EOL> '''<STR_LIT>''' <EOL> if self . _cache_time == self . _update_time and not kwargs . get ( '<STR_LIT>' ) : <EOL> return self . _vectors <EOL> vecs = [ ] <EOL> append = vecs . append <EOL> for tuid , l in self . _strokes . items ( ) : <EOL> lpts = l . points <EOL> append ( [ Vector ( * pts ) for pts in zip ( lpts [ : : <NUM_LIT:2> ] , lpts [ <NUM_LIT:1> : : <NUM_LIT:2> ] ) ] ) <EOL> self . _vectors = vecs <EOL> self . _cache_time = self . _update_time <EOL> return vecs <EOL> def handles ( self , touch ) : <EOL> '''<STR_LIT>''' <EOL> if not self . active : <EOL> return False <EOL> return str ( touch . uid ) in self . _strokes <EOL> def accept_stroke ( self , count = <NUM_LIT:1> ) : <EOL> '''<STR_LIT>''' <EOL> if not self . max_strokes : <EOL> return True <EOL> return len ( self . _strokes ) + count <= self . max_strokes <EOL> def update_bbox ( self , touch ) : <EOL> '''<STR_LIT>''' <EOL> x , y = touch . x , touch . y <EOL> bb = self . bbox <EOL> if x < bb [ '<STR_LIT>' ] : <EOL> bb [ '<STR_LIT>' ] = x <EOL> if y < bb [ '<STR_LIT>' ] : <EOL> bb [ '<STR_LIT>' ] = y <EOL> if x > bb [ '<STR_LIT>' ] : <EOL> bb [ '<STR_LIT>' ] = x <EOL> if y > bb [ '<STR_LIT>' ] : <EOL> bb [ '<STR_LIT>' ] = y <EOL> self . width = bb [ '<STR_LIT>' ] - bb [ '<STR_LIT>' ] <EOL> self . height = bb [ '<STR_LIT>' ] - bb [ '<STR_LIT>' ] <EOL> self . _update_time = Clock . get_time ( ) <EOL> def add_stroke ( self , touch , line ) : <EOL> '''<STR_LIT>''' <EOL> self . _update_time = Clock . get_time ( ) <EOL> self . _strokes [ str ( touch . uid ) ] = line <EOL> self . active_strokes += <NUM_LIT:1> <EOL> def complete_stroke ( self ) : <EOL> '''<STR_LIT>''' <EOL> self . _update_time = Clock . get_time ( ) <EOL> self . active_strokes -= <NUM_LIT:1> <EOL> def single_points_test ( self ) : <EOL> '''<STR_LIT>''' <EOL> for tuid , l in self . _strokes . items ( ) : <EOL> if len ( l . points ) > <NUM_LIT:2> : <EOL> return False <EOL> return True <EOL> class GestureSurface ( FloatLayout ) : <EOL> '''<STR_LIT>''' <EOL> temporal_window = NumericProperty ( <NUM_LIT> ) <EOL> draw_timeout = NumericProperty ( <NUM_LIT> ) <EOL> max_strokes = NumericProperty ( <NUM_LIT:4> ) <EOL> bbox_margin = NumericProperty ( <NUM_LIT:30> ) <EOL> line_width = NumericProperty ( <NUM_LIT:2> ) <EOL> color = ListProperty ( [ <NUM_LIT:1.> , <NUM_LIT:1.> , <NUM_LIT:1.> ] ) <EOL> use_random_color = BooleanProperty ( False ) <EOL> draw_bbox = BooleanProperty ( False ) <EOL> bbox_alpha = NumericProperty ( <NUM_LIT:0.1> ) <EOL> def __init__ ( self , ** kwargs ) : <EOL> super ( GestureSurface , self ) . __init__ ( ** kwargs ) <EOL> self . _gestures = [ ] <EOL> self . register_event_type ( '<STR_LIT>' ) <EOL> self . register_event_type ( '<STR_LIT>' ) <EOL> self . register_event_type ( '<STR_LIT>' ) <EOL> self . register_event_type ( '<STR_LIT>' ) <EOL> self . register_event_type ( '<STR_LIT>' ) <EOL> self . register_event_type ( '<STR_LIT>' ) <EOL> def on_touch_down ( self , touch ) : <EOL> '''<STR_LIT>''' <EOL> if not self . collide_point ( touch . x , touch . y ) : <EOL> return <EOL> touch . grab ( self ) <EOL> g = self . find_colliding_gesture ( touch ) <EOL> new = False <EOL> if g is None : <EOL> g = self . init_gesture ( touch ) <EOL> new = True <EOL> self . init_stroke ( g , touch ) <EOL> if new : <EOL> self . dispatch ( '<STR_LIT>' , g , touch ) <EOL> else : <EOL> self . dispatch ( '<STR_LIT>' , g , touch ) <EOL> return True <EOL> def on_touch_move ( self , touch ) : <EOL> '''<STR_LIT>''' <EOL> if touch . grab_current is not self : <EOL> return <EOL> if not self . collide_point ( touch . y , touch . y ) : <EOL> return <EOL> g = self . get_gesture ( touch ) <EOL> collision = self . find_colliding_gesture ( touch ) <EOL> if collision is not None and g . accept_stroke ( len ( collision . _strokes ) ) : <EOL> merge = self . merge_gestures ( g , collision ) <EOL> if g . was_merged : <EOL> self . dispatch ( '<STR_LIT>' , g , collision ) <EOL> else : <EOL> self . dispatch ( '<STR_LIT>' , collision , g ) <EOL> g = merge <EOL> else : <EOL> g . update_bbox ( touch ) <EOL> g . _strokes [ str ( touch . uid ) ] . points += ( touch . x , touch . y ) <EOL> if self . draw_bbox : <EOL> self . _update_canvas_bbox ( g ) <EOL> return True <EOL> def on_touch_up ( self , touch ) : <EOL> if touch . grab_current is not self : <EOL> return <EOL> touch . ungrab ( self ) <EOL> g = self . get_gesture ( touch ) <EOL> g . complete_stroke ( ) <EOL> if not g . accept_stroke ( ) : <EOL> self . _complete_dispatcher ( <NUM_LIT:0> ) <EOL> elif self . temporal_window > <NUM_LIT:0> : <EOL> Clock . schedule_once ( self . _complete_dispatcher , self . temporal_window ) <EOL> def init_gesture ( self , touch ) : <EOL> '''<STR_LIT>''' <EOL> col = self . color <EOL> if self . use_random_color is True : <EOL> col = hsv_to_rgb ( random ( ) , <NUM_LIT:1.> , <NUM_LIT:1.> ) <EOL> g = GestureContainer ( touch , max_strokes = self . max_strokes , color = col ) <EOL> if self . draw_bbox : <EOL> bb = g . bbox <EOL> with self . canvas : <EOL> Color ( col [ <NUM_LIT:0> ] , col [ <NUM_LIT:1> ] , col [ <NUM_LIT:2> ] , self . bbox_alpha , mode = '<STR_LIT>' , <EOL> group = g . id ) <EOL> g . _bbrect = Rectangle ( <EOL> group = g . id , <EOL> pos = ( bb [ '<STR_LIT>' ] , bb [ '<STR_LIT>' ] ) , <EOL> size = ( bb [ '<STR_LIT>' ] - bb [ '<STR_LIT>' ] , <EOL> bb [ '<STR_LIT>' ] - bb [ '<STR_LIT>' ] ) ) <EOL> self . _gestures . append ( g ) <EOL> return g <EOL> def init_stroke ( self , g , touch ) : <EOL> l = [ touch . x , touch . y ] <EOL> col = g . color <EOL> new_line = Line ( <EOL> points = l , <EOL> width = self . line_width , <EOL> group = g . id ) <EOL> g . _strokes [ str ( touch . uid ) ] = new_line <EOL> if self . line_width : <EOL> canvas_add = self . canvas . add <EOL> canvas_add ( Color ( col [ <NUM_LIT:0> ] , col [ <NUM_LIT:1> ] , col [ <NUM_LIT:2> ] , mode = '<STR_LIT>' , group = g . id ) ) <EOL> canvas_add ( new_line ) <EOL> g . update_bbox ( touch ) <EOL> if self . draw_bbox : <EOL> self . _update_canvas_bbox ( g ) <EOL> g . add_stroke ( touch , new_line ) <EOL> def get_gesture ( self , touch ) : <EOL> '''<STR_LIT>''' <EOL> for g in self . _gestures : <EOL> if g . active and g . handles ( touch ) : <EOL> return g <EOL> raise Exception ( '<STR_LIT>' + str ( touch . uid ) ) <EOL> def find_colliding_gesture ( self , touch ) : <EOL> '''<STR_LIT>''' <EOL> touch_x , touch_y = touch . pos <EOL> for g in self . _gestures : <EOL> if g . active and not g . handles ( touch ) and g . accept_stroke ( ) : <EOL> bb = g . bbox <EOL> margin = self . bbox_margin <EOL> minx = bb [ '<STR_LIT>' ] - margin <EOL> miny = bb [ '<STR_LIT>' ] - margin <EOL> maxx = bb [ '<STR_LIT>' ] + margin <EOL> maxy = bb [ '<STR_LIT>' ] + margin <EOL> if minx <= touch_x <= maxx and miny <= touch_y <= maxy : <EOL> return g <EOL> return None <EOL> def merge_gestures ( self , g , other ) : <EOL> '''<STR_LIT>''' <EOL> swap = other . _create_time < g . _create_time <EOL> a = swap and other or g <EOL> b = swap and g or other <EOL> abbox = a . bbox <EOL> bbbox = b . bbox <EOL> if bbbox [ '<STR_LIT>' ] < abbox [ '<STR_LIT>' ] : <EOL> abbox [ '<STR_LIT>' ] = bbbox [ '<STR_LIT>' ] <EOL> if bbbox [ '<STR_LIT>' ] < abbox [ '<STR_LIT>' ] : <EOL> abbox [ '<STR_LIT>' ] = bbbox [ '<STR_LIT>' ] <EOL> if bbbox [ '<STR_LIT>' ] > abbox [ '<STR_LIT>' ] : <EOL> abbox [ '<STR_LIT>' ] = bbbox [ '<STR_LIT>' ] <EOL> if bbbox [ '<STR_LIT>' ] > abbox [ '<STR_LIT>' ] : <EOL> abbox [ '<STR_LIT>' ] = bbbox [ '<STR_LIT>' ] <EOL> astrokes = a . _strokes <EOL> lw = self . line_width <EOL> a_id = a . id <EOL> col = a . color <EOL> self . canvas . remove_group ( b . id ) <EOL> canv_add = self . canvas . add <EOL> for uid , old in b . _strokes . items ( ) : <EOL> new_line = Line ( <EOL> points = old . points , <EOL> width = old . width , <EOL> group = a_id ) <EOL> astrokes [ uid ] = new_line <EOL> if lw : <EOL> canv_add ( Color ( col [ <NUM_LIT:0> ] , col [ <NUM_LIT:1> ] , col [ <NUM_LIT:2> ] , mode = '<STR_LIT>' , group = a_id ) ) <EOL> canv_add ( new_line ) <EOL> b . active = False <EOL> b . was_merged = True <EOL> a . active_strokes += b . active_strokes <EOL> a . _update_time = Clock . get_time ( ) <EOL> return a <EOL> def _update_canvas_bbox ( self , g ) : <EOL> if not hasattr ( g , '<STR_LIT>' ) : <EOL> return <EOL> bb = g . bbox <EOL> g . _bbrect . pos = ( bb [ '<STR_LIT>' ] , bb [ '<STR_LIT>' ] ) <EOL> g . _bbrect . size = ( bb [ '<STR_LIT>' ] - bb [ '<STR_LIT>' ] , <EOL> bb [ '<STR_LIT>' ] - bb [ '<STR_LIT>' ] ) <EOL> def _complete_dispatcher ( self , dt ) : <EOL> '''<STR_LIT>''' <EOL> need_cleanup = False <EOL> gest = self . _gestures <EOL> timeout = self . draw_timeout <EOL> twin = self . temporal_window <EOL> get_time = Clock . get_time <EOL> for idx , g in enumerate ( gest ) : <EOL> if g . was_merged : <EOL> del gest [ idx ] <EOL> continue <EOL> if not g . active or g . active_strokes != <NUM_LIT:0> : <EOL> continue <EOL> t1 = g . _update_time + twin <EOL> t2 = get_time ( ) + UNDERSHOOT_MARGIN <EOL> if not g . accept_stroke ( ) or t1 <= t2 : <EOL> discard = False <EOL> if g . width < <NUM_LIT:5> and g . height < <NUM_LIT:5> : <EOL> discard = True <EOL> elif g . single_points_test ( ) : <EOL> discard = True <EOL> need_cleanup = True <EOL> g . active = False <EOL> g . _cleanup_time = get_time ( ) + timeout <EOL> if discard : <EOL> self . dispatch ( '<STR_LIT>' , g ) <EOL> else : <EOL> self . dispatch ( '<STR_LIT>' , g ) <EOL> if need_cleanup : <EOL> Clock . schedule_once ( self . _cleanup , timeout ) <EOL> def _cleanup ( self , dt ) : <EOL> '''<STR_LIT>''' <EOL> m = UNDERSHOOT_MARGIN <EOL> rg = self . canvas . remove_group <EOL> gestures = self . _gestures <EOL> for idx , g in enumerate ( gestures ) : <EOL> if g . _cleanup_time is None : <EOL> continue <EOL> if g . _cleanup_time <= Clock . get_time ( ) + m : <EOL> rg ( g . id ) <EOL> del gestures [ idx ] <EOL> self . dispatch ( '<STR_LIT>' , g ) <EOL> def on_gesture_start ( self , * l ) : <EOL> pass <EOL> def on_gesture_extend ( self , * l ) : <EOL> pass <EOL> def on_gesture_merge ( self , * l ) : <EOL> pass <EOL> def on_gesture_complete ( self , * l ) : <EOL> pass <EOL> def on_gesture_discard ( self , * l ) : <EOL> pass <EOL> def on_gesture_cleanup ( self , * l ) : <EOL> pass </s>
