summaryrefslogtreecommitdiff
path: root/src/tangara/ui/ui_fsm.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/tangara/ui/ui_fsm.cpp')
-rw-r--r--src/tangara/ui/ui_fsm.cpp693
1 files changed, 693 insertions, 0 deletions
diff --git a/src/tangara/ui/ui_fsm.cpp b/src/tangara/ui/ui_fsm.cpp
new file mode 100644
index 00000000..1cbf1be4
--- /dev/null
+++ b/src/tangara/ui/ui_fsm.cpp
@@ -0,0 +1,693 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "ui_fsm.hpp"
+
+#include <memory>
+#include <memory_resource>
+#include <variant>
+
+#include "bluetooth_types.hpp"
+#include "db_events.hpp"
+#include "device_factory.hpp"
+#include "display_init.hpp"
+#include "esp_spp_api.h"
+#include "feedback_haptics.hpp"
+#include "freertos/portmacro.h"
+#include "freertos/projdefs.h"
+#include "input_device.hpp"
+#include "input_touch_wheel.hpp"
+#include "input_volume_buttons.hpp"
+#include "lua.h"
+#include "lua.hpp"
+
+#include "audio_fsm.hpp"
+#include "battery.hpp"
+#include "core/lv_group.h"
+#include "core/lv_obj.h"
+#include "core/lv_obj_tree.h"
+#include "database.hpp"
+#include "esp_heap_caps.h"
+#include "esp_timer.h"
+#include "haptics.hpp"
+#include "lauxlib.h"
+#include "lua_thread.hpp"
+#include "luavgl.h"
+#include "lvgl_input_driver.hpp"
+#include "memory_resource.hpp"
+#include "misc/lv_gc.h"
+
+#include "audio_events.hpp"
+#include "display.hpp"
+#include "event_queue.hpp"
+#include "gpios.hpp"
+#include "lua_registry.hpp"
+#include "lvgl_task.hpp"
+#include "nvs.hpp"
+#include "property.hpp"
+#include "samd.hpp"
+#include "screen.hpp"
+#include "screen_lua.hpp"
+#include "screen_splash.hpp"
+#include "spiffs.hpp"
+#include "storage.hpp"
+#include "system_events.hpp"
+#include "tinyfsm.hpp"
+#include "touchwheel.hpp"
+#include "track_queue.hpp"
+#include "ui_events.hpp"
+#include "widgets/lv_label.h"
+
+namespace ui {
+
+[[maybe_unused]] static constexpr char kTag[] = "ui_fsm";
+
+std::unique_ptr<UiTask> UiState::sTask;
+std::shared_ptr<system_fsm::ServiceLocator> UiState::sServices;
+std::unique_ptr<drivers::Display> UiState::sDisplay;
+
+std::shared_ptr<input::LvglInputDriver> UiState::sInput;
+std::unique_ptr<input::DeviceFactory> UiState::sDeviceFactory;
+
+std::stack<std::shared_ptr<Screen>> UiState::sScreens;
+std::shared_ptr<Screen> UiState::sCurrentScreen;
+std::shared_ptr<Modal> UiState::sCurrentModal;
+std::shared_ptr<lua::LuaThread> UiState::sLua;
+
+static TimerHandle_t sAlertTimer;
+static lv_obj_t* sAlertContainer;
+
+static void alert_timer_callback(TimerHandle_t timer) {
+ events::Ui().Dispatch(internal::DismissAlerts{});
+}
+
+lua::Property UiState::sBatteryPct{0};
+lua::Property UiState::sBatteryMv{0};
+lua::Property UiState::sBatteryCharging{false};
+
+lua::Property UiState::sBluetoothEnabled{
+ false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ if (std::get<bool>(val)) {
+ sServices->nvs().OutputMode(drivers::NvsStorage::Output::kBluetooth);
+ sServices->bluetooth().Enable();
+ } else {
+ sServices->nvs().OutputMode(drivers::NvsStorage::Output::kHeadphones);
+ sServices->bluetooth().Disable();
+ }
+ events::Audio().Dispatch(audio::OutputModeChanged{});
+ return true;
+ }};
+
+lua::Property UiState::sBluetoothConnected{false};
+lua::Property UiState::sBluetoothPairedDevice{
+ std::monostate{}, [](const lua::LuaValue& val) {
+ if (std::holds_alternative<drivers::bluetooth::Device>(val)) {
+ auto dev = std::get<drivers::bluetooth::Device>(val);
+ sServices->bluetooth().SetPreferredDevice(
+ drivers::bluetooth::MacAndName{
+ .mac = dev.address,
+ .name = {dev.name.data(), dev.name.size()},
+ });
+ }
+ return false;
+ }};
+lua::Property UiState::sBluetoothDevices{
+ std::vector<drivers::bluetooth::Device>{}};
+
+lua::Property UiState::sPlaybackPlaying{
+ false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool new_val = std::get<bool>(val);
+ events::Audio().Dispatch(audio::TogglePlayPause{.set_to = new_val});
+ return true;
+ }};
+
+lua::Property UiState::sPlaybackTrack{};
+lua::Property UiState::sPlaybackPosition{
+ 0, [](const lua::LuaValue& val) {
+ int current_val = std::get<int>(sPlaybackPosition.get());
+ if (!std::holds_alternative<int>(val)) {
+ return false;
+ }
+ int new_val = std::get<int>(val);
+ if (current_val != new_val) {
+ auto track = sPlaybackTrack.get();
+ if (!std::holds_alternative<audio::TrackInfo>(track)) {
+ return false;
+ }
+ events::Audio().Dispatch(audio::SetTrack{
+ .new_track = std::get<audio::TrackInfo>(track).uri,
+ .seek_to_second = (uint32_t)new_val,
+ });
+ }
+ return true;
+ }};
+
+lua::Property UiState::sQueuePosition{0};
+lua::Property UiState::sQueueSize{0};
+lua::Property UiState::sQueueRepeat{false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool new_val = std::get<bool>(val);
+ sServices->track_queue().repeat(new_val);
+ return true;
+ }};
+lua::Property UiState::sQueueReplay{false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool new_val = std::get<bool>(val);
+ sServices->track_queue().replay(new_val);
+ return true;
+ }};
+lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool new_val = std::get<bool>(val);
+ sServices->track_queue().random(new_val);
+ return true;
+ }};
+
+lua::Property UiState::sVolumeCurrentPct{
+ 0, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<int>(val)) {
+ return false;
+ }
+ events::Audio().Dispatch(audio::SetVolume{
+ .percent = std::get<int>(val),
+ .db = {},
+ });
+ return true;
+ }};
+lua::Property UiState::sVolumeCurrentDb{
+ 0, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<int>(val)) {
+ return false;
+ }
+ events::Audio().Dispatch(audio::SetVolume{
+ .percent = {},
+ .db = std::get<int>(val),
+ });
+ return true;
+ }};
+lua::Property UiState::sVolumeLeftBias{
+ 0, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<int>(val)) {
+ return false;
+ }
+ events::Audio().Dispatch(audio::SetVolumeBalance{
+ .left_bias = std::get<int>(val),
+ });
+ return true;
+ }};
+lua::Property UiState::sVolumeLimit{
+ 0, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<int>(val)) {
+ return false;
+ }
+ int limit = std::get<int>(val);
+ events::Audio().Dispatch(audio::SetVolumeLimit{
+ .limit_db = limit,
+ });
+ return true;
+ }};
+
+lua::Property UiState::sDisplayBrightness{
+ 0, [](const lua::LuaValue& val) {
+ std::optional<int> brightness = 0;
+ std::visit(
+ [&](auto&& v) {
+ using T = std::decay_t<decltype(v)>;
+ if constexpr (std::is_same_v<T, int>) {
+ brightness = v;
+ }
+ },
+ val);
+ if (!brightness) {
+ return false;
+ }
+ sDisplay->SetBrightness(*brightness);
+ sServices->nvs().ScreenBrightness(*brightness);
+ return true;
+ }};
+
+lua::Property UiState::sLockSwitch{false};
+
+lua::Property UiState::sDatabaseUpdating{false};
+lua::Property UiState::sDatabaseAutoUpdate{
+ false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ sServices->nvs().DbAutoIndex(std::get<bool>(val));
+ return true;
+ }};
+
+lua::Property UiState::sUsbMassStorageEnabled{
+ false, [](const lua::LuaValue& val) {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool enable = std::get<bool>(val);
+ // FIXME: Check for system busy.
+ events::System().Dispatch(system_fsm::SamdUsbMscChanged{.en = enable});
+ return true;
+ }};
+
+lua::Property UiState::sUsbMassStorageBusy{false};
+
+auto UiState::InitBootSplash(drivers::IGpios& gpios, drivers::NvsStorage& nvs)
+ -> bool {
+ events::Ui().Dispatch(internal::InitDisplay{
+ .gpios = gpios,
+ .nvs = nvs,
+ });
+ sTask.reset(UiTask::Start());
+ return true;
+}
+
+void UiState::react(const internal::InitDisplay& ev) {
+ // Init LVGL first, since the display driver registers itself with LVGL.
+ lv_init();
+
+ drivers::displays::InitialisationData init_data = drivers::displays::kST7735R;
+
+ // HACK: correct the display size for our prototypes.
+ // nvs.DisplaySize({161, 130});
+
+ auto actual_size = ev.nvs.DisplaySize();
+ init_data.width = actual_size.first.value_or(init_data.width);
+ init_data.height = actual_size.second.value_or(init_data.height);
+ sDisplay.reset(drivers::Display::Create(ev.gpios, init_data));
+
+ sCurrentScreen.reset(new screens::Splash());
+
+ // Display will only actually come on after LVGL finishes its first flush.
+ sDisplay->SetDisplayOn(!ev.gpios.IsLocked());
+}
+
+void UiState::PushScreen(std::shared_ptr<Screen> screen) {
+ lv_obj_set_parent(sAlertContainer, screen->alert());
+
+ if (sCurrentScreen) {
+ sCurrentScreen->onHidden();
+ sScreens.push(sCurrentScreen);
+ }
+ sCurrentScreen = screen;
+ sCurrentScreen->onShown();
+}
+
+int UiState::PopScreen() {
+ if (sScreens.empty()) {
+ return 0;
+ }
+ lv_obj_set_parent(sAlertContainer, sScreens.top()->alert());
+
+ sCurrentScreen->onHidden();
+
+ sCurrentScreen = sScreens.top();
+ sScreens.pop();
+
+ sCurrentScreen->onShown();
+
+ return sScreens.size();
+}
+
+void UiState::react(const system_fsm::KeyLockChanged& ev) {
+ sDisplay->SetDisplayOn(!ev.locking);
+ sInput->lock(ev.locking);
+ sLockSwitch.setDirect(ev.locking);
+}
+
+void UiState::react(const system_fsm::SamdUsbStatusChanged& ev) {
+ sUsbMassStorageBusy.setDirect(ev.new_status ==
+ drivers::Samd::UsbStatus::kAttachedBusy);
+}
+
+void UiState::react(const database::event::UpdateStarted&) {
+ sDatabaseUpdating.setDirect(true);
+}
+
+void UiState::react(const database::event::UpdateFinished&) {
+ sDatabaseUpdating.setDirect(false);
+}
+
+void UiState::react(const internal::DismissAlerts&) {
+ lv_obj_clean(sAlertContainer);
+}
+
+void UiState::react(const system_fsm::BatteryStateChanged& ev) {
+ sBatteryPct.setDirect(static_cast<int>(ev.new_state.percent));
+ sBatteryMv.setDirect(static_cast<int>(ev.new_state.millivolts));
+ sBatteryCharging.setDirect(ev.new_state.is_charging);
+}
+
+void UiState::react(const audio::QueueUpdate&) {
+ auto& queue = sServices->track_queue();
+ sQueueSize.setDirect(static_cast<int>(queue.totalSize()));
+
+ int current_pos = queue.currentPosition();
+ if (queue.current()) {
+ current_pos++;
+ }
+ sQueuePosition.setDirect(current_pos);
+ sQueueRandom.setDirect(queue.random());
+ sQueueRepeat.setDirect(queue.repeat());
+ sQueueReplay.setDirect(queue.replay());
+}
+
+void UiState::react(const audio::PlaybackUpdate& ev) {
+ if (ev.current_track) {
+ sPlaybackTrack.setDirect(*ev.current_track);
+ } else {
+ sPlaybackTrack.setDirect(std::monostate{});
+ }
+ sPlaybackPlaying.setDirect(!ev.paused);
+ sPlaybackPosition.setDirect(static_cast<int>(ev.track_position.value_or(0)));
+}
+
+void UiState::react(const audio::VolumeChanged& ev) {
+ sVolumeCurrentPct.setDirect(static_cast<int>(ev.percent));
+ sVolumeCurrentDb.setDirect(static_cast<int>(ev.db));
+}
+
+void UiState::react(const audio::VolumeBalanceChanged& ev) {
+ sVolumeLeftBias.setDirect(ev.left_bias);
+}
+
+void UiState::react(const audio::VolumeLimitChanged& ev) {
+ sVolumeLimit.setDirect(ev.new_limit_db);
+}
+
+void UiState::react(const system_fsm::BluetoothEvent& ev) {
+ auto bt = sServices->bluetooth();
+ auto dev = bt.ConnectedDevice();
+ switch (ev.event) {
+ case drivers::bluetooth::Event::kKnownDevicesChanged:
+ sBluetoothDevices.setDirect(bt.KnownDevices());
+ break;
+ case drivers::bluetooth::Event::kConnectionStateChanged:
+ sBluetoothConnected.setDirect(bt.IsConnected());
+ if (dev) {
+ sBluetoothPairedDevice.setDirect(drivers::bluetooth::Device{
+ .address = dev->mac,
+ .name = {dev->name.data(), dev->name.size()},
+ .class_of_device = 0,
+ .signal_strength = 0,
+ });
+ } else {
+ sBluetoothPairedDevice.setDirect(std::monostate{});
+ }
+ break;
+ case drivers::bluetooth::Event::kPreferredDeviceChanged:
+ break;
+ }
+}
+
+namespace states {
+
+void Splash::exit() {
+ // buzz a bit to tell the user we're done booting
+ events::System().Dispatch(system_fsm::HapticTrigger{
+ .effect = drivers::Haptics::Effect::kLongDoubleSharpTick1_100Pct,
+ });
+}
+
+void Splash::react(const system_fsm::BootComplete& ev) {
+ sServices = ev.services;
+
+ // The system has finished booting! We now need to prepare to show real UI.
+ // This basically just involves reading and applying the user's preferences.
+
+ lv_theme_t* base_theme = lv_theme_basic_init(NULL);
+ lv_disp_set_theme(NULL, base_theme);
+ themes::Theme::instance()->Apply();
+
+ int brightness = sServices->nvs().ScreenBrightness();
+ sDisplayBrightness.setDirect(brightness);
+ sDisplay->SetBrightness(brightness);
+
+ sDeviceFactory = std::make_unique<input::DeviceFactory>(sServices);
+ sInput = std::make_shared<input::LvglInputDriver>(sServices->nvs(),
+ *sDeviceFactory);
+
+ sTask->input(sInput);
+}
+
+void Splash::react(const system_fsm::StorageMounted&) {
+ transit<Lua>();
+}
+
+void Lua::entry() {
+ if (!sLua) {
+ sAlertTimer = xTimerCreate("ui_alerts", pdMS_TO_TICKS(1000), false, NULL,
+ alert_timer_callback);
+ sAlertContainer = lv_obj_create(sCurrentScreen->alert());
+ lv_obj_set_style_bg_opa(sAlertContainer, LV_OPA_TRANSP, 0);
+
+ auto& registry = lua::Registry::instance(*sServices);
+ sLua = registry.uiThread();
+ registry.AddPropertyModule("power", {
+ {"battery_pct", &sBatteryPct},
+ {"battery_millivolts", &sBatteryMv},
+ {"plugged_in", &sBatteryCharging},
+ });
+ registry.AddPropertyModule("bluetooth",
+ {
+ {"enabled", &sBluetoothEnabled},
+ {"connected", &sBluetoothConnected},
+ {"paired_device", &sBluetoothPairedDevice},
+ {"devices", &sBluetoothDevices},
+ });
+ registry.AddPropertyModule("playback", {
+ {"playing", &sPlaybackPlaying},
+ {"track", &sPlaybackTrack},
+ {"position", &sPlaybackPosition},
+ });
+ registry.AddPropertyModule(
+ "queue",
+ {
+ {"next", [&](lua_State* s) { return QueueNext(s); }},
+ {"previous", [&](lua_State* s) { return QueuePrevious(s); }},
+ {"position", &sQueuePosition},
+ {"size", &sQueueSize},
+ {"replay", &sQueueReplay},
+ {"repeat_track", &sQueueRepeat},
+ {"random", &sQueueRandom},
+ });
+ registry.AddPropertyModule("volume",
+ {
+ {"current_pct", &sVolumeCurrentPct},
+ {"current_db", &sVolumeCurrentDb},
+ {"left_bias", &sVolumeLeftBias},
+ {"limit_db", &sVolumeLimit},
+ });
+
+ registry.AddPropertyModule("display",
+ {
+ {"brightness", &sDisplayBrightness},
+ });
+
+ registry.AddPropertyModule(
+ "controls",
+ {
+ {"scheme", &sInput->mode()},
+ {"lock_switch", &sLockSwitch},
+ {"hooks", [&](lua_State* L) { return sInput->pushHooks(L); }},
+ });
+
+ if (sDeviceFactory->touch_wheel()) {
+ registry.AddPropertyModule(
+ "controls", {{"scroll_sensitivity",
+ &sDeviceFactory->touch_wheel()->sensitivity()}});
+ }
+
+ registry.AddPropertyModule(
+ "backstack",
+ {
+ {"push", [&](lua_State* s) { return PushLuaScreen(s); }},
+ {"pop", [&](lua_State* s) { return PopLuaScreen(s); }},
+ });
+ registry.AddPropertyModule(
+ "alerts", {
+ {"show", [&](lua_State* s) { return ShowAlert(s); }},
+ {"hide", [&](lua_State* s) { return HideAlert(s); }},
+ });
+
+ registry.AddPropertyModule(
+ "time", {
+ {"ticks", [&](lua_State* s) { return Ticks(s); }},
+ });
+ registry.AddPropertyModule("database",
+ {
+ {"updating", &sDatabaseUpdating},
+ {"auto_update", &sDatabaseAutoUpdate},
+ });
+ registry.AddPropertyModule("usb",
+ {
+ {"msc_enabled", &sUsbMassStorageEnabled},
+ {"msc_busy", &sUsbMassStorageBusy},
+ });
+
+ sDatabaseAutoUpdate.setDirect(sServices->nvs().DbAutoIndex());
+
+ auto bt = sServices->bluetooth();
+ sBluetoothEnabled.setDirect(bt.IsEnabled());
+ sBluetoothConnected.setDirect(bt.IsConnected());
+ sBluetoothDevices.setDirect(bt.KnownDevices());
+
+ sCurrentScreen.reset();
+ sLua->RunScript("/sdcard/config.lua");
+ sLua->RunScript("/lua/main.lua");
+ }
+}
+
+auto Lua::PushLuaScreen(lua_State* s) -> int {
+ // Ensure the arg looks right before continuing.
+ luaL_checktype(s, 1, LUA_TTABLE);
+
+ // First, create a new plain old Screen object. We will use its root and
+ // group for the Lua screen. Allocate it in external ram so that arbitrarily
+ // deep screen stacks don't cause too much memory pressure.
+ auto new_screen =
+ std::allocate_shared<screens::Lua,
+ std::pmr::polymorphic_allocator<screens::Lua>>(
+ &memory::kSpiRamResource);
+
+ // Tell lvgl about the new roots.
+ luavgl_set_root(s, new_screen->content());
+ lv_group_set_default(new_screen->group());
+
+ // Call the constructor for this screen.
+ // lua_settop(s, 1); // Make sure the screen is actually at top of stack
+ lua_pushliteral(s, "createUi");
+ if (lua_gettable(s, 1) == LUA_TFUNCTION) {
+ lua_pushvalue(s, 1);
+ lua::CallProtected(s, 1, 0);
+ }
+
+ // Store the reference for this screen's table.
+ lua_settop(s, 1);
+ new_screen->SetObjRef(s);
+
+ // Finally, push the now-initialised screen as if it were a regular C++
+ // screen.
+ PushScreen(new_screen);
+
+ return 0;
+}
+
+auto Lua::QueueNext(lua_State*) -> int {
+ sServices->track_queue().next();
+ return 0;
+}
+
+auto Lua::QueuePrevious(lua_State*) -> int {
+ sServices->track_queue().previous();
+ return 0;
+}
+
+auto Lua::PopLuaScreen(lua_State* s) -> int {
+ if (!sCurrentScreen->canPop()) {
+ return 0;
+ }
+ PopScreen();
+ luavgl_set_root(s, sCurrentScreen->content());
+ lv_group_set_default(sCurrentScreen->group());
+ return 0;
+}
+
+auto Lua::Ticks(lua_State* s) -> int {
+ lua_pushinteger(s, esp_timer_get_time() / 1000);
+ return 1;
+}
+
+auto Lua::ShowAlert(lua_State* s) -> int {
+ if (!sCurrentScreen) {
+ return 0;
+ }
+ xTimerReset(sAlertTimer, portMAX_DELAY);
+ tinyfsm::FsmList<UiState>::dispatch(internal::DismissAlerts{});
+
+ lv_group_t* prev_group = lv_group_get_default();
+
+ luavgl_set_root(s, sAlertContainer);
+ lv_group_t* catchall = lv_group_create();
+ lv_group_set_default(catchall);
+
+ // Call the constructor for the alert.
+ lua_settop(s, 1); // Make sure the function is actually at top of stack
+ lua::CallProtected(s, 0, 1);
+
+ // Restore the previous group and default object.
+ luavgl_set_root(s, sCurrentScreen->content());
+ lv_group_set_default(prev_group);
+
+ lv_group_del(catchall);
+
+ return 0;
+}
+
+auto Lua::HideAlert(lua_State* s) -> int {
+ xTimerStop(sAlertTimer, portMAX_DELAY);
+ tinyfsm::FsmList<UiState>::dispatch(internal::DismissAlerts{});
+ return 0;
+}
+
+auto Lua::SetRandom(const lua::LuaValue& val) -> bool {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool b = std::get<bool>(val);
+ sServices->track_queue().random(b);
+ return true;
+}
+
+auto Lua::SetRepeat(const lua::LuaValue& val) -> bool {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool b = std::get<bool>(val);
+ sServices->track_queue().repeat(b);
+ return true;
+}
+
+auto Lua::SetReplay(const lua::LuaValue& val) -> bool {
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool b = std::get<bool>(val);
+ sServices->track_queue().replay(b);
+ return true;
+}
+
+void Lua::exit() {
+ lv_group_set_default(NULL);
+}
+
+void Lua::react(const OnLuaError& err) {
+ ESP_LOGE("lua", "%s", err.message.c_str());
+}
+
+void Lua::react(const DumpLuaStack& ev) {
+ sLua->DumpStack();
+}
+
+void Lua::react(const internal::BackPressed& ev) {
+ PopLuaScreen(sLua->state());
+}
+
+} // namespace states
+} // namespace ui
+
+FSM_INITIAL_STATE(ui::UiState, ui::states::Splash)