Concepts and core objects¶
The Scene¶
To render a UI, you need to use a UIScene
:
from clubsandwich.director import DirectorLoop, Scene
from clubsandwich.ui import (
ButtonView,
)
class MainMenuScene(UIScene):
def __init__(self, *args, **kwargs):
views = [ButtonView(text="Quit", callback=self.quit)]
super().__init__(views, *args, **kwargs)
def quit(self):
self.director.pop_scene()
class GameLoop(DirectorLoop):
def get_initial_scene(self):
return MainMenuScene()
GameLoop().run()
-
class
clubsandwich.ui.
UIScene
(views, *args, **kwargs)¶ Parameters: views (list|View) – One or more subviews of the root view See
Scene
for the other args.Scene that renders a view hierarchy inside a
FirstResponderContainerView
.Log the view hierarchy by pressing the backslash key at any time.
Layout¶
Layouts, most of the time, are specified using
LayoutOptions
objects. These more
or less follow the UIKit springs-and-struts model. Here’s a diagram:
+----------------------------+
| ^ |
| top |
| v |
| +---------+ |
| | ^ | |
| | | | |
|<-left->|<-width->|<-right->|
| | | | |
| | height| |
| | v | |
| +---------+ |
| ^ |
| bottom |
| v |
+----------------------------+
Each listed metric can either be a spring or a strut. Struts are exact values based on either a constant or a percentage of the superview. Springs are totally unconstrained, and their values are derived after the struts have been enforced.
In clubsandwich, springs are represented by None
. All the other values
define some kind of strut:
- Ints >= 1 are constants.
- Floats from 0 (inclusive) to 1 (exclusive) are fractions of the superview’s width or height.
- The string
intrinsic
means “at layout time, take the value from myView.intrinsic_size
property.” This is useful when you don’t know in advance how big your content is. - The string
frame
means “at layout time, take the value from the corresponding attribute of myView.frame
property.”
So to put something 3 cells from the left that is 50% as wide as its
superview, you’d set left
to 3
and width
to 0.5
. To fill the
superview (this is the default), set left/right/top/bottom to 0
and
width/height to None
.
There is a special case: when width
is set but left
and right
are
None
, the view is centered horizontally. It works the same way for the
vertical axis.
The LayoutOptions
class has several convenience initializers and
methods to make these specs extremely concise.
-
class
clubsandwich.ui.
LayoutOptionValue
¶ This is not a real class, but in these docs, it represents the possible values for the attributes of
LayoutOptions
.None
: Do not constrain this value. It is a spring.0.0-1.0
left-inclusive: Use a fraction of the superview’s size on the appropriate axis.>=1
: Use a constant integer'intrinsic'
: The view defines anintrinsic_size
property; use this value. Mostly useful forLabelView
.'frame'
: Derive a constant from the initial frame of this view. This initial frame is stored inView.layout_spec
, so if you need to change it, you can just change that attribute.
-
class
clubsandwich.ui.layout_options.
LayoutOptions
¶ Parameters: - width (LayoutOptionValue) – width spec
- height (LayoutOptionValue) – height spec
- top (LayoutOptionValue) – top spec
- right (LayoutOptionValue) – right spec
- bottom (LayoutOptionValue) – bottom spec
- left (LayoutOptionValue) – left spec
It is possible to define values that conflict. The behavior in these cases is undefined.
-
width
¶ A
LayoutOptionValue
constraining this view’s width (or not).
-
height
¶ A
LayoutOptionValue
constraining this view’s height (or not).
-
top
¶ A
LayoutOptionValue
constraining this view’s distance from the top of its superview (or not).
-
right
¶ A
LayoutOptionValue
constraining this view’s distance from the right of its superview (or not).
-
bottom
¶ A
LayoutOptionValue
constraining this view’s distance from the bottom of its superview (or not).
-
left
¶ A
LayoutOptionValue
constraining this view’s distance from the left of its superview (or not).
-
classmethod
centered
(width, height)¶ Create a
LayoutOptions
object that positions the view in the center of the superview with a constant width and height.
-
classmethod
column_left
(width)¶ Create a
LayoutOptions
object that positions the view as a full-height left column with a constant width.
-
classmethod
column_right
(width)¶ Create a
LayoutOptions
object that positions the view as a full-height right column with a constant width.
-
classmethod
row_bottom
(height)¶ Create a
LayoutOptions
object that positions the view as a full-height bottom row with a constant height.
-
classmethod
row_top
(height)¶ Create a
LayoutOptions
object that positions the view as a full-height top row with a constant height.
-
with_updates
(**kwargs)¶ Returns a new
LayoutOptions
object with the given changes to its attributes. For example, here’s a view with a constant width, on the right side of its superview, with half the height of its superview:# "right column, but only half height" LayoutOptions.column_right(10).with_updates(bottom=0.5)
Views¶
-
class
clubsandwich.ui.
View
(frame=None, subviews=None, scene=None, layout_options=None, clear=False)¶ Parameters: - frame (Rect) – Rect relative to superview’s
View.bounds
- subviews (list) – List of subviews
- scene (UIScene) – Scene that’s handling this view
- layout_options (LayoutOptions) – How to position this view
- clear (bool) – If
True
, clear bounds each render. (Each individual view implements this because it depends on color.)
Renders itself and its subviews in an area of 2D space relative to its superview.
Variables: - subviews (list) – List of subviews
- is_hidden (bool) –
True
iff this view will not be drawn. Feel free to set this yourself. - layout_options (LayoutOptions) –
- layout_spec (Rect) – A copy of the frame made at init time, used by
LayoutOptions
to derive values during layout. - is_first_responder (bool) –
True
iff this view is currently the first responder.
- frame (Rect) – Rect relative to superview’s
Positioning¶
-
View.
bounds
¶ This view’s rect from its internal frame of reference. That means
self.bounds.origin
is alwaysPoint(0, 0)
.
-
View.
frame
¶ This view’s rect relative to its superview’s bounds.
-
View.
scene
¶ The scene this view is being rendered in, or
None
.
-
View.
intrinsic_size
¶ Optional. Values for
intrinsic
-valued attributes ofLayoutOptions
.
View hierarchy¶
-
View.
superview
¶ Weak reference to the view this view is a child of, or
None
.
Layout¶
-
View.
set_needs_layout
(val=True)¶ Parameters: val (bool) – If True
, view needs to be redrawn. (defaultTrue
)Call this if the view’s
frame()
or content changes.draw()
is only called if this was called first.Note that if you’re changing either
View.layout_options
or changing something that affects the view’s springs-and-struts layout metrics, you may need to callself.superview.set_needs_layout()
to have the layout algorithm re-run on your view.
-
View.
layout_subviews
()¶ Set the frames of all subviews relative to
self.bounds
. By default, applies the springs-and-struts algorithm using each view’slayout_options
andlayout_spec
properties.You shouldn’t need to override this unless
LayoutOptions
isn’t expressive enough for you.
Drawing¶
-
View.
draw
(ctx)¶ Parameters: ctx (BearLibTerminalContext) – Draw this view. ctx is a full copy of the BearLibTerminal API moved into this view’s frame of reference, so you can use (0, 0) as the upper left corner.
This method will not be called if
View.is_hidden
isTrue
.
First Responder¶
You might want to read about FirstResponderContainerView
before diving into this section.
-
View.
terminal_read
(val)¶ Parameters: val – Return value of terminal_read()
Returns: bool ( True
if you handled the event)Fires when an input event occurs, and either:
- This view is the first responder
- The first responder is a descendant, and no other descendants have already handled this event
You must return a truthy value if you handled the event so it doesn’t get handled twice.
-
View.
can_become_first_responder
¶ View subclasses should return
True
iff they want to be selectable and handle user input.
-
View.
did_become_first_responder
()¶ Called immediately after view becomes the first responder.
-
View.
descendant_did_become_first_responder
(view)¶ Parameters: view (View) – Called when any descendant of this view becomes the first responder. This is so scrollable view containers can scroll it into view.
-
View.
can_resign_first_responder
¶ View subclass can return
True
to prevent thetab
key from taking focus away. It should be rare to need this.
-
View.
did_resign_first_responder
()¶ Called immediately after view resigns first responder status.
-
View.
descendant_did_resign_first_responder
(view)¶ Parameters: view (View) – Called when any descendant of this view unbecomes the first responder. This is so scrollable view containers can release keyboard event handlers.
-
View.
first_responder_container_view
¶ The ancestor (including
self
) that is aFirstResponderContainerView
.The most common use for this will probably be to manually change the first responder:
def a_method_on_your_view(self): # forceably become first responder, muahaha! self.first_responder_container_view.set_first_responder(self)
Tree traversal¶
-
View.
leftmost_leaf
¶ Leftmost leaf of the tree.
-
View.
postorder_traversal
¶ Generator of all nodes in this subtree, including
self
, such that a view is visited after all its subviews.
-
View.
ancestors
¶ Generator of all ancestors of this view, not including
self
.
-
View.
get_ancestor_matching
(predicate)¶ Parameters: predicate (func) – predicate(View) -> bool
Returns the ancestor matching the given predicate, or
None
.
First Responder¶
-
class
clubsandwich.ui.
FirstResponderContainerView
(*args, **kwargs)¶ Manages the “first responder” system. The control that receives BearLibTerminal events at a given time is the first responder.
This container view listens for the tab key. When it’s pressed, the subview tree is walked until another candidate is found, or there are no others. That new subview is the new first responder.
You don’t need to create this class yourself.
UIScene
makes it for you.If you want to write a control that handles input, read the source of the
ButtonView
class.-
find_next_responder
()¶ Resign active first responder and switch to the next one.
-
find_prev_responder
()¶ Resign active first responder and switch to the previous one.
-
set_first_responder
(new_value)¶ Resign the active first responder and set a new one.
-
terminal_read_after_first_responder
(val, can_resign)¶ Parameters: - val (int) – Return value of
terminal_read()
- can_resign (bool) –
True
iff there is an active first responder that can resign
If writing a custom first responder container view, override this to customize input behavior. For example, if writing a list view, you might want to use the arrows to change the first responder.
- val (int) – Return value of
-