Sideband/sbapp/kivymd/uix/transition/transition.py

179 lines
6.0 KiB
Python
Raw Normal View History

2022-07-07 14:16:10 -06:00
"""
Components/Transition
=====================
.. rubric::
A set of classes for implementing transitions between application screens.
.. versionadded:: 1.0.0
Changing transitions
--------------------
You have multiple transitions available by default, such as:
- :class:`MDFadeSlideTransition`
state one: the new screen closes the previous screen by lifting from the
bottom of the screen and changing from transparent to non-transparent;
state two: the current screen goes down to the bottom of the screen,
passing from a non-transparent state to a transparent one, thus opening the
previous screen;
.. note::
You cannot control the direction of a slide using the direction attribute.
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/transition-md-fade-slide-transition.gif
:align: center
"""
__all__ = (
"MDFadeSlideTransition",
"MDSlideTransition",
"MDSwapTransition",
"MDTransitionBase",
)
from kivy.animation import Animation, AnimationTransition
from kivy.uix.screenmanager import (
ScreenManagerException,
SlideTransition,
SwapTransition,
TransitionBase,
)
from kivymd.uix.screenmanager import MDScreenManager
class MDTransitionBase(TransitionBase):
_direction = "in"
hero_widget = None
hero_from_widget = None # kivymd.uix.hero.MDHeroFrom object
def start(self, instance_screen_manager: MDScreenManager) -> None:
super().start(instance_screen_manager)
{"in": self.animated_hero_in, "out": self.animated_hero_out}[
self._direction
]()
def animated_hero_in(self) -> None:
if self.manager._heroes_data and self.manager.current_hero:
self.hero_from_widget = self.manager.get_hero_from_widget()
self._check_widget_properties()
self.hero_widget = self.hero_from_widget.children[0]
self.hero_from_widget.remove_widget(self.hero_widget)
self.hero_widget.pos = self.screen_out.to_widget(
*self.hero_from_widget.to_window(*self.hero_from_widget.pos)
)
self.hero_widget.size = self.hero_from_widget.size
self.manager.get_root_window().add_widget(self.hero_widget)
Animation(
size=self.screen_in.hero_to.size,
d=self.duration,
pos=self.screen_in.hero_to.pos,
).start(self.hero_widget)
self.hero_from_widget.dispatch(
"on_transform_in", self.hero_widget, self.duration
)
def animated_hero_out(self) -> None:
if self.manager._heroes_data and self.manager.current_hero:
self.screen_out.hero_to.remove_widget(self.hero_widget)
self.manager.get_root_window().add_widget(self.hero_widget)
self.hero_from_widget.dispatch(
"on_transform_out", self.hero_widget, self.duration
)
Animation(
pos=self.screen_in.to_widget(
*self.hero_from_widget.to_window(*self.hero_from_widget.pos)
),
size=self.hero_from_widget.size,
d=self.duration,
).start(self.hero_widget)
def on_complete(self) -> None:
super().on_complete()
if self._direction == "out":
self._direction = "in"
if self.manager._heroes_data and self.manager.current_hero:
self.manager.get_root_window().remove_widget(self.hero_widget)
self.hero_from_widget.add_widget(self.hero_widget)
else:
self._direction = "out"
if self.manager._heroes_data and self.manager.current_hero:
self.manager.get_root_window().remove_widget(self.hero_widget)
self.screen_in.hero_to.add_widget(self.hero_widget)
def _check_widget_properties(self):
if not self.screen_in.hero_to:
raise Exception(
f"The `hero_to` attribute is not specified for screen {self.screen_in}"
)
if len(self.hero_from_widget.children) > 1:
raise Exception(
f"{self.hero_from_widget.__class__} accept only one widget"
)
class MDSwapTransition(SwapTransition, MDTransitionBase):
pass
class MDSlideTransition(SlideTransition, MDTransitionBase):
pass
class MDFadeSlideTransition(MDSlideTransition):
def start(self, instance_screen_manager: MDScreenManager) -> None:
if self.is_active:
raise ScreenManagerException("start() is called twice!")
self.manager = instance_screen_manager
self._anim = Animation(d=self.duration, s=0)
self._anim.bind(
on_progress=self._on_progress, on_complete=self._on_complete
)
if self._direction == "in":
self.add_screen(self.screen_in)
self.animated_hero_in()
else:
self.animated_hero_out()
self.add_screen(self.screen_in)
self.add_screen(self.screen_out)
self.screen_in.transition_progress = 0.0
self.screen_in.transition_state = "in"
self.screen_out.transition_progress = 0.0
self.screen_out.transition_state = "out"
self.screen_in.dispatch("on_pre_enter")
self.screen_out.dispatch("on_pre_leave")
self.is_active = True
self._anim.start(self)
self.dispatch("on_progress", 0)
if self._direction == "in":
self.screen_in.y = 0
self.screen_in.opacity = 0
def on_progress(self, progression: float) -> None:
progression = AnimationTransition.out_quad(progression)
if self._direction == "in":
self.screen_in.y = (
self.manager.y + self.manager.height * progression
) - self.screen_in.height
self.screen_in.opacity = progression
if self._direction == "out":
self.screen_out.y = (
self.manager.y - self.manager.height * progression
)
self.screen_out.opacity = 1 - progression