Exemple #1
1
    def __init__(self, **kwargs):
        # XXX move to style.kv
        if 'size_hint' not in kwargs:
            if 'size_hint_x' not in kwargs:
                self.size_hint_x = None
            if 'size_hint_y' not in kwargs:
                self.size_hint_y = None
        if 'size' not in kwargs:
            if 'width' not in kwargs:
                self.width = 700
            if 'height' not in kwargs:
                self.height = 200
        if 'scale_min' not in kwargs:
            self.scale_min = .4
        if 'scale_max' not in kwargs:
            self.scale_max = 1.6
        if 'docked' not in kwargs:
            self.docked = False

        layout_mode = self._trigger_update_layout_mode = Clock.create_trigger(
            self._update_layout_mode)
        layouts = self._trigger_load_layouts = Clock.create_trigger(
            self._load_layouts)
        layout = self._trigger_load_layout = Clock.create_trigger(
            self._load_layout)
        fbind = self.fbind

        fbind('docked', self.setup_mode)
        fbind('have_shift', layout_mode)
        fbind('have_capslock', layout_mode)
        fbind('have_special', layout_mode)
        fbind('layout_path', layouts)
        fbind('layout', layout)
        super(VKeyboard, self).__init__(**kwargs)

        # load all the layouts found in the layout_path directory
        self._load_layouts()

        # ensure we have default layouts
        available_layouts = self.available_layouts
        if not available_layouts:
            Logger.critical('VKeyboard: unable to load default layouts')

        # load the default layout from configuration
        if self.layout is None:
            self.layout = Config.get('kivy', 'keyboard_layout')
        else:
            # ensure the current layout is found on the available layout
            self._trigger_load_layout()

        # update layout mode (shift or normal)
        self._trigger_update_layout_mode()

        # create a top layer to draw active keys on
        with self.canvas:
            self.background_key_layer = Canvas()
            self.active_keys_layer = Canvas()
Exemple #2
0
 def __init__(self, **kwargs):
     super(GeoJsonMapLayer, self).__init__(**kwargs)
     with self.canvas:
         self.canvas_polygon = Canvas()
         self.canvas_line = Canvas()
     with self.canvas_polygon.before:
         PushMatrix()
         self.g_matrix = MatrixInstruction()
         self.g_scale = Scale()
         self.g_translate = Translate()
     with self.canvas_polygon:
         self.g_canvas_polygon = Canvas()
     with self.canvas_polygon.after:
         PopMatrix()
Exemple #3
0
    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(
            self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas
        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

        super(ScrollView, self).__init__(**kwargs)

        self.register_event_type('on_scroll_start')
        self.register_event_type('on_scroll_move')
        self.register_event_type('on_scroll_stop')

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)

        trigger_update_from_scroll = self._trigger_update_from_scroll
        update_effect_widget = self._update_effect_widget
        update_effect_x_bounds = self._update_effect_x_bounds
        update_effect_y_bounds = self._update_effect_y_bounds
        fbind = self.fbind
        fbind('width', update_effect_x_bounds)
        fbind('height', update_effect_y_bounds)
        fbind('viewport_size', self._update_effect_bounds)
        fbind('_viewport', update_effect_widget)
        fbind('scroll_x', trigger_update_from_scroll)
        fbind('scroll_y', trigger_update_from_scroll)
        fbind('pos', trigger_update_from_scroll)
        fbind('size', trigger_update_from_scroll)

        trigger_update_from_scroll()
        update_effect_widget()
        update_effect_x_bounds()
        update_effect_y_bounds()
Exemple #4
0
    def __init__(self, **kwargs):
        # XXX move to style.kv
        kwargs.setdefault('size_hint', (None, None))
        kwargs.setdefault('scale_min', .4)
        kwargs.setdefault('scale_max', 1.6)
        kwargs.setdefault('size', (700, 200))
        kwargs.setdefault('docked', False)
        self._trigger_update_layout_mode = Clock.create_trigger(
            self._update_layout_mode)
        self._trigger_load_layouts = Clock.create_trigger(
            self._load_layouts)
        self._trigger_load_layout = Clock.create_trigger(
            self._load_layout)
        self.bind(
            docked=self.setup_mode,
            have_shift=self._trigger_update_layout_mode,
            have_capslock=self._trigger_update_layout_mode,
            layout_path=self._trigger_load_layouts,
            layout=self._trigger_load_layout)
        self.register_event_type('on_key_down')
        self.register_event_type('on_key_up')
        super(VKeyboard, self).__init__(**kwargs)

        # load all the layouts found in the layout_path directory
        self._load_layouts()

        # ensure we have default layouts
        available_layouts = self.available_layouts
        if not available_layouts:
            Logger.critical('VKeyboard: unable to load default layouts')

        # load the default layout from configuration
        if self.layout is None:
            self.layout = Config.get('kivy', 'keyboard_layout')
        else:
            # ensure the current layout is found on the available layout
            self._trigger_load_layout()

        # update layout mode (shift or normal)
        self._trigger_update_layout_mode()

        # create a top layer to draw active keys on
        with self.canvas:
            self.background_key_layer = Canvas()
            self.active_keys_layer = Canvas()

        # prepare layout widget
        self.refresh_keys_hint()
        self.refresh_keys()
Exemple #5
0
    def create_window(self, *largs):
        '''Will create the main window and configure it.

        .. warning::
            This method is called automatically at runtime. If you call it, it
            will recreate a RenderContext and Canvas. This means you'll have a
            new graphics tree, and the old one will be unusable.

            This method exist to permit the creation of a new OpenGL context
            AFTER closing the first one. (Like using runTouchApp() and
            stopTouchApp()).

            This method has only been tested in a unittest environment and
            is not suitable for Applications.

            Again, don't use this method unless you know exactly what you are
            doing!
        '''
        # just to be sure, if the trigger is set, and if this method is
        # manually called, unset the trigger
        Clock.unschedule(self.create_window)

        if not self.initialized:
            from kivy.core.gl import init_gl
            init_gl()

            # create the render context and canvas, only the first time.
            from kivy.graphics import RenderContext, Canvas
            self.render_context = RenderContext()
            self.canvas = Canvas()
            self.render_context.add(self.canvas)

        else:
            # if we get initialized more than once, then reload opengl state
            # after the second time.
            # XXX check how it's working on embed platform.
            if platform == 'linux' or Window.__class__.__name__ == 'WindowSDL':
                # on linux, it's safe for just sending a resize.
                self.dispatch('on_resize', *self.system_size)

            else:
                # on other platform, window are recreated, we need to reload.
                from kivy.graphics.context import get_context
                get_context().reload()
                Clock.schedule_once(lambda x: self.canvas.ask_update(), 0)
                self.dispatch('on_resize', *self.system_size)

        # ensure the gl viewport is correct
        self.update_viewport()
Exemple #6
0
 def __init__(self, **kwargs):
     self.first_time = True
     self.initial_zoom = None
     super(GeoJsonMapLayer, self).__init__(**kwargs)
     with self.canvas:
         self.canvas_polygon = Canvas()
     with self.canvas_polygon.before:
         PushMatrix()
         self.g_matrix = MatrixInstruction()
         self.g_scale = Scale()
         self.g_translate = Translate()
     with self.canvas_polygon:
         self.g_canvas_polygon = Canvas()
     with self.canvas_polygon.after:
         PopMatrix()
 def __init__(self, **kwargs):
     super(GeoJsonMapLayer, self).__init__(**kwargs)
     self.cache_dir = kwargs.get('cache_dir', None)
     with self.canvas:
         self.canvas_polygon = Canvas()
         self.canvas_line = Canvas()
     with self.canvas_polygon.before:
         PushMatrix()
         self.g_matrix = MatrixInstruction()
         self.g_scale = Scale()
         self.g_translate = Translate()
     with self.canvas_polygon:
         self.g_canvas_polygon = Canvas()
     with self.canvas_polygon.after:
         PopMatrix()
Exemple #8
0
class SpineAsset(Widget):
    filename = StringProperty()
    animations = ListProperty([])
    debug = BooleanProperty(False)
    valign = OptionProperty("middle", options=("bottom", "middle"))

    def __init__(self, **kwargs):
        self.canvas = Canvas()
        super(SpineAsset, self).__init__(**kwargs)
        Clock.schedule_interval(self.update, 0)

    def on_filename(self, *args):
        self.load_spine_asset(self.filename)
        self.canvas.clear()
        self.animations = [
            animation.name for animation in self.skeletonData.animations
        ]
        self.animate(self.animations[0])

    def on_debug(self, *args):
        self.skeleton.debug = self.debug

    def load_spine_asset(self, basefn):
        atlas = KivyAtlas(filename="{}.atlas".format(basefn))
        loader = KivyAtlasAttachmentLoader(atlas)
        skeletonJson = SkeletonJson(loader)
        self.skeletonData = skeletonJson.readSkeletonDataFile(
            "{}.json".format(basefn))
        self.skeleton = KivySkeleton(skeletonData=self.skeletonData)
        self.skeleton.debug = False
        self.animation = None

    def animate(self, name):
        self.animation = self.skeletonData.findAnimation(name)
        skeleton = self.skeleton

        skeleton.setToBindPose()
        skeleton.flipX = False
        skeleton.flipY = False
        skeleton.updateWorldTransform()

    def update(self, dt):
        if self.animation:
            self.animation.apply(skeleton=self.skeleton,
                                 time=Clock.get_time() / 2.,
                                 loop=True)
        self.skeleton.updateWorldTransform()
        self.skeleton.draw(self.canvas)
Exemple #9
0
    def show_lines(self):
        indices = []
        grid = self.grid
        cols = grid.cols
        rows = grid.rows
        for col in range(grid.cols + 1):
            indices.extend((
                col * (rows + 1),
                col * (rows + 1) + rows,
            ))
        for row in range(grid.rows + 1):
            indices.extend((
                row,
                row + (cols * (rows + 1)),
            ))

        with self.canvas:
            self.g_canvas = Canvas()

        with self.g_canvas:
            Color(1, 0, 0, 0.5)
            PushMatrix()
            Scale(self.width, self.height, 1.)
            self.g_mesh = Mesh(vertices=self.grid.line_vertices,
                               indices=self.grid.line_indices,
                               mode="lines",
                               source="projectionmapping/data/white.png")
            PopMatrix()

        self.rebuild_informations()
Exemple #10
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Register touch events
        self.register_event_type('on_touch_down')
        self.register_event_type('on_touch_move')
        self.register_event_type('on_touch_up')

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if not exist
        if self.canvas is None:
            self.canvas = Canvas()

        # Apply all the styles
        if '__no_builder' not in kwargs:
            #current_root = Builder.idmap.get('root')
            #Builder.idmap['root'] = self
            Builder.apply(self)
            #if current_root is not None:
            #    Builder.idmap['root'] = current_root
            #else:
            #    Builder.idmap.pop('root')

        # Bind all the events
        for argument, value in kwargs.items():
            if argument.startswith('on_'):
                self.bind(**{argument: value})
Exemple #11
0
 def __init__(self, cache_dir='cache', **kwargs):
     from kivy.base import EventLoop
     EventLoop.ensure_window()
     CACHE['directory'] = cache_dir
     self._invalid_scale = True
     self._tiles = []
     self._tiles_bg = []
     self._tilemap = {}
     self._layers = []
     self._default_marker_layer = None
     self._need_redraw_all = False
     self._transform_lock = False
     self.trigger_update(True)
     self.canvas = Canvas()
     self._scatter = MapViewScatter()
     self.add_widget(self._scatter)
     with self._scatter.canvas:
         self.canvas_map = Canvas()
         self.canvas_layers = Canvas()
     with self.canvas:
         self.canvas_layers_out = Canvas()
     self._scale_target_anim = False
     self._scale_target = 1.
     self._touch_count = 0
     Clock.schedule_interval(self._animate_color, 1 / 60.)
     self.lat = kwargs.get("lat", self.lat)
     self.lon = kwargs.get("lon", self.lon)
     super(MapView, self).__init__(**kwargs)
    def add_screen(self, screen):
        self.screen_in.pos = self.screen_out.pos
        self.screen_in.size = self.screen_out.size
        self.manager.real_remove_widget(self.screen_out)

        self.fbo_in = self.make_screen_fbo(self.screen_in)
        self.fbo_out = self.make_screen_fbo(self.screen_out)
        self.manager.canvas.add(self.fbo_in)
        self.manager.canvas.add(self.fbo_out)

        self.canvas = Canvas()
        self.c_front = RenderContext()
        self.c_front.shader.source = resource_find('front.glsl')
        self.c_back = RenderContext()
        self.c_back.shader.source = resource_find('back.glsl')
        self.c_backshadow = RenderContext()
        self.c_backshadow.shader.source = resource_find('backshadow.glsl')
        self.canvas.add(self.c_front)
        self.canvas.add(self.c_back)
        self.canvas.add(self.c_backshadow)

        with self.canvas.before:
            Color(1, 1, 1)
            Rectangle(
                size=self.fbo_in.size,
                texture=self.fbo_in.texture)
            Callback(self._enter_3d)
        self._build_mesh(self.fbo_in.size)
        with self.canvas.after:
            Callback(self._leave_3d)

        self.manager.canvas.add(self.canvas)
Exemple #13
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Assign the default context of the widget creation.
        if not hasattr(self, '_context'):
            self._context = get_current_context()

        no_builder = '__no_builder' in kwargs
        if no_builder:
            del kwargs['__no_builder']
        on_args = {k: v for k, v in kwargs.items() if k[:3] == 'on_'}
        for key in on_args:
            del kwargs[key]

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if it does not exist.
        if self.canvas is None:
            self.canvas = Canvas(opacity=self.opacity)

        # Apply all the styles.
        if not no_builder:
            #current_root = Builder.idmap.get('root')
            #Builder.idmap['root'] = self
            Builder.apply(self)
            #if current_root is not None:
            #    Builder.idmap['root'] = current_root
            #else:
            #    Builder.idmap.pop('root')

        # Bind all the events.
        if on_args:
            self.bind(**on_args)
Exemple #14
0
    def create_window(self):
        '''Will create the main window and configure it.

        .. warning::
            This method is called automatically at runtime. If you call it, it
            will recreate a RenderContext and Canvas. This mean you'll have a
            new graphics tree, and the old one will be unusable.

            This method exist to permit the creation of a new OpenGL context
            AFTER closing the first one. (Like using runTouchApp() and
            stopTouchApp()).

            This method have been only tested in unittest environment, and will
            be not suitable for Applications.

            Again, don't use this method unless you know exactly what you are
            doing !
        '''
        from kivy.core.gl import init_gl
        init_gl()

        # create the render context and canvas
        from kivy.graphics import RenderContext, Canvas
        self.render_context = RenderContext()
        self.canvas = Canvas()
        self.render_context.add(self.canvas)
Exemple #15
0
    def __init__(self, **kwargs):
        self.canvas = Canvas()
        with self.canvas.before:
            Callback(self._set_blend_func)
        #self.size
        self.fbo_texture = Texture.create(size=self.size, colorfmt='rgba')

        self.fbo_texture.mag_filter = 'linear'
        self.fbo_texture.min_filter = 'linear'

        with self.canvas:
            #self.cbs = Callback(self.prepare_canvas)
            self.fbo = Fbo(size=self.size, texture=self.fbo_texture)
            #Color(1, 1, 1, 0)
            #self.fbo_rect = Rectangle()

        with self.fbo:
            ClearColor(0.0, 0.0, 0.0, 0.0)
            ClearBuffers()
            self.fbo_rect = Rectangle(size=self.size)

        #self.fbo.shader.source = resource_find('./kivy3dgui/gles2.0/shaders/invert.glsl')
        #with self.fbo.after:
        #    self.cbr = Callback(self.reset_gl_context)
        #    PopMatrix()

        with self.canvas.before:
            Callback(self._set_blend_func)

        # wait that all the instructions are in the canvas to set texture

        self.texture = self.fbo.texture
        super(FboFloatLayout, self).__init__(**kwargs)
    def start(self, manager):
        '''(internal) Starts the transition. This is automatically
        called by the :class:`ScreenManager`.
        '''
        self.manager = manager
        if hasattr(manager, 'c'):
            manager.canvas.remove(manager.c)

        manager.c = Canvas()
        with manager.c:
            opacity = 0
            Color(0, 0, 0, 0.6)
            Rectangle(size=manager.size, pos=manager.pos)
        super(CardTransition, self).start(manager)
        mode = self.mode
        a = self.screen_in
        b = self.screen_out
        # ensure that the correct widget is "on top"
        if mode == 'push':
            manager.canvas.add(manager.c)
            manager.canvas.remove(a.canvas)
            manager.canvas.add(a.canvas)
        elif mode == 'pop':
            manager.canvas.add(manager.c)
            manager.canvas.remove(b.canvas)
            manager.canvas.add(b.canvas)
Exemple #17
0
    def __init__(self, overlay, **kwargs):
        super().__init__(**kwargs)
        # creating an interface
        self.controller = overlay
        self.size_hint = (.8, .8)
        self.orientation = 'vertical'
        # starting the gameloop
        self.event = Clock.schedule_interval(self.mainloop, 0)
        # create main canvas
        self.GAME = Widget()
        self.GAME.canvas = Canvas()
        self.add_widget(self.GAME)
        # game classes
        self.player = Player()
        self.sprites = Sprites()
        self.drawing = Drawing(self.GAME.canvas, None)

        # draw borders
        with self.canvas.after:
            Color(.05, .05, .05)
            Rectangle(pos=(0, 0), size=(NULLX, REAL_SCREEN_Y))
            Color(.01, .01, .01)
            Rectangle(pos=(NULLX, REAL_SCREEN_Y - NULLY),
                      size=(REAL_SCREEN_X - NULLX, NULLY))
            Color(.01, .01, .01)
            Rectangle(pos=(NULLX, 0), size=(REAL_SCREEN_X - NULLX, NULLY))
            Color(.05, .05, .05)
            Rectangle(pos=(REAL_SCREEN_X - NULLX, 0),
                      size=(REAL_SCREEN_X, REAL_SCREEN_Y))
Exemple #18
0
    def _prepare_fbo(self, *args):
        # put all the current canvas into an FBO
        # then use the fbo texture into a Quad, for animating when disable

        # create the Fbo
        self.fbo = Fbo(size=(1, 1))
        with self.fbo.before:
            self.g_translate = Translate(self.x, self.y)
        self.orig_canvas = self.canvas
        self.fbo.add(self.canvas)

        # create a new Canvas
        self.canvas = Canvas()
        self.canvas.add(self.fbo)
        with self.canvas:
            Color(1, 1, 1)
            self.g_quad = Quad(texture=self.fbo.texture)

        # replace the canvas from the parent with the new one
        self.parent.canvas.remove(self.orig_canvas)
        self.parent.canvas.add(self.canvas)

        # ensure we'll be updated when we'll change position
        self.bind(pos=self._update_mesh, size=self._update_mesh, alpha_rotation=self._update_mesh)
        self._update_mesh()
Exemple #19
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Register touch events
        self.register_event_type("on_touch_down")
        self.register_event_type("on_touch_move")
        self.register_event_type("on_touch_up")

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if not exist
        if self.canvas is None:
            self.canvas = Canvas(opacity=self.opacity)

        # Apply all the styles
        if "__no_builder" not in kwargs:
            # current_root = Builder.idmap.get('root')
            # Builder.idmap['root'] = self
            Builder.apply(self)
            # if current_root is not None:
            #    Builder.idmap['root'] = current_root
            # else:
            #    Builder.idmap.pop('root')

        # Bind all the events
        for argument in kwargs:
            if argument[:3] == "on_":
                self.bind(**{argument: kwargs[argument]})
Exemple #20
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Assign the default context of the widget creation.
        if not hasattr(self, '_context'):
            self._context = get_current_context()

        no_builder = '__no_builder' in kwargs
        self._disabled_value = False
        if no_builder:
            del kwargs['__no_builder']
        on_args = {k: v for k, v in kwargs.items() if k[:3] == 'on_'}
        for key in on_args:
            del kwargs[key]

        self._disabled_count = 0

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if it does not exist.
        if self.canvas is None:
            self.canvas = Canvas(opacity=self.opacity)

        # Apply all the styles.
        if not no_builder:
            Builder.apply(self, ignored_consts=self._kwargs_applied_init)

        # Bind all the events.
        if on_args:
            self.bind(**on_args)
Exemple #21
0
 def __init__(self, **kwargs):
     super(GeoJsonMapLayer, self).__init__(**kwargs)
     with self.canvas:
         self.scissor = ScissorPush(x=0, y=0, width=100, height=100)
         self.canvas_polygon = Canvas()
         self.canvas_line = Canvas()
         ScissorPop()
     with self.canvas_polygon.before:
         PushMatrix()
         self.g_matrix = MatrixInstruction()
         self.g_scale = Scale()
         self.g_translate = Translate()
     with self.canvas_polygon:
         self.g_canvas_polygon = Canvas()
     with self.canvas_polygon.after:
         PopMatrix()
Exemple #22
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # assign the default context of the widget creation
        if not hasattr(self, '_context'):
            self._context = get_current_context()

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if not exist
        if self.canvas is None:
            self.canvas = Canvas(opacity=self.opacity)

        # Apply all the styles
        if '__no_builder' not in kwargs:
            #current_root = Builder.idmap.get('root')
            #Builder.idmap['root'] = self
            Builder.apply(self)
            #if current_root is not None:
            #    Builder.idmap['root'] = current_root
            #else:
            #    Builder.idmap.pop('root')

        # Bind all the events
        for argument in kwargs:
            if argument[:3] == 'on_':
                self.bind(**{argument: kwargs[argument]})
Exemple #23
0
    def build(self):
        radius = self.radius

        if not self.canvas_points:
            self.canvas_points = Canvas()
            self.canvas.add(self.canvas_points)
            with self.canvas_points:
                Color(1, 0, 0)
                for marker in points:
                    Rectangle(pos=(marker.x * 600, marker.y * 600),
                              size=(2, 2))

        self.canvas.before.clear()
        with self.canvas.before:
            if self.selection_center:
                Color(0, 1, 0, 0.5)
                x, y = self.selection_center
                r = radius * 600
                r2 = r * 2
                Ellipse(pos=(x - r, y - r), size=(r2, r2))

            if self.selection:
                Color(0, 0, 1)
                for m_id in self.selection:
                    # x = kdbush.coords[m_id * 2]
                    # y = kdbush.coords[m_id * 2 + 1]
                    marker = points[m_id]
                    x = marker.x
                    y = marker.y
                    Rectangle(pos=(x * 600 - 4, y * 600 - 4), size=(8, 8))
Exemple #24
0
    def _highlight(self, results):
        from kivy.graphics import Color, Rectangle, Canvas
        from kivy.core.window import Window
        if not hasattr(self, "_canvas"):
            self._canvas = Canvas()

        Window.canvas.remove(self._canvas)
        Window.canvas.add(self._canvas)

        self._canvas.clear()
        with self._canvas:
            Color(1, 0, 0, 0.5)
            for widget, bounds in results:
                left, bottom, right, top = bounds
                Rectangle(pos=(left, bottom),
                          size=(right - left, top - bottom))
Exemple #25
0
    def __init__(self, **kwargs):
        super(Widget, self).__init__()

        # Register touch events
        self.register_event_type('on_touch_down')
        self.register_event_type('on_touch_move')
        self.register_event_type('on_touch_up')

        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Auto bind on own handler if exist
        properties = self.__properties.keys()
        for func in dir(self):
            if not func.startswith('on_'):
                continue
            name = func[3:]
            if name in properties:
                self.bind(**{name: getattr(self, func)})

        # Create the default canvas
        self.canvas = Canvas()

        # Apply the existing arguments to our widget
        for key, value in kwargs.iteritems():
            if hasattr(self, key):
                setattr(self, key, value)

        # Apply all the styles
        if '__no_builder' not in kwargs:
            Builder.apply(self)
Exemple #26
0
    def __init__(self, **kwargs):
        # XXX move to style.kv
        kwargs.setdefault('size_hint', (None, None))
        kwargs.setdefault('scale_min', .4)
        kwargs.setdefault('scale_max', 1.6)
        kwargs.setdefault('size', (700, 200))
        kwargs.setdefault('docked', False)
        self._trigger_update_layout_mode = Clock.create_trigger(
            self._update_layout_mode)
        self._trigger_load_layouts = Clock.create_trigger(
            self._load_layouts)
        self._trigger_load_layout = Clock.create_trigger(
            self._load_layout)
        self.bind(
            docked=self.setup_mode,
            have_shift=self._trigger_update_layout_mode,
            have_capslock=self._trigger_update_layout_mode,
            layout_path=self._trigger_load_layouts,
            layout=self._trigger_load_layout)
        super(VKeyboard, self).__init__(**kwargs)

        # load all the layouts found in the layout_path directory
        self._load_layouts()

        # ensure we have default layouts
        available_layouts = self.available_layouts
        if not available_layouts:
            Logger.critical('VKeyboard: unable to load default layouts')

        # load the default layout from configuration
        if self.layout is None:
            self.layout = Config.get('kivy', 'keyboard_layout')
        else:
            # ensure the current layout is found on the available layout
            self._trigger_load_layout()

        # update layout mode (shift or normal)
        self._trigger_update_layout_mode()

        # create a top layer to draw active keys on
        with self.canvas:
            self.background_key_layer = Canvas()
            self.active_keys_layer = Canvas()

        # prepare layout widget
        self.refresh_keys_hint()
        self.refresh_keys()
Exemple #27
0
class SpineAsset(Widget):
    filename = StringProperty()
    animations = ListProperty([])
    debug = BooleanProperty(False)
    valign = OptionProperty("middle", options=("bottom", "middle"))

    def __init__(self, **kwargs):
        self.canvas = Canvas()
        super(SpineAsset, self).__init__(**kwargs)
        Clock.schedule_interval(self.update, 0)

    def on_filename(self, *args):
        self.load_spine_asset(self.filename)
        self.canvas.clear()
        self.animations = [animation.name
                           for animation in self.skeletonData.animations]
        self.animate(self.animations[0])

    def on_debug(self, *args):
        self.skeleton.debug = self.debug

    def load_spine_asset(self, basefn):
        atlas = Atlas(filename="{}.atlas".format(basefn))
        loader = AtlasAttachmentLoader(atlas)
        skeletonJson = SkeletonJson(loader)
        self.skeletonData = skeletonJson.readSkeletonDataFile(
            "{}.json".format(basefn))
        self.skeleton = Skeleton(skeletonData=self.skeletonData)
        self.skeleton.debug = False
        self.animation = None

    def animate(self, name):
        self.animation = self.skeletonData.findAnimation(name)
        skeleton = self.skeleton

        skeleton.setToBindPose()
        skeleton.flipX = False
        skeleton.flipY = False
        skeleton.updateWorldTransform()

    def update(self, dt):
        if self.animation:
            self.animation.apply(skeleton=self.skeleton,
                                 time=Clock.get_time() / 2.,
                                 loop=True)
        self.skeleton.updateWorldTransform()
        self.skeleton.draw(self.canvas)
Exemple #28
0
    def __init__(self, **kwargs):
        self.register_event_type('on_touch_hover')
        self.chooser = None
        self.popup = None

        self.skeletons = []
        self.states = []

        # self.batch = PolygonSpriteBatch()

        self.renderer = SkeletonRenderer()
        self.debugRenderer = SkeletonRendererDebug()

        self.draw_canvas = Canvas()

        super().__init__(**kwargs)
        self.canvas.add(self.draw_canvas)
 def __init__(self, **kwargs):
     self.size_hint_y = None
     self.height = 6
     self.canvas = Canvas()
     self.rect = Rectangle()
     self.bind(size=self.on_change)
     self.bind(pos=self.on_change)
     super(HorizontalLine, self).__init__(**kwargs)
Exemple #30
0
 def build(self):
     #self.load_kv('MyApp.kv')
     self.title = "Graphic Test Framework"
     widget = MyWidget()
     widget.add_widget(Label(text="prueba"))
     myCanvas = Canvas()
     myCanvas.add(Rectangle(size=(350, 1), pos=(0, 500)))
     widget.canvas = myCanvas
     widget.add_widget(Button(text="Boton", pos=(100, 500)))
     return widget
     return MyWidget()
     return PageLayout()
     return StackLayout()
     return BoxLayout()
     return GridLayout()
     return FloatLayout()
     return CustomWidget()
Exemple #31
0
    def __init__(self, **kwargs):
        super(RippleBehavior, self).__init__(**kwargs)
        self.ripple_pane_bg = Canvas()
        self.canvas.add(self.ripple_pane_bg)
        self.bind(ripple_bg_color=self._ripple_set_bg_color, )
        self.ripple_rectangle = None
        self.ripple_bg_color_instruction = None

        self.ripple_pane = Canvas()
        self.canvas.add(self.ripple_pane)
        self.bind(ripple_color=self._ripple_set_color,
                  ripple_pos=self._ripple_set_ellipse,
                  ripple_rad=self._ripple_set_ellipse)
        self.ripple_ellipse = None
        self.ripple_col_instruction = None

        self.execution_index = 1
Exemple #32
0
    def __init__(self, **kwargs):
        if platform == "ios":
	    user_data_dir = App.get_running_app().user_data_dir
    	    self.cache_dir = join(user_data_dir, "cache_dir") 
        self.first_time = True
        self.initial_zoom = None
        super(GeoJsonMapLayer, self).__init__(**kwargs)
        with self.canvas:
            self.canvas_polygon = Canvas()
        with self.canvas_polygon.before:
            PushMatrix()
            self.g_matrix = MatrixInstruction()
            self.g_scale = Scale()
            self.g_translate = Translate()
        with self.canvas_polygon:
            self.g_canvas_polygon = Canvas()
        with self.canvas_polygon.after:
            PopMatrix()
Exemple #33
0
    def create_window(self, *largs):
        """Will create the main window and configure it.

        .. warning::
            This method is called automatically at runtime. If you call it, it
            will recreate a RenderContext and Canvas. This means you'll have a
            new graphics tree, and the old one will be unusable.

            This method exist to permit the creation of a new OpenGL context
            AFTER closing the first one. (Like using runTouchApp() and
            stopTouchApp()).

            This method has only been tested in a unittest environment and
            is not suitable for Applications.

            Again, don't use this method unless you know exactly what you are
            doing!
        """
        # just to be sure, if the trigger is set, and if this method is
        # manually called, unset the trigger
        Clock.unschedule(self.create_window)

        # ensure the window creation will not be called twice
        if platform in ("android", "ios"):
            self._unbind_create_window()

        if not self.initialized:
            from kivy.core.gl import init_gl

            init_gl()

            # create the render context and canvas, only the first time.
            from kivy.graphics import RenderContext, Canvas

            self.render_context = RenderContext()
            self.canvas = Canvas()
            self.render_context.add(self.canvas)

        else:
            # if we get initialized more than once, then reload opengl state
            # after the second time.
            # XXX check how it's working on embed platform.
            if platform == "linux" or Window.__class__.__name__ == "WindowSDL":
                # on linux, it's safe for just sending a resize.
                self.dispatch("on_resize", *self.system_size)

            else:
                # on other platform, window are recreated, we need to reload.
                from kivy.graphics.context import get_context

                get_context().reload()
                Clock.schedule_once(lambda x: self.canvas.ask_update(), 0)
                self.dispatch("on_resize", *self.system_size)

        # ensure the gl viewport is correct
        self.update_viewport()
Exemple #34
0
 def __init__(self, label, setting, color, **kwargs):
     super(Setting, self).__init__(**kwargs)
     Clock.schedule_once(self._finish_init)
     self._setting = setting
     self.label = label
     self.canvas = Canvas()
     with self.canvas.before:
         Color(rgba=color)
         self.rect = Rectangle(size=self.size, pos=self.pos)
     self.bind(pos=self.update_bg, size=self.update_bg)
    def __init__(self, **kwargs):
        self.canvas = Canvas()
        with self.canvas:
            self.fbo = Fbo(size=self.size)
            Color(1, 1, 1)
            self.fbo_rect = Rectangle()

        # wait that all the instructions are in the canvas to set texture
        self.texture = self.fbo.texture
        super(FboFloatLayout, self).__init__(**kwargs)
Exemple #36
0
    def __init__(self, **kwargs):
        # XXX move to style.kv
        kwargs.setdefault("size_hint", (None, None))
        kwargs.setdefault("scale_min", 0.4)
        kwargs.setdefault("scale_max", 1.6)
        kwargs.setdefault("size", (700, 200))
        kwargs.setdefault("docked", False)
        self._trigger_update_layout_mode = Clock.create_trigger(self._update_layout_mode)
        self._trigger_load_layouts = Clock.create_trigger(self._load_layouts)
        self._trigger_load_layout = Clock.create_trigger(self._load_layout)
        self.bind(
            docked=self.setup_mode,
            have_shift=self._trigger_update_layout_mode,
            have_capslock=self._trigger_update_layout_mode,
            have_special=self._trigger_update_layout_mode,
            layout_path=self._trigger_load_layouts,
            layout=self._trigger_load_layout,
        )
        super(VKeyboard, self).__init__(**kwargs)

        # load all the layouts found in the layout_path directory
        self._load_layouts()

        # ensure we have default layouts
        available_layouts = self.available_layouts
        if not available_layouts:
            Logger.critical("VKeyboard: unable to load default layouts")

        # load the default layout from configuration
        if self.layout is None:
            self.layout = Config.get("kivy", "keyboard_layout")
        else:
            # ensure the current layout is found on the available layout
            self._trigger_load_layout()

        # update layout mode (shift or normal)
        self._trigger_update_layout_mode()

        # create a top layer to draw active keys on
        with self.canvas:
            self.background_key_layer = Canvas()
            self.active_keys_layer = Canvas()
Exemple #37
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Register touch events
        self.register_event_type('on_touch_down')
        self.register_event_type('on_touch_move')
        self.register_event_type('on_touch_up')

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if not exist
        if self.canvas is None:
            self.canvas = Canvas()

        # Apply all the styles
        if '__no_builder' not in kwargs:
            #current_root = Builder.idmap.get('root')
            #Builder.idmap['root'] = self
            Builder.apply(self)
class HorizontalLine(Widget):
    separator_color = Popup.separator_color.defaultvalue
    separator_height = Popup.separator_height

    def __init__(self, **kwargs):
        self.size_hint_y = None
        self.height = 6
        self.canvas = Canvas()
        self.rect = Rectangle()
        self.bind(size=self.on_change)
        self.bind(pos=self.on_change)
        super(HorizontalLine, self).__init__(**kwargs)

    def on_change(self, widget, size):
        self.color = Color()  # Gets broken when moving
        self.color.rgba = self.separator_color  # GETTING DEFAULT COLOR
        self.canvas.add(self.color)
        self.rect.pos = self.x, self.y + (self.height - self.separator_height) / 2.0
        self.rect.size = self.width, self.separator_height
        self.canvas.add(self.rect)
Exemple #39
0
    def __init__(self, **kwargs):
        self.canvas = Canvas()
        with self.canvas:
            self.fbo = Fbo(size=self.size,
                           with_depthbuffer=True,
                           compute_normal_mat=True,
                           clear_color=(0., 0., 0., 0.))

            self.viewport = Rectangle(size=self.size, pos=self.pos)

        self.fbo.shader.source = resource_find(
            join(dirname(__file__), 'simple.glsl'))
        super(ObjectRenderer, self).__init__(**kwargs)
    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(
            self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas
        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

        super(ScrollView, self).__init__(**kwargs)

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)
        self.bind(width=self._update_effect_x_bounds,
                  height=self._update_effect_y_bounds,
                  viewport_size=self._update_effect_bounds,
                  _viewport=self._update_effect_widget,
                  scroll_x=self._trigger_update_from_scroll,
                  scroll_y=self._trigger_update_from_scroll,
                  pos=self._trigger_update_from_scroll,
                  size=self._trigger_update_from_scroll)

        self._update_effect_widget()
        self._update_effect_x_bounds()
        self._update_effect_y_bounds()
Exemple #41
0
    def __init__(self, **kwargs):
        self.mesh_data = MeshData()
        self.canvas = Canvas()
        with self.canvas:
            self.fbo = Fbo(size=(10, 10), compute_normal_mat=True)
            self.fbo.add_reload_observer(self.populate_fbo)
        with self.fbo:
            ClearColor(0, 0, 0, 0)
            ClearBuffers()
        self.populate_fbo(self.fbo)

        super(GLWindow, self).__init__(**kwargs)

        Clock.schedule_interval(self.update_glsl, 1 / 60.)
    def __init__(self, **kwargs):
        """
        Set up inheritance of parent with the kwargs as well as add variables needed by all instances.

        :param kwargs: all available kwargs can be found at https://kivy.org/docs/api-kivy.uix.floatlayout.html
        """

        super().__init__(**kwargs)
        self.root = root = self
        self.root.canvas = Canvas()
        root.bind(angle=self.on_angle)

        self.rotate_animation = Animation(angle=0.5, duration=1)
        self.rotate_animation.start(self)
Exemple #43
0
    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(
            self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas
        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

        super(ScrollView, self).__init__(**kwargs)

        self.register_event_type('on_scroll_start')
        self.register_event_type('on_scroll_move')
        self.register_event_type('on_scroll_stop')

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)

        trigger_update_from_scroll = self._trigger_update_from_scroll
        update_effect_widget = self._update_effect_widget
        update_effect_x_bounds = self._update_effect_x_bounds
        update_effect_y_bounds = self._update_effect_y_bounds
        fbind = self.fbind
        fbind('width', update_effect_x_bounds)
        fbind('height', update_effect_y_bounds)
        fbind('viewport_size', self._update_effect_bounds)
        fbind('_viewport', update_effect_widget)
        fbind('scroll_x', trigger_update_from_scroll)
        fbind('scroll_y', trigger_update_from_scroll)
        fbind('pos', trigger_update_from_scroll)
        fbind('size', trigger_update_from_scroll)
        fbind('scroll_y', self._update_effect_bounds)
        fbind('scroll_x', self._update_effect_bounds)

        update_effect_widget()
        update_effect_x_bounds()
        update_effect_y_bounds()
Exemple #44
0
    def __init__(self, **kw):
        self.shader_file = kw.pop("shader_file", None)
        self.canvas = Canvas()
        super(Renderer, self).__init__(**kw)

        with self.canvas:
            self._viewport = Rectangle(size=self.size, pos=self.pos)
            self.fbo = Fbo(size=self.size,
                           with_depthbuffer=True, compute_normal_mat=True,
                           clear_color=(0., 0., 0., 0.))
        self._config_fbo()
        self.texture = self.fbo.texture
        self.camera = None
        self.scene = None
Exemple #45
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if not exist
        if self.canvas is None:
            self.canvas = Canvas(opacity=self.opacity)

        # Apply all the styles
        if '__no_builder' not in kwargs:
            #current_root = Builder.idmap.get('root')
            #Builder.idmap['root'] = self
            Builder.apply(self)
            #if current_root is not None:
            #    Builder.idmap['root'] = current_root
            #else:
            #    Builder.idmap.pop('root')

        # Bind all the events
        for argument in kwargs:
            if argument[:3] == 'on_':
                self.bind(**{argument: kwargs[argument]})
Exemple #46
0
def _highlight(results):
    from kivy.graphics import Color, Rectangle, Canvas
    from kivy.core.window import Window
    if not hasattr(Window, "_telenium_canvas"):
        Window._telenium_canvas = Canvas()
    _canvas = Window._telenium_canvas

    Window.canvas.remove(_canvas)
    Window.canvas.add(_canvas)

    _canvas.clear()
    with _canvas:
        Color(1, 0, 0, 0.5)
        for widget, bounds in results:
            left, bottom, right, top = bounds
            Rectangle(pos=(left, bottom), size=(right - left, top - bottom))
Exemple #47
0
 def prepare_graphics(self, canvas):
     self._mesh_vertices = vertices = [0] * 16
     vertices[2::4] = self._tex_coords[::2]
     vertices[3::4] = self._tex_coords[1::2]
     self.canvas = Canvas()
     with self.canvas:
         Color(1, 1, 1, 1)
         PushMatrix()
         self.g_mesh = Mesh(vertices=vertices,
                            indices=range(4),
                            mode="triangle_fan",
                            texture=self.texture)
         self.g_debug_color = Color(1, 0, 0, 1)
         self.g_debug_mesh = Mesh(
             vertices=vertices,
             indices=range(4),
             mode="line_loop",
         )
         PopMatrix()
Exemple #48
0
    def __init__(self, **kwargs):
        self.lock = Lock()
        self.gl_depth = -3
        self.mesh_data = MeshData()
        self.mesh_data.vertices = np.array([0, 0, 0, 0, 0, 0, 0, 0])
        self.mesh_data.indices = np.array([0])
        self.points = None

        self.canvas = Canvas()
        with self.canvas:
            self.fbo = Fbo(size=(10, 10), compute_normal_mat=True)
            self.fbo.add_reload_observer(self.populate_fbo)
        with self.fbo:
            ClearColor(1, 1, 1, 1)
            ClearBuffers()

        self.populate_fbo(self.fbo)

        super(ObjectRenderer, self).__init__(**kwargs)
        Clock.schedule_interval(self.update_glsl, 1 / 10.)
Exemple #49
0
    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Register touch events
        self.register_event_type('on_touch_down')
        self.register_event_type('on_touch_move')
        self.register_event_type('on_touch_up')

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if not exist
        if self.canvas is None:
            self.canvas = Canvas()

        # Apply all the styles
        if '__no_builder' not in kwargs:
            #current_root = Builder.idmap.get('root')
            #Builder.idmap['root'] = self
            Builder.apply(self)
    def __init__(self, **kwargs):
        self.canvas = Canvas()

        with self.canvas:
            self.fbo = Fbo(
                size=self.size,
                with_depthbuffer=True,
            )
            self.fbo_color = Color(1, 1, 1, 1)
            self.viewport = Rectangle(
                pos=self.pos,
                size=self.size,
            )

        with self.fbo:
            ClearColor(0, 0, 0, 0)
            ClearBuffers()

        self.texture = self.fbo.texture

        super(TransparentLayout, self).__init__(**kwargs)
    def create_canvas(self):
        self.canvas = Canvas()
        self.c_front = RenderContext()
        self.c_front.shader.source = join(curdir, 'front.glsl')
        self.c_back = RenderContext()
        self.c_back.shader.source = join(curdir, 'back.glsl')
        self.c_backshadow = RenderContext()
        self.c_backshadow.shader.source = join(curdir, 'backshadow.glsl')
        self.canvas.add(self.c_front)
        self.canvas.add(self.c_back)
        self.canvas.add(self.c_backshadow)

        with self.canvas.before:
            Color(1, 1, 1)
            Rectangle(
                size=self.fbo_in.size,
                texture=self.fbo_in.texture)
            Callback(self._enter_3d)
        self._build_mesh(self.fbo_in.size)
        with self.canvas.after:
            Callback(self._leave_3d)
Exemple #52
0
    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas

        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

        super(ScrollView, self).__init__(**kwargs)

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)
        self.bind(
            width=self._update_effect_x_bounds,
            height=self._update_effect_y_bounds,
            viewport_size=self._update_effect_bounds,
            _viewport=self._update_effect_widget,
            scroll_x=self._trigger_update_from_scroll,
            scroll_y=self._trigger_update_from_scroll,
            pos=self._trigger_update_from_scroll,
            size=self._trigger_update_from_scroll,
        )

        self._update_effect_widget()
        self._update_effect_x_bounds()
        self._update_effect_y_bounds()
    def add_screen(self, screen):
        self.screen_in.pos = self.screen_out.pos
        self.screen_in.size = self.screen_out.size
        self.manager.real_remove_widget(self.screen_out)

        print '-----------'
        print 'add_screen', screen, screen.canvas
        print 'screen_in', self.screen_in, self.screen_in.parent
        print 'screen_out', self.screen_out, self.screen_out.parent
        print '-----------'
        self.fbo_in = self.make_screen_fbo(self.screen_in, mode='in')
        self.fbo_out = self.make_screen_fbo(self.screen_out, mode='out')
        self.manager.canvas.add(self.fbo_in)
        self.manager.canvas.add(self.fbo_out)

        self.canvas = Canvas()
        self.c_front = RenderContext()
        self.c_front.shader.source = join(curdir, 'front.glsl')
        self.c_back = RenderContext()
        self.c_back.shader.source = join(curdir, 'back.glsl')
        self.c_backshadow = RenderContext()
        self.c_backshadow.shader.source = join(curdir, 'backshadow.glsl')
        self.canvas.add(self.c_front)
        self.canvas.add(self.c_back)
        self.canvas.add(self.c_backshadow)

        with self.canvas.before:
            Color(1, 1, 1)
            Rectangle(
                size=self.fbo_in.size,
                texture=self.fbo_in.texture)
            Callback(self._enter_3d)
        self._build_mesh(self.fbo_in.size)
        with self.canvas.after:
            Callback(self._leave_3d)

        self.manager.canvas.add(self.canvas)
        self.on_progress(0)
Exemple #54
0
 def __init__(self, **kwargs):
     from kivy.base import EventLoop
     EventLoop.ensure_window()
     self._tiles = []
     self._tiles_bg = []
     self._tilemap = {}
     self._layers = []
     self._default_marker_layer = None
     self._need_redraw_all = False
     self._transform_lock = False
     self.trigger_update(True)
     self.canvas = Canvas()
     self._scatter = MapViewScatter()
     self.add_widget(self._scatter)
     with self._scatter.canvas:
         self.canvas_map = Canvas()
         self.canvas_layers = Canvas()
     with self.canvas:
         self.canvas_layers_out = Canvas()
     self._scale_target_anim = False
     self._scale_target = 1.
     Clock.schedule_interval(self._animate_color, 1 / 60.)
     super(MapView, self).__init__(**kwargs)
Exemple #55
0
class MapView(Widget):
    """MapView is the widget that control the map displaying, navigation, and
    layers management.
    """

    lon = NumericProperty()
    """Longitude at the center of the widget
    """

    lat = NumericProperty()
    """Latitude at the center of the widget
    """

    zoom = NumericProperty(0)
    """Zoom of the widget. Must be between :meth:`MapSource.get_min_zoom` and
    :meth:`MapSource.get_max_zoom`. Default to 0.
    """

    map_source = ObjectProperty(MapSource())
    """Provider of the map, default to a empty :class:`MapSource`.
    """

    double_tap_zoom = BooleanProperty(False)
    """If True, this will activate the double-tap to zoom.
    """

    pause_on_action = BooleanProperty(True)
    """Pause any map loading / tiles loading when an action is done.
    This allow better performance on mobile, but can be safely deactivated on
    desktop.
    """

    snap_to_zoom = BooleanProperty(True)
    """When the user initiate a zoom, it will snap to the closest zoom for
    better graphics. The map can be blur if the map is scaled between 2 zoom.
    Default to True, even if it doesn't fully working yet.
    """

    animation_duration = NumericProperty(100)
    """Duration to animate Tiles alpha from 0 to 1 when it's ready to show.
    Default to 100 as 100ms. Use 0 to deactivate.
    """

    delta_x = NumericProperty(0)
    delta_y = NumericProperty(0)
    background_color = ListProperty([181 / 255., 208 / 255., 208 / 255., 1])
    _zoom = NumericProperty(0)
    _pause = BooleanProperty(False)
    _scale = 1.

    __events__ = ["on_map_relocated"]

    # Public API

    @property
    def viewport_pos(self):
        vx, vy = self._scatter.to_local(self.x, self.y)
        return vx - self.delta_x, vy - self.delta_y

    @property
    def scale(self):
        if self._invalid_scale:
            self._invalid_scale = False
            self._scale = self._scatter.scale
        return self._scale

    def get_bbox(self, margin=0):
        """Returns the bounding box from the bottom/left (lat1, lon1) to
        top/right (lat2, lon2).
        """
        x1, y1 = self.to_local(0 - margin, 0 - margin)
        x2, y2 = self.to_local((self.width + margin),
                               (self.height + margin))
        c1 = self.get_latlon_at(x1, y1)
        c2 = self.get_latlon_at(x2, y2)
        return Bbox((c1.lat, c1.lon, c2.lat, c2.lon))

    bbox = AliasProperty(get_bbox, None, bind=["lat", "lon", "_zoom"])

    def unload(self):
        """Unload the view and all the layers.
        It also cancel all the remaining downloads.
        """
        self.remove_all_tiles()

    def get_window_xy_from(self, lat, lon, zoom):
        """Returns the x/y position in the widget absolute coordinates
        from a lat/lon"""
        scale = self.scale
        vx, vy = self.viewport_pos
        ms = self.map_source
        x = ms.get_x(zoom, lon) - vx
        y = ms.get_y(zoom, lat) - vy
        x *= scale
        y *= scale
        return x, y

    def center_on(self, *args):
        """Center the map on the coordinate :class:`Coordinate`, or a (lat, lon)
        """
        map_source = self.map_source
        zoom = self._zoom

        if len(args) == 1 and isinstance(args[0], Coordinate):
            coord = args[0]
            lat = coord.lat
            lon = coord.lon
        elif len(args) == 2:
            lat, lon = args
        else:
            raise Exception("Invalid argument for center_on")
        lon = clamp(lon, MIN_LONGITUDE, MAX_LONGITUDE)
        lat = clamp(lat, MIN_LATITUDE, MAX_LATITUDE)
        scale = self._scatter.scale
        x = map_source.get_x(zoom, lon) - self.center_x / scale
        y = map_source.get_y(zoom, lat) - self.center_y / scale
        self.delta_x = -x
        self.delta_y = -y
        self.lon = lon
        self.lat = lat
        self._scatter.pos = 0, 0
        self.trigger_update(True)

    def set_zoom_at(self, zoom, x, y, scale=None):
        """Sets the zoom level, leaving the (x, y) at the exact same point
        in the view.
        """
        zoom = clamp(zoom,
                     self.map_source.get_min_zoom(),
                     self.map_source.get_max_zoom())
        if int(zoom) == int(self._zoom):
            if scale is None:
                return
            elif scale == self.scale:
                return
        scale = scale or 1.

        # first, rescale the scatter
        scatter = self._scatter
        scale = clamp(scale, scatter.scale_min, scatter.scale_max)
        rescale = scale * 1.0 / scatter.scale
        scatter.apply_transform(Matrix().scale(rescale, rescale, rescale),
                             post_multiply=True,
                             anchor=scatter.to_local(x, y))

        # adjust position if the zoom changed
        c1 = self.map_source.get_col_count(self._zoom)
        c2 = self.map_source.get_col_count(zoom)
        if c1 != c2:
            f = float(c2) / float(c1)
            self.delta_x = scatter.x + self.delta_x * f
            self.delta_y = scatter.y + self.delta_y * f
            # back to 0 every time
            scatter.apply_transform(Matrix().translate(
                -scatter.x, -scatter.y, 0
            ), post_multiply=True)

        # avoid triggering zoom changes.
        self._zoom = zoom
        self.zoom = self._zoom

    def on_zoom(self, instance, zoom):
        if zoom == self._zoom:
            return
        x = self.map_source.get_x(zoom, self.lon) - self.delta_x
        y = self.map_source.get_y(zoom, self.lat) - self.delta_y
        self.set_zoom_at(zoom, x, y)
        self.center_on(self.lat, self.lon)

    def get_latlon_at(self, x, y, zoom=None):
        """Return the current :class:`Coordinate` within the (x, y) widget
        coordinate.
        """
        if zoom is None:
            zoom = self._zoom
        vx, vy = self.viewport_pos
        scale = self._scale
        return Coordinate(
            lat=self.map_source.get_lat(zoom, y / scale + vy),
            lon=self.map_source.get_lon(zoom, x / scale + vx))

    def add_marker(self, marker, layer=None):
        """Add a marker into the layer. If layer is None, it will be added in
        the default marker layer. If there is no default marker layer, a new
        one will be automatically created
        """
        if layer is None:
            if not self._default_marker_layer:
                layer = MarkerMapLayer()
                self.add_layer(layer)
            else:
                layer = self._default_marker_layer
        layer.add_widget(marker)
        layer.set_marker_position(self, marker)

    def remove_marker(self, marker):
        """Remove a marker from its layer
        """
        marker.detach()

    def add_layer(self, layer, mode="window"):
        """Add a new layer to update at the same time the base tile layer.
        mode can be either "scatter" or "window". If "scatter", it means the
        layer will be within the scatter transformation. It's perfect if you
        want to display path / shape, but not for text.
        If "window", it will have no transformation. You need to position the
        widget yourself: think as Z-sprite / billboard.
        Defaults to "window".
        """
        assert(mode in ("scatter", "window"))
        if self._default_marker_layer is None and \
            isinstance(layer, MarkerMapLayer):
            self._default_marker_layer = layer
        self._layers.append(layer)
        c = self.canvas
        if mode == "scatter":
            self.canvas = self.canvas_layers
        else:
            self.canvas = self.canvas_layers_out
        layer.canvas_parent = self.canvas
        super(MapView, self).add_widget(layer)
        self.canvas = c

    def remove_layer(self, layer):
        """Remove the layer
        """
        c = self.canvas
        self._layers.remove(layer)
        self.canvas = layer.canvas_parent
        super(MapView, self).remove_widget(layer)
        self.canvas = c

    def sync_to(self, other):
        """Reflect the lat/lon/zoom of the other MapView to the current one.
        """
        if self._zoom != other._zoom:
            self.set_zoom_at(other._zoom, *self.center)
        self.center_on(other.get_latlon_at(*self.center))


    # Private API

    def __init__(self, cache_dir='cache', **kwargs):
        from kivy.base import EventLoop
        EventLoop.ensure_window()
        CACHE['directory'] = cache_dir
        self._invalid_scale = True
        self._tiles = []
        self._tiles_bg = []
        self._tilemap = {}
        self._layers = []
        self._default_marker_layer = None
        self._need_redraw_all = False
        self._transform_lock = False
        self.trigger_update(True)
        self.canvas = Canvas()
        self._scatter = MapViewScatter()
        self.add_widget(self._scatter)
        with self._scatter.canvas:
            self.canvas_map = Canvas()
            self.canvas_layers = Canvas()
        with self.canvas:
            self.canvas_layers_out = Canvas()
        self._scale_target_anim = False
        self._scale_target = 1.
        self._touch_count = 0
        Clock.schedule_interval(self._animate_color, 1 / 60.)
        self.lat = kwargs.get("lat", self.lat)
        self.lon = kwargs.get("lon", self.lon)
        super(MapView, self).__init__(**kwargs)

    def _animate_color(self, dt):
        # fast path
        d = self.animation_duration
        if d == 0:
            for tile in self._tiles:
                if tile.state == "need-animation":
                    tile.g_color.a = 1.
                    tile.state = "animated"
            for tile in self._tiles_bg:
                if tile.state == "need-animation":
                    tile.g_color.a = 1.
                    tile.state = "animated"
        else:
            d = d / 1000.
            for tile in self._tiles:
                if tile.state != "need-animation":
                    continue
                tile.g_color.a += dt / d
                if tile.g_color.a >= 1:
                    tile.state = "animated"
            for tile in self._tiles_bg:
                if tile.state != "need-animation":
                    continue
                tile.g_color.a += dt / d
                if tile.g_color.a >= 1:
                    tile.state = "animated"

    def add_widget(self, widget):
        if isinstance(widget, MapMarker):
            self.add_marker(widget)
        elif isinstance(widget, MapLayer):
            self.add_layer(widget)
        else:
            super(MapView, self).add_widget(widget)

    def remove_widget(self, widget):
        if isinstance(widget, MapMarker):
            self.remove_marker(widget)
        elif isinstance(widget, MapLayer):
            self.remove_layer(widget)
        else:
            super(MapView, self).remove_widget(widget)

    def on_map_relocated(self, zoom, coord):
        pass

    def animated_diff_scale_at(self, d, x, y):
        self._scale_target_time = 1.
        self._scale_target_pos = x, y
        if self._scale_target_anim == False:
            self._scale_target_anim = True
            self._scale_target = d
        else:
            self._scale_target += d
        Clock.unschedule(self._animate_scale)
        Clock.schedule_interval(self._animate_scale, 1 / 60.)

    def _animate_scale(self, dt):
        diff = self._scale_target / 3.
        if abs(diff) < 0.01:
            diff = self._scale_target
            self._scale_target = 0
        else:
            self._scale_target -= diff
        self._scale_target_time -= dt
        self.diff_scale_at(diff, *self._scale_target_pos)
        ret = self._scale_target != 0
        if not ret:
            self._pause = False
        return ret

    def diff_scale_at(self, d, x, y):
        scatter = self._scatter
        scale = scatter.scale * (2 ** d)
        self.scale_at(scale, x, y)

    def scale_at(self, scale, x, y):
        scatter = self._scatter
        scale = clamp(scale, scatter.scale_min, scatter.scale_max)
        rescale = scale * 1.0 / scatter.scale
        scatter.apply_transform(Matrix().scale(rescale, rescale, rescale),
                             post_multiply=True,
                             anchor=scatter.to_local(x, y))

    def on_touch_down(self, touch):
        if not self.collide_point(*touch.pos):
            return
        if self.pause_on_action:
            self._pause = True
        if "button" in touch.profile and touch.button in ("scrolldown", "scrollup"):
            d = 1 if touch.button == "scrollup" else -1
            self.animated_diff_scale_at(d, *touch.pos)
            return True
        elif touch.is_double_tap and self.double_tap_zoom:
            self.animated_diff_scale_at(1, *touch.pos)
            return True
        touch.grab(self)
        self._touch_count += 1
        if self._touch_count == 1:
            self._touch_zoom = (self.zoom, self._scale)
        return super(MapView, self).on_touch_down(touch)

    def on_touch_up(self, touch):
        if touch.grab_current == self:
            touch.ungrab(self)
            self._touch_count -= 1
            if self._touch_count == 0:
                # animate to the closest zoom
                zoom, scale = self._touch_zoom
                cur_zoom = self.zoom
                cur_scale = self._scale
                if cur_zoom < zoom or cur_scale < scale:
                    self.animated_diff_scale_at(1. - cur_scale, *touch.pos)
                elif cur_zoom > zoom or cur_scale > scale:
                    self.animated_diff_scale_at(2. - cur_scale, *touch.pos)
                self._pause = False
            return True
        return super(MapView, self).on_touch_up(touch)

    def on_transform(self, *args):
        self._invalid_scale = True
        if self._transform_lock:
            return
        self._transform_lock = True
        # recalculate viewport
        map_source = self.map_source
        zoom = self._zoom
        scatter = self._scatter
        scale = scatter.scale
        if scale >= 2.:
            zoom += 1
            scale /= 2.
        elif scale < 1:
            zoom -= 1
            scale *= 2.
        zoom = clamp(zoom, map_source.min_zoom, map_source.max_zoom)
        if zoom != self._zoom:
            self.set_zoom_at(zoom, scatter.x, scatter.y, scale=scale)
            self.trigger_update(True)
        else:
            if zoom == map_source.min_zoom and scatter.scale < 1.:
                scatter.scale = 1.
                self.trigger_update(True)
            else:
                self.trigger_update(False)

        if map_source.bounds:
            self._apply_bounds()
        self._transform_lock = False
        self._scale = self._scatter.scale

    def _apply_bounds(self):
        # if the map_source have any constraints, apply them here.
        map_source = self.map_source
        zoom = self._zoom
        min_lon, min_lat, max_lon, max_lat = map_source.bounds
        xmin = map_source.get_x(zoom, min_lon)
        xmax = map_source.get_x(zoom, max_lon)
        ymin = map_source.get_y(zoom, min_lat)
        ymax = map_source.get_y(zoom, max_lat)

        dx = self.delta_x
        dy = self.delta_y
        oxmin, oymin = self._scatter.to_local(self.x, self.y)
        oxmax, oymax = self._scatter.to_local(self.right, self.top)
        s = self._scale
        cxmin = (oxmin - dx)
        if cxmin < xmin:
            self._scatter.x += (cxmin - xmin) * s
        cymin = (oymin - dy)
        if cymin < ymin:
            self._scatter.y += (cymin - ymin) * s
        cxmax = (oxmax - dx)
        if cxmax > xmax:
            self._scatter.x -= (xmax - cxmax) * s
        cymax = (oymax - dy)
        if cymax > ymax:
            self._scatter.y -= (ymax - cymax) * s

    def on__pause(self, instance, value):
        if not value:
            self.trigger_update(True)

    def trigger_update(self, full):
        self._need_redraw_full = full or self._need_redraw_full
        Clock.unschedule(self.do_update)
        Clock.schedule_once(self.do_update, -1)

    def do_update(self, dt):
        zoom = self._zoom
        scale = self._scale
        self.lon = self.map_source.get_lon(zoom,
                (self.center_x - self._scatter.x) / scale - self.delta_x)
        self.lat = self.map_source.get_lat(zoom,
                (self.center_y - self._scatter.y) / scale - self.delta_y)
        self.dispatch("on_map_relocated", zoom, Coordinate(self.lon, self.lat))
        for layer in self._layers:
            layer.reposition()

        if self._need_redraw_full:
            self._need_redraw_full = False
            self.move_tiles_to_background()
            self.load_visible_tiles()
        else:
            self.load_visible_tiles()

    def bbox_for_zoom(self, vx, vy, w, h, zoom):
        # return a tile-bbox for the zoom
        map_source = self.map_source
        size = map_source.dp_tile_size
        scale = self._scale

        max_x_end = map_source.get_col_count(zoom)
        max_y_end = map_source.get_row_count(zoom)

        x_count = int(ceil(w / scale / float(size))) + 1
        y_count = int(ceil(h / scale / float(size))) + 1

        tile_x_first = int(clamp(vx / float(size), 0, max_x_end))
        tile_y_first = int(clamp(vy / float(size), 0, max_y_end))
        tile_x_last = tile_x_first + x_count
        tile_y_last = tile_y_first + y_count
        tile_x_last = int(clamp(tile_x_last, tile_x_first, max_x_end))
        tile_y_last = int(clamp(tile_y_last, tile_y_first, max_y_end))

        x_count = tile_x_last - tile_x_first
        y_count = tile_y_last - tile_y_first
        return (tile_x_first, tile_y_first, tile_x_last, tile_y_last,
                x_count, y_count)

    def load_visible_tiles(self):
        map_source = self.map_source
        vx, vy = self.viewport_pos
        zoom = self._zoom
        dirs = [0, 1, 0, -1, 0]
        bbox_for_zoom = self.bbox_for_zoom
        size = map_source.dp_tile_size

        tile_x_first, tile_y_first, tile_x_last, tile_y_last, \
            x_count, y_count = bbox_for_zoom(vx, vy, self.width, self.height, zoom)

        #print "Range {},{} to {},{}".format(
        #    tile_x_first, tile_y_first,
        #    tile_x_last, tile_y_last)

        # Adjust tiles behind us
        for tile in self._tiles_bg[:]:
            tile_x = tile.tile_x
            tile_y = tile.tile_y

            f = 2 ** (zoom - tile.zoom)
            w = self.width / f
            h = self.height / f
            btile_x_first, btile_y_first, btile_x_last, btile_y_last, \
                _, _ = bbox_for_zoom(vx / f, vy / f, w, h, tile.zoom)

            if tile_x < btile_x_first or tile_x >= btile_x_last or \
               tile_y < btile_y_first or tile_y >= btile_y_last:
               tile.state = "done"
               self._tiles_bg.remove(tile)
               self.canvas_map.before.remove(tile.g_color)
               self.canvas_map.before.remove(tile)
               continue

            tsize = size * f
            tile.size = tsize, tsize
            tile.pos = (
                tile_x * tsize + self.delta_x,
                tile_y * tsize + self.delta_y)

        # Get rid of old tiles first
        for tile in self._tiles[:]:
            tile_x = tile.tile_x
            tile_y = tile.tile_y

            if tile_x < tile_x_first or tile_x >= tile_x_last or \
               tile_y < tile_y_first or tile_y >= tile_y_last:
                tile.state = "done"
                self.tile_map_set(tile_x, tile_y, False)
                self._tiles.remove(tile)
                self.canvas_map.remove(tile)
                self.canvas_map.remove(tile.g_color)
            else:
                tile.size = (size, size)
                tile.pos = (tile_x * size + self.delta_x, tile_y * size + self.delta_y)

        # Load new tiles if needed
        x = tile_x_first + x_count // 2 - 1
        y = tile_y_first + y_count // 2 - 1
        arm_max = max(x_count, y_count) + 2
        arm_size = 1
        turn = 0
        while arm_size < arm_max:
            for i in range(arm_size):
                if not self.tile_in_tile_map(x, y) and \
                   y >= tile_y_first and y < tile_y_last and \
                   x >= tile_x_first and x < tile_x_last:
                    self.load_tile(x, y, size, zoom)

                x += dirs[turn % 4 + 1]
                y += dirs[turn % 4]

            if turn % 2 == 1:
                arm_size += 1

            turn += 1

    def load_tile(self, x, y, size, zoom):
        if self.tile_in_tile_map(x, y) or zoom != self._zoom:
            return
        self.load_tile_for_source(self.map_source, 1., size, x, y, zoom)
        # XXX do overlay support
        self.tile_map_set(x, y, True)

    def load_tile_for_source(self, map_source, opacity, size, x, y, zoom):
        tile = Tile(size=(size, size))
        tile.g_color = Color(1, 1, 1, 0)
        tile.tile_x = x
        tile.tile_y = y
        tile.zoom = zoom
        tile.pos = (x * size + self.delta_x, y * size + self.delta_y)
        tile.map_source = map_source
        tile.state = "loading"
        if not self._pause:
            map_source.fill_tile(tile)
        self.canvas_map.add(tile.g_color)
        self.canvas_map.add(tile)
        self._tiles.append(tile)

    def move_tiles_to_background(self):
        # remove all the tiles of the main map to the background map
        # retain only the one who are on the current zoom level
        # for all the tile in the background, stop the download if not yet started.
        zoom = self._zoom
        tiles = self._tiles
        btiles = self._tiles_bg
        canvas_map = self.canvas_map
        tile_size = self.map_source.tile_size

        # move all tiles to background
        while tiles:
            tile = tiles.pop()
            if tile.state == "loading":
                tile.state == "done"
                continue
            btiles.append(tile)

        # clear the canvas
        canvas_map.clear()
        canvas_map.before.clear()
        self._tilemap = {}

        # unsure if it's really needed, i personnally didn't get issues right now
        #btiles.sort(key=lambda z: -z.zoom)

        # add all the btiles into the back canvas.
        # except for the tiles that are owned by the current zoom level
        for tile in btiles[:]:
            if tile.zoom == zoom:
                btiles.remove(tile)
                tiles.append(tile)
                tile.size = tile_size, tile_size
                canvas_map.add(tile.g_color)
                canvas_map.add(tile)
                self.tile_map_set(tile.tile_x, tile.tile_y, True)
                continue
            canvas_map.before.add(tile.g_color)
            canvas_map.before.add(tile)

    def remove_all_tiles(self):
        # clear the map of all tiles.
        self.canvas_map.clear()
        self.canvas_map.before.clear()
        for tile in self._tiles:
            tile.state = "done"
        del self._tiles[:]
        del self._tiles_bg[:]
        self._tilemap = {}

    def tile_map_set(self, tile_x, tile_y, value):
        key = tile_y * self.map_source.get_col_count(self._zoom) + tile_x
        if value:
            self._tilemap[key] = value
        else:
            self._tilemap.pop(key, None)

    def tile_in_tile_map(self, tile_x, tile_y):
        key = tile_y * self.map_source.get_col_count(self._zoom) + tile_x
        return key in self._tilemap

    def on_size(self, instance, size):
        for layer in self._layers:
            layer.size = size
        self.center_on(self.lat, self.lon)
        self.trigger_update(True)

    def on_pos(self, instance, pos):
        self.center_on(self.lat, self.lon)
        self.trigger_update(True)

    def on_map_source(self, instance, source):
        if isinstance(source, string_types):
            self.map_source = MapSource.from_provider(source)
        elif isinstance(source, (tuple, list)):
            cache_key, min_zoom, max_zoom, url, attribution, options = source
            self.map_source = MapSource(url=url, cache_key=cache_key,
                                        min_zoom=min_zoom, max_zoom=max_zoom,
                                        attribution=attribution, **options)
        elif isinstance(source, MapSource):
            self.map_source = source
        else:
            raise Exception("Invalid map source provider")
        self.zoom = clamp(self.zoom,
                          self.map_source.min_zoom, self.map_source.max_zoom)
        self.remove_all_tiles()
        self.trigger_update(True)
Exemple #56
0
class ScrollView(StencilView):
    '''ScrollView class. See module documentation for more information.

    .. versionchanged:: 1.7.0
        `auto_scroll`, `scroll_friction`, `scroll_moves`, `scroll_stoptime' has
        been deprecated, use :attr:`effect_cls` instead.
    '''

    scroll_distance = NumericProperty(_scroll_distance)
    '''Distance to move before scrolling the :class:`ScrollView`, in pixels. As
    soon as the distance has been traveled, the :class:`ScrollView` will start
    to scroll, and no touch event will go to children.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    :attr:`scroll_distance` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 20 (pixels), according to the default value in user
    configuration.
    '''

    scroll_wheel_distance = NumericProperty(20)
    '''Distance to move when scrolling with a mouse wheel.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    .. versionadded:: 1.8.0

    :attr:`scroll_wheel_distance` is a
    :class:`~kivy.properties.NumericProperty` , defaults to 20 pixels.
    '''

    scroll_timeout = NumericProperty(_scroll_timeout)
    '''Timeout allowed to trigger the :attr:`scroll_distance`, in milliseconds.
    If the user has not moved :attr:`scroll_distance` within the timeout,
    the scrolling will be disabled, and the touch event will go to the
    children.

    :attr:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 55 (milliseconds) according to the default value in user
    configuration.

    .. versionchanged:: 1.5.0
        Default value changed from 250 to 55.
    '''

    scroll_x = NumericProperty(0.)
    '''X scrolling value, between 0 and 1. If 0, the content's left side will
    touch the left side of the ScrollView. If 1, the content's right side will
    touch the right side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_x` is True.

    :attr:`scroll_x` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.
    '''

    scroll_y = NumericProperty(1.)
    '''Y scrolling value, between 0 and 1. If 0, the content's bottom side will
    touch the bottom side of the ScrollView. If 1, the content's top side will
    touch the top side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_y` is True.

    :attr:`scroll_y` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 1.
    '''

    do_scroll_x = BooleanProperty(True)
    '''Allow scroll on X axis.

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

    do_scroll_y = BooleanProperty(True)
    '''Allow scroll on Y axis.

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

    def _get_do_scroll(self):
        return (self.do_scroll_x, self.do_scroll_y)

    def _set_do_scroll(self, value):
        if type(value) in (list, tuple):
            self.do_scroll_x, self.do_scroll_y = value
        else:
            self.do_scroll_x = self.do_scroll_y = bool(value)
    do_scroll = AliasProperty(_get_do_scroll, _set_do_scroll,
                              bind=('do_scroll_x', 'do_scroll_y'))
    '''Allow scroll on X or Y axis.

    :attr:`do_scroll` is a :class:`~kivy.properties.AliasProperty` of
    (:attr:`do_scroll_x` + :attr:`do_scroll_y`)
    '''

    def _get_vbar(self):
        # must return (y, height) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vh = self._viewport.height
        h = self.height
        if vh < h or vh == 0:
            return 0, 1.
        ph = max(0.01, h / float(vh))
        sy = min(1.0, max(0.0, self.scroll_y))
        py = (1. - ph) * sy
        return (py, ph)

    vbar = AliasProperty(_get_vbar, None, bind=(
        'scroll_y', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the vertical scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little vertical bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    def _get_hbar(self):
        # must return (x, width) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vw = self._viewport.width
        w = self.width
        if vw < w or vw == 0:
            return 0, 1.
        pw = max(0.01, w / float(vw))
        sx = min(1.0, max(0.0, self.scroll_x))
        px = (1. - pw) * sx
        return (px, pw)

    hbar = AliasProperty(_get_hbar, None, bind=(
        'scroll_x', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the horizontal scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little horizontal bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    bar_color = ListProperty([.7, .7, .7, .9])
    '''Color of horizontal / vertical scroll bar, in RGBA format.

    .. versionadded:: 1.2.0

    :attr:`bar_color` is a :class:`~kivy.properties.ListProperty` and defaults
    to [.7, .7, .7, .9].
    '''

    bar_inactive_color = ListProperty([.7, .7, .7, .2])
    '''Color of horizontal / vertical scroll bar (in RGBA format), when no
    scroll is happening.

    .. versionadded:: 1.9.0

    :attr:`bar_inactive_color` is a
    :class:`~kivy.properties.ListProperty` and defaults to [.7, .7, .7, .2].
    '''

    bar_width = NumericProperty('2dp')
    '''Width of the horizontal / vertical scroll bar. The width is interpreted
    as a height for the horizontal bar.

    .. versionadded:: 1.2.0

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

    bar_pos_x = OptionProperty('bottom', options=('top', 'bottom'))
    '''Which side of the ScrollView the horizontal scroll bar should go
    on. Possible values are 'top' and 'bottom'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_x` is an :class:`~kivy.properties.OptionProperty`,
    defaults to 'bottom'.

    '''

    bar_pos_y = OptionProperty('right', options=('left', 'right'))
    '''Which side of the ScrollView the vertical scroll bar should go
    on. Possible values are 'left' and 'right'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_y` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'right'.

    '''

    bar_pos = ReferenceListProperty(bar_pos_x, bar_pos_y)
    '''Which side of the scroll view to place each of the bars on.

    :attr:`bar_pos` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`bar_pos_x`, :attr:`bar_pos_y`)
    '''

    bar_margin = NumericProperty(0)
    '''Margin between the bottom / right side of the scrollview when drawing
    the horizontal / vertical scroll bar.

    .. versionadded:: 1.2.0

    :attr:`bar_margin` is a :class:`~kivy.properties.NumericProperty`, default
    to 0
    '''

    effect_cls = ObjectProperty(DampedScrollEffect, allownone=True)
    '''Class effect to instanciate for X and Y axis.

    .. versionadded:: 1.7.0

    :attr:`effect_cls` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to :class:`DampedScrollEffect`.

    .. versionchanged:: 1.8.0
        If you set a string, the :class:`~kivy.factory.Factory` will be used to
        resolve the class.

    '''

    effect_x = ObjectProperty(None, allownone=True)
    '''Effect to apply for the X axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

    :attr:`effect_x` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None.
    '''

    effect_y = ObjectProperty(None, allownone=True)
    '''Effect to apply for the Y axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

    :attr:`effect_y` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None, read-only.
    '''

    viewport_size = ListProperty([0, 0])
    '''(internal) Size of the internal viewport. This is the size of your only
    child in the scrollview.
    '''

    scroll_type = OptionProperty(['content'], options=(['content'], ['bars'],
                                 ['bars', 'content'], ['content', 'bars']))
    '''Sets the type of scrolling to use for the content of the scrollview.
    Available options are: ['content'], ['bars'], ['bars', 'content'].

    .. versionadded:: 1.8.0

    :attr:`scroll_type` is a :class:`~kivy.properties.OptionProperty`, defaults
    to ['content'].
    '''

    # private, for internal use only

    _viewport = ObjectProperty(None, allownone=True)
    _bar_color = ListProperty([0, 0, 0, 0])

    def _set_viewport_size(self, instance, value):
        self.viewport_size = value

    def on__viewport(self, instance, value):
        if value:
            value.bind(size=self._set_viewport_size)
            self.viewport_size = value.size

    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(
            self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas
        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

        super(ScrollView, self).__init__(**kwargs)

        self.register_event_type('on_scroll_start')
        self.register_event_type('on_scroll_move')
        self.register_event_type('on_scroll_stop')

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)
        self.bind(
            width=self._update_effect_x_bounds,
            height=self._update_effect_y_bounds,
            viewport_size=self._update_effect_bounds,
            _viewport=self._update_effect_widget,
            scroll_x=self._trigger_update_from_scroll,
            scroll_y=self._trigger_update_from_scroll,
            pos=self._trigger_update_from_scroll,
            size=self._trigger_update_from_scroll)

        self._update_effect_widget()
        self._update_effect_x_bounds()
        self._update_effect_y_bounds()

    def on_effect_x(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_x)
            value.target_widget = self._viewport

    def on_effect_y(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_y)
            value.target_widget = self._viewport

    def on_effect_cls(self, instance, cls):
        if isinstance(cls, string_types):
            cls = Factory.get(cls)
        self.effect_x = cls(target_widget=self._viewport)
        self.effect_x.bind(scroll=self._update_effect_x)
        self.effect_y = cls(target_widget=self._viewport)
        self.effect_y.bind(scroll=self._update_effect_y)

    def _update_effect_widget(self, *args):
        if self.effect_x:
            self.effect_x.target_widget = self._viewport
        if self.effect_y:
            self.effect_y.target_widget = self._viewport

    def _update_effect_x_bounds(self, *args):
        if not self._viewport or not self.effect_x:
            return
        self.effect_x.min = -(self.viewport_size[0] - self.width)
        self.effect_x.max = 0
        self.effect_x.value = self.effect_x.min * self.scroll_x

    def _update_effect_y_bounds(self, *args):
        if not self._viewport or not self.effect_y:
            return
        self.effect_y.min = -(self.viewport_size[1] - self.height)
        self.effect_y.max = 0
        self.effect_y.value = self.effect_y.min * self.scroll_y

    def _update_effect_bounds(self, *args):
        if not self._viewport:
            return
        if self.effect_x:
            self._update_effect_x_bounds()
        if self.effect_y:
            self._update_effect_y_bounds()

    def _update_effect_x(self, *args):
        vp = self._viewport
        if not vp or not self.effect_x:
            return
        sw = vp.width - self.width
        if sw < 1:
            return
        sx = self.effect_x.scroll / float(sw)
        self.scroll_x = -sx
        self._trigger_update_from_scroll()

    def _update_effect_y(self, *args):
        vp = self._viewport
        if not vp or not self.effect_y:
            return
        sh = vp.height - self.height
        if sh < 1:
            return
        sy = self.effect_y.scroll / float(sh)
        self.scroll_y = -sy
        self._trigger_update_from_scroll()

    def to_local(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x - tx, y - ty

    def to_parent(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x + tx, y + ty

    def _apply_transform(self, m):
        tx, ty = self.g_translate.xy
        m.translate(tx, ty, 0)
        m.translate(self.x, self.y, 0)
        return super(ScrollView, self)._apply_transform(m)

    def simulate_touch_down(self, touch):
        # at this point the touch is in parent coords
        touch.push()
        touch.apply_transform_2d(self.to_local)
        ret = super(ScrollView, self).on_touch_down(touch)
        touch.pop()
        return ret

    def on_touch_down(self, touch):
        if self.dispatch('on_scroll_start', touch):
            self._touch = touch
            touch.grab(self)
            return True

    def on_scroll_start(self, touch, check_children=True):
        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_start', touch):
                return True
            touch.pop()

        if not self.collide_point(*touch.pos):
            touch.ud[self._get_uid('svavoid')] = True
            return
        if self.disabled:
            return True
        if self._touch or (not (self.do_scroll_x or self.do_scroll_y)):
            return self.simulate_touch_down(touch)

        # handle mouse scrolling, only if the viewport size is bigger than the
        # scrollview size, and if the user allowed to do it
        vp = self._viewport
        if not vp:
            return True
        scroll_type = self.scroll_type
        ud = touch.ud
        scroll_bar = 'bars' in scroll_type

        # check if touch is in bar_x(horizontal) or bay_y(bertical)
        ud['in_bar_x'] = ud['in_bar_y'] = False
        width_scrollable = vp.width > self.width
        height_scrollable = vp.height > self.height
        bar_pos_x = self.bar_pos_x[0]
        bar_pos_y = self.bar_pos_y[0]

        d = {'b': True if touch.y < self.y + self.bar_width else False,
             't': True if touch.y > self.top - self.bar_width else False,
             'l': True if touch.x < self.x + self.bar_width else False,
             'r': True if touch.x > self.right - self.bar_width else False}
        if scroll_bar:
            if (width_scrollable and d[bar_pos_x]):
                ud['in_bar_x'] = True
            if (height_scrollable and d[bar_pos_y]):
                ud['in_bar_y'] = True

        if vp and 'button' in touch.profile and \
                touch.button.startswith('scroll'):
            btn = touch.button
            m = sp(self.scroll_wheel_distance)
            e = None

            if ((btn == 'scrolldown' and self.scroll_y >= 1) or
                (btn == 'scrollup' and self.scroll_y <= 0) or
                (btn == 'scrollleft' and self.scroll_x >= 1) or
                (btn == 'scrollright' and self.scroll_x <= 0)):
                return False

            if (self.effect_x and self.do_scroll_y and height_scrollable
                    and btn in ('scrolldown', 'scrollup')):
                e = self.effect_x if ud['in_bar_x'] else self.effect_y

            elif (self.effect_y and self.do_scroll_x and width_scrollable
                    and btn in ('scrollleft', 'scrollright')):
                e = self.effect_y if ud['in_bar_y'] else self.effect_x

            if e:
                if btn in ('scrolldown', 'scrollleft'):
                    e.value = max(e.value - m, e.min)
                    e.velocity = 0
                elif btn in ('scrollup', 'scrollright'):
                    e.value = min(e.value + m, e.max)
                    e.velocity = 0
                touch.ud[self._get_uid('svavoid')] = True
                e.trigger_velocity_update()
            return True

        # no mouse scrolling, so the user is going to drag the scrollview with
        # this touch.
        self._touch = touch
        uid = self._get_uid()

        ud[uid] = {
            'mode': 'unknown',
            'dx': 0,
            'dy': 0,
            'user_stopped': False,
            'frames': Clock.frames,
            'time': touch.time_start}

        if self.do_scroll_x and self.effect_x and not ud['in_bar_x']:
            self.effect_x.start(touch.x)
            self._scroll_x_mouse = self.scroll_x
        if self.do_scroll_y and self.effect_y and not ud['in_bar_y']:
            self.effect_y.start(touch.y)
            self._scroll_y_mouse = self.scroll_y

        if (ud.get('in_bar_x', False) or ud.get('in_bar_y', False)):
            return True

        Clock.schedule_once(self._change_touch_mode,
                                self.scroll_timeout / 1000.)
        if scroll_type == ['bars']:
            return False
        else:
            return True

    def on_touch_move(self, touch):
        if self._touch is not touch:
            # touch is in parent
            touch.push()
            touch.apply_transform_2d(self.to_local)
            super(ScrollView, self).on_touch_move(touch)
            touch.pop()
            return self._get_uid() in touch.ud
        if touch.grab_current is not self:
            return True

        if not (self.do_scroll_y or self.do_scroll_x):
            return super(ScrollView, self).on_touch_move(touch)

        touch.ud['sv.handled'] = {'x': False, 'y': False}
        if self.dispatch('on_scroll_move', touch):
            return True

    def on_scroll_move(self, touch):
        if self._get_uid('svavoid') in touch.ud:
            return False

        touch.push()
        touch.apply_transform_2d(self.to_local)
        if self.dispatch_children('on_scroll_move', touch):
            return True
        touch.pop()

        rv = True

        uid = self._get_uid()
        if not uid in touch.ud:
            self._touch = False
            return self.on_scroll_start(touch, False)
        ud = touch.ud[uid]
        mode = ud['mode']

        # check if the minimum distance has been travelled
        if mode == 'unknown' or mode == 'scroll':
            if not touch.ud['sv.handled']['x'] and self.do_scroll_x \
                    and self.effect_x:
                width = self.width
                if touch.ud.get('in_bar_x', False):
                    dx = touch.dx / float(width - width * self.hbar[1])
                    self.scroll_x = min(max(self.scroll_x + dx, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_x.update(touch.x)
                if self.scroll_x < 0 or self.scroll_x > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['x'] = True
            if not touch.ud['sv.handled']['y'] and self.do_scroll_y \
                    and self.effect_y:
                height = self.height
                if touch.ud.get('in_bar_y', False):
                    dy = touch.dy / float(height - height * self.vbar[1])
                    self.scroll_y = min(max(self.scroll_y + dy, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_y.update(touch.y)
                if self.scroll_y < 0 or self.scroll_y > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['y'] = True

        if mode == 'unknown':
            ud['dx'] += abs(touch.dx)
            ud['dy'] += abs(touch.dy)
            if ud['dx'] > self.scroll_distance:
                if not self.do_scroll_x:
                    # touch is in parent, but _change expects window coords
                    touch.push()
                    touch.apply_transform_2d(self.to_local)
                    touch.apply_transform_2d(self.to_window)
                    self._change_touch_mode()
                    touch.pop()
                    return
                mode = 'scroll'

            if ud['dy'] > self.scroll_distance:
                if not self.do_scroll_y:
                    # touch is in parent, but _change expects window coords
                    touch.push()
                    touch.apply_transform_2d(self.to_local)
                    touch.apply_transform_2d(self.to_window)
                    self._change_touch_mode()
                    touch.pop()
                    return
                mode = 'scroll'
            ud['mode'] = mode

        if mode == 'scroll':
            ud['dt'] = touch.time_update - ud['time']
            ud['time'] = touch.time_update
            ud['user_stopped'] = True

        return rv

    def on_touch_up(self, touch):
        if self._touch is not touch and self.uid not in touch.ud:
            # touch is in parents
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if super(ScrollView, self).on_touch_up(touch):
                return True
            touch.pop()
            return False

        if self.dispatch('on_scroll_stop', touch):
            touch.ungrab(self)
            return True

    def on_scroll_stop(self, touch, check_children=True):
        self._touch = None

        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_stop', touch):
                return True
            touch.pop()

        if self._get_uid('svavoid') in touch.ud:
            return
        if self._get_uid() not in touch.ud:
            return False

        self._touch = None
        uid = self._get_uid()
        ud = touch.ud[uid]
        if self.do_scroll_x and self.effect_x:
            if not touch.ud.get('in_bar_x', False) and\
                    self.scroll_type != ['bars']:
                self.effect_x.stop(touch.x)
        if self.do_scroll_y and self.effect_y and\
                self.scroll_type != ['bars']:
            if not touch.ud.get('in_bar_y', False):
                self.effect_y.stop(touch.y)
        if ud['mode'] == 'unknown':
            # we must do the click at least..
            # only send the click if it was not a click to stop
            # autoscrolling
            if not ud['user_stopped']:
                self.simulate_touch_down(touch)
            Clock.schedule_once(partial(self._do_touch_up, touch), .2)
        Clock.unschedule(self._update_effect_bounds)
        Clock.schedule_once(self._update_effect_bounds)

        # if we do mouse scrolling, always accept it
        if 'button' in touch.profile and touch.button.startswith('scroll'):
            return True

        return self._get_uid() in touch.ud

    def convert_distance_to_scroll(self, dx, dy):
        '''Convert a distance in pixels to a scroll distance, depending on the
        content size and the scrollview size.

        The result will be a tuple of scroll distance that can be added to
        :data:`scroll_x` and :data:`scroll_y`
        '''
        if not self._viewport:
            return 0, 0
        vp = self._viewport
        if vp.width > self.width:
            sw = vp.width - self.width
            sx = dx / float(sw)
        else:
            sx = 0
        if vp.height > self.height:
            sh = vp.height - self.height
            sy = dy / float(sh)
        else:
            sy = 1
        return sx, sy

    def update_from_scroll(self, *largs):
        '''Force the reposition of the content, according to current value of
        :attr:`scroll_x` and :attr:`scroll_y`.

        This method is automatically called when one of the :attr:`scroll_x`,
        :attr:`scroll_y`, :attr:`pos` or :attr:`size` properties change, or
        if the size of the content changes.
        '''
        if not self._viewport:
            return
        vp = self._viewport

        # update from size_hint
        if vp.size_hint_x is not None:
            vp.width = vp.size_hint_x * self.width
        if vp.size_hint_y is not None:
            vp.height = vp.size_hint_y * self.height

        if vp.width > self.width:
            sw = vp.width - self.width
            x = self.x - self.scroll_x * sw
        else:
            x = self.x
        if vp.height > self.height:
            sh = vp.height - self.height
            y = self.y - self.scroll_y * sh
        else:
            y = self.top - vp.height

        # from 1.8.0, we now use a matrix by default, instead of moving the
        # widget position behind. We set it here, but it will be a no-op most of
        # the time.
        vp.pos = 0, 0
        self.g_translate.xy = x, y

        # New in 1.2.0, show bar when scrolling happens and (changed in 1.9.0)
        # fade to bar_inactive_color when no scroll is happening.
        Clock.unschedule(self._bind_inactive_bar_color)
        self.unbind(bar_inactive_color=self._change_bar_color)
        Animation.stop_all(self, '_bar_color')
        self.bind(bar_color=self._change_bar_color)
        self._bar_color = self.bar_color
        Clock.schedule_once(self._bind_inactive_bar_color, .5)

    def _bind_inactive_bar_color(self, *l):
        self.unbind(bar_color=self._change_bar_color)
        self.bind(bar_inactive_color=self._change_bar_color)
        Animation(
            _bar_color=self.bar_inactive_color, d=.5, t='out_quart').start(self)

    def _change_bar_color(self, inst, value):
        self._bar_color = value

    #
    # Private
    #
    def add_widget(self, widget, index=0):
        if self._viewport:
            raise Exception('ScrollView accept only one widget')
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).add_widget(widget, index)
        self.canvas = canvas
        self._viewport = widget
        widget.bind(size=self._trigger_update_from_scroll)
        self._trigger_update_from_scroll()

    def remove_widget(self, widget):
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).remove_widget(widget)
        self.canvas = canvas
        if widget is self._viewport:
            self._viewport = None

    def _get_uid(self, prefix='sv'):
        return '{0}.{1}'.format(prefix, self.uid)

    def _change_touch_mode(self, *largs):
        if not self._touch:
            return
        uid = self._get_uid()
        touch = self._touch
        ud = touch.ud[uid]
        if ud['mode'] != 'unknown' or ud['user_stopped']:
            return
        diff_frames = Clock.frames - ud['frames']

        # in order to be able to scroll on very slow devices, let at least 3
        # frames displayed to accumulate some velocity. And then, change the
        # touch mode. Otherwise, we might never be able to compute velocity, and
        # no way to scroll it. See #1464 and #1499
        if diff_frames < 3:
            Clock.schedule_once(self._change_touch_mode, 0)
            return

        if self.do_scroll_x and self.effect_x:
            self.effect_x.cancel()
        if self.do_scroll_y and self.effect_y:
            self.effect_y.cancel()
        # XXX the next line was in the condition. But this stop
        # the possibily to "drag" an object out of the scrollview in the
        # non-used direction: if you have an horizontal scrollview, a
        # vertical gesture will not "stop" the scroll view to look for an
        # horizontal gesture, until the timeout is done.
        # and touch.dx + touch.dy == 0:
        touch.ungrab(self)
        self._touch = None
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        touch.apply_transform_2d(self.to_parent)
        self.simulate_touch_down(touch)
        touch.pop()
        return

    def _do_touch_up(self, touch, *largs):
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        super(ScrollView, self).on_touch_up(touch)
        touch.pop()
        # don't forget about grab event!
        for x in touch.grab_list[:]:
            touch.grab_list.remove(x)
            x = x()
            if not x:
                continue
            touch.grab_current = x
            # touch is in window coords
            touch.push()
            touch.apply_transform_2d(self.to_widget)
            super(ScrollView, self).on_touch_up(touch)
            touch.pop()
        touch.grab_current = None
Exemple #57
0
class Widget(EventDispatcher):
    '''Widget class. See module documentation for more informations.

    :Events:
        `on_touch_down`:
            Fired when a new touch appear
        `on_touch_move`:
            Fired when an existing touch is moved
        `on_touch_down`:
            Fired when an existing touch disapear
    '''

    # UID counter
    __widget_uid = 0

    def __new__(__cls__, *largs, **kwargs):
        self = super(Widget, __cls__).__new__(__cls__)

        # XXX for the moment, we need to create a uniq id for properties.
        # Properties need a identifier to the class instance. hash() and id()
        # are longer than using a custom __uid. I hope we can figure out a way
        # of doing that without require any python code. :)
        Widget.__widget_uid += 1
        self.__dict__['__uid'] = Widget.__widget_uid

        # First loop, link all the properties storage to our instance
        attrs_found = {}
        attrs = dir(__cls__)
        for k in attrs:
            attr = getattr(__cls__, k)
            if isinstance(attr, Property):
                if k in Widget_forbidden_properties:
                    raise Exception(
                        'The property <%s> have a forbidden name' % k)
                attr.link(self, k)
                attrs_found[k] = attr

        # Second loop, resolve all the reference
        for k in attrs:
            attr = getattr(__cls__, k)
            if isinstance(attr, Property):
                attr.link_deps(self, k)

        self.__properties = attrs_found

        # Then, return the class instance
        return self

    def __del__(self):
        # The thing here, since the storage of the property is inside the
        # Property class, we must remove ourself from the storage of each
        # Property. The usage is faster, the creation / deletion is longer.
        for attr in self.__properties.itervalues():
            attr.unlink(self)

    def __init__(self, **kwargs):
        super(Widget, self).__init__()

        # Register touch events
        self.register_event_type('on_touch_down')
        self.register_event_type('on_touch_move')
        self.register_event_type('on_touch_up')

        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # Auto bind on own handler if exist
        properties = self.__properties.keys()
        for func in dir(self):
            if not func.startswith('on_'):
                continue
            name = func[3:]
            if name in properties:
                self.bind(**{name: getattr(self, func)})

        # Create the default canvas
        self.canvas = Canvas()

        # Apply the existing arguments to our widget
        for key, value in kwargs.iteritems():
            if hasattr(self, key):
                setattr(self, key, value)

        # Apply all the styles
        if '__no_builder' not in kwargs:
            Builder.apply(self)

    def create_property(self, name):
        '''Create a new property at runtime.

        .. warning::

            This function is designed for the Kivy language, don't use it in
            your code. You should declare the property in your class instead of
            using this method.

        :Parameters:
            `name`: string
                Name of the property

        The class of the property cannot be specified, it will be always an
        :class:`~kivy.properties.ObjectProperty` class. The default value of the
        property will be None, until you set a new value.

        >>> mywidget = Widget()
        >>> mywidget.create_property('custom')
        >>> mywidget.custom = True
        >>> print mywidget.custom
        True
        '''
        prop = ObjectProperty(None)
        prop.link(self, name)
        prop.link_deps(self, name)
        self.__properties[name] = prop
        setattr(self, name, prop)


    #
    # Collision
    #
    def collide_point(self, x, y):
        '''Check if a point (x, y) is inside the widget bounding box.

        :Parameters:
            `x`: numeric
                X position of the point
            `y`: numeric
                Y position of the point

        :Returns:
            bool, True if the point is inside the bounding box

        >>> Widget(pos=(10, 10), size=(50, 50)).collide_point(40, 40)
        True
        '''
        return self.x <= x <= self.right and self.y <= y <= self.top

    def collide_widget(self, wid):
        '''Check if widget (bounding box) is colliding with our widget bounding
        box.

        :Parameters:
            `wid`: :class:`Widget` class
                Widget to collide to.

        :Returns:
            bool, True if the widget is colliding us.

        >>> wid = Widget(size=(50, 50))
        >>> wid2 = Widget(size=(50, 50), pos=(25, 25))
        >>> wid.collide_widget(wid2)
        True
        >>> wid2.pos = (55, 55)
        >>> wid.collide_widget(wid2)
        False
        '''
        if self.right < wid.x:
            return False
        if self.x > wid.right:
            return False
        if self.top < wid.y:
            return False
        if self.y > wid.top:
            return False
        return True


    #
    # Default event handlers
    #
    def on_touch_down(self, touch):
        '''Receive a touch down event

        :Parameters:
            `touch`: :class:`~kivy.input.motionevent.MotionEvent` class
                Touch received

        :Returns:
            bool. If True, the dispatching will stop.
        '''
        for child in self.children[:]:
            if child.dispatch('on_touch_down', touch):
                return True

    def on_touch_move(self, touch):
        '''Receive a touch move event.

        See :func:`on_touch_down` for more information
        '''
        for child in self.children[:]:
            if child.dispatch('on_touch_move', touch):
                return True

    def on_touch_up(self, touch):
        '''Receive a touch up event.

        See :func:`on_touch_down` for more information
        '''
        for child in self.children[:]:
            if child.dispatch('on_touch_up', touch):
                return True


    #
    # Events
    #
    def bind(self, **kwargs):
        '''Bind properties or event to handler.

        Example of usage::

            def my_x_callback(obj, value):
                print 'on object', obj', 'x changed to', value
            def my_width_callback(obj, value):
                print 'on object', obj, 'width changed to', value
            self.bind(x=my_x_callback, width=my_width_callback)
        '''
        super(Widget, self).bind(**kwargs)
        for key, value in kwargs.iteritems():
            if key.startswith('on_'):
                continue
            self.__properties[key].bind(self, value)

    def unbind(self, **kwargs):
        '''Unbind properties or event from handler

        See :func:`bind()` for more information.
        '''
        super(Widget, self).unbind(**kwargs)
        for key, value in kwargs.iteritems():
            if key.startswith('on_'):
                continue
            self.__properties[key].unbind(self, value)


    #
    # Tree management
    #
    def add_widget(self, widget):
        '''Add a new widget as a child of current widget

        :Parameters:
            `widget`: :class:`Widget`
                Widget to add in our children list.

        >>> root = Widget()
        >>> root.add_widget(Button())
        >>> slider = Slider()
        >>> root.add_widget(slider)
        '''
        if not isinstance(widget, Widget):
            raise WidgetException(
                'add_widget() can be used only with Widget classes.')
        widget.parent = self
        self.children = [widget] + self.children
        self.canvas.add(widget.canvas)

    def remove_widget(self, widget):
        '''Remove a widget from the children of current widget

        :Parameters:
            `widget`: :class:`Widget`
                Widget to add in our children list.

        >>> root = Widget()
        >>> button = Button()
        >>> root.add_widget(button)
        >>> root.remove_widget(button)
        '''
        if widget not in self.children:
            return
        self.children.remove(widget)
        self.children = self.children[:]
        self.canvas.remove(widget.canvas)
        widget.parent = None

    def clear_widgets(self):
        '''Remove all widgets added to the widget.
        '''
        remove_widget = self.remove_widget
        for child in self.children[:]:
            remove_widget(child)

    def get_root_window(self):
        '''Return the root window

        :Returns:
            Instance of the root window. Can be
            :class:`~kivy.core.window.WindowBase` or
            :class:`Widget`
        '''
        if self.parent:
            return self.parent.get_root_window()

    def get_parent_window(self):
        '''Return the parent window

        :Returns:
            Instance of the root window. Can be
            :class:`~kivy.core.window.WindowBase` or
            :class:`Widget`
        '''
        if self.parent:
            return self.parent.get_parent_window()

    def to_widget(self, x, y, relative=False):
        '''Return the coordinate from window to local widget'''
        if self.parent:
            x, y = self.parent.to_widget(x, y)
        return self.to_local(x, y, relative=relative)

    def to_window(self, x, y, initial=True, relative=False):
        '''Transform local coordinate to window coordinate'''
        if not initial:
            x, y = self.to_parent(x, y, relative=relative)
        if self.parent:
            return self.parent.to_window(x, y, initial=False, relative=relative)
        return (x, y)

    def to_parent(self, x, y, relative=False):
        '''Transform local coordinate to parent coordinate

        :Parameters:
            `relative`: bool, default to False
                Change to True is you want to translate relative position from
                widget to his parent.
        '''
        if relative:
            return (x + self.x, y + self.y)
        return (x, y)

    def to_local(self, x, y, relative=False):
        '''Transform parent coordinate to local coordinate

        :Parameters:
            `relative`: bool, default to False
                Change to True is you want to translate a coordinate to a
                relative coordinate from widget.
        '''
        if relative:
            return (x - self.x, y - self.y)
        return (x, y)


    #
    # Properties
    #
    def setter(self, name):
        '''Return the setter of a property. Useful if you want to directly bind
        a property to another.

        For example, if you want to position one widget next to you ::

            self.bind(right=nextchild.setter('x'))
        '''
        return self.__properties[name].__set__

    def getter(self, name):
        '''Return the getter of a property.
        '''
        return self.__properties[name].__get__

    x = NumericProperty(0)
    '''X position of the widget.

    :data:`x` is a :class:`~kivy.properties.NumericProperty`, default to 0.
    '''

    y = NumericProperty(0)
    '''Y position of the widget.

    :data:`y` is a :class:`~kivy.properties.NumericProperty`, default to 0.
    '''

    width = NumericProperty(100)
    '''Width of the widget.

    :data:`width` is a :class:`~kivy.properties.NumericProperty`, default
    to 100.
    '''

    height = NumericProperty(100)
    '''Height of the widget.

    :data:`height` is a :class:`~kivy.properties.NumericProperty`, default
    to 100.
    '''

    pos = ReferenceListProperty(x, y)
    '''Position of the widget.

    :data:`pos` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:data:`x`, :data:`y`) properties.
    '''

    size = ReferenceListProperty(width, height)
    '''Size of the widget.

    :data:`size` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:data:`width`, :data:`height`) properties.
    '''

    def get_right(self):
        return self.x + self.width

    def set_right(self, value):
        self.x = value - self.width

    right = AliasProperty(get_right, set_right, bind=('x', 'width'))
    '''Right position of the widget

    :data:`right` is a :class:`~kivy.properties.AliasProperty` of
    (:data:`x` + :data:`width`)
    '''

    def get_top(self):
        return self.y + self.height

    def set_top(self, value):
        self.y = value - self.height

    top = AliasProperty(get_top, set_top, bind=('y', 'height'))
    '''Top position of the widget

    :data:`top` is a :class:`~kivy.properties.AliasProperty` of
    (:data:`y` + :data:`height`)
    '''

    def get_center_x(self):
        return self.x + self.width / 2.

    def set_center_x(self, value):
        self.x = value - self.width / 2.
    center_x = AliasProperty(get_center_x, set_center_x, bind=('x', 'width'))
    '''X center position of the widget

    :data:`center_x` is a :class:`~kivy.properties.AliasProperty` of
    (:data:`x` + :data:`width` / 2.)
    '''

    def get_center_y(self):
        return self.y + self.height / 2.

    def set_center_y(self, value):
        self.y = value - self.height / 2.
    center_y = AliasProperty(get_center_y, set_center_y, bind=('y', 'height'))
    '''Y center position of the widget

    :data:`center_y` is a :class:`~kivy.properties.AliasProperty` of
    (:data:`y` + :data:`height` / 2.)
    '''

    center = ReferenceListProperty(center_x, center_y)
    '''Center position of the widget

    :data:`center` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:data:`center_x`, :data:`center_y`)
    '''

    cls = ListProperty([])
    '''Class of the widget, used for styling.
    '''

    id = StringProperty(None, allownone=True)
    '''Uniq identifier of the widget in the tree.

    :data:`id` is a :class:`~kivy.properties.StringProperty`, default to None.

    .. warning::

        If the :data:`id` is already used in the tree, an exception will
        occur.
    '''

    children = ListProperty([])
    '''Children list

    :data:`children` is a :class:`~kivy.properties.ListProperty` instance,
    default to an empty list.

    Use :func:`add_widget` and :func:`remove_widget` for manipulate children
    list. Don't manipulate children list directly until you know what you are
    doing.
    '''

    parent = ObjectProperty(None, allownone=True)
    '''Parent of the widget

    :data:`parent` is a :class:`~kivy.properties.ObjectProperty` instance,
    default to None.

    The parent of a widget is set when the widget is added to another one, and
    unset when the widget is removed from his parent.
    '''

    size_hint_x = NumericProperty(1, allownone=True)
    '''X size hint. It represent how much space the widget should use in the X
    axis from his parent. Only :class:`~kivy.uix.layout.Layout` and
    :class:`~kivy.core.window.Window` are using the hint.

    Value is in percent, 1. will mean the full size of his parent, aka 100%. 0.5
    will represent 50%.

    :data:`size_hint_x` is a :class:`~kivy.properties.NumericProperty`, default
    to 1.
    '''

    size_hint_y = NumericProperty(1, allownone=True)
    '''Y size hint.

    :data:`size_hint_y` is a :class:`~kivy.properties.NumericProperty`, default
    to 1.

    See :data:`size_hint_x` for more information
    '''

    size_hint = ReferenceListProperty(size_hint_x, size_hint_y)
    '''Size hint.

    :data:`size_hint` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:data:`size_hint_x`, :data:`size_hint_y`)

    See :data:`size_hint_x` for more information
    '''

    pos_hint = ObjectProperty({})
    '''Position hint. This property permit you to set the position of the widget
    inside his parent layout, in percent.

    For example, if you want to set the top of the widget to be at 90% height of
    his parent layout, you can write:

        widget = Widget(pos_hint={'top': 0.9})

    The keys 'x', 'right' will use the parent width.
    The keys 'y', 'top' will use the parent height.

    Check :doc:`api-kivy.uix.floatlayout` for more informations.

    Position hint is only used in :class:`~kivy.uix.floatlayout.FloatLayout` and
    :class:`~kivy.core.window.Window`.

    :data:`pos_hint` is a :class:`~kivy.properties.ObjectProperty` containing a
    dict.
    '''

    canvas = None
    '''Canvas of the widget.
class PageCurlTransition(TransitionBase):

    cy_x = NumericProperty(520.)
    cy_y = NumericProperty(-50)
    cy_dir = NumericProperty(1.18)
    cy_radius = NumericProperty(150.)

    def __init__(self, **kwargs):
        super(PageCurlTransition, self).__init__(**kwargs)
        self.fbo_in = None
        self.fbo_out = None

    def make_screen_fbo(self, screen, mode=None):
        assert(mode is not None)
        attr = 'fbo_'  + mode
        fbo = getattr(self, attr)

        w, h = screen.size
        w = (w - w % TILE) + TILE
        h = (h - h % TILE) + TILE
        size = w, h

        if not fbo:
            fbo = Fbo(size=size)
            setattr(self, attr, fbo)

        fbo.clear()
        with fbo:
            ClearColor(0, 0, 0, 1)
            ClearBuffers()

        fbo.add(screen.canvas)
        fbo.draw()
        return fbo

    def add_screen(self, screen):
        self.screen_in.pos = self.screen_out.pos
        self.screen_in.size = self.screen_out.size
        self.manager.real_remove_widget(self.screen_out)

        print '-----------'
        print 'add_screen', screen, screen.canvas
        print 'screen_in', self.screen_in, self.screen_in.parent
        print 'screen_out', self.screen_out, self.screen_out.parent
        print '-----------'
        self.fbo_in = self.make_screen_fbo(self.screen_in, mode='in')
        self.fbo_out = self.make_screen_fbo(self.screen_out, mode='out')
        self.manager.canvas.add(self.fbo_in)
        self.manager.canvas.add(self.fbo_out)

        self.canvas = Canvas()
        self.c_front = RenderContext()
        self.c_front.shader.source = join(curdir, 'front.glsl')
        self.c_back = RenderContext()
        self.c_back.shader.source = join(curdir, 'back.glsl')
        self.c_backshadow = RenderContext()
        self.c_backshadow.shader.source = join(curdir, 'backshadow.glsl')
        self.canvas.add(self.c_front)
        self.canvas.add(self.c_back)
        self.canvas.add(self.c_backshadow)

        with self.canvas.before:
            Color(1, 1, 1)
            Rectangle(
                size=self.fbo_in.size,
                texture=self.fbo_in.texture)
            Callback(self._enter_3d)
        self._build_mesh(self.fbo_in.size)
        with self.canvas.after:
            Callback(self._leave_3d)

        self.manager.canvas.add(self.canvas)
        self.on_progress(0)

    def remove_screen(self, screen):
        self.manager.canvas.remove(self.fbo_in)
        self.manager.canvas.remove(self.fbo_out)
        self.manager.canvas.remove(self.canvas)
        self.manager.real_add_widget(self.screen_in)

    def on_progress(self, t):
        d = 0.8
        if t < d:
            dt = t / d
            self.cy_dir = funcLinear(AnimationTransition.out_circ(dt), 0, 1.55)
        else:
            self.cy_dir = 1.5
        self.cy_x = funcLinear(t, self.screen_in.width, -self.screen_in.width / 2.0)
        self.update_glsl()

    def update_glsl(self, *largs):
        size = self.manager.size
        proj = Matrix().view_clip(0, size[0], 0, size[1], -1000, 1000, 0)
        self.c_front['projection_mat'] = proj
        self.c_front['cylinder_position'] = map(float, (self.cy_x, self.cy_y))
        self.c_front['cylinder_direction'] = (cos(self.cy_dir), sin(self.cy_dir))
        self.c_front['cylinder_radius'] = float(self.cy_radius)
        self.c_front['texture1'] = 1

        for key in ('projection_mat', 'cylinder_position', 'cylinder_radius',
                'cylinder_direction', 'texture1'):
            self.c_back[key] = self.c_front[key]
            self.c_backshadow[key] = self.c_front[key]

    def _enter_3d(self, *args):
        glEnable(GL_DEPTH_TEST)
        glEnable(GL_CULL_FACE)

    def _leave_3d(self, *args):
        glDisable(GL_DEPTH_TEST)
        glDisable(GL_CULL_FACE)

    def _build_mesh(self, size):
        m = TILE
        width, height = map(int, size)
        step_width = int(width / (width / m))
        step_height = int(height / (height / m))
        vertices = []
        indices = []
        indices_back = []
        fw = float(width)
        fh = float(height)

        # create all the vertices
        for y in xrange(0, height + step_height, step_height):
            for x in xrange(0, width + step_width, step_width):
                vertices += [x, y, 0, x / fw, y / fh]

        # trace a triangles mesh
        mx = 1 + width / step_width
        my = 1 + height / step_height

        self.vertex_format = [
            ('vPosition', 3, 'float'),
            ('vTexCoords0', 2, 'float')]

        mode = 'line_loop' if DEBUG else 'triangles'
        for x in xrange(mx - 1):
            for y in xrange(my - 1):
                i = y * mx + x
                indices += [i, i + 1, i + 1 + mx,
                            i, i + 1 + mx, i + mx]
                indices_back += [i, i + 1 + mx, i + 1,
                            i, i + mx, i + 1 + mx]

        fbo_out_texture = None if DEBUG else self.fbo_out.texture
        self.g_mesh = Mesh(vertices=vertices, indices=indices,
                mode=mode, texture=fbo_out_texture, fmt=self.vertex_format)
        self.g_mesh_back = Mesh(vertices=vertices, indices=indices_back,
                mode=mode, texture=fbo_out_texture, fmt=self.vertex_format)
        self.o_vertices = vertices
        print 'vertices', len(vertices)
        print 'indices', len(indices)
        print 'indices_back', len(indices_back)

        self.c_front.add(BindTexture(source=join(curdir, 'frontshadow.png'), index=1))
        self.c_front.add(self.g_mesh)
        self.c_backshadow.add(Rectangle(size=size))
        self.c_back.add(BindTexture(source=join(curdir, 'backshadow.png'), index=1))
        self.c_back.add(self.g_mesh_back)
Exemple #59
0
class Widget(WidgetBase):
    '''Widget class. See module documentation for more information.

    :Events:
        `on_touch_down`:
            Fired when a new touch event occurs
        `on_touch_move`:
            Fired when an existing touch moves
        `on_touch_up`:
            Fired when an existing touch disappears

    .. warning::
        Adding a `__del__` method to a class derived from Widget with python
        prior to 3.4 will disable automatic garbage collection for instances
        of that class. This is because the Widget class creates reference
        cycles, thereby `preventing garbage collection
        <https://docs.python.org/2/library/gc.html#gc.garbage>`_.

    .. versionchanged:: 1.0.9
        Everything related to event properties has been moved to the
        :class:`~kivy.event.EventDispatcher`. Event properties can now be used
        when contructing a simple class without subclassing :class:`Widget`.

    .. versionchanged:: 1.5.0
        The constructor now accepts on_* arguments to automatically bind
        callbacks to properties or events, as in the Kv language.
    '''

    __metaclass__ = WidgetMetaclass
    __events__ = ('on_touch_down', 'on_touch_move', 'on_touch_up')

    def __init__(self, **kwargs):
        # Before doing anything, ensure the windows exist.
        EventLoop.ensure_window()

        # assign the default context of the widget creation
        if not hasattr(self, '_context'):
            self._context = get_current_context()

        super(Widget, self).__init__(**kwargs)

        # Create the default canvas if not exist
        if self.canvas is None:
            self.canvas = Canvas(opacity=self.opacity)

        # Apply all the styles
        if '__no_builder' not in kwargs:
            #current_root = Builder.idmap.get('root')
            #Builder.idmap['root'] = self
            Builder.apply(self)
            #if current_root is not None:
            #    Builder.idmap['root'] = current_root
            #else:
            #    Builder.idmap.pop('root')

        # Bind all the events
        for argument in kwargs:
            if argument[:3] == 'on_':
                self.bind(**{argument: kwargs[argument]})

    @property
    def proxy_ref(self):
        '''Return a proxy reference to the widget, i.e. without creating a
        reference to the widget. See `weakref.proxy
        <http://docs.python.org/2/library/weakref.html?highlight\
        =proxy#weakref.proxy>`_ for more information.

        .. versionadded:: 1.7.2
        '''
        if hasattr(self, '_proxy_ref'):
            return self._proxy_ref

        f = partial(_widget_destructor, self.uid)
        self._proxy_ref = _proxy_ref = proxy(self, f)
        # only f should be enough here, but it appears that is a very
        # specific case, the proxy destructor is not called if both f and
        # _proxy_ref are not together in a tuple
        _widget_destructors[self.uid] = (f, _proxy_ref)
        return _proxy_ref

    def __eq__(self, other):
        if not isinstance(other, Widget):
            return False
        return self.proxy_ref is other.proxy_ref

    def __hash__(self):
        return id(self)

    @property
    def __self__(self):
        return self

    #
    # Collision
    #
    def collide_point(self, x, y):
        '''Check if a point (x, y) is inside the widget's axis aligned bounding
        box.

        :Parameters:
            `x`: numeric
                X position of the point (in window coordinates)
            `y`: numeric
                Y position of the point (in window coordinates)

        :Returns:
            bool, True if the point is inside the bounding box.

    .. code-block:: python

        >>> Widget(pos=(10, 10), size=(50, 50)).collide_point(40, 40)
        True
        '''
        return self.x <= x <= self.right and self.y <= y <= self.top

    def collide_widget(self, wid):
        '''Check if the other widget collides with this widget.
        Performs an axis-aligned bounding box intersection test by default.

        :Parameters:
            `wid`: :class:`Widget` class
                Widget to collide with.

        :Returns:
            bool, True if the other widget collides with this widget.

    .. code-block:: python

        >>> wid = Widget(size=(50, 50))
        >>> wid2 = Widget(size=(50, 50), pos=(25, 25))
        >>> wid.collide_widget(wid2)
        True
        >>> wid2.pos = (55, 55)
        >>> wid.collide_widget(wid2)
        False
        '''
        if self.right < wid.x:
            return False
        if self.x > wid.right:
            return False
        if self.top < wid.y:
            return False
        if self.y > wid.top:
            return False
        return True

    #
    # Default event handlers
    #
    def on_touch_down(self, touch):
        '''Receive a touch down event.

        :Parameters:
            `touch`: :class:`~kivy.input.motionevent.MotionEvent` class
                Touch received. The touch is in parent coordinates. See
                :mod:`~kivy.uix.relativelayout` for a discussion on
                coordinate systems.

        :Returns:
            bool. If True, the dispatching of the touch event will stop.
        '''
        if self.disabled and self.collide_point(*touch.pos):
            return True
        for child in self.children[:]:
            if child.dispatch('on_touch_down', touch):
                return True

    def on_touch_move(self, touch):
        '''Receive a touch move event. The touch is in parent coordinates.

        See :meth:`on_touch_down` for more information.
        '''
        if self.disabled:
            return
        for child in self.children[:]:
            if child.dispatch('on_touch_move', touch):
                return True

    def on_touch_up(self, touch):
        '''Receive a touch up event. The touch is in parent coordinates.

        See :meth:`on_touch_down` for more information.
        '''
        if self.disabled:
            return
        for child in self.children[:]:
            if child.dispatch('on_touch_up', touch):
                return True

    def on_disabled(self, instance, value):
        for child in self.children:
            child.disabled = value

    #
    # Tree management
    #
    def add_widget(self, widget, index=0, canvas=None):
        '''Add a new widget as a child of this widget.

        :Parameters:
            `widget`: :class:`Widget`
                Widget to add to our list of children.
            `index`: int, defaults to 0
                Index to insert the widget in the list

                .. versionadded:: 1.0.5
            `canvas`: str, defaults to None
                Canvas to add widget's canvas to. Can be 'before', 'after' or
                None for the default canvas.

                .. versionadded:: 1.9.0

    .. code-block:: python

        >>> from kivy.uix.button import Button
        >>> from kivy.uix.slider import Slider
        >>> root = Widget()
        >>> root.add_widget(Button())
        >>> slider = Slider()
        >>> root.add_widget(slider)

        '''
        if not isinstance(widget, Widget):
            raise WidgetException(
                'add_widget() can be used only with Widget classes.')

        widget = widget.__self__
        if widget is self:
            raise WidgetException('You cannot add yourself in a Widget')
        parent = widget.parent
        # check if widget is already a child of another widget
        if parent:
            raise WidgetException('Cannot add %r, it already has a parent %r'
                                  % (widget, parent))
        widget.parent = parent = self
        # child will be disabled if added to a disabled parent
        if parent.disabled:
            widget.disabled = True

        canvas = self.canvas.before if canvas == 'before' else \
            self.canvas.after if canvas == 'after' else self.canvas

        if index == 0 or len(self.children) == 0:
            self.children.insert(0, widget)
            canvas.add(widget.canvas)
        else:
            canvas = self.canvas
            children = self.children
            if index >= len(children):
                index = len(children)
                next_index = 0
            else:
                next_child = children[index]
                next_index = canvas.indexof(next_child.canvas)
                if next_index == -1:
                    next_index = canvas.length()
                else:
                    next_index += 1

            children.insert(index, widget)
            # we never want to insert widget _before_ canvas.before.
            if next_index == 0 and canvas.has_before:
                next_index = 1
            canvas.insert(next_index, widget.canvas)

    def remove_widget(self, widget):
        '''Remove a widget from the children of this widget.

        :Parameters:
            `widget`: :class:`Widget`
                Widget to remove from our children list.

    .. code-block:: python

        >>> from kivy.uix.button import Button
        >>> root = Widget()
        >>> button = Button()
        >>> root.add_widget(button)
        >>> root.remove_widget(button)
        '''
        if widget not in self.children:
            return
        self.children.remove(widget)
        if widget.canvas in self.canvas.children:
            self.canvas.remove(widget.canvas)
        elif widget.canvas in self.canvas.after.children:
            self.canvas.after.remove(widget.canvas)
        elif widget.canvas in self.canvas.before.children:
            self.canvas.before.remove(widget.canvas)
        widget.parent = None

    def clear_widgets(self, children=None):
        '''Remove all widgets added to this widget.

        .. versionchanged:: 1.8.0
            `children` argument can be used to select the children we want to
            remove. It should be a list of children (or filtered list) of the
            current widget.
        '''

        if not children:
            children = self.children
        remove_widget = self.remove_widget
        for child in children[:]:
            remove_widget(child)

    def export_to_png(self, filename, *args):
        '''Saves an image of the widget and its children in png format at the
        specified filename. Works by removing the widget canvas from its
        parent, rendering to an :class:`~kivy.graphics.fbo.Fbo`, and calling
        :meth:`~kivy.graphics.texture.Texture.save`.

        .. note::

            The image includes only this widget and its children. If you want to
            include widgets elsewhere in the tree, you must call
            :meth:`~Widget.export_to_png` from their common parent, or use
            :meth:`~kivy.core.window.Window.screenshot` to capture the whole
            window.

        .. note::

            The image will be saved in png format, you should include the
            extension in your filename.

        .. versionadded:: 1.9.0
        '''

        if self.parent is not None:
            canvas_parent_index = self.parent.canvas.indexof(self.canvas)
            self.parent.canvas.remove(self.canvas)

        fbo = Fbo(size=self.size, with_stencilbuffer=True)

        with fbo:
            ClearColor(0, 0, 0, 1)
            ClearBuffers()
            Translate(-self.x, -self.y, 0)

        fbo.add(self.canvas)
        fbo.draw()
        fbo.texture.save(filename)
        fbo.remove(self.canvas)

        if self.parent is not None:
            self.parent.canvas.insert(canvas_parent_index, self.canvas)

        return True

    def get_root_window(self):
        '''Return the root window.

        :Returns:
            Instance of the root window. Can be a
            :class:`~kivy.core.window.WindowBase` or
            :class:`Widget`.
        '''
        if self.parent:
            return self.parent.get_root_window()

    def get_parent_window(self):
        '''Return the parent window.

        :Returns:
            Instance of the parent window. Can be a
            :class:`~kivy.core.window.WindowBase` or
            :class:`Widget`.
        '''
        if self.parent:
            return self.parent.get_parent_window()

    def _walk(self, restrict=False, loopback=False, index=None):
        # we pass index only when we are going on the parent.
        # so don't yield the parent as well.
        if index is None:
            index = len(self.children)
            yield self

        for child in reversed(self.children[:index]):
            for walk_child in child._walk(restrict=True):
                yield walk_child

        # if we want to continue with our parent, just do it
        if not restrict:
            parent = self.parent
            try:
                if parent is None or not isinstance(parent, Widget):
                    raise ValueError
                index = parent.children.index(self)
            except ValueError:
                # self is root, if wanted to loopback from first element then ->
                if not loopback:
                    return
                # if we started with root (i.e. index==None), then we have to
                # start from root again, so we return self again. Otherwise, we
                # never returned it, so return it now starting with it
                parent = self
                index = None
            for walk_child in parent._walk(loopback=loopback, index=index):
                yield walk_child

    def walk(self, restrict=False, loopback=False):
        ''' Iterator that walks the widget tree starting with this widget and
        goes forward returning widgets in the order in which layouts display
        them.

        :Parameters:
            `restrict`: bool, defaults to False
                If True, it will only iterate through the widget and its
                children (or children of its children etc.). Defaults to False.
            `loopback`: bool, defaults to False
                If True, when the last widget in the tree is reached,
                it'll loop back to the uppermost root and start walking until
                we hit this widget again. Naturally, it can only loop back when
                `restrict` is False. Defaults to False.

        :return:
            A generator that walks the tree, returning widgets in the
            forward layout order.

        For example, given a tree with the following structure::

            GridLayout:
                Button
                BoxLayout:
                    id: box
                    Widget
                    Button
                Widget

        walking this tree:

        .. code-block:: python

            >>> # Call walk on box with loopback True, and restrict False
            >>> [type(widget) for widget in box.walk(loopback=True)]
            [<class 'BoxLayout'>, <class 'Widget'>, <class 'Button'>,
                <class 'Widget'>, <class 'GridLayout'>, <class 'Button'>]
            >>> # Now with loopback False, and restrict False
            >>> [type(widget) for widget in box.walk()]
            [<class 'BoxLayout'>, <class 'Widget'>, <class 'Button'>,
                <class 'Widget'>]
            >>> # Now with restrict True
            >>> [type(widget) for widget in box.walk(restrict=True)]
            [<class 'BoxLayout'>, <class 'Widget'>, <class 'Button'>]

        .. versionadded:: 1.9.0
        '''
        gen = self._walk(restrict, loopback)
        yield next(gen)
        for node in gen:
            if node is self:
                return
            yield node

    def _walk_reverse(self, loopback=False, go_up=False):
        # process is walk up level, walk down its children tree, then walk up
        # next level etc.
        # default just walk down the children tree
        root = self
        index = 0
        # we need to go up a level before walking tree
        if go_up:
            root = self.parent
            try:
                if root is None or not isinstance(root, Widget):
                    raise ValueError
                index = root.children.index(self) + 1
            except ValueError:
                if not loopback:
                    return
                index = 0
                go_up = False
                root = self

        # now walk children tree starting with last-most child
        for child in islice(root.children, index, None):
            for walk_child in child._walk_reverse(loopback=loopback):
                yield walk_child
        # we need to return ourself last, in all cases
        yield root

        # if going up, continue walking up the parent tree
        if go_up:
            for walk_child in root._walk_reverse(loopback=loopback,
                                                 go_up=go_up):
                yield walk_child

    def walk_reverse(self, loopback=False):
        ''' Iterator that walks the widget tree backwards starting with the
        widget before this, and going backwards returning widgets in the
        reverse order in which layouts display them.

        This walks in the opposite direction of :meth:`walk`, so a list of the
        tree generated with :meth:`walk` will be in reverse order compared
        to the list generated with this, provided `loopback` is True.

        :Parameters:
            `loopback`: bool, defaults to False
                If True, when the uppermost root in the tree is
                reached, it'll loop back to the last widget and start walking
                back until after we hit widget again. Defaults to False

        :return:
            A generator that walks the tree, returning widgets in the
            reverse layout order.

        For example, given a tree with the following structure::

            GridLayout:
                Button
                BoxLayout:
                    id: box
                    Widget
                    Button
                Widget

        walking this tree:

        .. code-block:: python

            >>> # Call walk on box with loopback True
            >>> [type(widget) for widget in box.walk_reverse(loopback=True)]
            [<class 'Button'>, <class 'GridLayout'>, <class 'Widget'>,
                <class 'Button'>, <class 'Widget'>, <class 'BoxLayout'>]
            >>> # Now with loopback False
            >>> [type(widget) for widget in box.walk_reverse()]
            [<class 'Button'>, <class 'GridLayout'>]
            >>> forward = [w for w in box.walk(loopback=True)]
            >>> backward = [w for w in box.walk_reverse(loopback=True)]
            >>> forward == backward[::-1]
            True

        .. versionadded:: 1.9.0

        '''
        for node in self._walk_reverse(loopback=loopback, go_up=True):
            yield node
            if node is self:
                return

    def to_widget(self, x, y, relative=False):
        '''Convert the given coordinate from window to local widget
        coordinates. See :mod:`~kivy.uix.relativelayout` for details on the
        coordinate systems.
        '''
        if self.parent:
            x, y = self.parent.to_widget(x, y)
        return self.to_local(x, y, relative=relative)

    def to_window(self, x, y, initial=True, relative=False):
        '''Transform local coordinates to window coordinates. See
        :mod:`~kivy.uix.relativelayout` for details on the coordinate systems.
        '''
        if not initial:
            x, y = self.to_parent(x, y, relative=relative)
        if self.parent:
            return self.parent.to_window(x, y, initial=False,
                                         relative=relative)
        return (x, y)

    def to_parent(self, x, y, relative=False):
        '''Transform local coordinates to parent coordinates. See
        :mod:`~kivy.uix.relativelayout` for details on the coordinate systems.

        :Parameters:
            `relative`: bool, defaults to False
                Change to True if you want to translate relative positions from
                a widget to its parent coordinates.
        '''
        if relative:
            return (x + self.x, y + self.y)
        return (x, y)

    def to_local(self, x, y, relative=False):
        '''Transform parent coordinates to local coordinates. See
        :mod:`~kivy.uix.relativelayout` for details on the coordinate systems.

        :Parameters:
            `relative`: bool, defaults to False
                Change to True if you want to translate coordinates to
                relative widget coordinates.
        '''
        if relative:
            return (x - self.x, y - self.y)
        return (x, y)

    x = NumericProperty(0)
    '''X position of the widget.

    :attr:`x` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.
    '''

    y = NumericProperty(0)
    '''Y position of the widget.

    :attr:`y` is a :class:`~kivy.properties.NumericProperty` and defaults to 0.
    '''

    width = NumericProperty(100)
    '''Width of the widget.

    :attr:`width` is a :class:`~kivy.properties.NumericProperty` and defaults
    to 100.

    .. warning::
        Keep in mind that the `width` property is subject to layout logic and
        that this has not yet happened at the time of the widget's `__init__`
        method.
    '''

    height = NumericProperty(100)
    '''Height of the widget.

    :attr:`height` is a :class:`~kivy.properties.NumericProperty` and defaults
    to 100.

    .. warning::
        Keep in mind that the `height` property is subject to layout logic and
        that this has not yet happened at the time of the widget's `__init__`
        method.
    '''

    pos = ReferenceListProperty(x, y)
    '''Position of the widget.

    :attr:`pos` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`x`, :attr:`y`) properties.
    '''

    size = ReferenceListProperty(width, height)
    '''Size of the widget.

    :attr:`size` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`width`, :attr:`height`) properties.
    '''

    def get_right(self):
        return self.x + self.width

    def set_right(self, value):
        self.x = value - self.width

    right = AliasProperty(get_right, set_right, bind=('x', 'width'))
    '''Right position of the widget.

    :attr:`right` is an :class:`~kivy.properties.AliasProperty` of
    (:attr:`x` + :attr:`width`),
    '''

    def get_top(self):
        return self.y + self.height

    def set_top(self, value):
        self.y = value - self.height

    top = AliasProperty(get_top, set_top, bind=('y', 'height'))
    '''Top position of the widget.

    :attr:`top` is an :class:`~kivy.properties.AliasProperty` of
    (:attr:`y` + :attr:`height`),
    '''

    def get_center_x(self):
        return self.x + self.width / 2.

    def set_center_x(self, value):
        self.x = value - self.width / 2.
    center_x = AliasProperty(get_center_x, set_center_x, bind=('x', 'width'))
    '''X center position of the widget.

    :attr:`center_x` is an :class:`~kivy.properties.AliasProperty` of
    (:attr:`x` + :attr:`width` / 2.),
    '''

    def get_center_y(self):
        return self.y + self.height / 2.

    def set_center_y(self, value):
        self.y = value - self.height / 2.
    center_y = AliasProperty(get_center_y, set_center_y, bind=('y', 'height'))
    '''Y center position of the widget.

    :attr:`center_y` is an :class:`~kivy.properties.AliasProperty` of
    (:attr:`y` + :attr:`height` / 2.)
    '''

    center = ReferenceListProperty(center_x, center_y)
    '''Center position of the widget.

    :attr:`center` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`center_x`, :attr:`center_y`)
    '''

    cls = ListProperty([])
    '''Class of the widget, used for styling.
    '''

    id = StringProperty(None, allownone=True)
    '''Unique identifier of the widget in the tree.

    :attr:`id` is a :class:`~kivy.properties.StringProperty` and defaults to
    None.

    .. warning::

        If the :attr:`id` is already used in the tree, an exception will
        be raised.
    '''

    children = ListProperty([])
    '''List of children of this widget.

    :attr:`children` is a :class:`~kivy.properties.ListProperty` and
    defaults to an empty list.

    Use :meth:`add_widget` and :meth:`remove_widget` for manipulating the
    children list. Don't manipulate the children list directly unless you know
    what you are doing.
    '''

    parent = ObjectProperty(None, allownone=True)
    '''Parent of this widget.

    :attr:`parent` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None.

    The parent of a widget is set when the widget is added to another widget
    and unset when the widget is removed from its parent.
    '''

    size_hint_x = NumericProperty(1, allownone=True)
    '''X size hint. Represents how much space the widget should use in the
    direction of the X axis relative to its parent's width.
    Only the :class:`~kivy.uix.layout.Layout` and
    :class:`~kivy.core.window.Window` classes make use of the hint.

    The value is in percent as a float from 0. to 1., where 1. means the full
    size of his parent. 0.5 represents 50%.

    :attr:`size_hint_x` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 1.
    '''

    size_hint_y = NumericProperty(1, allownone=True)
    '''Y size hint.

    :attr:`size_hint_y` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 1.

    See :attr:`size_hint_x` for more information
    '''

    size_hint = ReferenceListProperty(size_hint_x, size_hint_y)
    '''Size hint.

    :attr:`size_hint` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`size_hint_x`, :attr:`size_hint_y`).

    See :attr:`size_hint_x` for more information
    '''

    pos_hint = ObjectProperty({})
    '''Position hint. This property allows you to set the position of
    the widget inside its parent layout, in percent (similar to
    size_hint).

    For example, if you want to set the top of the widget to be at 90%
    height of its parent layout, you can write::

        widget = Widget(pos_hint={'top': 0.9})

    The keys 'x', 'right' and 'center_x' will use the parent width.
    The keys 'y', 'top' and 'center_y' will use the parent height.

    See :doc:`api-kivy.uix.floatlayout` for further reference.

    Position hint is only used by the
    :class:`~kivy.uix.floatlayout.FloatLayout` and
    :class:`~kivy.core.window.Window`.

    :attr:`pos_hint` is an :class:`~kivy.properties.ObjectProperty`
    containing a dict.
    '''

    ids = DictProperty({})
    '''This is a Dictionary of id's defined in your kv language. This will only
    be populated if you use id's in your kv language code.

    .. versionadded:: 1.7.0

    :attr:`ids` is a :class:`~kivy.properties.DictProperty` and defaults to a
    empty dict {}.

    The :attr:`ids` are populated for each root level widget definition. For
    example::

        # in kv
        <MyWidget@Widget>:
            id: my_widget
            Label:
                id: label_widget
                Widget:
                    id: inner_widget
                    Label:
                        id: inner_label
            TextInput:
                id: text_input
            OtherWidget:
                id: other_widget


        <OtherWidget@Widget>
            id: other_widget
            Label:
                id: other_label
                TextInput:
                    id: other_textinput

    Then, in python:

    .. code-block:: python

        >>> widget = MyWidget()
        >>> print(widget.ids)
        {'other_widget': <weakproxy at 041CFED0 to OtherWidget at 041BEC38>,
        'inner_widget': <weakproxy at 04137EA0 to Widget at 04138228>,
        'inner_label': <weakproxy at 04143540 to Label at 04138260>,
        'label_widget': <weakproxy at 04137B70 to Label at 040F97A0>,
        'text_input': <weakproxy at 041BB5D0 to TextInput at 041BEC00>}
        >>> print(widget.ids['other_widget'].ids)
        {'other_textinput': <weakproxy at 041DBB40 to TextInput at 041BEF48>,
        'other_label': <weakproxy at 041DB570 to Label at 041BEEA0>}
        >>> print(widget.ids['label_widget'].ids)
        {}
    '''

    opacity = NumericProperty(1.0)
    '''Opacity of the widget and all the children.

    .. versionadded:: 1.4.1

    The opacity attribute controls the opacity of the widget and its children.
    Be careful, it's a cumulative attribute: the value is multiplied by the
    current global opacity and the result is applied to the current context
    color.

    For example, if the parent has an opacity of 0.5 and a child has an
    opacity of 0.2, the real opacity of the child will be 0.5 * 0.2 = 0.1.

    Then, the opacity is applied by the shader as:

    .. code-block:: python

        frag_color = color * vec4(1.0, 1.0, 1.0, opacity);

    :attr:`opacity` is a :class:`~kivy.properties.NumericProperty` and defaults
    to 1.0.
    '''

    def on_opacity(self, instance, value):
        canvas = self.canvas
        if canvas is not None:
            canvas.opacity = value

    canvas = None
    '''Canvas of the widget.

    The canvas is a graphics object that contains all the drawing instructions
    for the graphical representation of the widget.

    There are no general properties for the Widget class, such as background
    color, to keep the design simple and lean. Some derived classes, such as
    Button, do add such convenience properties but generally the developer is
    responsible for implementing the graphics representation for a custom
    widget from the ground up. See the derived widget classes for patterns to
    follow and extend.

    See :class:`~kivy.graphics.Canvas` for more information about the usage.
    '''

    disabled = BooleanProperty(False)
    '''Indicates whether this widget can interact with input or not.
Exemple #60
0
class ScrollView(StencilView):
    '''ScrollView class. See module documentation for more information.

    :Events:
        `on_scroll_start`
            Generic event fired when scrolling starts from touch.
        `on_scroll_move`
            Generic event fired when scrolling move from touch.
        `on_scroll_stop`
            Generic event fired when scrolling stops from touch.

    .. versionchanged:: 1.9.0
        `on_scroll_start`, `on_scroll_move` and `on_scroll_stop` events are
        now dispatched when scrolling to handle nested ScrollViews.

    .. versionchanged:: 1.7.0
        `auto_scroll`, `scroll_friction`, `scroll_moves`, `scroll_stoptime' has
        been deprecated, use :attr:`effect_cls` instead.
    '''

    scroll_distance = NumericProperty(_scroll_distance)
    '''Distance to move before scrolling the :class:`ScrollView`, in pixels. As
    soon as the distance has been traveled, the :class:`ScrollView` will start
    to scroll, and no touch event will go to children.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    :attr:`scroll_distance` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 20 (pixels), according to the default value in user
    configuration.
    '''

    scroll_wheel_distance = NumericProperty(20)
    '''Distance to move when scrolling with a mouse wheel.
    It is advisable that you base this value on the dpi of your target device's
    screen.

    .. versionadded:: 1.8.0

    :attr:`scroll_wheel_distance` is a
    :class:`~kivy.properties.NumericProperty` , defaults to 20 pixels.
    '''

    scroll_timeout = NumericProperty(_scroll_timeout)
    '''Timeout allowed to trigger the :attr:`scroll_distance`, in milliseconds.
    If the user has not moved :attr:`scroll_distance` within the timeout,
    the scrolling will be disabled, and the touch event will go to the
    children.

    :attr:`scroll_timeout` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 55 (milliseconds) according to the default value in user
    configuration.

    .. versionchanged:: 1.5.0
        Default value changed from 250 to 55.
    '''

    scroll_x = NumericProperty(0.)
    '''X scrolling value, between 0 and 1. If 0, the content's left side will
    touch the left side of the ScrollView. If 1, the content's right side will
    touch the right side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_x` is True.

    :attr:`scroll_x` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 0.
    '''

    scroll_y = NumericProperty(1.)
    '''Y scrolling value, between 0 and 1. If 0, the content's bottom side will
    touch the bottom side of the ScrollView. If 1, the content's top side will
    touch the top side.

    This property is controled by :class:`ScrollView` only if
    :attr:`do_scroll_y` is True.

    :attr:`scroll_y` is a :class:`~kivy.properties.NumericProperty` and
    defaults to 1.
    '''

    do_scroll_x = BooleanProperty(True)
    '''Allow scroll on X axis.

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

    do_scroll_y = BooleanProperty(True)
    '''Allow scroll on Y axis.

    :attr:`do_scroll_y` is a :class:`~kivy.properties.BooleanProperty` and
    defaults to True.
    '''
    def _get_do_scroll(self):
        return (self.do_scroll_x, self.do_scroll_y)

    def _set_do_scroll(self, value):
        if type(value) in (list, tuple):
            self.do_scroll_x, self.do_scroll_y = value
        else:
            self.do_scroll_x = self.do_scroll_y = bool(value)

    do_scroll = AliasProperty(_get_do_scroll,
                              _set_do_scroll,
                              bind=('do_scroll_x', 'do_scroll_y'))
    '''Allow scroll on X or Y axis.

    :attr:`do_scroll` is a :class:`~kivy.properties.AliasProperty` of
    (:attr:`do_scroll_x` + :attr:`do_scroll_y`)
    '''

    def _get_vbar(self):
        # must return (y, height) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vh = self._viewport.height
        h = self.height
        if vh < h or vh == 0:
            return 0, 1.
        ph = max(0.01, h / float(vh))
        sy = min(1.0, max(0.0, self.scroll_y))
        py = (1. - ph) * sy
        return (py, ph)

    vbar = AliasProperty(_get_vbar,
                         None,
                         bind=('scroll_y', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the vertical scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little vertical bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    def _get_hbar(self):
        # must return (x, width) in %
        # calculate the viewport size / scrollview size %
        if self._viewport is None:
            return 0, 1.
        vw = self._viewport.width
        w = self.width
        if vw < w or vw == 0:
            return 0, 1.
        pw = max(0.01, w / float(vw))
        sx = min(1.0, max(0.0, self.scroll_x))
        px = (1. - pw) * sx
        return (px, pw)

    hbar = AliasProperty(_get_hbar,
                         None,
                         bind=('scroll_x', '_viewport', 'viewport_size'))
    '''Return a tuple of (position, size) of the horizontal scrolling bar.

    .. versionadded:: 1.2.0

    The position and size are normalized between 0-1, and represent a
    percentage of the current scrollview height. This property is used
    internally for drawing the little horizontal bar when you're scrolling.

    :attr:`vbar` is a :class:`~kivy.properties.AliasProperty`, readonly.
    '''

    bar_color = ListProperty([.7, .7, .7, .9])
    '''Color of horizontal / vertical scroll bar, in RGBA format.

    .. versionadded:: 1.2.0

    :attr:`bar_color` is a :class:`~kivy.properties.ListProperty` and defaults
    to [.7, .7, .7, .9].
    '''

    bar_inactive_color = ListProperty([.7, .7, .7, .2])
    '''Color of horizontal / vertical scroll bar (in RGBA format), when no
    scroll is happening.

    .. versionadded:: 1.9.0

    :attr:`bar_inactive_color` is a
    :class:`~kivy.properties.ListProperty` and defaults to [.7, .7, .7, .2].
    '''

    bar_width = NumericProperty('2dp')
    '''Width of the horizontal / vertical scroll bar. The width is interpreted
    as a height for the horizontal bar.

    .. versionadded:: 1.2.0

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

    bar_pos_x = OptionProperty('bottom', options=('top', 'bottom'))
    '''Which side of the ScrollView the horizontal scroll bar should go
    on. Possible values are 'top' and 'bottom'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_x` is an :class:`~kivy.properties.OptionProperty`,
    defaults to 'bottom'.

    '''

    bar_pos_y = OptionProperty('right', options=('left', 'right'))
    '''Which side of the ScrollView the vertical scroll bar should go
    on. Possible values are 'left' and 'right'.

    .. versionadded:: 1.8.0

    :attr:`bar_pos_y` is an :class:`~kivy.properties.OptionProperty` and
    defaults to 'right'.

    '''

    bar_pos = ReferenceListProperty(bar_pos_x, bar_pos_y)
    '''Which side of the scroll view to place each of the bars on.

    :attr:`bar_pos` is a :class:`~kivy.properties.ReferenceListProperty` of
    (:attr:`bar_pos_x`, :attr:`bar_pos_y`)
    '''

    bar_margin = NumericProperty(0)
    '''Margin between the bottom / right side of the scrollview when drawing
    the horizontal / vertical scroll bar.

    .. versionadded:: 1.2.0

    :attr:`bar_margin` is a :class:`~kivy.properties.NumericProperty`, default
    to 0
    '''

    effect_cls = ObjectProperty(DampedScrollEffect, allownone=True)
    '''Class effect to instanciate for X and Y axis.

    .. versionadded:: 1.7.0

    :attr:`effect_cls` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to :class:`DampedScrollEffect`.

    .. versionchanged:: 1.8.0
        If you set a string, the :class:`~kivy.factory.Factory` will be used to
        resolve the class.

    '''

    effect_x = ObjectProperty(None, allownone=True)
    '''Effect to apply for the X axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

    :attr:`effect_x` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None.
    '''

    effect_y = ObjectProperty(None, allownone=True)
    '''Effect to apply for the Y axis. If None is set, an instance of
    :attr:`effect_cls` will be created.

    .. versionadded:: 1.7.0

    :attr:`effect_y` is an :class:`~kivy.properties.ObjectProperty` and
    defaults to None, read-only.
    '''

    viewport_size = ListProperty([0, 0])
    '''(internal) Size of the internal viewport. This is the size of your only
    child in the scrollview.
    '''

    scroll_type = OptionProperty(['content'],
                                 options=(['content'], ['bars'],
                                          ['bars',
                                           'content'], ['content', 'bars']))
    '''Sets the type of scrolling to use for the content of the scrollview.
    Available options are: ['content'], ['bars'], ['bars', 'content'].

    .. versionadded:: 1.8.0

    :attr:`scroll_type` is a :class:`~kivy.properties.OptionProperty`, defaults
    to ['content'].
    '''

    # private, for internal use only

    _viewport = ObjectProperty(None, allownone=True)
    _bar_color = ListProperty([0, 0, 0, 0])

    def _set_viewport_size(self, instance, value):
        self.viewport_size = value

    def on__viewport(self, instance, value):
        if value:
            value.bind(size=self._set_viewport_size)
            self.viewport_size = value.size

    __events__ = ('on_scroll_start', 'on_scroll_move', 'on_scroll_stop')

    def __init__(self, **kwargs):
        self._touch = None
        self._trigger_update_from_scroll = Clock.create_trigger(
            self.update_from_scroll, -1)
        # create a specific canvas for the viewport
        from kivy.graphics import PushMatrix, Translate, PopMatrix, Canvas
        self.canvas_viewport = Canvas()
        self.canvas = Canvas()
        with self.canvas_viewport.before:
            PushMatrix()
            self.g_translate = Translate(0, 0)
        with self.canvas_viewport.after:
            PopMatrix()

        super(ScrollView, self).__init__(**kwargs)

        self.register_event_type('on_scroll_start')
        self.register_event_type('on_scroll_move')
        self.register_event_type('on_scroll_stop')

        # now add the viewport canvas to our canvas
        self.canvas.add(self.canvas_viewport)

        effect_cls = self.effect_cls
        if isinstance(effect_cls, string_types):
            effect_cls = Factory.get(effect_cls)
        if self.effect_x is None and effect_cls is not None:
            self.effect_x = effect_cls(target_widget=self._viewport)
        if self.effect_y is None and effect_cls is not None:
            self.effect_y = effect_cls(target_widget=self._viewport)
        self.bind(width=self._update_effect_x_bounds,
                  height=self._update_effect_y_bounds,
                  viewport_size=self._update_effect_bounds,
                  _viewport=self._update_effect_widget,
                  scroll_x=self._trigger_update_from_scroll,
                  scroll_y=self._trigger_update_from_scroll,
                  pos=self._trigger_update_from_scroll,
                  size=self._trigger_update_from_scroll)

        self._update_effect_widget()
        self._update_effect_x_bounds()
        self._update_effect_y_bounds()

    def on_effect_x(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_x)
            value.target_widget = self._viewport

    def on_effect_y(self, instance, value):
        if value:
            value.bind(scroll=self._update_effect_y)
            value.target_widget = self._viewport

    def on_effect_cls(self, instance, cls):
        if isinstance(cls, string_types):
            cls = Factory.get(cls)
        self.effect_x = cls(target_widget=self._viewport)
        self.effect_x.bind(scroll=self._update_effect_x)
        self.effect_y = cls(target_widget=self._viewport)
        self.effect_y.bind(scroll=self._update_effect_y)

    def _update_effect_widget(self, *args):
        if self.effect_x:
            self.effect_x.target_widget = self._viewport
        if self.effect_y:
            self.effect_y.target_widget = self._viewport

    def _update_effect_x_bounds(self, *args):
        if not self._viewport or not self.effect_x:
            return
        self.effect_x.min = -(self.viewport_size[0] - self.width)
        self.effect_x.max = 0
        self.effect_x.value = self.effect_x.min * self.scroll_x

    def _update_effect_y_bounds(self, *args):
        if not self._viewport or not self.effect_y:
            return
        self.effect_y.min = -(self.viewport_size[1] - self.height)
        self.effect_y.max = 0
        self.effect_y.value = self.effect_y.min * self.scroll_y

    def _update_effect_bounds(self, *args):
        if not self._viewport:
            return
        if self.effect_x:
            self._update_effect_x_bounds()
        if self.effect_y:
            self._update_effect_y_bounds()

    def _update_effect_x(self, *args):
        vp = self._viewport
        if not vp or not self.effect_x:
            return
        sw = vp.width - self.width
        if sw < 1:
            return
        sx = self.effect_x.scroll / float(sw)
        self.scroll_x = -sx
        self._trigger_update_from_scroll()

    def _update_effect_y(self, *args):
        vp = self._viewport
        if not vp or not self.effect_y:
            return
        sh = vp.height - self.height
        if sh < 1:
            return
        sy = self.effect_y.scroll / float(sh)
        self.scroll_y = -sy
        self._trigger_update_from_scroll()

    def to_local(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x - tx, y - ty

    def to_parent(self, x, y, **k):
        tx, ty = self.g_translate.xy
        return x + tx, y + ty

    def _apply_transform(self, m, pos=None):
        tx, ty = self.g_translate.xy
        m.translate(tx, ty, 0)
        return super(ScrollView, self)._apply_transform(m, (0, 0))

    def simulate_touch_down(self, touch):
        # at this point the touch is in parent coords
        touch.push()
        touch.apply_transform_2d(self.to_local)
        ret = super(ScrollView, self).on_touch_down(touch)
        touch.pop()
        return ret

    def on_touch_down(self, touch):
        if self.dispatch('on_scroll_start', touch):
            self._touch = touch
            touch.grab(self)
            return True

    def on_scroll_start(self, touch, check_children=True):
        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_start', touch):
                return True
            touch.pop()

        if not self.collide_point(*touch.pos):
            touch.ud[self._get_uid('svavoid')] = True
            return
        if self.disabled:
            return True
        if self._touch or (not (self.do_scroll_x or self.do_scroll_y)):
            return self.simulate_touch_down(touch)

        # handle mouse scrolling, only if the viewport size is bigger than the
        # scrollview size, and if the user allowed to do it
        vp = self._viewport
        if not vp:
            return True
        scroll_type = self.scroll_type
        ud = touch.ud
        scroll_bar = 'bars' in scroll_type

        # check if touch is in bar_x(horizontal) or bay_y(bertical)
        ud['in_bar_x'] = ud['in_bar_y'] = False
        width_scrollable = vp.width > self.width
        height_scrollable = vp.height > self.height
        bar_pos_x = self.bar_pos_x[0]
        bar_pos_y = self.bar_pos_y[0]

        d = {
            'b': True if touch.y < self.y + self.bar_width else False,
            't': True if touch.y > self.top - self.bar_width else False,
            'l': True if touch.x < self.x + self.bar_width else False,
            'r': True if touch.x > self.right - self.bar_width else False
        }
        if scroll_bar:
            if (width_scrollable and d[bar_pos_x]):
                ud['in_bar_x'] = True
            if (height_scrollable and d[bar_pos_y]):
                ud['in_bar_y'] = True

        if vp and 'button' in touch.profile and \
                touch.button.startswith('scroll'):
            btn = touch.button
            m = sp(self.scroll_wheel_distance)
            e = None

            if ((btn == 'scrolldown' and self.scroll_y >= 1)
                    or (btn == 'scrollup' and self.scroll_y <= 0)
                    or (btn == 'scrollleft' and self.scroll_x >= 1)
                    or (btn == 'scrollright' and self.scroll_x <= 0)):
                return False

            if (self.effect_x and self.do_scroll_y and height_scrollable
                    and btn in ('scrolldown', 'scrollup')):
                e = self.effect_x if ud['in_bar_x'] else self.effect_y

            elif (self.effect_y and self.do_scroll_x and width_scrollable
                  and btn in ('scrollleft', 'scrollright')):
                e = self.effect_y if ud['in_bar_y'] else self.effect_x

            if e:
                if btn in ('scrolldown', 'scrollleft'):
                    e.value = max(e.value - m, e.min)
                    e.velocity = 0
                elif btn in ('scrollup', 'scrollright'):
                    e.value = min(e.value + m, e.max)
                    e.velocity = 0
                touch.ud[self._get_uid('svavoid')] = True
                e.trigger_velocity_update()
            return True

        # no mouse scrolling, so the user is going to drag the scrollview with
        # this touch.
        self._touch = touch
        uid = self._get_uid()

        ud[uid] = {
            'mode': 'unknown',
            'dx': 0,
            'dy': 0,
            'user_stopped': False,
            'frames': Clock.frames,
            'time': touch.time_start
        }

        if self.do_scroll_x and self.effect_x and not ud['in_bar_x']:
            self.effect_x.start(touch.x)
            self._scroll_x_mouse = self.scroll_x
        if self.do_scroll_y and self.effect_y and not ud['in_bar_y']:
            self.effect_y.start(touch.y)
            self._scroll_y_mouse = self.scroll_y

        if (ud.get('in_bar_x', False) or ud.get('in_bar_y', False)):
            return True

        Clock.schedule_once(self._change_touch_mode,
                            self.scroll_timeout / 1000.)
        if scroll_type == ['bars']:
            return False
        else:
            return True

    def on_touch_move(self, touch):
        if self._touch is not touch:
            # touch is in parent
            touch.push()
            touch.apply_transform_2d(self.to_local)
            super(ScrollView, self).on_touch_move(touch)
            touch.pop()
            return self._get_uid() in touch.ud
        if touch.grab_current is not self:
            return True

        if not (self.do_scroll_y or self.do_scroll_x):
            return super(ScrollView, self).on_touch_move(touch)

        touch.ud['sv.handled'] = {'x': False, 'y': False}
        if self.dispatch('on_scroll_move', touch):
            return True

    def on_scroll_move(self, touch):
        if self._get_uid('svavoid') in touch.ud:
            return False

        touch.push()
        touch.apply_transform_2d(self.to_local)
        if self.dispatch_children('on_scroll_move', touch):
            return True
        touch.pop()

        rv = True

        uid = self._get_uid()
        if not uid in touch.ud:
            self._touch = False
            return self.on_scroll_start(touch, False)
        ud = touch.ud[uid]
        mode = ud['mode']

        # check if the minimum distance has been travelled
        if mode == 'unknown' or mode == 'scroll':
            if not touch.ud['sv.handled']['x'] and self.do_scroll_x \
                    and self.effect_x:
                width = self.width
                if touch.ud.get('in_bar_x', False):
                    dx = touch.dx / float(width - width * self.hbar[1])
                    self.scroll_x = min(max(self.scroll_x + dx, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_x.update(touch.x)
                if self.scroll_x < 0 or self.scroll_x > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['x'] = True
            if not touch.ud['sv.handled']['y'] and self.do_scroll_y \
                    and self.effect_y:
                height = self.height
                if touch.ud.get('in_bar_y', False):
                    dy = touch.dy / float(height - height * self.vbar[1])
                    self.scroll_y = min(max(self.scroll_y + dy, 0.), 1.)
                    self._trigger_update_from_scroll()
                else:
                    if self.scroll_type != ['bars']:
                        self.effect_y.update(touch.y)
                if self.scroll_y < 0 or self.scroll_y > 1:
                    rv = False
                else:
                    touch.ud['sv.handled']['y'] = True

        if mode == 'unknown':
            ud['dx'] += abs(touch.dx)
            ud['dy'] += abs(touch.dy)
            if ud['dx'] > self.scroll_distance:
                if not self.do_scroll_x:
                    # touch is in parent, but _change expects window coords
                    touch.push()
                    touch.apply_transform_2d(self.to_local)
                    touch.apply_transform_2d(self.to_window)
                    self._change_touch_mode()
                    touch.pop()
                    return
                mode = 'scroll'

            if ud['dy'] > self.scroll_distance:
                if not self.do_scroll_y:
                    # touch is in parent, but _change expects window coords
                    touch.push()
                    touch.apply_transform_2d(self.to_local)
                    touch.apply_transform_2d(self.to_window)
                    self._change_touch_mode()
                    touch.pop()
                    return
                mode = 'scroll'
            ud['mode'] = mode

        if mode == 'scroll':
            ud['dt'] = touch.time_update - ud['time']
            ud['time'] = touch.time_update
            ud['user_stopped'] = True

        return rv

    def on_touch_up(self, touch):
        if self._touch is not touch and self.uid not in touch.ud:
            # touch is in parents
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if super(ScrollView, self).on_touch_up(touch):
                return True
            touch.pop()
            return False

        if self.dispatch('on_scroll_stop', touch):
            touch.ungrab(self)
            return True

    def on_scroll_stop(self, touch, check_children=True):
        self._touch = None

        if check_children:
            touch.push()
            touch.apply_transform_2d(self.to_local)
            if self.dispatch_children('on_scroll_stop', touch):
                return True
            touch.pop()

        if self._get_uid('svavoid') in touch.ud:
            return
        if self._get_uid() not in touch.ud:
            return False

        self._touch = None
        uid = self._get_uid()
        ud = touch.ud[uid]
        if self.do_scroll_x and self.effect_x:
            if not touch.ud.get('in_bar_x', False) and\
                    self.scroll_type != ['bars']:
                self.effect_x.stop(touch.x)
        if self.do_scroll_y and self.effect_y and\
                self.scroll_type != ['bars']:
            if not touch.ud.get('in_bar_y', False):
                self.effect_y.stop(touch.y)
        if ud['mode'] == 'unknown':
            # we must do the click at least..
            # only send the click if it was not a click to stop
            # autoscrolling
            if not ud['user_stopped']:
                self.simulate_touch_down(touch)
            Clock.schedule_once(partial(self._do_touch_up, touch), .2)
        Clock.unschedule(self._update_effect_bounds)
        Clock.schedule_once(self._update_effect_bounds)

        # if we do mouse scrolling, always accept it
        if 'button' in touch.profile and touch.button.startswith('scroll'):
            return True

        return self._get_uid() in touch.ud

    def scroll_to(self, widget, padding=10, animate=True):
        '''Scrolls the viewport to ensure that the given widget is visible,
        optionally with padding and animation. If animate is True (the
        default), then the default animation parameters will be used.
        Otherwise, it should be a dict containing arguments to pass to
        :class:`~kivy.animation.Animation` constructor.

        .. versionadded:: 1.9.1
        '''
        if not self.parent:
            return

        if isinstance(padding, (int, float)):
            padding = (padding, padding)

        pos = self.parent.to_widget(*widget.to_window(*widget.pos))
        cor = self.parent.to_widget(
            *widget.to_window(widget.right, widget.top))

        dx = dy = 0

        if pos[1] < self.y:
            dy = self.y - pos[1] + dp(padding[1])
        elif cor[1] > self.top:
            dy = self.top - cor[1] - dp(padding[1])

        if pos[0] < self.x:
            dx = self.x - pos[0] + dp(padding[0])
        elif cor[0] > self.right:
            dx = self.right - cor[0] - dp(padding[0])

        dsx, dsy = self.convert_distance_to_scroll(dx, dy)
        sxp = min(1, max(0, self.scroll_x - dsx))
        syp = min(1, max(0, self.scroll_y - dsy))

        if animate:
            if animate is True:
                animate = {'d': 0.2, 't': 'out_quad'}
            Animation.stop_all(self, 'scroll_x', 'scroll_y')
            Animation(scroll_x=sxp, scroll_y=syp, **animate).start(self)
        else:
            self.scroll_x = sxp
            self.scroll_y = syp

    def convert_distance_to_scroll(self, dx, dy):
        '''Convert a distance in pixels to a scroll distance, depending on the
        content size and the scrollview size.

        The result will be a tuple of scroll distance that can be added to
        :data:`scroll_x` and :data:`scroll_y`
        '''
        if not self._viewport:
            return 0, 0
        vp = self._viewport
        if vp.width > self.width:
            sw = vp.width - self.width
            sx = dx / float(sw)
        else:
            sx = 0
        if vp.height > self.height:
            sh = vp.height - self.height
            sy = dy / float(sh)
        else:
            sy = 1
        return sx, sy

    def update_from_scroll(self, *largs):
        '''Force the reposition of the content, according to current value of
        :attr:`scroll_x` and :attr:`scroll_y`.

        This method is automatically called when one of the :attr:`scroll_x`,
        :attr:`scroll_y`, :attr:`pos` or :attr:`size` properties change, or
        if the size of the content changes.
        '''
        if not self._viewport:
            return
        vp = self._viewport

        # update from size_hint
        if vp.size_hint_x is not None:
            vp.width = vp.size_hint_x * self.width
        if vp.size_hint_y is not None:
            vp.height = vp.size_hint_y * self.height

        if vp.width > self.width:
            sw = vp.width - self.width
            x = self.x - self.scroll_x * sw
        else:
            x = self.x
        if vp.height > self.height:
            sh = vp.height - self.height
            y = self.y - self.scroll_y * sh
        else:
            y = self.top - vp.height

        # from 1.8.0, we now use a matrix by default, instead of moving the
        # widget position behind. We set it here, but it will be a no-op most of
        # the time.
        vp.pos = 0, 0
        self.g_translate.xy = x, y

        # New in 1.2.0, show bar when scrolling happens and (changed in 1.9.0)
        # fade to bar_inactive_color when no scroll is happening.
        Clock.unschedule(self._bind_inactive_bar_color)
        self.unbind(bar_inactive_color=self._change_bar_color)
        Animation.stop_all(self, '_bar_color')
        self.bind(bar_color=self._change_bar_color)
        self._bar_color = self.bar_color
        Clock.schedule_once(self._bind_inactive_bar_color, .5)

    def _bind_inactive_bar_color(self, *l):
        self.unbind(bar_color=self._change_bar_color)
        self.bind(bar_inactive_color=self._change_bar_color)
        Animation(_bar_color=self.bar_inactive_color, d=.5,
                  t='out_quart').start(self)

    def _change_bar_color(self, inst, value):
        self._bar_color = value

    #
    # Private
    #
    def add_widget(self, widget, index=0):
        if self._viewport:
            raise Exception('ScrollView accept only one widget')
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).add_widget(widget, index)
        self.canvas = canvas
        self._viewport = widget
        widget.bind(size=self._trigger_update_from_scroll)
        self._trigger_update_from_scroll()

    def remove_widget(self, widget):
        canvas = self.canvas
        self.canvas = self.canvas_viewport
        super(ScrollView, self).remove_widget(widget)
        self.canvas = canvas
        if widget is self._viewport:
            self._viewport = None

    def _get_uid(self, prefix='sv'):
        return '{0}.{1}'.format(prefix, self.uid)

    def _change_touch_mode(self, *largs):
        if not self._touch:
            return
        uid = self._get_uid()
        touch = self._touch
        if uid not in touch.ud:
            self._touch = False
            return
        ud = touch.ud[uid]
        if ud['mode'] != 'unknown' or ud['user_stopped']:
            return
        diff_frames = Clock.frames - ud['frames']

        # in order to be able to scroll on very slow devices, let at least 3
        # frames displayed to accumulate some velocity. And then, change the
        # touch mode. Otherwise, we might never be able to compute velocity, and
        # no way to scroll it. See #1464 and #1499
        if diff_frames < 3:
            Clock.schedule_once(self._change_touch_mode, 0)
            return

        if self.do_scroll_x and self.effect_x:
            self.effect_x.cancel()
        if self.do_scroll_y and self.effect_y:
            self.effect_y.cancel()
        # XXX the next line was in the condition. But this stop
        # the possibily to "drag" an object out of the scrollview in the
        # non-used direction: if you have an horizontal scrollview, a
        # vertical gesture will not "stop" the scroll view to look for an
        # horizontal gesture, until the timeout is done.
        # and touch.dx + touch.dy == 0:
        touch.ungrab(self)
        self._touch = None
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        touch.apply_transform_2d(self.to_parent)
        self.simulate_touch_down(touch)
        touch.pop()
        return

    def _do_touch_up(self, touch, *largs):
        # touch is in window coords
        touch.push()
        touch.apply_transform_2d(self.to_widget)
        super(ScrollView, self).on_touch_up(touch)
        touch.pop()
        # don't forget about grab event!
        for x in touch.grab_list[:]:
            touch.grab_list.remove(x)
            x = x()
            if not x:
                continue
            touch.grab_current = x
            # touch is in window coords
            touch.push()
            touch.apply_transform_2d(self.to_widget)
            super(ScrollView, self).on_touch_up(touch)
            touch.pop()
        touch.grab_current = None