diff --git a/examples/widgets/screenmanager.py b/examples/widgets/screenmanager.py new file mode 100644 index 000000000..b7ec13565 --- /dev/null +++ b/examples/widgets/screenmanager.py @@ -0,0 +1,84 @@ +from kivy.app import App +from kivy.uix.screenmanager import ScreenManager, Screen +from kivy.properties import NumericProperty +from kivy.lang import Builder + +Builder.load_string(''' +#:import random random.random +#:import SlideTransition kivy.uix.screenmanager.SlideTransition +#:import SwapTransition kivy.uix.screenmanager.SwapTransition +#:import WipeTransition kivy.uix.screenmanager.WipeTransition +#:import FadeTransition kivy.uix.screenmanager.FadeTransition +: + hue: random() + canvas: + Color: + hsv: self.hue, .5, .3 + Rectangle: + size: self.size + + Label: + font_size: 42 + text: root.name + + Button: + text: 'Next screen' + size_hint: None, None + pos_hint: {'right': 1} + size: 150, 50 + on_release: root.manager.current = root.manager.next() + + Button: + text: 'Previous screen' + size_hint: None, None + size: 150, 50 + on_release: root.manager.current = root.manager.previous() + + BoxLayout: + size_hint: .5, None + height: 250 + pos_hint: {'center_x': .5} + orientation: 'vertical' + + Button: + text: 'Use SlideTransition with "up" direction' + on_release: root.manager.transition = SlideTransition(direction="up") + + Button: + text: 'Use SlideTransition with "down" direction' + on_release: root.manager.transition = SlideTransition(direction="down") + + Button: + text: 'Use SlideTransition with "left" direction' + on_release: root.manager.transition = SlideTransition(direction="left") + + Button: + text: 'Use SlideTransition with "right" direction' + on_release: root.manager.transition = SlideTransition(direction="right") + + Button: + text: 'Use SwapTransition' + on_release: root.manager.transition = SwapTransition() + + Button: + text: 'Use WipeTransition' + on_release: root.manager.transition = WipeTransition() + + Button: + text: 'Use FadeTransition' + on_release: root.manager.transition = FadeTransition() +''') + +class CustomScreen(Screen): + hue = NumericProperty(0) + +class ScreenManagerApp(App): + + def build(self): + root = ScreenManager() + for x in xrange(4): + root.add_widget(CustomScreen(name='Screen %d' % x)) + return root + +if __name__ == '__main__': + ScreenManagerApp().run() diff --git a/kivy/animation.py b/kivy/animation.py index 4ba30518e..ca50ce8bd 100644 --- a/kivy/animation.py +++ b/kivy/animation.py @@ -69,12 +69,18 @@ from kivy.clock import Clock class Animation(EventDispatcher): '''Create an animation definition that can be used to animate a Widget + .. versionchanged:: 1.4.0 + + Added s/step parameter. + :Parameters: `duration` or `d`: float, default to 1. Duration of the animation, in seconds `transition` or `t`: str or func Transition function for animate properties. It can be the name of a method from :class:`AnimationTransition` + `step` or `s`: float + Step in milliseconds of the animation. Default to 1 / 60. :Events: `on_start`: widget @@ -99,9 +105,10 @@ class Animation(EventDispatcher): self._clock_installed = False self._duration = kw.get('d', kw.get('duration', 1.)) self._transition = kw.get('t', kw.get('transition', 'linear')) + self._step = kw.get('s', kw.get('step', 1. / 60.)) if isinstance(self._transition, basestring): self._transition = getattr(AnimationTransition, self._transition) - for key in ('d', 't', 'duration', 'transition'): + for key in ('d', 't', 's', 'step', 'duration', 'transition'): kw.pop(key, None) self._animated_properties = kw self._widgets = {} @@ -210,7 +217,7 @@ class Animation(EventDispatcher): def _clock_install(self): if self._clock_installed: return - Clock.schedule_interval(self._update, 1 / 60.) + Clock.schedule_interval(self._update, self._step) self._clock_installed = True def _clock_uninstall(self): diff --git a/kivy/core/window/window_pygame.py b/kivy/core/window/window_pygame.py index 338d8b150..8792fb1e4 100644 --- a/kivy/core/window/window_pygame.py +++ b/kivy/core/window/window_pygame.py @@ -105,7 +105,7 @@ class WindowPygame(WindowBase): try: filename_icon = Config.get('kivy', 'window_icon') if filename_icon == '': - logo_size = 512 if platform() == 'darwin' else 32 + logo_size = 512 if platform() == 'macosx' else 32 filename_icon = join(kivy_home_dir, 'icon', 'kivy-icon-%d.png' % logo_size) self.set_icon(filename_icon) diff --git a/kivy/data/style.kv b/kivy/data/style.kv index fa5eacd0e..b489e7a5e 100644 --- a/kivy/data/style.kv +++ b/kivy/data/style.kv @@ -748,3 +748,12 @@ size: 32, 32 pos: int(self.center_x - 16), int(self.center_y - 16) +# ============================================================================= +# Relative Layout +# ============================================================================= + +: + do_translation: False + do_rotation: False + do_scale: False + auto_bring_to_front: False diff --git a/kivy/factory_registers.py b/kivy/factory_registers.py index 6fe49b3e4..df21bd547 100644 --- a/kivy/factory_registers.py +++ b/kivy/factory_registers.py @@ -82,6 +82,7 @@ r('BubbleButton', module='kivy.uix.bubble') r('Camera', module='kivy.uix.camera') r('CheckBox', module='kivy.uix.checkbox') r('FloatLayout', module='kivy.uix.floatlayout') +r('RelativeLayout', module='kivy.uix.relativelayout') r('FileChooserListView', module='kivy.uix.filechooser') r('FileChooserIconView', module='kivy.uix.filechooser') r('Image', module='kivy.uix.image') @@ -95,6 +96,8 @@ r('ScatterPlane', module='kivy.uix.scatter') r('ScrollView', module='kivy.uix.scrollview') r('Settings', module='kivy.uix.settings') r('Slider', module='kivy.uix.slider') +r('Screen', module='kivy.uix.screenmanager') +r('ScreenManager', module='kivy.uix.screenmanager') r('StackLayout', module='kivy.uix.stacklayout') r('StencilView', module='kivy.uix.stencilview') r('Switch', module='kivy.uix.switch') @@ -105,6 +108,11 @@ r('ToggleButton', module='kivy.uix.togglebutton') r('TreeView', module='kivy.uix.treeview') r('TreeViewLabel', module='kivy.uix.treeview') r('TreeViewNode', module='kivy.uix.treeview') +r('ShaderTransition', module='kivy.uix.screenmanager') +r('SlideTransition', module='kivy.uix.screenmanager') +r('SwapTransition', module='kivy.uix.screenmanager') +r('WipeTransition', module='kivy.uix.screenmanager') +r('FadeTransition', module='kivy.uix.screenmanager') r('Video', module='kivy.uix.video') r('VideoPlayer', module='kivy.uix.videoplayer') r('VideoPlayerVolume', module='kivy.uix.videoplayer') diff --git a/kivy/graphics/gl_instructions.pyx b/kivy/graphics/gl_instructions.pyx index fe2c3947b..9d598ef83 100644 --- a/kivy/graphics/gl_instructions.pyx +++ b/kivy/graphics/gl_instructions.pyx @@ -17,7 +17,7 @@ instructions like this example:: ''' -__all__ = ('ClearColor', 'ColorBuffers') +__all__ = ('ClearColor', 'ClearBuffers') include "config.pxi" diff --git a/kivy/uix/__init__.py b/kivy/uix/__init__.py index c809627e0..18fc08443 100644 --- a/kivy/uix/__init__.py +++ b/kivy/uix/__init__.py @@ -38,5 +38,10 @@ Read first: :doc:`api-kivy.uix.widget` :doc:`api-kivy.uix.scatter`, :doc:`api-kivy.uix.stencilview` +- **Screen manager**: Manage screens, and transition when switching from one to + another. + + :doc:`api-kivy.uix.screenmanager` + ---- ''' diff --git a/kivy/uix/floatlayout.py b/kivy/uix/floatlayout.py index ab528cb3c..c15d11779 100644 --- a/kivy/uix/floatlayout.py +++ b/kivy/uix/floatlayout.py @@ -35,7 +35,7 @@ For example, if you create a FloatLayout with size of (300, 300):: :: - # If you want to create a button that will always be the size of layout + # If you want to create a button that will always be the size of layout # minus 20% on each side: button = Button(text='Hello world', size_hint=(.6, .6), pos_hint={'x':.2, 'y':.2}) @@ -53,7 +53,7 @@ For example, if you create a FloatLayout with size of (300, 300):: ''' -__all__ = ('FloatLayout', ) +__all__ = ('FloatLayout', 'RelativeFloatLayout') from kivy.uix.layout import Layout @@ -119,3 +119,4 @@ class FloatLayout(Layout): pos = self._trigger_layout, pos_hint = self._trigger_layout) return super(Layout, self).remove_widget(widget) + diff --git a/kivy/uix/relativelayout.py b/kivy/uix/relativelayout.py new file mode 100644 index 000000000..cd4f55790 --- /dev/null +++ b/kivy/uix/relativelayout.py @@ -0,0 +1,56 @@ +''' +Relative Layout +=============== + +.. versionadded:: 1.4.0 + +This layout allow you to set relative coordinate for children. If you want +absolute positioning, check :class:`~kivy.uix.floatlayout.FloatLayout`. + +The :class:`RelativeFloatLayout` class behaves just like the regular Float +Layout, except that it's child widgets are positioned relative to the layout. + +For example, if you create a RelativeFloatLayout, add a widgets with +position = (0,0), the child widget will also move, when you change the +position of the RelativeFloatLayout. The child widgets coordiantes remain +(0,0), i.e. they are relative to the containing layout. + +..note:: + + The :class:`RelativeFloatLayout` is implemented as a :class`FloatLayout` + inside a :class:`Scatter`. + +.. warning:: + + Since the actual RelativeFloatLayout is a Scatter, its add_widget and + remove_widget functions are overwritten to add children to the embedded + FloatLayout (accessible as `content` property of RelativeFloatLayout) + automatically. So if you want to access the added child elements, + you need self.content.children, instead of self.children. +''' + +from kivy.uix.scatter import Scatter +from kivy.uix.floatlayout import FloatLayout +from kivy.properties import ObjectProperty + + +class RelativeLayout(Scatter): + '''RelativeLayout class, see module documentation for more information. + ''' + + content = ObjectProperty() + + def __init__(self, **kw): + self.content = FloatLayout() + super(RelativeLayout, self).__init__(**kw) + super(RelativeLayout, self).add_widget(self.content) + self.bind(size=self.update_size) + + def update_size(self, instance, size): + self.content.size = size + + def add_widget(self, *l): + self.content.add_widget(*l) + + def remove_widget(self, *l): + self.content.remove_widget(*l) diff --git a/kivy/uix/screenmanager.py b/kivy/uix/screenmanager.py new file mode 100644 index 000000000..c0c32d413 --- /dev/null +++ b/kivy/uix/screenmanager.py @@ -0,0 +1,652 @@ +''' +Screen Manager +============== + +.. versionadded:: 1.4.0 + +.. warning:: + + This widget is still experimental, and his API is subject to change in a + future version. + +The screen manager is a widget dedicated to manage multiple screens on your +application. The default :class:`ScreenManager` display only one +:class:`Screen` at time, and use a :class:`TransitionBase` to switch from one to +another Screen. + +Multiple transitions are supported, based of moving the screen coordinate / +scale, or even do fancy animation using custom shaders. + +Basic Usage +----------- + +Let's construct a Screen Manager with 4 named screen. When you are creating +screen, you absolutely need to give a name to it:: + + from kivy.uix.screenmanager import ScreenManager, Screen + + # Create the manager + sm = ScreenManager() + + # Add few screens + for i in xrange(4): + screen = Screen(name='Title %d' % i) + sm.add_widget(sm) + + # By default, the first screen added into the ScreenManager will be + # displayed. Then, you can change to another screen: + + # Let's display the screen named 'Title 2' + # The transition will be automatically used. + sm.current = 'Title 2' + + +Please note that by default, a :class:`Screen` display nothing, it's just a +:class:`~kivy.uix.relativelayout.RelativeLayout`. You need to use that class as +a root widget for your own screen. Best way is to subclass. + +Here is an example with a 'Menu Screen', and a 'Setting Screen':: + + from kivy.lang import Builder + from kivy.uix.screenmanager import ScreenManager, Screen + + # Create both screen. Please note the root.manager.current: this is how you + # can control the ScreenManager from kv. Each screen have by default a + # property manager that give you the instance of the ScreenManager used. + Builder.load_string(""" + : + BoxLayout: + Button: + text: 'Goto settings' + on_press: root.manager.current = 'settings' + Button: + text: 'Quit' + + : + BoxLayout: + Button: + text: 'My setting button' + Button: + text: 'Back to menu' + on_press: root.manager.current = 'menu' + """) + + # Declare both screen + class MenuScreen(Screen): + pass + + class SettingsScreen(Screen): + pass + + # Create the screen manager + sm = ScreenManager() + sm.add_widget(MenuScreen(name='menu')) + sm.add_widget(SettingsScreen(name='settings')) + + +Changing transition +------------------- + +You have multiple transition available by default, such as: + +- :class:`SlideTransition` - slide screen in/out, from any direction +- :class:`SwapTransition` - implementation of the iOS swap transition +- :class:`FadeTransition` - shader to fade in/out the screens +- :class:`WipeTransition` - shader to wipe from right to left the screens + +You can easily switch to a new transition by changing the +:data:`ScreenManager.transition` property:: + + sm = ScreenManager(transition=FadeTransition()) + +.. note:: + + Currently, all Shader based Transition doesn't have any anti-aliasing. This + is because we are using FBO, and don't have any logic to do supersampling on + them. This is a know issue, and working to have a transparent implementation + that will give the same result as it would be rendered on the screen. + + To be more concrete, if you see sharped-text during the animation, it's + normal. + +''' + +__all__ = ('Screen', 'ScreenManager', 'ScreenManagerException', + 'TransitionBase', 'ShaderTransition', 'SlideTransition', 'SwapTransition', + 'FadeTransition', 'WipeTransition') + +from kivy.event import EventDispatcher +from kivy.uix.floatlayout import FloatLayout +from kivy.properties import StringProperty, ObjectProperty, \ + NumericProperty, ListProperty, OptionProperty +from kivy.animation import Animation, AnimationTransition +from kivy.uix.relativelayout import RelativeLayout +from kivy.lang import Builder +from kivy.graphics.transformation import Matrix +from kivy.graphics import RenderContext, Rectangle, Fbo, \ + ClearColor, ClearBuffers, BindTexture + + +class ScreenManagerException(Exception): + pass + + +class Screen(RelativeLayout): + '''Screen is an element intented to be used within :class:`ScreenManager`. + Check module documentation for more information. + ''' + + name = StringProperty('') + ''' + Name of the screen, must be unique within a :class:`ScreenManager`. This is + the name used for :data:`ScreenManager.current` + + :data:`name` is a :class:`~kivy.properties.StringProperty`, default to '' + ''' + + manager = ObjectProperty() + '''Screen manager object, set when the screen is added within a manager. + + :data:`manager` is a :class:`~kivy.properties.ObjectProperty`, default to + None, read-only. + ''' + + transition_alpha = NumericProperty(0.) + '''Value that represent the completion of the current transition, if any is + occuring. + + If a transition is going on, whatever is the mode, the value will got from 0 + to 1. If you want to know if it's an entering or leaving animation, check + the :data:`transition_state` + + :data:`transition_alpha` is a :class:`~kivy.properties.NumericProperty`, + default to 0. + ''' + + transition_state = OptionProperty('out', options=('in', 'out')) + '''Value that represent the state of the transition: + + - 'in' if the transition is going to show your screen + - 'out' if the transition is going to hide your screen + + After the transition is done, the state will stay on the last one (in or + out). + + :data:`transition_state` is an :class:`~kivy.properties.OptionProperty`, + default to 'out'. + ''' + + def __repr__(self): + return '' % self.name + + +class TransitionBase(EventDispatcher): + '''Transition class is used to animate 2 screens within the + :class:`ScreenManager`. This class act as a base for others implementation, + like :class:`SlideTransition`, :class:`SwapTransition`. + + :Events: + `on_progress`: Transition object, progression float + Fired during the animation of the transition + `on_complete`: Transition object + Fired when the transition is fininshed. + ''' + + screen_out = ObjectProperty() + '''Property that contain the screen to hide. + Automatically set by the :class:`ScreenManager`. + + :class:`screen_out` is a :class:`~kivy.properties.ObjectProperty`, default + to None. + ''' + + screen_in = ObjectProperty() + '''Property that contain the screen to show. + Automatically set by the :class:`ScreenManager`. + + :class:`screen_in` is a :class:`~kivy.properties.ObjectProperty`, default + to None. + ''' + + duration = NumericProperty(.7) + '''Duration in seconds of the transition. + + :class:`duration` is a :class:`~kivy.properties.NumericProperty`, default to + .7 (= 700ms) + ''' + + manager = ObjectProperty() + '''Screen manager object, set when the screen is added within a manager. + + :data:`manager` is a :class:`~kivy.properties.ObjectProperty`, default to + None, read-only. + ''' + + # privates + + _anim = ObjectProperty(allownone=True) + + def __init__(self, **kw): + self.register_event_type('on_progress') + self.register_event_type('on_complete') + super(TransitionBase, self).__init__(**kw) + + def start(self, manager): + '''(internal) Start the transition. This is automatically called by the + :class:`ScreenManager`. + ''' + self.manager = manager + self._anim = Animation(d=self.duration, s=0) + self._anim.bind(on_progress=self._on_progress, on_complete=self._on_complete) + + self.add_screen(self.screen_in) + self.screen_in.transition_value = 0. + self.screen_in.transition_mode = 'in' + self.screen_out.transition_value = 0. + self.screen_out.transition_mode = 'out' + + self._anim.start(self) + self.dispatch('on_progress', 0) + + def stop(self): + '''(internal) Stop the transition. This is automatically called by the + :class:`ScreenManager`. + ''' + if self._anim: + self._anim.cancel(self) + self.dispatch('on_complete') + self._anim = None + + def add_screen(self, screen): + '''(internal) Used to add a screen into the :class:`ScreenManager` + ''' + self.manager.real_add_widget(screen) + + def remove_screen(self, screen): + '''(internal) Used to remove a screen into the :class:`ScreenManager` + ''' + self.manager.real_remove_widget(screen) + + def on_complete(self): + self.remove_screen(self.screen_out) + + def on_progress(self, progression): + pass + + def _on_progress(self, *l): + alpha = l[-1] + self.screen_in.transition_value = alpha + self.screen_out.transition_value = 1. - alpha + self.dispatch('on_progress', alpha) + + def _on_complete(self, *l): + self.dispatch('on_complete') + self._anim = None + + +class ShaderTransition(TransitionBase): + '''Transition class that use a Shader for animating the transition between 2 + screens. By default, this class doesn't any assign fragment/vertex shader. + + If you want to create your own fragment shader for transition, you need to + declare the header yourself, and include the "t", "tex_in" and "tex_out" + uniform:: + + # Create your own transition. This is shader implement a "fading" + # transition. + fs = """$HEADER + uniform float t; + uniform sampler2D tex_in; + uniform sampler2D tex_out; + + void main(void) { + vec4 cin = texture2D(tex_in, tex_coord0); + vec4 cout = texture2D(tex_out, tex_coord0); + gl_FragColor = mix(cout, cin, t); + } + """ + + # And create your transition + tr = ShaderTransition(fs=fs) + sm = ScreenManager(transition=tr) + + ''' + + fs = StringProperty(None) + '''Fragment shader to use. + + :data:`fs` is a :class:`~kivy.properties.StringProperty`, default to None. + ''' + + vs = StringProperty(None) + '''Vertex shader to use. + + :data:`vs` is a :class:`~kivy.properties.StringProperty`, default to None. + ''' + + def make_screen_fbo(self, screen): + fbo = Fbo(size=screen.size) + with fbo: + ClearColor(0,1,0,1) + ClearBuffers() + fbo.add(screen.canvas) + return fbo + + def on_progress(self, progress): + self.render_ctx['t'] = progress + + 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.render_ctx = RenderContext(fs=self.fs) + with self.render_ctx: + BindTexture(texture=self.fbo_out.texture, index=1) + BindTexture(texture=self.fbo_in.texture, index=2) + Rectangle(size=(1,1)) + self.render_ctx['projection_mat'] = Matrix().view_clip(0,1,0,1,0,1,0) + self.render_ctx['tex_out'] = 1 + self.render_ctx['tex_in'] = 2 + self.manager.canvas.add(self.render_ctx) + + def remove_screen(self, screen): + self.manager.canvas.remove(self.fbo_in) + self.manager.canvas.remove(self.fbo_out) + self.manager.canvas.remove(self.render_ctx) + self.manager.real_add_widget(self.screen_in) + + +class SlideTransition(TransitionBase): + '''Slide Transition, can be used to show a new screen from any direction: + left, right, up or down. + ''' + + direction = OptionProperty('left', options=('left', 'right', 'up', 'down')) + '''Direction of the transition. + + :data:`direction` is an :class:`~kivy.properties.OptionProperty`, default to + left. Can be one of 'left', 'right', 'up' or 'down'. + ''' + + def on_progress(self, progression): + a = self.screen_in + b = self.screen_out + manager = self.manager + x, y = manager.pos + width, height = manager.size + direction = self.direction + al = AnimationTransition.out_quad + progression = al(progression) + if direction == 'left': + a.y = b.y = y + a.x = x + width * (1 - progression) + b.x = x - width * progression + elif direction == 'right': + a.y = b.y = y + b.x = x + width * progression + a.x = x - width * (1 - progression) + elif direction == 'up': + a.x = b.x = x + a.y = y + height * (1 - progression) + b.y = y - height * progression + elif direction == 'down': + a.x = b.x = manager.x + b.y = y + height * progression + a.y = y - height * (1 - progression) + + +class SwapTransition(TransitionBase): + '''Swap transition, that look like iOS transition, when a new window appear + on the screen. + ''' + + def add_screen(self, screen): + self.manager.real_add_widget(screen, 1) + + def on_complete(self): + self.screen_in.scale = 1. + self.screen_out.scale = 1. + + def on_progress(self, progression): + a = self.screen_in + b = self.screen_out + manager = self.manager + + b.scale = 1. - progression * 0.7 + a.scale = 0.5 + progression * 0.5 + a.center_y = b.center_y = manager.center_y + + al = AnimationTransition.in_out_sine + + if progression < 0.5: + p2 = al(progression * 2) + width = manager.width * 0.7 + widthb = manager.width * 0.2 + a.x = manager.center_x + p2 * width / 2. + b.center_x = manager.center_x - p2 * widthb / 2. + else: + if self.screen_in is self.manager.children[-1]: + self.manager.real_remove_widget(self.screen_in) + self.manager.real_add_widget(self.screen_in) + p2 = al((progression - 0.5) * 2) + width = manager.width * 0.85 + widthb = manager.width * 0.2 + a.x = manager.x + width * (1 - p2) + b.center_x = manager.center_x - (1 - p2) * widthb / 2. + + +class WipeTransition(ShaderTransition): + '''Wipe transition, based on a fragment Shader. + ''' + + WIPE_TRANSITION_FS = '''$HEADER$ + uniform float t; + uniform sampler2D tex_in; + uniform sampler2D tex_out; + + void main(void) { + vec4 cin = texture2D(tex_in, tex_coord0); + vec4 cout = texture2D(tex_out, tex_coord0); + gl_FragColor = mix(cout, cin, clamp((-1.5 + 1.5*tex_coord0.x + 2.5*t), 0.0, 1.0)); + } + ''' + fs = StringProperty(WIPE_TRANSITION_FS) + + +class FadeTransition(ShaderTransition): + '''Fade transition, based on a fragment Shader. + ''' + + FADE_TRANSITION_FS = '''$HEADER$ + uniform float t; + uniform sampler2D tex_in; + uniform sampler2D tex_out; + + void main(void) { + vec4 cin = texture2D(tex_in, tex_coord0); + vec4 cout = texture2D(tex_out, tex_coord0); + gl_FragColor = mix(cout, cin, t); + } + ''' + fs = StringProperty(FADE_TRANSITION_FS) + + +class ScreenManager(FloatLayout): + '''Screen manager. This is the main class that will control your + :class:`Screen` stack, and memory. + + By default, the manager will show only one screen at time. + ''' + + current = StringProperty(None) + '''Name of the screen currently show, or the screen to show. + + :: + + from kivy.uix.screenmanager import ScreenManager, Screen + + sm = ScreenManager() + sm.add_widget(Screen(name='first')) + sm.add_widget(Screen(name='second')) + + # by default, the first added screen will be showed. If you want to show + # another one, just set the current string: + sm.current = 'second' + ''' + + transition = ObjectProperty(SwapTransition()) + '''Transition object to use for animate the screen that will be hidden, and + the screen that will be showed. By default, an instance of + :class:`SwapTransition` will be given. + + For example, if you want to change to a :class:`WipeTransition`:: + + from kivy.uix.screenmanager import ScreenManager, Screen, WipeTransition + + sm = ScreenManager(transition=WipeTransition()) + sm.add_widget(Screen(name='first')) + sm.add_widget(Screen(name='second')) + + # by default, the first added screen will be showed. If you want to show + # another one, just set the current string: + sm.current = 'second' + ''' + + screens = ListProperty() + '''List of all the :class:`Screen` widgets added. You must not change the + list manually. Use :meth:`Screen.add_widget` instead. + + :data:`screens` is a :class:`~kivy.properties.ListProperty`, default to [], + read-only. + ''' + + current_screen = ObjectProperty(None) + '''Contain the current displayed screen. You must not change this property + manually, use :data:`current` instead. + + :data:`current_screen` is an :class:`~kivy.properties.ObjectProperty`, + default to None, read-only. + ''' + + def add_widget(self, screen): + if not isinstance(screen, Screen): + raise ScreenManagerException( + 'ScreenManager accept only Screen widget.') + if screen.name in [s.name for s in self.screens]: + raise ScreenManagerException( + 'Name %r already used' % screen.name) + if screen.manager: + raise ScreenManagerException( + 'Screen already managed by another ScreenManager.') + screen.manager = self + self.screens.append(screen) + if self.current is None: + self.current = screen.name + + def real_add_widget(self, *l): + super(ScreenManager, self).add_widget(*l) + + def real_remove_widget(self, *l): + super(ScreenManager, self).remove_widget(*l) + + def on_current(self, instance, value): + screen = self.get_screen(value) + if not screen: + return + + previous_screen = self.current_screen + self.current_screen = screen + if previous_screen: + self.transition.stop() + self.transition.screen_in = screen + self.transition.screen_out = previous_screen + self.transition.start(self) + else: + self.real_add_widget(screen) + + def get_screen(self, name): + '''Return the screen widget associated to the name, or None if not + found. + ''' + for screen in self.screens: + if screen.name == name: + return screen + + def next(self): + '''Return the name of the next screen from the screen list. + ''' + screens = self.screens + if not screens: + return + try: + index = screens.index(self.current_screen) + index = (index + 1) % len(screens) + return screens[index].name + except ValueError: + return + + def previous(self): + '''Return the name of the previous screen from the screen list. + ''' + screens = self.screens + if not screens: + return + try: + index = screens.index(self.current_screen) + index = (index - 1) % len(screens) + return screens[index].name + except ValueError: + return + +if __name__ == '__main__': + from kivy.app import App + from kivy.uix.button import Button + from kivy.lang import Builder + Builder.load_string(''' +: + canvas: + Color: + rgb: .2, .2, .2 + Rectangle: + size: self.size + + GridLayout: + cols: 2 + Button: + text: 'Hello world' + Button: + text: 'Hello world' + Button: + text: 'Hello world' + Button: + text: 'Hello world' +''') + + class TestApp(App): + def change_view(self, *l): + #d = ('left', 'up', 'down', 'right') + #di = d.index(self.sm.transition.direction) + #self.sm.transition.direction = d[(di + 1) % len(d)] + self.sm.current = 'test2' if self.sm.current == 'test1' else 'test1' + + def build(self): + root = FloatLayout() + self.sm = sm = ScreenManager(transition=SwapTransition()) + + sm.add_widget(Screen(name='test1')) + sm.add_widget(Screen(name='test2')) + + btn = Button(size_hint=(None, None)) + btn.bind(on_release=self.change_view) + root.add_widget(sm) + root.add_widget(btn) + return root + + TestApp().run() +