![]() |
Chesto 0.9
A declarative and element-based library for creating GUIs on homebrew'd consoles
|
Chesto is a declarative and element-based library for creating user interfaces using SDL2. It borrows some design, syntax, and lifecycle philosophies from React, libgui, and SwiftUI.
Powering the UI for hb-appstore and vgedit, it supports touch screen and gamepad controls, and currently targets Wii U, Switch, and PC.
It is named after the Chesto Berry, as it seems to prevent sleep while working on it!
Eating it makes you sleepless. It prevents the sleep status condition and other sleep-related status conditions.
All UI objects in Chesto extend the base Element class. This class provides some lifecycle functions, as well as providing the ability to process input, and add relative children elements.
The RootDisplay class (or a subclass of it) itself extends the base Element. After calling the constructor, you should build and add the desired subclass'd Elements of yours for the first view.
The order of operations is as follows:
bool process(InputEvents* event) method is invoked, giving that child an opportunity to respond to any new input.process invoked on them as well.void render (Element* parent) method is invoked, only if at least one Element returned true during process.render is called on these children's childrenDue to this method of processing, and then rendering only if at least one Element in the hierarchy responded to InputEvents, the app should use very little CPU if nothing on the screen is being actively changed/animated.
On top of being able to subclass Element to create groups of other custom Elements laid out how you want them, Chesto also includes some stock elements that have convenient behavior, to be detailed below.
TODO: Add image examples of each code sample!
The base Element class provides super process and render methods that go through the children Elements and take care of propogating their invocations to the children's children. If touchable is set on the Element, a few touch events (onTouchDown, onTouchDrag, and onTouchUp) will automatically be handled.
If a touch event is successfully received, the Element will be highlighted and the bound action will be invoked, which must be set ahead of time by the subclassing Element. For an example of how this looks, see the Button section.
removeAll() can also be called on an Element to completely remove its children, for instance to replace lists of elements, or clean up old elements between page changes.
The ImageElement class can be used to display images either from disk or the network. For example, a romfs image can be instantiated like this:
The NetworkImageElement class can be used to display images downloaded from the internet. It downloads the image in the background, and will automatically update the displayed image once the download is complete. A fallback can also be provided:
The TextElement class is used to display sentences or paragraphs of text, with or without wrappinig. Below instantiates gray text at (40, 20) relative to the current Element:
The Button class automatically subclasses Element and bundles together a TextElement, as well as some touch/gamepad input handling. Like other Elements, the action callback can be set to a member function of another class or function (see here for more info), which will be invoked either when the InputEvent gamepad is triggered or the button is touched.
To create a button, give it the text, the button which corresponds to it, whether it's light or dark themed, and a font size. It can optionally also take a width as the last element, otherwise it will automatically fit the width to the inner text.
The DropDown class can be used to create a drop-down selection menu. It contains a list of string options, and an onChange callback that is invoked when the user selects a new option.
Elements can be constrained relative to their parent or other Elements using the constrain and constrainToTarget methods. This allows for dynamic positioning based on screen size or other elements.
For more on the different Constraint options, see src/Constraint.hpp. Constraints are applied during the internal recalcPosition method during/before render in the lifecycle.
Elements can also have animations applied to them. For example, to slide in from the right, the following code can be used, as an example:
This leverages both the constraint system and the animation system, which are automatically called every frame. The code is in src/Animation.cpp.
There are a few layout-based containers, for drawing rows or columns of elements. These are similar to VStack or HStack in SwiftUI. See src/Container.hpp for more info.
An example of a horizontal row, which uses add to put more children elements inside, with a 40px spacing between them:
The ListElement class should be subclassed and used to contain other groups that automatically need to be presented in a format so that they can trail off the page and be scrolled through either via touch events or gamepad buttons.
You can provide your own cursor logic here as well by overriding process in the LisitElement subclass, if you want the gamepad controls to do more than just scroll the page. It should play nicely with sub-elements that are marked as touchable.
The Screen class can be subclassed to push and pop different full screens of Element's onto the display. This can be used to manage overlays and layers, and HB AppStore uses it to show different pages, pop ups, or modals. DropDown uses this to display its elements.
Screens are the same as Elements (They even subclass Element), but they initialize themselves to be fullscreen, and have another lifecycle method: rebuildUI. This method is called when the Screen is first pushed, and can be used to wipe and reconstruct the children Elements according to the Screen's state / field variables.
A basic internationalization system is included in TextElement.cpp. See HB AppStore for more examples on how it can be used. Once loaded, i18n("my.key.name") can be used to retrieve the localized string for the current language.
A cross-platform onscreen keyboard is in EKeyboard.cpp. This was originally the vgedit keyboard! It's not currently implemented as a Screen, although it is an overlay. This means that you can push it on top of any existing Element/Screen, but still interact with the views below it.
For a better example of how to use it, see VGEdit. The keyboardInputCallback is invoked with the single key that was selected. If the storeOwnText field is true, then the keyboard will maintain its own internal text string, which can be accessed with: .textInput.
The ProgressBar element takes a percent float between 0.0 and 1.0, as well as an overall width for how long the progress bar should be at 100%.
Can be created as follows. If dimBg is set, then the entire screen under this progress bar will be covered with a transparent gray sheet.
Chesto maintains a download queue via the DownloadQueue class, which can be used to download files from the internet in the background. It supports multiple simultaneous downloads, and will retry failed downloads up to a specified number of times.
NOTE: To use any networking features, the app must be built with these Makefile flags:
And resin/res/cacert.pem must be included in the romfs folder, as it is used to verify SSL certificates, even on platforms with no certificate store.
A bare minimum Chesto makefile looks like this:
As much as possible is tried to be handled by the Makefiles in the helpers folder! When you run make, it will also prompt you to specify a platform, as seen below:
To see other ways these makefiles can be augmented, see the following other Chesto projects:
If you want to follow the Makefile logic further, see the main Makefile (which is included in the above snippet, on the last line), and see how it calls out to the other Makefiles depending on what platform is specified.
Building a Chesto app will depend on what platform is being targeted. In general "just" SDL2 and libcurl are required, but each platform may have its own quirks.
A container is available at fortheusers/sealeo which has all the dependencies pre-installed for building Chesto apps for all supported platforms. It can be ran via Docker like:
To see how to setup dependencies outside of Docker, check dependency_helper.sh.
This software is licensed under the GPLv3.
For an example of what an app that integrates Chesto looks like, see ChestoTesto by CompuCat.
Chesto makes use of resinfs to display images and other assets from memory rather than files. Any files in the top-level resin folder will be bundled using this dependency.
To reference any resinfs paths in the app, prefix the string with RAMFS. This will automatically be set to the correct value based on the platform, to either resin:/ or ./resin (local folder, no bundling).
This is included as a submodule, so Chesto should be cloned with --recursive to use this functionality.