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()
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()
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()
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()
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()
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()
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)
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()
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})
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)
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)
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)
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)
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))
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()
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]})
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)
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()
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]})
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))
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))
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 __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()
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)
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)
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()
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
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()
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()
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)
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()
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)
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()
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)
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()
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
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]})
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))
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()
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.)
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)
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)
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)
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)
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
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)
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.
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