st3m.ui.view
module
Warning
In earlier versions of flow3r there was no clear distinction between View
and
st3m.ui.Application
. This was inconvenient so we are slowly transitioning to a new model
where an application may use views while being a distinct entity. This transition is incomplete
as we tried to wrap up the release for Chaos Communication Congress 2024 and many new features
rely on the new model.
This documentation is the old view
documentation. We did not rewrite it for this release
yet as it is unclear how to use it future safely. Features like .on_enter()
clash with
their st3m.ui.application
counterpart, which will be rectified soon by introducing new
methods to Application
. Right now it is difficult to use them for both purposes.
We will try to keep existing View
/ViewManager
applications running and will provide a
clean way for new applications to do so soon, but in the meantime please don’t try to get hacky
with these APIs because cannot maintain every edge case.
Managing multiple views
If you want to write a more advanced application you probably also want to display more than one screen (or view as we call them). With just the Responder class this can become a bit tricky as it never knows when it is visible and when it is not. It also doesn’t directly allow you to launch a new screen.
To help you with that you can use a View
instead. It can tell you when
it becomes visible, when it is about to become inactive (invisible) and you can
also use it to bring a new screen or widget into the foreground or remove it
again from the screen.
Example 1: Managing two views
In this example we use a basic View to switch between to different screens using a button. One screen shows a red square, the other one a green square. You can of course put any kind of complex processing into the two different views. We make use of an InputController again to handle the button presses.
from st3m.input import InputController
from st3m.ui.view import View
import st3m.run
class SecondScreen(View):
def __init__(self) -> None:
self.input = InputController()
self._vm = None
def on_enter(self, vm: Optional[ViewManager]) -> None:
self._vm = vm
# Ignore the button which brought us here until it is released
self.input._ignore_pressed()
def draw(self, ctx: Context) -> None:
# Paint the background black
ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
# Green square
ctx.rgb(0, 1, 0).rectangle(-20, -20, 40, 40).fill()
def think(self, ins: InputState, delta_ms: int) -> None:
self.input.think(ins, delta_ms) # let the input controller to its magic
# No need to handle returning back to Example on button press - the
# flow3r's ViewManager takes care of that automatically.
class Example(View):
def __init__(self) -> None:
self.input = InputController()
self._vm = None
def draw(self, ctx: Context) -> None:
# Paint the background black
ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
# Red square
ctx.rgb(1, 0, 0).rectangle(-20, -20, 40, 40).fill()
def on_enter(self, vm: Optional[ViewManager]) -> None:
self._vm = vm
self.input._ignore_pressed()
def think(self, ins: InputState, delta_ms: int) -> None:
self.input.think(ins, delta_ms) # let the input controller to its magic
if self.input.buttons.app.middle.pressed:
self._vm.push(SecondScreen())
st3m.run.run_view(Example())
Try it using mpremote. The OS shoulder button (right shoulder unless swapped in settings) switches between the two views. To avoid that the still pressed button immediately closes SecondScreen we make us of a special method of the InputController which hides the pressed button from the view until it is released again.
Note
Pressing the OS shoulder button in REPL mode will currently reset the badge.
Until this is fixed, you can test view switching by copying the app to your badge and running from the menu.
Example 2: Easier view management
The above code is so universal that we provide a special view which takes care
of this boilerplate: BaseView
. It integrated a local
InputController on self.input
and a copy of the ViewManager
which caused the View to enter on self.vm
.
Here is our previous example rewritten to make use of BaseView:
from st3m.ui.view import BaseView
import st3m.run
class SecondScreen(BaseView):
def __init__(self) -> None:
# Remember to call super().__init__() if you implement your own
# constructor!
super().__init__()
def on_enter(self, vm: Optional[ViewManager]) -> None:
# Remember to call super().on_enter() if you implement your own
# on_enter!
super().on_enter(vm)
def draw(self, ctx: Context) -> None:
# Paint the background black
ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
# Green square
ctx.rgb(0, 1, 0).rectangle(-20, -20, 40, 40).fill()
class Example(BaseView):
def draw(self, ctx: Context) -> None:
# Paint the background black
ctx.rgb(0, 0, 0).rectangle(-120, -120, 240, 240).fill()
# Red square
ctx.rgb(1, 0, 0).rectangle(-20, -20, 40, 40).fill()
def think(self, ins: InputState, delta_ms: int) -> None:
super().think(ins, delta_ms) # Let BaseView do its thing
if self.input.buttons.app.middle.pressed:
self.vm.push(SecondScreen())
st3m.run.run_view(Example())
In some cases, it’s useful to know more about the view’s lifecycle, so tasks like long-blocking loading screens can be synchronized with view transition animations. You’ll also be interested in those events when doing unusual things with rendering, such as partial redraws or direct framebuffer manipulation.
Here’s the list of methods that you can implement in your class:
Function |
Meaning |
---|---|
|
The view has became active and is about to start receiving |
|
The view transition has finished animating; the whole screen is now under the control of the view. If you want to perform a long running blocking task (such as loading audio samples), it’s a good idea to initiate it here to not block during the transition animation. |
|
The view has became inactive and started to transition out; no further
Do the clean-up of shared resources (such as LEDs or the media engine)
here. This way you won’t step onto the next view’s shoes, as it may
want to use them in its Note: When returning |
|
The view transition has finished animating; no further A good place to do the clean-up of resources you have exclusive control over, such as bl00mbox channels. |
|
Return |
ViewManager
also provides some methods to make handling common
cases easier:
Function / property |
Meaning |
---|---|
|
Returns a bool indicating whether the passed view is currently
active. The active view is the one that’s expected to be in control
of user’s input and the view stack. A view becomes active at
|
|
Whether a transition animation is currently in progress. |
|
Returns the direction in which the currently active view has became one:
|
On top of that, BaseView
implements an additional helper method
.is_active()
, which is simply a bit less awkward way to call
self.vm.is_active(self)
.