GUI Concepts
GUI elements are represented as instances of UIWidget
.
The GUI is structured like a tree; every widget can have other widgets as
children.
The root of the tree is the UIManager
. The
UIManager
connects the user interactions with the GUI. Read more about
User-interface events.
Classes of Arcade’s GUI code are prefixed with UI-
to make them easy to
identify and search for in autocompletion.
Classes
Following classes provide the basic structure of the GUI.
UIManager
UIManager
is the starting point for the GUI.
To use the GUI, you need to create a UIManager
instance and
call its add()
method to add widgets to the GUI.
Each View
should have its own UIManager
.
(If you don’t use views, you can use a single UIManager
for the window.)
The UIManager
does not react to any user input initially.
You have to call enable()
within on_show_view()
.
And disable it with disable()
within on_hide_view()
.
To draw the GUI, call draw()
within the on_draw()
method.
The :py:class`~arcade.gui.UIView` class is a subclass of View
and provides
a convenient way to use the GUI. It instanciates a UIManager
which can be accessed
via the ui
attribute.
It automatically enables and disables the
UIManager
when the view is shown or hidden.
UIWidget
The UIWidget
class is the core of Arcade’s GUI system.
Widgets specify the behavior and graphical representation of any UI element,
such as buttons or labels.
User interaction with widgets is processed within on_event()
.
A UIWidget
has following properties.
rect
A tuple with four slots. The first two are x and y coordinates (bottom left of the widget), and the last two are width and height.
children
Child widgets rendered within this widget. A
UIWidget
will not move or resize its children; use aUILayout
instead.visible
A boolean indicating if the widget is visible or not. If a widget is not visible, itself and any child widget will not be rendered. Especially useful for hiding parts of the GUI like dialogs or popups.
size_hint
A tuple of two normalized floats (
0.0
-1.0
) describing the portion of the parent’s width and height this widget prefers to occupy.Examples:
# Prefer to take up all space within the parent widget.size_hint = (1.0, 1.0) # Prefer to take up the full width & half the height of the parent widget.size_hint = (1.0, 0.5) # Prefer using 1/10th of the available width & height widget.size_hint = (0.1, 0.1)
size_hint_min
A tuple of two integers defining the minimum width and height of the widget. These values should be taken into account by
UILayout
when asize_hint
is given for the axis.size_hint_max
A tuple of two integers defining the maximum width and height of the widget. These values should be taken into account by
UILayout
when asize_hint
is given for the axis.
Warning
Size hints do nothing on their own!
They are hints to UILayout
instances, which may choose to use or
ignore them.
UILayout
UILayout
are widgets, which reserve the right to move
or resize children. They might respect special properties of a widget like
size_hint
, size_hint_min
, or size_hint_max
.
The arcade.gui.UILayout
must only resizes a child’s dimension (x or y
axis) if size_hint
provides a value for the axis, which is not None
for
the dimension.
Drawing
The GUI is optimised to be as performant as possible. This means that the GUI splits up the positioning and rendering of each widget and drawing of the result on screen.
Widgets are positioned and then rendered into a framebuffer (something like a window sized image),
which is only updated if a widget changed and requested rendering
(via trigger_render()
or trigger_full_render()
).
The UIManager
draw method, will check if updates are required and
finally draws the framebuffer on screen.
Layouting and Rendering
UIManager
triggers layouting and rendering of the GUI before the actual frame draw (if necessary).
This way, the GUI can adjust to multiple changes only once.
Layouting is a two-step process: 1. Prepare layout, which prepares children and updates own values 2. Do layout, which actually sets the position and size of the children
Rendering is not executed during each draw call. Changes to following widget properties will trigger rendering:
rect
children
background
border_width, border_color
padding
widget-specific properties (like text, texture, …)
do_render()
is called recursively if rendering
was requested via trigger_render()
. In case
widgets have to request their parents to render, use
arcade.gui.UIWidget.trigger_full_render()
.
The widget has to draw itself and child widgets within
do_render()
. Due to the deferred functionality
render does not have to check any dirty variables, as long as state changes use
the trigger_full_render()
method.
For widgets, that might have transparent areas, they have to request a full rendering.
Warning
Enforced rendering of the whole GUI might be very expensive!
Layout Algorithm by example
arcade.gui.UIManager
triggers the layout and render process right
before the actual frame draw. This opens the possibility to adjust to multiple
changes only once.
Example: Executed steps within UIBoxLayout
:
prepare_layout()
updates own size_hintsdo_layout()
Collect current
size
,size_hint
,size_hint_min
of childrenCalculate the new position and sizes
Set position and size of children
Recursively call
do_layout
on child layouts (last step indo_layout()
)
┌─────────┐ ┌────────┐ ┌────────┐
│UIManager│ │UILayout│ │children│
└────┬────┘ └───┬────┘ └───┬────┘
│ prepare_layout() ┌┴┐ │
│─────────────────>│ │ │
│ │ │ │
│ ╔═══════╤════╪═╪══════════════════════════════╪══════════════╗
│ ║ LOOP │ sub layouts │ ║
│ ╟───────┘ │ │ │ ║
│ ║ │ │ prepare_layout() │ ║
│ ║ │ │ ─────────────────────────────> ║
│ ╚════════════╪═╪══════════════════════════════╪══════════════╝
│ │ │ │
│<─ ─ ─ ─ ─ ─ ─ ─ ─│ │ │
│ │ │ │
│ do_layout() │ │ │
│─────────────────>│ │ │
│ ╔════════════╪═╪════╤═════════════════════════╪══════════════╗
│ ║ place children │ │ ║
│ ╟───────────────────┘ │ ║
│ ║ │ │ use size, size_hint, ... │ ║
│ ║ │ │ <───────────────────────────── ║
│ ║ │ │ │ ║
│ ║ │ │ set size and pos │ ║
│ ║ │ │ ─────────────────────────────> ║
│ ╚════════════╪═╪══════════════════════════════╪══════════════╝
│ │ │ │
│ │ │ │
│ ╔═══════╤════╪═╪══════════════════════════════╪══════════════╗
│ ║ LOOP │ sub layouts │ ║
│ ╟───────┘ │ │ │ ║
│ ║ │ │ do_layout() │ ║
│ ║ │ │ ─────────────────────────────> ║
│ ╚════════════╪═╪══════════════════════════════╪══════════════╝
│ └┬┘ │
│ │ │
│<─ ─ ─ ─ ─ ─ ─ ─ ─ │ │
┌────┴────┐ ┌───┴────┐ ┌───┴────┐
│UIManager│ │UILayout│ │children│
└─────────┘ └────────┘ └────────┘
Size hint support
size_hint |
size_hint_min |
size_hint_max |
|
---|---|---|---|
|
X |
X |
X |
|
X |
X |
X |
|
X |
X |
X |
|
X |
X |
X |
UIMixin
Mixin classes are a base class which can be used to apply some specific behaviour. Currently the available Mixins are still under heavy development.
Available:
UIDraggableMixin
- Makes a widget draggable with the mouse.UIMouseFilterMixin
- Captures all mouse events.UIWindowLikeMixin
- Makes a widget behave like a window, combining draggable and mouse filter behaviour.
UIConstructs
Constructs are predefined structures of widgets and layouts like a message box.
Available:
UIMessageBox
- A simple message box with a title, message and buttons.UIButtonRow
- A row of buttons.
Available Elements
Text widgets
All text widgets take x
and y
positioning parameters. They also accept
text
and multiline
options.
Label
Name: UILabel
A label is used to display text as instruction for the user. Multiline text is supported, and what would have been its style options were moved into the parameters.
This widget has no style options whatsoever, and they have been moved into the
parameters. bold
and italic
will set the text to bold or italic.
align
specifies the justification of the text. Additionally it takes
font_name
, font_size
, and text_color
options.
Using the label
property accesses the internal
Text
class.
Hint
A text
attribute can modify the displayed
text. Beware-calling this again and again will give a lot of lag. Use
begin_update()
and py:meth:~arcade.Text.end_update
to speed things up.
Text input field
Name: UIInputText
A text field allows a user to input a basic string. It uses pyglet’s
IncrementalTextLayout
and its
Caret
. These are stored in layout
and
caret
properties.
This widget takes width
and height
properties and uses a rectangle to
display a background behind the layout.
A text input field allows the user to move a caret around text to modify it, as
well as selecting parts of text to replace or delete it. Motion symbols for a
text field are listed in pyglet.window.key
module.
Text area
Name: UITextArea
A text area is a scrollable text widget. A user can scroll the mouse to view a rendered text document. This does not support editing text. Think of it as a scrollable label instead of a text field.
width
and height
allocate a size for the text area. If text does not
fit within these dimensions then only part of it will be displayed. Scrolling
the mouse will display other sections of the text incrementally. Other
parameters include multiline
and scroll_speed
. See
view_y
on scroll speed.
Use layout
and doc
to get the pyglet layout and document for the
text area, respectively.
User-interface events
Arcade’s GUI events are fully typed dataclasses, which provide information about an event affecting the UI.
All pyglet window events are converted by the
UIManager
into UIEvents
and passed via
dispatch_event()
to the
on_event()
callbacks.
Widget-specific events (such as UIOnClickEvent
are
dispatched via on_event
and are then dispatched as specific event types
(like on_click
).
A full list of event attributes is shown below.
Event |
Attributes |
---|---|
|
None |
|
|
|
|
|
|
|
|
|
|
|
|
|
None |
|
|
|
|
|
|
|
None |
|
|
|
|
|
|
arcade.gui.UIEvent
. Base class for all events.arcade.gui.UIMouseEvent
. Base class for mouse-related events.arcade.gui.UIMouseMovementEvent
. Mouse motion. This event has an additionalpos
property that returns a tuple of the x and y coordinates.UIMousePressEvent
. Mouse button pressed.UIMouseDragEvent
. Mouse pressed and moved (drag).UIMouseReleaseEvent
. Mouse button release.UIMouseScrollEvent
. Mouse scroll.
UITextEvent
. Text input from user. This is only used for text fields and is the text as a string that was inputed.UITextMotionEvent
. Text motion events. This includes moving the text around with the caret. Examples include using the arrow keys, backspace, delete, or any of the home/end and PgUp/PgDn keys. HoldingControl
with an arrow key shifts the caret by a entire word or paragraph. Moving the caret via the mouse does not trigger this event.UITextMotionSelectEvent
. Text motion events for selection. Holding down theShift
key and pressing arrow keys (Control
optional) will select character(s). Additionally, using aControl-A
keyboard combination will select all text. Selecting text via the mouse does not trigger this event.UIOnUpdateEvent
. This is a callback to the arcadeon_update
method.
Widget-specific events
Widget events are only dispatched as a pyglet event on a widget itself and are not passed through the widget tree.
UIOnClickEvent
. Click event ofUIInteractiveWidget
class. This is triggered on widget press.UIOnChangeEvent
. A value of aUIWidget
has changed.UIOnActionEvent
. An action results from interaction with theUIWidget
(mostly used in constructs)
Different event systems
Arcade’s GUI uses different event systems, dependent on the required flow. A
game developer should mostly interact with user-interface events, which are
dispatched from specific UIWidget`s like an ``on_click`
of a button.
In cases where a developer implement own widgets themselves or want to
modify the existing GUI behavior, the developer might register own
pyglet event types on widgets or overwrite the
on_event
method. In that case, refer to
existing widgets as an example.
Pyglet window events
Pyglet window events are received by UIManager
.
You can dispatch them via:
UIWidget.dispatch_event("on_event", UIEvent(...))
Window events are wrapped into subclasses of UIEvent
.
Pyglet event dispatcher - UIWidget
Widgets implement pyglet’s EventDispatcher
and
register an on_event
event type.
on_event()
contains specific event handling and
should not be overwritten without deeper understanding of the consequences.
To add custom event handling, use the decorator syntax to add another listener:
@UIWidget.event("on_event")
User-interface events
User-interface events are typed representations of events that are passed within the GUI. Widgets might define and dispatch their own subclasses of these events.
Property
Property
is an pure-Python implementation of Kivy-like Properties.
They are used to detect attribute changes of widgets and especially to trigger
rendering. They are mostly used within GUI widgets, but are globally available since 3.0.0.
Properties are a less verbose way to implement the observer pattern compared to the property decorator.