Select Git revision
extra_coverage.py.exp
-
Damien George authoredDamien George authored
view.py 6.05 KiB
from st3m.reactor import Responder
from st3m.goose import ABCBase, abstractmethod, Optional, List
from st3m.input import InputState, InputController
from st3m.ui.ctx import Ctx
class View(Responder):
"""
A View extends a reactor Responder with callbacks related to the Responder's
lifecycle in terms of being foregrounded or backgrounded.
These signals can be used to alter input processing, se the
ViewWithInputState class.
"""
def on_enter(self) -> None:
"""
Called when the View has just become active. This is guaranteed to be
called before think().
"""
pass
class ViewWithInputState(View):
"""
A base class helper for implementing views which respond to inputs and who
want to do their own, separate input processing.
Derive this class, then use self.input to access the InputController.
Remember to call super().think() in think()!
"""
__slots__ = ("input",)
def __init__(self) -> None:
self.input = InputController()
def on_enter(self) -> None:
self.input._ignore_pressed()
def think(self, ins: InputState, delta_ms: int) -> None:
self.input.think(ins, delta_ms)
class ViewTransition(ABCBase):
"""
A transition from one View/Responder to another.
Can be implemented by the user to provide transition animations.
"""
@abstractmethod
def draw(
self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder
) -> None:
"""
Called when the ViewManager performs a transition from the outgoing
responder to the incoming responder. The implementer should draw both
Responders when appropriate.
The transition value is a float from 0 to 1 which represents the
progress of the transition.
"""
pass
class ViewTransitionBlend(ViewTransition):
"""
Transition from one view to another by opacity blending.
"""
def draw(
self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder
) -> None:
ctx.start_group()
outgoing.draw(ctx)
ctx.end_group()
ctx.start_group()
ctx.global_alpha = transition
incoming.draw(ctx)
ctx.end_group()
class ViewTransitionSwipeLeft(ViewTransition):
"""
Swipe the outoing view to the left and replace it with the incoming view.
"""
def draw(
self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder
) -> None:
ctx.save()
ctx.translate(transition * -240, 0)
outgoing.draw(ctx)
ctx.restore()
ctx.save()
ctx.translate(240 + transition * -240, 0)
incoming.draw(ctx)
ctx.restore()
class ViewTransitionSwipeRight(ViewTransition):
"""
Swipe the outoing view to the right and replace it with the incoming view.
"""
def draw(
self, ctx: Ctx, transition: float, incoming: Responder, outgoing: Responder
) -> None:
ctx.save()
ctx.translate(transition * 240, 0)
outgoing.draw(ctx)
ctx.restore()
ctx.save()
ctx.translate(-240 + transition * 240, 0)
incoming.draw(ctx)
ctx.restore()
class ViewManager(Responder):
"""
The ViewManager implements stateful routing between Views.
It manages a history of Views, to which new Views can be pushed and then
popped.
"""
def __init__(self, vt: ViewTransition) -> None:
"""
Create a new ViewManager with a default ViewTransition.
"""
self._incoming: Optional[View] = None
self._outgoing: Optional[View] = None
# Transition time.
self._time_ms = 150
self._default_vt = vt
self._overriden_vt: Optional[ViewTransition] = None
self._transitioning = False
self._transition = 0.0
self._history: List[View] = []
def think(self, ins: InputState, delta_ms: int) -> None:
if self._transitioning:
self._transition += (delta_ms / 1000.0) * (1000 / self._time_ms)
if self._transition >= 1.0:
self._transition = 0
self._transitioning = False
self._outgoing = None
if self._outgoing is not None:
self._outgoing.think(ins, delta_ms)
if self._incoming is not None:
self._incoming.think(ins, delta_ms)
def draw(self, ctx: Ctx) -> None:
if self._transitioning:
vt = self._default_vt
if self._overriden_vt is not None:
vt = self._overriden_vt
if self._incoming is not None and self._outgoing is not None:
vt.draw(ctx, self._transition, self._incoming, self._outgoing)
return
if self._incoming is not None:
self._incoming.draw(ctx)
def replace(self, r: View, overide_vt: Optional[ViewTransition] = None) -> None:
"""
Replace the existing view with the given View, optionally using a given
ViewTransition instead of the default.
The new view will _not_ be added to history!
"""
self._outgoing = self._incoming
self._incoming = r
self._incoming.on_enter()
self._overriden_vt = overide_vt
if self._outgoing is None:
return
self._transitioning = True
self._transition = 0.0
def push(self, r: View, override_vt: Optional[ViewTransition] = None) -> None:
"""
Push a View to the history stack and start transitioning to it. If set,
override_vt will be used instead of the default ViewTransition
animation.
"""
if self._incoming is not None:
self._history.append(self._incoming)
self.replace(r, override_vt)
def pop(self, override_vt: Optional[ViewTransition] = None) -> None:
"""
Pop a view from the history stack and start transitioning to it. If set,
override_vt will be used instead of the default ViewTransition
animation.
"""
r = self._history.pop()
self.replace(r, override_vt)