Aurore
A Switch-like interface for your app, on every screen
Loading...
Searching...
No Matches
Aurore in 15 minutes

From nothing to a screen of your own on the Switch: build the demo, describe a screen in XML, add a view of your own, restyle it with a theme, and ship it as a .nro. The API reference is built from the headers (doxygen docs/Doxyfile, then build_docs/html/index.html).

1. Build the demo (3 minutes)

On Linux (Debian, Ubuntu), the packages first:

sudo apt install build-essential cmake ninja-build git libdbus-1-dev libxkbcommon-dev \
libwayland-dev wayland-protocols xorg-dev

Then:

git clone --recursive https://github.com/JigSawFr/aurore.git
cd aurore
cmake -B build -G Ninja -DAURORE_PLATFORM=DESKTOP -DAURORE_WINDOW=GLFW -DAURORE_LIBROMFS=ON
cmake --build build
./build/aurore_demo

The arrows move the focus, Enter is A, Escape is B, X, Y, L and R are themselves (the other keys), the mouse and a controller work too. -DAURORE_LIBROMFS=ON builds the resources/ folder into the program; without it, the folder is copied next to it. Press F12: that is the inspector, showing the views on screen, the focus and what a frame costs. Press it twice more to close it.

The demo's code is in demo/, its screens in resources/xml/: they are the examples for what follows.

2. Your app

Add Aurore to your project as a submodule, and its resources next to yours:

git submodule add -b main https://github.com/JigSawFr/aurore.git library/aurore
git submodule update --init --recursive
cp -r library/aurore/resources resources

Aurore's template/ folder (see its README.md) is such an app, with the CMakeLists.txt for the desktop and the Switch (platforms.md has the other platforms'). The program itself starts the app, pushes a first screen and runs the loop:

#include <aurore.hpp>
#include "hello_activity.hpp"
int main(int argc, char* argv[])
{
return EXIT_FAILURE;
aurore::Application::pushActivity(new HelloActivity());
;
return EXIT_SUCCESS;
}
static bool mainLoop()
static void pushActivity(Activity *view, TransitionAnimation animation=TransitionAnimation::FADE)
static bool init()
static void createWindow(std::string title)

3. A screen from XML (3 minutes)

A screen is an activity. Its content can come from an XML file in resources/xml/:

// hello_activity.hpp
#pragma once
#include <aurore.hpp>
class HelloActivity : public aurore::Activity
{
public:
CONTENT_FROM_XML_RES("activity/hello.xml");
void onContentAvailable() override
{
this->button->registerClickAction([](aurore::View*) {
return true;
});
}
private:
AURORE_BIND(aurore::Button, button, "hello_button");
};
#define CONTENT_FROM_XML_RES(x)
Definition activity.hpp:29
#define AURORE_BIND(type, name, id)
Definition bind.hpp:39
Definition activity.hpp:45
virtual void onContentAvailable()
Definition activity.hpp:91
static void notify(const std::string &text, View *leading=nullptr)
Definition button.hpp:146
Definition view.hpp:246
<!-- resources/xml/activity/hello.xml -->
<aurore:AppletFrame>
<aurore:Box
title="Hello"
width="auto"
height="auto"
axis="column"
alignItems="center"
justifyContent="center"
gap="20">
<aurore:Label
text="Aurore lays this out with flexbox"
fontSize="24" />
<aurore:Button
id="hello_button"
style="primary"
width="300"
text="Say hello" />
</aurore:Box>
</aurore:AppletFrame>

Every XML tag is a view (aurore:Box, aurore:Label, aurore:Button, aurore:Image, aurore:ScrollingFrame, aurore:TabFrame…); its attributes are the yoga layout (width, height, grow, margin, padding, gap, axis, alignItems…) and the view's own (text, fontSize, backgroundColor, cornerRadius…). @i18n/… reads a translation, @theme/… a theme colour, @style/… a metric, @res/… a resource. AURORE_BIND finds a view by its id the first time it is used. A second screen is pushed with aurore::Application::pushActivity(new OtherActivity()), and B takes it off.

While you work on the XML, start the app with AURORE_HOT_RELOAD=$PWD/resources ./build/my_app: each save builds the screen again, the focus where it was (hot reload).

4. A view of your own (3 minutes)

A view of your own is a Box that fills itself from its own XML, and is registered under a tag name:

class ScoreCard : public aurore::Box
{
public:
ScoreCard()
{
this->inflateFromXMLRes("xml/views/score_card.xml");
this->forwardXMLAttribute("player", this->name, "text");
}
void setScore(int score) { this->score->setText(std::to_string(score)); }
static aurore::View* create() { return new ScoreCard(); }
private:
AURORE_BIND(aurore::Label, name, "name");
AURORE_BIND(aurore::Label, score, "score");
};
Generic FlexBox layout.
Definition box.hpp:74
static View * create()
void inflateFromXMLRes(const std::string &res)
void forwardXMLAttribute(std::string attributeName, View *target)
Definition label.hpp:65
<!-- resources/xml/views/score_card.xml -->
<aurore:Box
width="260"
height="auto"
axis="column"
padding="16"
cornerRadius="12"
backgroundColor="@theme/aurore/highlight/background"
focusable="true">
<aurore:Label id="name" fontSize="18" />
<aurore:Label id="score" fontSize="40" text="0" />
</aurore:Box>

Register it before the first screen that uses it, aurore::Application::registerXMLView("ScoreCard", ScoreCard::create);, and it can be written in any XML: <ScoreCard id="p1" player="Ada" />. Override draw() for a view that draws itself with nanovg (demo/src/view/ has both kinds), and onFocusGained(), registerAction() or gesture recognizers to react.

Slow work (a file, the network) runs on the task thread with aurore::async, and its result comes back to the UI thread with aurore::sync; views are only touched there. The view may be gone by then: capture its aliveToken() and check it first.

aurore::async([this, alive = aliveToken()] {
std::string text = loadScore(); // the task thread: not `this`
aurore::sync([this, alive, text] {
if (alive.expired())
return; // the view was deleted meanwhile
this->score->setText(text);
});
});
void async(std::function< void()> func)
void sync(std::function< void()> func)
Definition text.hpp:13

ASYNC_RETAIN, ASYNC_TOKEN and ASYNC_RELEASE (from borealis) still work and do the same; they are kept for older code.

5. A theme (2 minutes)

Colours and metrics come from the theme (light or dark, following the system) and can be changed without rebuilding, from a file:

# my_theme.ini
inherit = dark
[colors]
aurore/accent = #ff7a59
aurore/background = #14161c
aurore/highlight/color1 = rgb(255, 122, 89)
[metrics]
aurore/sidebar/width = 320
if (aurore::Theme::loadFile("my_theme.ini", &variant))
static Platform * getPlatform()
The platform the app runs on (window, inputs, video, audio, fonts), made by init().
virtual void setThemeVariant(ThemeVariant theme)=0
static bool loadFile(const std::string &path, ThemeVariant *variant=nullptr)
ThemeVariant
The two sets of colours every theme has: light and dark.
Definition theme.hpp:37

An app adds its own values with aurore::Theme::getDarkTheme().addColor("my_app/score", nvgRGB(…)) and aurore::getStyle().addMetric(…), and its XML reads them with @theme/my_app/score. With hot reload on, a save of the theme file restyles the screen at once. Aurore draws everything in Cadran's look by default; aurore::Application::setCadran(false) before init() keeps borealis' own.

6. Ship it on the Switch (4 minutes)

Install devkitPro with the switch-dev group and the portlibs Aurore uses (switch-glfw, switch-mesa, switch-glm; or nothing more with deko3d), then:

export DEVKITPRO=/opt/devkitpro
cmake -B build_switch -G Ninja -DAURORE_PLATFORM=SWITCH -DAURORE_GRAPHICS=DEKO3D -DCMAKE_BUILD_TYPE=Release
cmake --build build_switch --target aurore_demo.nro

The .nro holds the program, its icon and its resources (built in with romfs). Copy it to /switch/ on the SD card and start it from the Homebrew Menu, or send it to a console running the menu with nxlink -s build_switch/aurore_demo.nro (the app's logs come back to the terminal). Without -DAURORE_GRAPHICS=DEKO3D it builds the OpenGL driver instead. The inspector works there too, with L3 + R3.

The same project builds for Windows, macOS, PS4, PS Vita, Android, iOS and a browser: platforms.md has their toolchains and commands, porting.md what an app should mind on each.

Further