U
    PeZ                     @   s   d Z dZddlmZ ddlmZ ddlmZ ddlmZ ddl	m
Z
 ddlmZmZmZ dd	lmZmZmZmZ dd
lmZ dZG dd deZG dd de
ZdS )a  
Gesture Surface
===============

.. versionadded::
    1.9.0

.. warning::

    This is experimental and subject to change as long as this warning notice
    is present.

See :file:`kivy/examples/demo/multistroke/main.py` for a complete application
example.
)GestureSurfaceGestureContainer    )random)EventDispatcher)ClockVector)FloatLayout)ColorLine	Rectangle)NumericPropertyBooleanPropertyDictPropertyColorProperty)
hsv_to_rgb皙?c                       s   e Zd ZdZedZedZedZedZ	e
ededededdZedZedZ fdd	Zd
d Zdd ZdddZdd Zdd Zdd Zdd Z  ZS )r   aO  Container object that stores information about a gesture. It has
    various properties that are updated by `GestureSurface` as drawing
    progresses.

    :Arguments:
        `touch`
            Touch object (as received by on_touch_down) used to initialize
            the gesture container. Required.

    :Properties:
        `active`
            Set to False once the gesture is complete (meets
            `max_stroke` setting or `GestureSurface.temporal_window`)

            :attr:`active` is a
            :class:`~kivy.properties.BooleanProperty`

        `active_strokes`
            Number of strokes currently active in the gesture, ie
            concurrent touches associated with this gesture.

            :attr:`active_strokes` is a
            :class:`~kivy.properties.NumericProperty`

        `max_strokes`
            Max number of strokes allowed in the gesture. This
            is set by `GestureSurface.max_strokes` but can
            be overridden for example from `on_gesture_start`.

            :attr:`max_strokes` is a
            :class:`~kivy.properties.NumericProperty`

        `was_merged`
            Indicates that this gesture has been merged with another
            gesture and should be considered discarded.

            :attr:`was_merged` is a
            :class:`~kivy.properties.BooleanProperty`

        `bbox`
            Dictionary with keys minx, miny, maxx, maxy. Represents the size
            of the gesture bounding box.

            :attr:`bbox` is a
            :class:`~kivy.properties.DictProperty`

        `width`
            Represents the width of the gesture.

            :attr:`width` is a
            :class:`~kivy.properties.NumericProperty`

        `height`
            Represents the height of the gesture.

            :attr:`height` is a
            :class:`~kivy.properties.NumericProperty`
    Tr   Finfz-inf)minxminymaxxmaxyc                    sh   | ddddg| _tt| jf | t|j| _t	 | _
d | _d | _d| _d | _i | _| | d S )Ncolor      ?r   )popr   superr   __init__struididr   get_time_create_time_update_time_cleanup_time_cache_time_vectors_strokesupdate_bbox)selftouchkwargs	__class__ ;/tmp/pip-unpacked-wheel-xzebddm3/kivy/uix/gesturesurface.pyr   d   s    
zGestureContainer.__init__c              	   K   s~   | j | jkr|ds| jS g }|j}| j D ]:\}}|j}|dd t|ddd |ddd D  q0|| _| j| _ |S )a  Return strokes in a format that is acceptable for
        `kivy.multistroke.Recognizer` as a gesture candidate or template. The
        result is cached automatically; the cache is invalidated at the start
        and end of a stroke and if `update_bbox` is called. If you are going
        to analyze a gesture mid-stroke, you may need to set the `no_cache`
        argument to True.no_cachec                 S   s   g | ]}t | qS r-   r   ).0Zptsr-   r-   r.   
<listcomp>   s     z0GestureContainer.get_vectors.<locals>.<listcomp>N      )	r$   r"   getr%   appendr&   itemspointszip)r(   r*   Zvecsr5   tuidlZlptsr-   r-   r.   get_vectors}   s    .zGestureContainer.get_vectorsc                 C   s   | j s
dS t|j| jkS )z6Returns True if this container handles the given touchF)activer   r   r&   )r(   r)   r-   r-   r.   handles   s    zGestureContainer.handlesr3   c                 C   s   | j s
dS t| j| | j kS )z=Returns True if this container can accept `count` new strokesT)max_strokeslenr&   )r(   countr-   r-   r.   accept_stroke   s    zGestureContainer.accept_strokec                 C   s   |j |j }}| j}||d k r(||d< ||d k r<||d< ||d krP||d< ||d krd||d< |d |d  | _|d |d  | _t | _dS )z+Update gesture bbox from a touch coordinater   r   r   r   N)xybboxwidthheightr   r    r"   )r(   r)   rB   rC   bbr-   r-   r.   r'      s    zGestureContainer.update_bboxc                 C   s,   t  | _|| jt|j< |  jd7  _dS )zAssociate a list of points with a touch.uid; the line itself is
        created by the caller, but subsequent move/up events look it
        up via us. This is done to avoid problems during merge.r3   N)r   r    r"   r&   r   r   active_strokes)r(   r)   liner-   r-   r.   
add_stroke   s    
zGestureContainer.add_strokec                 C   s   t  | _|  jd8  _dS )zCalled on touch up events to keep track of how many strokes
        are active in the gesture (we only want to dispatch event when
        the *last* stroke in the gesture is released)r3   N)r   r    r"   rH   )r(   r-   r-   r.   complete_stroke   s    
z GestureContainer.complete_strokec                 C   s,   | j  D ]\}}t|jdkr
 dS q
dS )zReturns True if the gesture consists only of single-point strokes,
        we must discard it in this case, or an exception will be raisedr2   FT)r&   r6   r?   r7   )r(   r9   r:   r-   r-   r.   single_points_test   s    z#GestureContainer.single_points_test)r3   )__name__
__module____qualname____doc__r   r<   r   rH   r>   
was_mergedr   floatrD   rE   rF   r   r;   r=   rA   r'   rJ   rK   rL   __classcell__r-   r-   r+   r.   r       s&   : 
r   c                       s   e Zd ZdZedZedZedZedZedZ	e
ddddgZedZedZed	Z fd
dZdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zdd Zd d! Zd"d# Zd$d% Zd&d' Zd(d) Zd*d+ Z d,d- Z!  Z"S ).r   a  Simple gesture surface to track/draw touch movements. Typically used
    to gather user input suitable for :class:`kivy.multistroke.Recognizer`.

    :Properties:
        `temporal_window`
            Time to wait from the last touch_up event before attempting
            to recognize the gesture. If you set this to 0, the
            `on_gesture_complete` event is not fired unless the
            :attr:`max_strokes` condition is met.

            :attr:`temporal_window` is a
            :class:`~kivy.properties.NumericProperty` and defaults to 2.0

        `max_strokes`
            Max number of strokes in a single gesture; if this is reached,
            recognition will start immediately on the final touch_up event.
            If this is set to 0, the `on_gesture_complete` event is not
            fired unless the :attr:`temporal_window` expires.

            :attr:`max_strokes` is a
            :class:`~kivy.properties.NumericProperty` and defaults to 2.0

        `bbox_margin`
            Bounding box margin for detecting gesture collisions, in
            pixels.

            :attr:`bbox_margin` is a
            :class:`~kivy.properties.NumericProperty` and defaults to 30

        `draw_timeout`
            Number of seconds to keep lines/bbox on canvas after the
            `on_gesture_complete` event is fired. If this is set to 0,
            gestures are immediately removed from the surface when
            complete.

            :attr:`draw_timeout` is a
            :class:`~kivy.properties.NumericProperty` and defaults to 3.0

        `color`
            Color used to draw the gesture, in RGB. This option does not
            have an effect if :attr:`use_random_color` is True.

            :attr:`color` is a
            :class:`~kivy.properties.ColorProperty` and defaults to
            [1, 1, 1, 1] (white)

            .. versionchanged:: 2.0.0
                Changed from :class:`~kivy.properties.ListProperty` to
                :class:`~kivy.properties.ColorProperty`.

        `use_random_color`
            Set to True to pick a random color for each gesture, if you do
            this then `color` is ignored. Defaults to False.

            :attr:`use_random_color` is a
            :class:`~kivy.properties.BooleanProperty` and defaults to False

        `line_width`
            Line width used for tracing touches on the surface. Set to 0
            if you only want to detect gestures without drawing anything.
            If you use 1.0, OpenGL GL_LINE is used for drawing; values > 1
            will use an internal drawing method based on triangles (less
            efficient), see :mod:`kivy.graphics`.

            :attr:`line_width` is a
            :class:`~kivy.properties.NumericProperty` and defaults to 2

        `draw_bbox`
            Set to True if you want to draw bounding box behind gestures.
            This only works if `line_width` >= 1. Default is False.

            :attr:`draw_bbox` is a
            :class:`~kivy.properties.BooleanProperty` and defaults to True

        `bbox_alpha`
            Opacity for bounding box if `draw_bbox` is True. Default 0.1

            :attr:`bbox_alpha` is a
            :class:`~kivy.properties.NumericProperty` and defaults to 0.1

    :Events:
        `on_gesture_start` :class:`GestureContainer`
            Fired when a new gesture is initiated on the surface, i.e. the
            first on_touch_down that does not collide with an existing
            gesture on the surface.

        `on_gesture_extend` :class:`GestureContainer`
            Fired when a touch_down event occurs within an existing gesture.

        `on_gesture_merge` :class:`GestureContainer`, :class:`GestureContainer`
            Fired when two gestures collide and get merged to one gesture.
            The first argument is the gesture that has been merged (no longer
            valid); the second is the combined (resulting) gesture.

        `on_gesture_complete` :class:`GestureContainer`
            Fired when a set of strokes is considered a complete gesture,
            this happens when `temporal_window` expires or `max_strokes`
            is reached. Typically you will bind to this event and use
            the provided `GestureContainer` get_vectors() method to
            match against your gesture database.

        `on_gesture_cleanup` :class:`GestureContainer`
            Fired `draw_timeout` seconds after `on_gesture_complete`,
            The gesture will be removed from the canvas (if line_width > 0 or
            draw_bbox is True) and the internal gesture list before this.

        `on_gesture_discard` :class:`GestureContainer`
            Fired when a gesture does not meet the minimum size requirements
            for recognition (width/height < 5, or consists only of single-
            point strokes).
    g       @g      @      r2   r   Fr   c                    sX   t t| jf | g | _| d | d | d | d | d | d d S )Non_gesture_starton_gesture_extendon_gesture_mergeon_gesture_completeon_gesture_cleanupon_gesture_discard)r   r   r   	_gesturesZregister_event_type)r(   r*   r+   r-   r.   r   B  s    




zGestureSurface.__init__c                 C   st   |  |j|jsdS ||  | |}d}|dkrB| |}d}| || |rb| d|| n| d|| dS )zWhen a new touch is registered, the first thing we do is to test if
        it collides with the bounding box of another known gesture. If so, it
        is assumed to be part of that gesture.
        NFTrV   rW   )collide_pointrB   rC   Zgrabfind_colliding_gestureinit_gestureinit_strokedispatch)r(   r)   gnewr-   r-   r.   on_touch_downP  s    


zGestureSurface.on_touch_downc                 C   s   |j | k	rdS | |j|js"dS | |}| |}|dk	r|t|jr| 	||}|j
rp| d|| n| d|| |}n
|| |jt|j  j|j|jf7  _| jr| | dS )zWhen a touch moves, we add a point to the line on the canvas so the
        path is updated. We must also check if the new point collides with the
        bounding box of another gesture - if so, they should be merged.NrX   T)grab_currentr]   rB   rC   get_gesturer^   rA   r?   r&   merge_gesturesrQ   ra   r'   r   r   r7   	draw_bbox_update_canvas_bbox)r(   r)   rb   Z	collisionmerger-   r-   r.   on_touch_movel  s"    



"
zGestureSurface.on_touch_movec                 C   s\   |j | k	rd S ||  | |}|  | s>| d n| jdkrXt| j| j d S )Nr   )	re   Zungrabrf   rK   rA   _complete_dispatchertemporal_windowr   schedule_oncer(   r)   rb   r-   r-   r.   on_touch_up  s    



zGestureSurface.on_touch_upc              	   C   s   | j }| jrtt dd}t|| j|d}| jr|j}| jd t	|d |d |d | j
d|jd t|j|d |d	 f|d
 |d  |d |d	  fd|_W 5 Q R X | j| |S )z~Create a new gesture from touch, i.e. it's the first on
        surface, or was not close enough to any existing gesture (yet)r   )r>   r   r   r3   r2   Zrgbamodegroupr   r   r   r   )rs   possize)r   use_random_colorr   r   r   r>   rh   rD   canvasr
   
bbox_alphar   r   _bbrectr\   r5   )r(   r)   colrb   rG   r-   r-   r.   r_     s&    zGestureSurface.init_gesturec                 C   s   |j |jg}|j}t|| j|jd}||jt|j< | jrn| j	j
}|t|d |d |d d|jd || || | jr| | ||| d S )Nr7   rE   rs   r   r3   r2   rgbrq   )rB   rC   r   r   
line_widthr   r&   r   r   rw   addr
   r'   rh   ri   rJ   )r(   rb   r)   r7   rz   new_lineZ
canvas_addr-   r-   r.   r`     s     $

zGestureSurface.init_strokec                 C   s:   | j D ]}|jr||r|  S qtdt|j dS )z4Returns GestureContainer associated with given touchz!get_gesture() failed to identify N)r\   r<   r=   	Exceptionr   r   ro   r-   r-   r.   rf     s    

zGestureSurface.get_gesturec                 C   s   |j \}}| jD ]}|jr||s| r|j}| j}|d | }|d | }|d | }	|d | }
||  kr||	krn q||  kr|
krn q|  S qdS )zChecks if a touch x/y collides with the bounding box of an existing
        gesture. If so, return it (otherwise returns None)
        r   r   r   r   N)rt   r\   r<   r=   rA   rD   bbox_margin)r(   r)   Ztouch_xZtouch_yrb   rG   marginr   r   r   r   r-   r-   r.   r^     s    

0
z%GestureSurface.find_colliding_gesturec              	   C   sT  |j |j k }|r|p|}|r |p"|}|j}|j}|d |d k rL|d |d< |d |d k rh|d |d< |d |d kr|d |d< |d |d kr|d |d< |j}| j}	|j}
|j}| j|j | jj}|j	 D ]P\}}t
|j|j|
d}|||< |	r|t|d |d |d d	|
d
 || qd|_d|_| j|j7  _t |_|S )zMerges two gestures together, the oldest one is retained and the
        newer one gets the `GestureContainer.was_merged` flag raised.r   r   r   r   r{   r   r3   r2   r|   rq   FT)r!   rD   r&   r}   r   r   rw   remove_groupr~   r6   r   r7   rE   r
   r<   rQ   rH   r   r    r"   )r(   rb   otherZswapabZabboxZbbboxZastrokesZlwZa_idrz   Zcanv_addr   oldr   r-   r-   r.   rg     sD    "

zGestureSurface.merge_gesturesc                 C   sP   t |dsd S |j}|d |d f|j_|d |d  |d |d  f|j_d S )Nry   r   r   r   r   )hasattrrD   ry   rt   ru   )r(   rb   rG   r-   r-   r.   ri     s    
z"GestureSurface._update_canvas_bboxc                 C   s   d}| j }| j}| j}tj}t|D ]\}}|jr:||= q$|jr$|jdkrLq$|j	| }	| t
 }
| rp|	|
kr$d}|jdk r|jdk rd}n| rd}d}d|_| | |_|r| d| q$| d| q$|rt| j| dS )zThis method is scheduled on all touch up events. It will dispatch
        the `on_gesture_complete` event for all completed gestures, and remove
        merged gestures from the internal gesture list.Fr      Tr[   rY   N)r\   draw_timeoutrm   r   r    	enumeraterQ   r<   rH   r"   UNDERSHOOT_MARGINrA   rE   rF   rL   r#   ra   rn   _cleanup)r(   dtZneed_cleanupZgesttimeoutZtwinr    idxrb   t1t2discardr-   r-   r.   rl   &  s6    

z#GestureSurface._complete_dispatcherc                 C   sb   t }| jj}| j}t|D ]B\}}|jdkr.q|jt | kr||j ||= | 	d| qdS )zThis method is scheduled from _complete_dispatcher to clean up the
        canvas and internal gesture list after a gesture is completed.NrZ   )
r   rw   r   r\   r   r#   r   r    r   ra   )r(   r   mZrgZgesturesr   rb   r-   r-   r.   r   S  s    

zGestureSurface._cleanupc                 G   s   d S Nr-   r(   r:   r-   r-   r.   rV   a  s    zGestureSurface.on_gesture_startc                 G   s   d S r   r-   r   r-   r-   r.   rW   d  s    z GestureSurface.on_gesture_extendc                 G   s   d S r   r-   r   r-   r-   r.   rX   g  s    zGestureSurface.on_gesture_mergec                 G   s   d S r   r-   r   r-   r-   r.   rY   j  s    z"GestureSurface.on_gesture_completec                 G   s   d S r   r-   r   r-   r-   r.   r[   m  s    z!GestureSurface.on_gesture_discardc                 G   s   d S r   r-   r   r-   r-   r.   rZ   p  s    z!GestureSurface.on_gesture_cleanup)#rM   rN   rO   rP   r   rm   r   r>   r   r}   r   r   r   rv   rh   rx   r   rd   rk   rp   r_   r`   rf   r^   rg   ri   rl   r   rV   rW   rX   rY   r[   rZ   rS   r-   r-   r+   r.   r      s8   p /-r   N)rP   __all__r   Z
kivy.eventr   Z
kivy.clockr   Zkivy.vectorr   Zkivy.uix.floatlayoutr	   Zkivy.graphicsr
   r   r   Zkivy.propertiesr   r   r   r   Zcolorsysr   r   r   r   r-   r-   r-   r.   <module>   s    '