summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio/audio_fsm.cpp30
-rw-r--r--src/audio/include/audio_events.hpp19
-rw-r--r--src/audio/include/audio_fsm.hpp6
-rw-r--r--src/database/database.cpp28
-rw-r--r--src/database/include/database.hpp4
-rw-r--r--src/drivers/include/gpios.hpp2
-rw-r--r--src/drivers/include/nvs.hpp3
-rw-r--r--src/drivers/nvs.cpp12
-rw-r--r--src/lua/bridge.cpp25
-rw-r--r--src/lua/include/bridge.hpp2
-rw-r--r--src/lua/lua_database.cpp34
-rw-r--r--src/lua/lua_version.cpp9
-rw-r--r--src/ui/CMakeLists.txt6
-rw-r--r--src/ui/event_binding.cpp23
-rw-r--r--src/ui/include/event_binding.hpp30
-rw-r--r--src/ui/include/modal.hpp10
-rw-r--r--src/ui/include/modal_confirm.hpp29
-rw-r--r--src/ui/include/modal_progress.hpp34
-rw-r--r--src/ui/include/model_playback.hpp26
-rw-r--r--src/ui/include/model_top_bar.hpp26
-rw-r--r--src/ui/include/screen.hpp35
-rw-r--r--src/ui/include/screen_settings.hpp116
-rw-r--r--src/ui/include/ui_events.hpp15
-rw-r--r--src/ui/include/ui_fsm.hpp104
-rw-r--r--src/ui/include/widget_top_bar.hpp45
-rw-r--r--src/ui/lvgl_task.cpp4
-rw-r--r--src/ui/modal.cpp4
-rw-r--r--src/ui/modal_confirm.cpp76
-rw-r--r--src/ui/modal_progress.cpp67
-rw-r--r--src/ui/screen.cpp41
-rw-r--r--src/ui/screen_settings.cpp575
-rw-r--r--src/ui/ui_fsm.cpp337
-rw-r--r--src/ui/widget_top_bar.cpp61
33 files changed, 301 insertions, 1537 deletions
diff --git a/src/audio/audio_fsm.cpp b/src/audio/audio_fsm.cpp
index a2f467cb..b903a171 100644
--- a/src/audio/audio_fsm.cpp
+++ b/src/audio/audio_fsm.cpp
@@ -82,11 +82,32 @@ void AudioState::react(const system_fsm::HasPhonesChanged& ev) {
}
}
-void AudioState::react(const ChangeMaxVolume& ev) {
+void AudioState::react(const SetVolume& ev) {
+ // TODO.
+}
+
+void AudioState::react(const SetVolumeLimit& ev) {
ESP_LOGI(kTag, "new max volume %i db",
- (ev.new_max - drivers::wm8523::kLineLevelReferenceVolume) / 4);
- sI2SOutput->SetMaxVolume(ev.new_max);
- sServices->nvs().AmpMaxVolume(ev.new_max);
+ (ev.new_limit - drivers::wm8523::kLineLevelReferenceVolume) / 4);
+ sI2SOutput->SetMaxVolume(ev.new_limit);
+ sServices->nvs().AmpMaxVolume(ev.new_limit);
+
+ events::Ui().Dispatch(VolumeLimitChanged{
+ .new_limit = ev.new_limit,
+ });
+ events::Ui().Dispatch(VolumeChanged{
+ .percent = sOutput->GetVolumePct(),
+ .db = sOutput->GetVolumeDb(),
+ });
+}
+
+void AudioState::react(const SetVolumeBalance& ev) {
+ sOutput->SetVolumeImbalance(ev.left_bias);
+ sServices->nvs().AmpLeftBias(ev.left_bias);
+
+ events::Ui().Dispatch(VolumeBalanceChanged{
+ .left_bias = ev.left_bias,
+ });
}
void AudioState::react(const OutputModeChanged& ev) {
@@ -136,6 +157,7 @@ void Uninitialised::react(const system_fsm::BootComplete& ev) {
auto& nvs = sServices->nvs();
sI2SOutput->SetMaxVolume(nvs.AmpMaxVolume());
sI2SOutput->SetVolume(nvs.AmpCurrentVolume());
+ sI2SOutput->SetVolumeImbalance(nvs.AmpLeftBias());
if (sServices->nvs().OutputMode() ==
drivers::NvsStorage::Output::kHeadphones) {
diff --git a/src/audio/include/audio_events.hpp b/src/audio/include/audio_events.hpp
index 3c5ab723..0e4d0bc9 100644
--- a/src/audio/include/audio_events.hpp
+++ b/src/audio/include/audio_events.hpp
@@ -47,12 +47,27 @@ struct PlayFile : tinyfsm::Event {
struct StepUpVolume : tinyfsm::Event {};
struct StepDownVolume : tinyfsm::Event {};
+struct SetVolume : tinyfsm::Event {
+ std::optional<uint_fast8_t> percent;
+ std::optional<int32_t> db;
+};
+struct SetVolumeBalance : tinyfsm::Event {
+ int left_bias;
+};
+
struct VolumeChanged : tinyfsm::Event {
uint_fast8_t percent;
int db;
};
-struct ChangeMaxVolume : tinyfsm::Event {
- uint16_t new_max;
+struct VolumeBalanceChanged : tinyfsm::Event {
+ int left_bias;
+};
+struct VolumeLimitChanged : tinyfsm::Event {
+ uint16_t new_limit;
+};
+
+struct SetVolumeLimit : tinyfsm::Event {
+ uint16_t new_limit;
};
struct TogglePlayPause : tinyfsm::Event {};
diff --git a/src/audio/include/audio_fsm.hpp b/src/audio/include/audio_fsm.hpp
index 590c6463..b8c505b0 100644
--- a/src/audio/include/audio_fsm.hpp
+++ b/src/audio/include/audio_fsm.hpp
@@ -44,7 +44,11 @@ class AudioState : public tinyfsm::Fsm<AudioState> {
void react(const StepUpVolume&);
void react(const StepDownVolume&);
virtual void react(const system_fsm::HasPhonesChanged&);
- void react(const ChangeMaxVolume&);
+
+ void react(const SetVolume&);
+ void react(const SetVolumeLimit&);
+ void react(const SetVolumeBalance&);
+
void react(const OutputModeChanged&);
virtual void react(const system_fsm::BootComplete&) {}
diff --git a/src/database/database.cpp b/src/database/database.cpp
index 27b5c24c..4bd9d2db 100644
--- a/src/database/database.cpp
+++ b/src/database/database.cpp
@@ -17,6 +17,7 @@
#include <memory>
#include <optional>
#include <sstream>
+#include <string>
#include <variant>
#include "collation.hpp"
@@ -198,6 +199,33 @@ Database::~Database() {
sIsDbOpen.store(false);
}
+auto Database::schemaVersion() -> std::string {
+ // If the database is open, then it must have the current schema.
+ return std::to_string(kCurrentDbVersion);
+}
+
+auto Database::sizeOnDiskBytes() -> size_t {
+ auto lock = drivers::acquire_spi();
+
+ FF_DIR dir;
+ FRESULT res = f_opendir(&dir, kDbPath);
+ if (res != FR_OK) {
+ return 0;
+ }
+
+ size_t total_size = 0;
+ for (;;) {
+ FILINFO info;
+ res = f_readdir(&dir, &info);
+ if (res != FR_OK || info.fname[0] == 0) {
+ break;
+ }
+ total_size += info.fsize;
+ }
+
+ return total_size;
+}
+
auto Database::put(const std::string& key, const std::string& val) -> void {
db_->Put(leveldb::WriteOptions{}, kKeyCustom + key, val);
}
diff --git a/src/database/include/database.hpp b/src/database/include/database.hpp
index 88a18e17..cb4064fb 100644
--- a/src/database/include/database.hpp
+++ b/src/database/include/database.hpp
@@ -63,6 +63,10 @@ class Database {
~Database();
+ auto schemaVersion() -> std::string;
+
+ auto sizeOnDiskBytes() -> size_t;
+
/* Adds an arbitrary record to the database. */
auto put(const std::string& key, const std::string& val) -> void;
diff --git a/src/drivers/include/gpios.hpp b/src/drivers/include/gpios.hpp
index 55486be7..fffd1f75 100644
--- a/src/drivers/include/gpios.hpp
+++ b/src/drivers/include/gpios.hpp
@@ -79,7 +79,7 @@ class IGpios {
*/
virtual auto Get(Pin) const -> bool = 0;
- virtual auto IsLocked() const -> bool { return Get(Pin::kKeyLock); }
+ virtual auto IsLocked() const -> bool { return !Get(Pin::kKeyLock); }
};
class Gpios : public IGpios {
diff --git a/src/drivers/include/nvs.hpp b/src/drivers/include/nvs.hpp
index 46a3d154..b82013b5 100644
--- a/src/drivers/include/nvs.hpp
+++ b/src/drivers/include/nvs.hpp
@@ -41,6 +41,9 @@ class NvsStorage {
auto AmpCurrentVolume() -> uint16_t;
auto AmpCurrentVolume(uint16_t) -> bool;
+ auto AmpLeftBias() -> int_fast8_t;
+ auto AmpLeftBias(int_fast8_t) -> bool;
+
auto HasShownOnboarding() -> bool;
auto HasShownOnboarding(bool) -> bool;
diff --git a/src/drivers/nvs.cpp b/src/drivers/nvs.cpp
index c8a9a7c7..c8d4d636 100644
--- a/src/drivers/nvs.cpp
+++ b/src/drivers/nvs.cpp
@@ -30,6 +30,7 @@ static constexpr char kKeyOutput[] = "out";
static constexpr char kKeyBrightness[] = "bright";
static constexpr char kKeyAmpMaxVolume[] = "hp_vol_max";
static constexpr char kKeyAmpCurrentVolume[] = "hp_vol";
+static constexpr char kKeyAmpLeftBias[] = "hp_bias";
static constexpr char kKeyOnboarded[] = "intro";
static constexpr char kKeyPrimaryInput[] = "in_pri";
@@ -156,6 +157,17 @@ auto NvsStorage::AmpCurrentVolume(uint16_t val) -> bool {
return nvs_commit(handle_) == ESP_OK;
}
+auto NvsStorage::AmpLeftBias() -> int_fast8_t {
+ int8_t out = 0;
+ nvs_get_i8(handle_, kKeyAmpLeftBias, &out);
+ return out;
+}
+
+auto NvsStorage::AmpLeftBias(int_fast8_t val) -> bool {
+ nvs_set_i8(handle_, kKeyAmpLeftBias, val);
+ return nvs_commit(handle_) == ESP_OK;
+}
+
auto NvsStorage::HasShownOnboarding() -> bool {
uint8_t out = false;
nvs_get_u8(handle_, kKeyOnboarded, &out);
diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp
index e7344e0e..82721d4e 100644
--- a/src/lua/bridge.cpp
+++ b/src/lua/bridge.cpp
@@ -37,21 +37,6 @@ namespace lua {
static constexpr char kBridgeKey[] = "bridge";
-static auto open_settings_fn(lua_State* state) -> int {
- events::Ui().Dispatch(ui::internal::ShowSettingsPage{
- .page = ui::internal::ShowSettingsPage::Page::kRoot});
- return 0;
-}
-
-static const struct luaL_Reg kLegacyUiFuncs[] = {
- {"open_settings", open_settings_fn},
- {NULL, NULL}};
-
-static auto lua_legacy_ui(lua_State* state) -> int {
- luaL_newlib(state, kLegacyUiFuncs);
- return 1;
-}
-
auto Bridge::Get(lua_State* state) -> Bridge* {
lua_pushstring(state, kBridgeKey);
lua_gettable(state, LUA_REGISTRYINDEX);
@@ -64,9 +49,6 @@ Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s)
lua_pushlightuserdata(&s, this);
lua_settable(&s, LUA_REGISTRYINDEX);
- luaL_requiref(&s, "legacy_ui", lua_legacy_ui, true);
- lua_pop(&s, 1);
-
luaL_requiref(&s, "linenoise", luaopen_linenoise, true);
lua_pop(&s, 1);
@@ -94,8 +76,7 @@ inline constexpr bool always_false_v = false;
auto Bridge::AddPropertyModule(
const std::string& name,
- std::vector<std::pair<std::string,
- std::variant<LuaFunction, std::shared_ptr<Property>>>>
+ std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>
props) -> void {
// Create the module (or retrieve it if one with this name already exists)
luaL_requiref(&state_, name.c_str(), new_property_module, true);
@@ -107,8 +88,8 @@ auto Bridge::AddPropertyModule(
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, LuaFunction>) {
bindings_.Register(&state_, arg);
- } else if constexpr (std::is_same_v<T, std::shared_ptr<Property>>) {
- bindings_.Register(&state_, arg.get());
+ } else if constexpr (std::is_same_v<T, Property*>) {
+ bindings_.Register(&state_, arg);
} else {
static_assert(always_false_v<T>, "missing case");
}
diff --git a/src/lua/include/bridge.hpp b/src/lua/include/bridge.hpp
index 91153d67..62fbc340 100644
--- a/src/lua/include/bridge.hpp
+++ b/src/lua/include/bridge.hpp
@@ -26,7 +26,7 @@ class Bridge {
const std::string&,
std::vector<
std::pair<std::string,
- std::variant<LuaFunction, std::shared_ptr<Property>>>>)
+ std::variant<LuaFunction, Property*>>>)
-> void;
system_fsm::ServiceLocator& services() { return services_; }
diff --git a/src/lua/lua_database.cpp b/src/lua/lua_database.cpp
index 82b22343..ac7d711b 100644
--- a/src/lua/lua_database.cpp
+++ b/src/lua/lua_database.cpp
@@ -73,8 +73,38 @@ static auto indexes(lua_State* state) -> int {
return 1;
}
-static const struct luaL_Reg kDatabaseFuncs[] = {{"indexes", indexes},
- {NULL, NULL}};
+static auto version(lua_State* L) -> int {
+ Bridge* instance = Bridge::Get(L);
+ auto db = instance->services().database().lock();
+ if (!db) {
+ return 0;
+ }
+ auto res = db->schemaVersion();
+ lua_pushlstring(L, res.data(), res.size());
+ return 1;
+}
+
+static auto size(lua_State* L) -> int {
+ Bridge* instance = Bridge::Get(L);
+ auto db = instance->services().database().lock();
+ if (!db) {
+ return 0;
+ }
+ lua_pushinteger(L, db->sizeOnDiskBytes());
+ return 1;
+}
+
+static auto recreate(lua_State* L) -> int {
+ return 0;
+}
+
+static auto update(lua_State* L) -> int {
+ return 0;
+}
+
+static const struct luaL_Reg kDatabaseFuncs[] = {
+ {"indexes", indexes}, {"version", version}, {"size", size},
+ {"recreate", recreate}, {"update", update}, {NULL, NULL}};
/*
* Struct to be used as userdata for the Lua representation of database records.
diff --git a/src/lua/lua_version.cpp b/src/lua/lua_version.cpp
index ac72d3ae..c1098a1b 100644
--- a/src/lua/lua_version.cpp
+++ b/src/lua/lua_version.cpp
@@ -34,8 +34,17 @@ static auto samd(lua_State* L) -> int {
return 1;
}
+static auto collator(lua_State* L) -> int {
+ Bridge* instance = Bridge::Get(L);
+ auto& collator = instance->services().collator();
+ auto version = collator.Describe().value_or("None");
+ lua_pushlstring(L, version.data(), version.size());
+ return 1;
+}
+
static const struct luaL_Reg kVersionFuncs[] = {{"esp", esp},
{"samd", samd},
+ {"collator", collator},
{NULL, NULL}};
static auto lua_version(lua_State* L) -> int {
diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt
index 0d9de5a4..6d45fc9f 100644
--- a/src/ui/CMakeLists.txt
+++ b/src/ui/CMakeLists.txt
@@ -4,10 +4,8 @@
idf_component_register(
SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "encoder_input.cpp"
- "themes.cpp" "widget_top_bar.cpp" "screen.cpp" "modal_progress.cpp"
- "modal.cpp" "modal_confirm.cpp" "screen_settings.cpp" "event_binding.cpp"
- "screen_lua.cpp"
+ "themes.cpp" "screen.cpp" "modal.cpp" "screen_lua.cpp"
"splash.c" "font_fusion_12.c" "font_fusion_10.c"
INCLUDE_DIRS "include"
- REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "bindey" "lua" "luavgl" "esp_app_format")
+ REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "lua" "luavgl" "esp_app_format")
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
diff --git a/src/ui/event_binding.cpp b/src/ui/event_binding.cpp
deleted file mode 100644
index ed15ccfb..00000000
--- a/src/ui/event_binding.cpp
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "event_binding.hpp"
-
-#include "core/lv_event.h"
-
-namespace ui {
-
-static auto event_cb(lv_event_t* ev) -> void {
- EventBinding* binding =
- static_cast<EventBinding*>(lv_event_get_user_data(ev));
- binding->signal()(lv_event_get_target(ev));
-}
-
-EventBinding::EventBinding(lv_obj_t* obj, lv_event_code_t ev) {
- lv_obj_add_event_cb(obj, event_cb, ev, this);
-}
-
-} // namespace ui
diff --git a/src/ui/include/event_binding.hpp b/src/ui/include/event_binding.hpp
deleted file mode 100644
index 19514db4..00000000
--- a/src/ui/include/event_binding.hpp
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <cstdint>
-
-#include "lvgl.h"
-
-#include "core/lv_event.h"
-#include "core/lv_obj.h"
-#include "nod/nod.hpp"
-
-namespace ui {
-
-class EventBinding {
- public:
- EventBinding(lv_obj_t* obj, lv_event_code_t ev);
-
- auto signal() -> nod::signal<void(lv_obj_t*)>& { return signal_; }
-
- private:
- lv_obj_t* obj_;
- nod::signal<void(lv_obj_t*)> signal_;
-};
-
-} // namespace ui
diff --git a/src/ui/include/modal.hpp b/src/ui/include/modal.hpp
index 61e52cdf..6b7e792e 100644
--- a/src/ui/include/modal.hpp
+++ b/src/ui/include/modal.hpp
@@ -12,7 +12,6 @@
#include "core/lv_obj.h"
#include "core/lv_obj_tree.h"
#include "lvgl.h"
-#include "widget_top_bar.hpp"
#include "screen.hpp"
@@ -30,15 +29,6 @@ class Modal {
lv_obj_t* const root_;
lv_group_t* const group_;
- std::pmr::vector<std::unique_ptr<EventBinding>> event_bindings_;
-
- template <typename T>
- auto lv_bind(lv_obj_t* obj, lv_event_code_t ev, T fn) -> void {
- auto binding = std::make_unique<EventBinding>(obj, ev);
- binding->signal().connect(fn);
- event_bindings_.push_back(std::move(binding));
- }
-
private:
Screen* host_;
};
diff --git a/src/ui/include/modal_confirm.hpp b/src/ui/include/modal_confirm.hpp
deleted file mode 100644
index 29d80041..00000000
--- a/src/ui/include/modal_confirm.hpp
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "index.hpp"
-#include "lvgl.h"
-
-#include "modal.hpp"
-
-namespace ui {
-namespace modals {
-
-class Confirm : public Modal {
- public:
- Confirm(Screen*, const std::pmr::string& title, bool has_cancel);
-
- private:
- lv_obj_t* container_;
-};
-
-} // namespace modals
-} // namespace ui
diff --git a/src/ui/include/modal_progress.hpp b/src/ui/include/modal_progress.hpp
deleted file mode 100644
index 2ccb671a..00000000
--- a/src/ui/include/modal_progress.hpp
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <memory>
-#include <vector>
-
-#include "index.hpp"
-#include "lvgl.h"
-
-#include "modal.hpp"
-
-namespace ui {
-namespace modals {
-
-class Progress : public Modal {
- public:
- Progress(Screen*, std::pmr::string title, std::pmr::string subtitle = "");
-
- void title(const std::pmr::string&);
- void subtitle(const std::pmr::string&);
-
- private:
- lv_obj_t* container_;
- lv_obj_t* title_;
- lv_obj_t* subtitle_;
-};
-
-} // namespace modals
-} // namespace ui
diff --git a/src/ui/include/model_playback.hpp b/src/ui/include/model_playback.hpp
deleted file mode 100644
index f932dcfd..00000000
--- a/src/ui/include/model_playback.hpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include "bindey/property.h"
-
-#include "track.hpp"
-
-namespace ui {
-namespace models {
-
-struct Playback {
- bindey::property<bool> is_playing;
- bindey::property<std::optional<database::TrackId>> current_track;
- bindey::property<std::vector<database::TrackId>> upcoming_tracks;
-
- bindey::property<uint32_t> current_track_position;
- bindey::property<uint32_t> current_track_duration;
-};
-
-} // namespace models
-} // namespace ui \ No newline at end of file
diff --git a/src/ui/include/model_top_bar.hpp b/src/ui/include/model_top_bar.hpp
deleted file mode 100644
index c0f148f3..00000000
--- a/src/ui/include/model_top_bar.hpp
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include "battery.hpp"
-#include "bindey/property.h"
-
-#include "track.hpp"
-
-namespace ui {
-namespace models {
-
-struct TopBar {
- bindey::property<battery::Battery::BatteryState> battery_state;
-
- // Shared with the Playback model
- bindey::property<bool>& is_playing;
- bindey::property<std::optional<database::TrackId>>& current_track;
-};
-
-} // namespace models
-} // namespace ui
diff --git a/src/ui/include/screen.hpp b/src/ui/include/screen.hpp
index 4fe0a3b7..60939660 100644
--- a/src/ui/include/screen.hpp
+++ b/src/ui/include/screen.hpp
@@ -10,15 +10,10 @@
#include <optional>
#include <vector>
-#include "bindey/binding.h"
#include "core/lv_group.h"
#include "core/lv_obj.h"
#include "core/lv_obj_tree.h"
-#include "event_binding.hpp"
#include "lvgl.h"
-#include "model_top_bar.hpp"
-#include "nod/nod.hpp"
-#include "widget_top_bar.hpp"
namespace ui {
@@ -32,12 +27,6 @@ class Screen {
Screen();
virtual ~Screen();
- /*
- * Called periodically to allow the screen to update itself, e.g. to handle
- * std::futures that are still loading in.
- */
- virtual auto Tick() -> void {}
-
auto root() -> lv_obj_t* { return root_; }
auto content() -> lv_obj_t* { return content_; }
auto alert() -> lv_obj_t* { return alert_; }
@@ -52,20 +41,6 @@ class Screen {
}
protected:
- auto CreateTopBar(lv_obj_t* parent,
- const widgets::TopBar::Configuration&,
- models::TopBar& model) -> widgets::TopBar*;
-
- std::pmr::vector<bindey::scoped_binding> data_bindings_;
- std::pmr::vector<std::unique_ptr<EventBinding>> event_bindings_;
-
- template <typename T>
- auto lv_bind(lv_obj_t* obj, lv_event_code_t ev, T fn) -> void {
- auto binding = std::make_unique<EventBinding>(obj, ev);
- binding->signal().connect(fn);
- event_bindings_.push_back(std::move(binding));
- }
-
lv_obj_t* const root_;
lv_obj_t* content_;
lv_obj_t* modal_content_;
@@ -73,16 +48,6 @@ class Screen {
lv_group_t* const group_;
lv_group_t* modal_group_;
-
- private:
- std::unique_ptr<widgets::TopBar> top_bar_;
-};
-
-class MenuScreen : public Screen {
- public:
- MenuScreen(models::TopBar&,
- const std::pmr::string& title,
- bool show_back_button = true);
};
} // namespace ui
diff --git a/src/ui/include/screen_settings.hpp b/src/ui/include/screen_settings.hpp
deleted file mode 100644
index 7402f9f9..00000000
--- a/src/ui/include/screen_settings.hpp
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <stdint.h>
-#include <cstdint>
-#include <list>
-#include <memory>
-#include <vector>
-
-#include "bluetooth.hpp"
-#include "bluetooth_types.hpp"
-#include "display.hpp"
-#include "index.hpp"
-#include "lvgl.h"
-
-#include "model_top_bar.hpp"
-#include "nvs.hpp"
-#include "samd.hpp"
-#include "screen.hpp"
-
-namespace ui {
-namespace screens {
-
-class Settings : public MenuScreen {
- public:
- Settings(models::TopBar&);
-};
-
-class Bluetooth : public MenuScreen {
- public:
- Bluetooth(models::TopBar&, drivers::Bluetooth& bt, drivers::NvsStorage& nvs);
- ~Bluetooth();
-
- auto ChangeEnabledState(bool enabled) -> void;
- auto RefreshDevicesList() -> void;
- auto OnDeviceSelected(ssize_t index) -> void;
-
- private:
- auto RemoveAllDevices() -> void;
- auto AddPreferredDevice(const drivers::bluetooth::Device&) -> void;
- auto AddDevice(const drivers::bluetooth::Device&) -> void;
-
- drivers::Bluetooth& bt_;
- drivers::NvsStorage& nvs_;
-
- lv_obj_t* devices_list_;
- lv_obj_t* preferred_device_;
-
- std::list<drivers::bluetooth::mac_addr_t> macs_in_list_;
-};
-
-class Headphones : public MenuScreen {
- public:
- Headphones(models::TopBar&, drivers::NvsStorage& nvs);
-
- auto ChangeMaxVolume(uint8_t index) -> void;
- auto ChangeCustomVolume(int8_t diff) -> void;
-
- private:
- auto UpdateCustomVol(uint16_t) -> void;
-
- drivers::NvsStorage& nvs_;
- lv_obj_t* custom_vol_container_;
- lv_obj_t* custom_vol_label_;
-
- std::vector<uint16_t> index_to_level_;
- uint16_t custom_limit_;
-};
-
-class Appearance : public MenuScreen {
- public:
- Appearance(models::TopBar&,
- drivers::NvsStorage& nvs,
- drivers::Display& display);
-
- auto ChangeBrightness(uint_fast8_t) -> void;
- auto CommitBrightness() -> void;
-
- private:
- drivers::NvsStorage& nvs_;
- drivers::Display& display_;
-
- lv_obj_t* current_brightness_label_;
- uint_fast8_t current_brightness_;
-};
-
-class InputMethod : public MenuScreen {
- public:
- InputMethod(models::TopBar&, drivers::NvsStorage& nvs);
-
- private:
- drivers::NvsStorage& nvs_;
-};
-
-class Storage : public MenuScreen {
- public:
- Storage(models::TopBar&);
-};
-
-class FirmwareUpdate : public MenuScreen {
- public:
- FirmwareUpdate(models::TopBar&, drivers::Samd&);
-};
-
-class About : public MenuScreen {
- public:
- About(models::TopBar&);
-};
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/include/ui_events.hpp b/src/ui/include/ui_events.hpp
index 59be7606..07b18dea 100644
--- a/src/ui/include/ui_events.hpp
+++ b/src/ui/include/ui_events.hpp
@@ -35,21 +35,6 @@ struct ReindexDatabase : tinyfsm::Event {};
struct BackPressed : tinyfsm::Event {};
struct ShowNowPlaying : tinyfsm::Event {};
-struct ShowSettingsPage : tinyfsm::Event {
- enum class Page {
- kRoot,
- kBluetooth,
- kHeadphones,
- kAppearance,
- kInput,
- kStorage,
- kFirmwareUpdate,
- kAbout,
- } page;
-};
-struct OnboardingNavigate : tinyfsm::Event {
- bool forwards;
-};
struct ModalConfirmPressed : tinyfsm::Event {};
struct ModalCancelPressed : tinyfsm::Event {};
diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp
index 33f9eac4..0b883ee0 100644
--- a/src/ui/include/ui_fsm.hpp
+++ b/src/ui/include/ui_fsm.hpp
@@ -12,26 +12,21 @@
#include "audio_events.hpp"
#include "battery.hpp"
-#include "bindey/property.h"
#include "db_events.hpp"
+#include "display.hpp"
+#include "encoder_input.hpp"
#include "gpios.hpp"
#include "lua_thread.hpp"
#include "lvgl_task.hpp"
-#include "model_playback.hpp"
-#include "model_top_bar.hpp"
+#include "modal.hpp"
#include "nvs.hpp"
#include "property.hpp"
#include "relative_wheel.hpp"
-#include "screen_settings.hpp"
-#include "service_locator.hpp"
-#include "tinyfsm.hpp"
-
-#include "display.hpp"
-#include "encoder_input.hpp"
-#include "modal.hpp"
#include "screen.hpp"
+#include "service_locator.hpp"
#include "storage.hpp"
#include "system_events.hpp"
+#include "tinyfsm.hpp"
#include "touchwheel.hpp"
#include "track.hpp"
#include "track_queue.hpp"
@@ -60,20 +55,21 @@ class UiState : public tinyfsm::Fsm<UiState> {
virtual void react(const audio::PlaybackFinished&);
virtual void react(const audio::PlaybackUpdate&);
virtual void react(const audio::QueueUpdate&);
- virtual void react(const audio::VolumeChanged&){};
+
+ virtual void react(const audio::VolumeChanged&);
+ virtual void react(const audio::VolumeBalanceChanged&);
+ virtual void react(const audio::VolumeLimitChanged&);
virtual void react(const system_fsm::KeyLockChanged&);
virtual void react(const OnLuaError&) {}
virtual void react(const internal::BackPressed&) {}
- virtual void react(const internal::ShowSettingsPage&){};
virtual void react(const internal::ModalCancelPressed&) {
sCurrentModal.reset();
}
virtual void react(const internal::ModalConfirmPressed&) {
sCurrentModal.reset();
}
- virtual void react(const internal::OnboardingNavigate&) {}
void react(const internal::ControlSchemeChanged&);
virtual void react(const internal::ReindexDatabase&){};
@@ -102,10 +98,29 @@ class UiState : public tinyfsm::Fsm<UiState> {
static std::shared_ptr<Modal> sCurrentModal;
static std::shared_ptr<lua::LuaThread> sLua;
- static std::weak_ptr<screens::Bluetooth> bluetooth_screen_;
+ static lua::Property sBatteryPct;
+ static lua::Property sBatteryMv;
+ static lua::Property sBatteryCharging;
- static models::Playback sPlaybackModel;
- static models::TopBar sTopBarModel;
+ static lua::Property sBluetoothEnabled;
+ static lua::Property sBluetoothConnected;
+
+ static lua::Property sPlaybackPlaying;
+
+ static lua::Property sPlaybackTrack;
+ static lua::Property sPlaybackPosition;
+
+ static lua::Property sQueuePosition;
+ static lua::Property sQueueSize;
+ static lua::Property sQueueRepeat;
+ static lua::Property sQueueRandom;
+
+ static lua::Property sVolumeCurrentPct;
+ static lua::Property sVolumeCurrentDb;
+ static lua::Property sVolumeLeftBias;
+ static lua::Property sVolumeLimit;
+
+ static lua::Property sDisplayBrightness;
};
namespace states {
@@ -113,8 +128,10 @@ namespace states {
class Splash : public UiState {
public:
void exit() override;
+
void react(const system_fsm::BootComplete&) override;
void react(const system_fsm::StorageMounted&) override;
+
using UiState::react;
};
@@ -124,15 +141,6 @@ class Lua : public UiState {
void exit() override;
void react(const OnLuaError&) override;
-
- void react(const internal::ShowSettingsPage&) override;
-
- void react(const system_fsm::BatteryStateChanged&) override;
- void react(const audio::QueueUpdate&) override;
- void react(const audio::PlaybackStarted&) override;
- void react(const audio::PlaybackUpdate&) override;
- void react(const audio::PlaybackFinished&) override;
- void react(const audio::VolumeChanged&) override;
void react(const internal::BackPressed&) override;
using UiState::react;
@@ -147,54 +155,8 @@ class Lua : public UiState {
auto SetPlaying(const lua::LuaValue&) -> bool;
auto SetRandom(const lua::LuaValue&) -> bool;
auto SetRepeat(const lua::LuaValue&) -> bool;
-
- std::shared_ptr<lua::Property> battery_pct_;
- std::shared_ptr<lua::Property> battery_mv_;
- std::shared_ptr<lua::Property> battery_charging_;
-
- std::shared_ptr<lua::Property> bluetooth_en_;
-
- std::shared_ptr<lua::Property> playback_playing_;
- std::shared_ptr<lua::Property> playback_track_;
- std::shared_ptr<lua::Property> playback_position_;
-
- std::shared_ptr<lua::Property> queue_position_;
- std::shared_ptr<lua::Property> queue_size_;
- std::shared_ptr<lua::Property> queue_repeat_;
- std::shared_ptr<lua::Property> queue_random_;
-
- std::shared_ptr<lua::Property> volume_current_pct_;
- std::shared_ptr<lua::Property> volume_current_db_;
-};
-
-class Browse : public UiState {
- public:
- void entry() override;
-
- void react(const internal::BackPressed&) override;
-
- void react(const internal::ShowSettingsPage&) override;
- void react(const internal::ReindexDatabase&) override;
-
- void react(const system_fsm::BluetoothDevicesChanged&) override;
-
- using UiState::react;
};
-class Indexing : public UiState {
- public:
- void entry() override;
- void exit() override;
-
- void react(const database::event::UpdateStarted&) override;
- void react(const database::event::UpdateProgress&) override;
- void react(const database::event::UpdateFinished&) override;
-
- using UiState::react;
-};
-
-class FatalError : public UiState {};
-
} // namespace states
} // namespace ui
diff --git a/src/ui/include/widget_top_bar.hpp b/src/ui/include/widget_top_bar.hpp
deleted file mode 100644
index b240188c..00000000
--- a/src/ui/include/widget_top_bar.hpp
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#pragma once
-
-#include <cstdint>
-#include <string>
-
-#include "bindey/binding.h"
-#include "lvgl.h"
-
-#include "memory_resource.hpp"
-#include "model_top_bar.hpp"
-
-namespace ui {
-
-namespace widgets {
-
-class TopBar {
- public:
- struct Configuration {
- bool show_back_button;
- std::pmr::string title;
- };
-
- explicit TopBar(lv_obj_t* parent,
- const Configuration& config,
- models::TopBar& model);
-
- auto root() -> lv_obj_t* { return container_; }
- auto button() -> lv_obj_t* { return back_button_; }
-
- private:
- std::vector<bindey::scoped_binding> bindings_;
-
- lv_obj_t* container_;
- lv_obj_t* back_button_;
-};
-
-} // namespace widgets
-
-} // namespace ui
diff --git a/src/ui/lvgl_task.cpp b/src/ui/lvgl_task.cpp
index 96f9bdf5..f0184766 100644
--- a/src/ui/lvgl_task.cpp
+++ b/src/ui/lvgl_task.cpp
@@ -81,10 +81,6 @@ auto UiTask::Main() -> void {
lv_group_set_focus_cb(current_group, &group_focus_cb);
}
- if (current_screen_) {
- current_screen_->Tick();
- }
-
TickType_t delay = lv_timer_handler();
vTaskDelay(pdMS_TO_TICKS(std::clamp<TickType_t>(delay, 0, 100)));
}
diff --git a/src/ui/modal.cpp b/src/ui/modal.cpp
index 3cab85f5..88f6d3ef 100644
--- a/src/ui/modal.cpp
+++ b/src/ui/modal.cpp
@@ -5,8 +5,9 @@
* SPDX-License-Identifier: GPL-3.0-only
*/
+#include "modal.hpp"
+
#include "misc/lv_color.h"
-#include "modal_progress.hpp"
#include "core/lv_event.h"
#include "esp_log.h"
@@ -24,7 +25,6 @@
#include "themes.hpp"
#include "ui_events.hpp"
#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
#include "widgets/lv_label.h"
namespace ui {
diff --git a/src/ui/modal_confirm.cpp b/src/ui/modal_confirm.cpp
deleted file mode 100644
index dfb1b1eb..00000000
--- a/src/ui/modal_confirm.cpp
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "modal_confirm.hpp"
-
-#include "core/lv_event.h"
-#include "core/lv_obj.h"
-#include "core/lv_obj_tree.h"
-#include "esp_log.h"
-
-#include "core/lv_group.h"
-#include "core/lv_obj_pos.h"
-#include "event_queue.hpp"
-#include "extra/widgets/list/lv_list.h"
-#include "extra/widgets/menu/lv_menu.h"
-#include "extra/widgets/spinner/lv_spinner.h"
-#include "hal/lv_hal_disp.h"
-#include "index.hpp"
-#include "misc/lv_area.h"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
-#include "widgets/lv_btn.h"
-#include "widgets/lv_label.h"
-
-namespace ui {
-namespace modals {
-
-static void button_cancel_cb(lv_event_t* e) {
- events::Ui().Dispatch(internal::ModalCancelPressed{});
-}
-
-static void button_confirm_cb(lv_event_t* e) {
- events::Ui().Dispatch(internal::ModalConfirmPressed{});
-}
-
-Confirm::Confirm(Screen* host,
- const std::pmr::string& title_text,
- bool has_cancel)
- : Modal(host) {
- lv_obj_set_layout(root_, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER);
-
- lv_obj_t* title = lv_label_create(root_);
- lv_label_set_text(title, title_text.c_str());
- lv_obj_set_size(title, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
-
- lv_obj_t* button_container = lv_obj_create(root_);
- lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- if (has_cancel) {
- lv_obj_t* cancel_btn = lv_btn_create(button_container);
- lv_obj_t* cancel_label = lv_label_create(cancel_btn);
- lv_label_set_text(cancel_label, "Cancel");
- lv_group_add_obj(group_, cancel_btn);
- lv_obj_add_event_cb(cancel_btn, button_cancel_cb, LV_EVENT_CLICKED, NULL);
- }
-
- lv_obj_t* ok_btn = lv_btn_create(button_container);
- lv_obj_t* ok_label = lv_label_create(ok_btn);
- lv_label_set_text(ok_label, "Okay");
- lv_group_add_obj(group_, ok_btn);
- lv_obj_add_event_cb(ok_btn, button_confirm_cb, LV_EVENT_CLICKED, NULL);
-}
-
-} // namespace modals
-} // namespace ui
diff --git a/src/ui/modal_progress.cpp b/src/ui/modal_progress.cpp
deleted file mode 100644
index d3d3e9b9..00000000
--- a/src/ui/modal_progress.cpp
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "modal_progress.hpp"
-
-#include "core/lv_event.h"
-#include "core/lv_obj.h"
-#include "core/lv_obj_tree.h"
-#include "esp_log.h"
-
-#include "core/lv_group.h"
-#include "core/lv_obj_pos.h"
-#include "event_queue.hpp"
-#include "extra/widgets/list/lv_list.h"
-#include "extra/widgets/menu/lv_menu.h"
-#include "extra/widgets/spinner/lv_spinner.h"
-#include "hal/lv_hal_disp.h"
-#include "index.hpp"
-#include "misc/lv_area.h"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
-#include "widgets/lv_label.h"
-
-namespace ui {
-namespace modals {
-
-Progress::Progress(Screen* host,
- std::pmr::string title_text,
- std::pmr::string subtitle_text)
- : Modal(host) {
- lv_obj_set_layout(root_, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER);
-
- title_ = lv_label_create(root_);
- lv_obj_set_size(title_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
-
- subtitle_ = lv_label_create(root_);
- lv_obj_set_size(subtitle_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
-
- lv_obj_t* spinner = lv_spinner_create(root_, 3000, 45);
- lv_obj_set_size(spinner, 16, 16);
-
- title(title_text);
- subtitle(subtitle_text);
-}
-
-void Progress::title(const std::pmr::string& s) {
- lv_label_set_text(title_, s.c_str());
-}
-
-void Progress::subtitle(const std::pmr::string& s) {
- if (s.empty()) {
- lv_obj_add_flag(subtitle_, LV_OBJ_FLAG_HIDDEN);
- } else {
- lv_obj_clear_flag(subtitle_, LV_OBJ_FLAG_HIDDEN);
- lv_label_set_text(subtitle_, s.c_str());
- }
-}
-
-} // namespace modals
-} // namespace ui
diff --git a/src/ui/screen.cpp b/src/ui/screen.cpp
index 9ac5ec0e..3e4f8e42 100644
--- a/src/ui/screen.cpp
+++ b/src/ui/screen.cpp
@@ -13,8 +13,6 @@
#include "hal/lv_hal_disp.h"
#include "misc/lv_area.h"
#include "misc/lv_color.h"
-#include "model_top_bar.hpp"
-#include "widget_top_bar.hpp"
namespace ui {
@@ -49,43 +47,4 @@ Screen::~Screen() {
lv_obj_del(root_);
}
-auto Screen::CreateTopBar(lv_obj_t* parent,
- const widgets::TopBar::Configuration& config,
- models::TopBar& model) -> widgets::TopBar* {
- assert(top_bar_ == nullptr);
- top_bar_ = std::make_unique<widgets::TopBar>(parent, config, model);
- if (top_bar_->button()) {
- lv_group_add_obj(group_, top_bar_->button());
- }
- return top_bar_.get();
-}
-
-MenuScreen::MenuScreen(models::TopBar& top_bar_model,
- const std::pmr::string& title,
- bool show_back_button)
- : Screen() {
- lv_group_set_wrap(group_, false);
-
- lv_obj_set_layout(content_, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER);
-
- widgets::TopBar::Configuration config{
- .show_back_button = show_back_button,
- .title = title.c_str(),
- };
- CreateTopBar(content_, config, top_bar_model);
-
- content_ = lv_obj_create(content_);
- lv_obj_set_flex_grow(content_, 1);
- lv_obj_set_width(content_, lv_pct(100));
- lv_obj_set_layout(content_, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_START);
-
- lv_obj_set_style_pad_all(content_, 4, LV_PART_MAIN);
-}
-
} // namespace ui
diff --git a/src/ui/screen_settings.cpp b/src/ui/screen_settings.cpp
deleted file mode 100644
index 3f4c2c46..00000000
--- a/src/ui/screen_settings.cpp
+++ /dev/null
@@ -1,575 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "screen_settings.hpp"
-#include <stdint.h>
-#include <string>
-
-#include "audio_events.hpp"
-#include "bluetooth.hpp"
-#include "bluetooth_types.hpp"
-#include "core/lv_event.h"
-#include "core/lv_obj.h"
-#include "core/lv_obj_tree.h"
-#include "display.hpp"
-#include "esp_app_desc.h"
-#include "esp_log.h"
-
-#include "core/lv_group.h"
-#include "core/lv_obj_pos.h"
-#include "event_queue.hpp"
-#include "extra/layouts/flex/lv_flex.h"
-#include "extra/widgets/list/lv_list.h"
-#include "extra/widgets/menu/lv_menu.h"
-#include "extra/widgets/spinbox/lv_spinbox.h"
-#include "extra/widgets/spinner/lv_spinner.h"
-#include "hal/lv_hal_disp.h"
-#include "index.hpp"
-#include "lv_api_map.h"
-#include "misc/lv_anim.h"
-#include "misc/lv_area.h"
-#include "model_top_bar.hpp"
-#include "nvs.hpp"
-#include "samd.hpp"
-#include "screen.hpp"
-#include "themes.hpp"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
-#include "widgets/lv_bar.h"
-#include "widgets/lv_btn.h"
-#include "widgets/lv_dropdown.h"
-#include "widgets/lv_label.h"
-#include "widgets/lv_slider.h"
-#include "widgets/lv_switch.h"
-#include "wm8523.hpp"
-
-namespace ui {
-namespace screens {
-
-using Page = internal::ShowSettingsPage::Page;
-
-static void open_sub_menu_cb(lv_event_t* e) {
- Page next_page = static_cast<Page>(reinterpret_cast<uintptr_t>(e->user_data));
- events::Ui().Dispatch(internal::ShowSettingsPage{
- .page = next_page,
- });
-}
-
-static void sub_menu(lv_obj_t* list,
- lv_group_t* group,
- const std::pmr::string& text,
- Page page) {
- lv_obj_t* item = lv_list_add_btn(list, NULL, text.c_str());
- lv_group_add_obj(group, item);
- lv_obj_add_event_cb(item, open_sub_menu_cb, LV_EVENT_CLICKED,
- reinterpret_cast<void*>(static_cast<uintptr_t>(page)));
-}
-
-Settings::Settings(models::TopBar& bar) : MenuScreen(bar, "Settings") {
- lv_obj_t* list = lv_list_create(content_);
- lv_obj_set_size(list, lv_pct(100), lv_pct(100));
-
- themes::Theme::instance()->ApplyStyle(lv_list_add_text(list, "Audio"),
- themes::Style::kMenuSubheadFirst);
- sub_menu(list, group_, "Bluetooth", Page::kBluetooth);
- sub_menu(list, group_, "Headphones", Page::kHeadphones);
-
- themes::Theme::instance()->ApplyStyle(lv_list_add_text(list, "Interface"),
- themes::Style::kMenuSubhead);
- sub_menu(list, group_, "Appearance", Page::kAppearance);
- sub_menu(list, group_, "Input Method", Page::kInput);
-
- themes::Theme::instance()->ApplyStyle(lv_list_add_text(list, "System"),
- themes::Style::kMenuSubhead);
- sub_menu(list, group_, "Storage", Page::kStorage);
- sub_menu(list, group_, "Firmware Update", Page::kFirmwareUpdate);
- sub_menu(list, group_, "About", Page::kAbout);
-}
-
-static auto settings_container(lv_obj_t* parent) -> lv_obj_t* {
- lv_obj_t* res = lv_obj_create(parent);
- lv_obj_set_layout(res, LV_LAYOUT_FLEX);
- lv_obj_set_size(res, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_flex_flow(res, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(res, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_START);
- return res;
-}
-
-static auto label_pair(lv_obj_t* parent,
- const std::pmr::string& left,
- const std::pmr::string& right) -> lv_obj_t* {
- lv_obj_t* container = settings_container(parent);
- lv_obj_t* left_label = lv_label_create(container);
- lv_label_set_text(left_label, left.c_str());
- lv_obj_t* right_label = lv_label_create(container);
- lv_label_set_text(right_label, right.c_str());
- return right_label;
-}
-
-static auto toggle_bt_cb(lv_event_t* ev) {
- Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data);
- instance->ChangeEnabledState(lv_obj_has_state(ev->target, LV_STATE_CHECKED));
-}
-
-static auto select_device_cb(lv_event_t* ev) {
- Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data);
- instance->OnDeviceSelected(lv_obj_get_index(ev->target));
-}
-
-static auto remove_preferred_cb(lv_event_t* ev) {
- Bluetooth* instance = reinterpret_cast<Bluetooth*>(ev->user_data);
- instance->OnDeviceSelected(-1);
-}
-
-Bluetooth::Bluetooth(models::TopBar& bar,
- drivers::Bluetooth& bt,
- drivers::NvsStorage& nvs)
- : MenuScreen(bar, "Bluetooth"), bt_(bt), nvs_(nvs) {
- lv_obj_t* toggle_container = settings_container(content_);
- lv_obj_t* toggle_label = lv_label_create(toggle_container);
- lv_label_set_text(toggle_label, "Enable");
- lv_obj_set_flex_grow(toggle_label, 1);
- lv_obj_t* toggle = lv_switch_create(toggle_container);
- lv_group_add_obj(group_, toggle);
-
- if (bt.IsEnabled()) {
- lv_obj_add_state(toggle, LV_STATE_CHECKED);
- }
-
- lv_obj_add_event_cb(toggle, toggle_bt_cb, LV_EVENT_VALUE_CHANGED, this);
-
- lv_obj_t* devices_label = lv_label_create(content_);
- lv_label_set_text(devices_label, "Devices");
-
- devices_list_ = lv_list_create(content_);
- RefreshDevicesList();
- bt_.SetDeviceDiscovery(true);
-}
-
-Bluetooth::~Bluetooth() {
- bt_.SetDeviceDiscovery(false);
-}
-
-auto Bluetooth::ChangeEnabledState(bool enabled) -> void {
- if (enabled) {
- events::System().RunOnTask([&]() { bt_.Enable(); });
- nvs_.OutputMode(drivers::NvsStorage::Output::kBluetooth);
- } else {
- events::System().RunOnTask([&]() { bt_.Disable(); });
- nvs_.OutputMode(drivers::NvsStorage::Output::kHeadphones);
- }
- events::Audio().Dispatch(audio::OutputModeChanged{});
- RefreshDevicesList();
-}
-
-auto Bluetooth::RefreshDevicesList() -> void {
- if (!bt_.IsEnabled()) {
- // Bluetooth is disabled, so we just clear the list.
- RemoveAllDevices();
- return;
- }
-
- auto devices = bt_.KnownDevices();
- std::optional<drivers::bluetooth::mac_addr_t> preferred_device =
- nvs_.PreferredBluetoothDevice();
-
- // If the user's current selection is within the devices list, then we need
- // to be careful not to rearrange the list items underneath them.
- lv_obj_t* current_selection = lv_group_get_focused(group_);
- bool is_in_list = current_selection != NULL &&
- lv_obj_get_parent(current_selection) == devices_list_;
-
- if (!is_in_list) {
- // The user isn't in the list! We can blow everything away and recreate it
- // without issues.
- RemoveAllDevices();
-
- // First look to see if the user's preferred device is in the list. If it
- // is, we hoist it up to the top of the list.
- if (preferred_device) {
- for (size_t i = 0; i < devices.size(); i++) {
- if (devices[i].address == *preferred_device) {
- AddPreferredDevice(devices[i]);
- devices.erase(devices.begin() + i);
- break;
- }
- }
- }
-
- // The rest of the list is already sorted by signal strength.
- for (const auto& device : devices) {
- AddDevice(device);
- }
- } else {
- // The user's selection is within the device list. We need to work out
- // which devices are new, then add them to the end.
- for (const auto& mac : macs_in_list_) {
- auto pos = std::find_if(
- devices.begin(), devices.end(),
- [&mac](const auto& device) { return device.address == mac; });
-
- if (pos != devices.end()) {
- devices.erase(pos);
- }
- }
-
- // The remaining list is now just the new devices.
- for (const auto& device : devices) {
- if (preferred_device && device.address == *preferred_device) {
- AddPreferredDevice(device);
- } else {
- AddDevice(device);
- }
- }
- }
-}
-
-auto Bluetooth::RemoveAllDevices() -> void {
- while (lv_obj_get_child_cnt(devices_list_) > 0) {
- lv_obj_del(lv_obj_get_child(devices_list_, 0));
- }
- macs_in_list_.clear();
- preferred_device_ = nullptr;
-}
-
-auto Bluetooth::AddPreferredDevice(const drivers::bluetooth::Device& dev)
- -> void {
- preferred_device_ = lv_list_add_btn(devices_list_, NULL, dev.name.c_str());
- lv_obj_t* remove = lv_btn_create(preferred_device_);
- lv_obj_t* remove_icon = lv_label_create(remove);
- lv_label_set_text(remove_icon, "x");
- lv_group_add_obj(group_, remove);
-
- macs_in_list_.push_back(dev.address);
-}
-
-auto Bluetooth::AddDevice(const drivers::bluetooth::Device& dev) -> void {
- lv_obj_t* item = lv_list_add_btn(devices_list_, NULL, dev.name.c_str());
- lv_group_add_obj(group_, item);
- lv_obj_add_event_cb(item, select_device_cb, LV_EVENT_CLICKED, this);
- macs_in_list_.push_back(dev.address);
-}
-
-auto Bluetooth::OnDeviceSelected(ssize_t index) -> void {
- if (index == -1) {
- events::System().RunOnTask([=]() {
- nvs_.PreferredBluetoothDevice({});
- bt_.SetPreferredDevice({});
- });
- RefreshDevicesList();
- return;
- }
-
- // Tell the bluetooth driver that our preference changed.
- auto it = macs_in_list_.begin();
- std::advance(it, index);
- events::System().RunOnTask([=]() { bt_.SetPreferredDevice(*it); });
-
- // Update which devices are selectable.
- if (preferred_device_) {
- lv_group_add_obj(group_, preferred_device_);
- // Bubble the newly added object up to its visible position in the list.
- size_t pos = lv_obj_get_index(preferred_device_);
- while (pos > 0) {
- lv_group_swap_obj(preferred_device_,
- lv_obj_get_child(devices_list_, pos - 1));
- pos--;
- }
- }
-
- preferred_device_ = lv_obj_get_child(devices_list_, index);
- lv_group_remove_obj(preferred_device_);
-}
-
-static void change_vol_limit_cb(lv_event_t* ev) {
- int selected_index = lv_dropdown_get_selected(ev->target);
- Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data);
- instance->ChangeMaxVolume(selected_index);
-}
-
-static void increase_vol_limit_cb(lv_event_t* ev) {
- Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data);
- instance->ChangeCustomVolume(2);
-}
-
-static void decrease_vol_limit_cb(lv_event_t* ev) {
- Headphones* instance = reinterpret_cast<Headphones*>(ev->user_data);
- instance->ChangeCustomVolume(-2);
-}
-
-Headphones::Headphones(models::TopBar& bar, drivers::NvsStorage& nvs)
- : MenuScreen(bar, "Headphones"), nvs_(nvs), custom_limit_(0) {
- uint16_t reference = drivers::wm8523::kLineLevelReferenceVolume;
- index_to_level_.push_back(reference - (10 * 4));
- index_to_level_.push_back(reference + (6 * 4));
- index_to_level_.push_back(reference + (9.5 * 4));
-
- lv_obj_t* vol_label = lv_label_create(content_);
- lv_label_set_text(vol_label, "Volume Limit");
- lv_obj_t* vol_dropdown = lv_dropdown_create(content_);
- lv_obj_set_width(vol_dropdown, lv_pct(100));
- lv_dropdown_set_options(
- vol_dropdown,
- "Line Level (-10 dB)\nCD Level (+6 dB)\nMaximum (+10dB)\nCustom");
- lv_group_add_obj(group_, vol_dropdown);
- themes::Theme::instance()->ApplyStyle(lv_dropdown_get_list(vol_dropdown),
- themes::Style::kPopup);
-
- uint16_t level = nvs.AmpMaxVolume();
- for (int i = 0; i < index_to_level_.size() + 1; i++) {
- if (i == index_to_level_.size() || index_to_level_[i] == level) {
- lv_dropdown_set_selected(vol_dropdown, i);
- break;
- }
- }
-
- lv_obj_add_event_cb(vol_dropdown, change_vol_limit_cb, LV_EVENT_VALUE_CHANGED,
- this);
-
- custom_vol_container_ = settings_container(content_);
-
- lv_obj_t* decrease_btn = lv_btn_create(custom_vol_container_);
- lv_obj_t* btn_label = lv_label_create(decrease_btn);
- lv_label_set_text(btn_label, "-");
- lv_obj_add_event_cb(decrease_btn, decrease_vol_limit_cb, LV_EVENT_CLICKED,
- this);
-
- custom_vol_label_ = lv_label_create(custom_vol_container_);
- UpdateCustomVol(level);
-
- lv_obj_t* increase_btn = lv_btn_create(custom_vol_container_);
- btn_label = lv_label_create(increase_btn);
- lv_label_set_text(btn_label, "+");
- lv_obj_add_event_cb(increase_btn, increase_vol_limit_cb, LV_EVENT_CLICKED,
- this);
-
- if (lv_dropdown_get_selected(vol_dropdown) != index_to_level_.size()) {
- lv_obj_add_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN);
- }
-
- lv_obj_t* spacer = lv_obj_create(content_);
- lv_obj_set_size(spacer, 1, 4);
-
- lv_obj_t* balance_label = lv_label_create(content_);
- lv_label_set_text(balance_label, "Left/Right Balance");
-
- spacer = lv_obj_create(content_);
- lv_obj_set_size(spacer, 1, 4);
-
- lv_obj_t* balance = lv_slider_create(content_);
- lv_obj_set_size(balance, lv_pct(100), 5);
- lv_slider_set_range(balance, -10, 10);
- lv_slider_set_value(balance, 0, LV_ANIM_OFF);
- lv_slider_set_mode(balance, LV_SLIDER_MODE_SYMMETRICAL);
- lv_group_add_obj(group_, balance);
- lv_obj_t* current_balance_label = lv_label_create(content_);
- lv_label_set_text(current_balance_label, "0dB");
- lv_obj_set_size(current_balance_label, lv_pct(100), LV_SIZE_CONTENT);
-
- lv_obj_move_foreground(lv_dropdown_get_list(vol_dropdown));
-}
-
-auto Headphones::ChangeMaxVolume(uint8_t index) -> void {
- if (index >= index_to_level_.size()) {
- lv_obj_clear_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN);
- return;
- }
- auto vol = index_to_level_[index];
- lv_obj_add_flag(custom_vol_container_, LV_OBJ_FLAG_HIDDEN);
- UpdateCustomVol(vol);
- events::Audio().Dispatch(audio::ChangeMaxVolume{.new_max = vol});
-}
-
-auto Headphones::ChangeCustomVolume(int8_t diff) -> void {
- UpdateCustomVol(custom_limit_ + diff);
-}
-
-auto Headphones::UpdateCustomVol(uint16_t level) -> void {
- custom_limit_ = level;
- int16_t db = (static_cast<int32_t>(level) -
- drivers::wm8523::kLineLevelReferenceVolume) /
- 4;
- int16_t db_parts = (static_cast<int32_t>(level) -
- drivers::wm8523::kLineLevelReferenceVolume) %
- 4;
-
- std::ostringstream builder;
- if (db >= 0) {
- builder << "+";
- }
- builder << db << ".";
- builder << (db_parts * 100 / 4);
- builder << " dBV";
-
- lv_label_set_text(custom_vol_label_, builder.str().c_str());
-}
-
-static void change_brightness_cb(lv_event_t* ev) {
- Appearance* instance = reinterpret_cast<Appearance*>(ev->user_data);
- instance->ChangeBrightness(lv_slider_get_value(ev->target));
-}
-
-static void release_brightness_cb(lv_event_t* ev) {
- Appearance* instance = reinterpret_cast<Appearance*>(ev->user_data);
- instance->CommitBrightness();
-}
-
-static auto brightness_str(uint_fast8_t percent) -> std::string {
- return std::to_string(percent) + "%";
-}
-
-Appearance::Appearance(models::TopBar& bar,
- drivers::NvsStorage& nvs,
- drivers::Display& display)
- : MenuScreen(bar, "Appearance"), nvs_(nvs), display_(display) {
- lv_obj_t* toggle_container = settings_container(content_);
- lv_obj_t* toggle_label = lv_label_create(toggle_container);
- lv_obj_set_flex_grow(toggle_label, 1);
- lv_label_set_text(toggle_label, "Dark Mode");
- lv_obj_t* toggle = lv_switch_create(toggle_container);
- lv_group_add_obj(group_, toggle);
-
- uint_fast8_t initial_brightness = nvs_.ScreenBrightness();
-
- lv_obj_t* brightness_label = lv_label_create(content_);
- lv_label_set_text(brightness_label, "Brightness");
- lv_obj_t* brightness = lv_slider_create(content_);
- lv_obj_set_size(brightness, lv_pct(100), 5);
- lv_slider_set_range(brightness, 10, 100);
- lv_slider_set_value(brightness, initial_brightness, LV_ANIM_OFF);
- lv_group_add_obj(group_, brightness);
- current_brightness_label_ = lv_label_create(content_);
- lv_label_set_text(current_brightness_label_,
- brightness_str(initial_brightness).c_str());
- lv_obj_set_size(current_brightness_label_, lv_pct(100), LV_SIZE_CONTENT);
-
- lv_obj_add_event_cb(brightness, change_brightness_cb, LV_EVENT_VALUE_CHANGED,
- this);
- lv_obj_add_event_cb(brightness, release_brightness_cb, LV_EVENT_RELEASED,
- this);
-}
-
-auto Appearance::ChangeBrightness(uint_fast8_t new_level) -> void {
- current_brightness_ = new_level;
- display_.SetBrightness(new_level);
- lv_label_set_text(current_brightness_label_,
- brightness_str(new_level).c_str());
-}
-
-auto Appearance::CommitBrightness() -> void {
- nvs_.ScreenBrightness(current_brightness_);
-}
-
-InputMethod::InputMethod(models::TopBar& bar, drivers::NvsStorage& nvs)
- : MenuScreen(bar, "Input Method"), nvs_(nvs) {
- lv_obj_t* primary_label = lv_label_create(content_);
- lv_label_set_text(primary_label, "Control scheme");
- lv_obj_t* primary_dropdown = lv_dropdown_create(content_);
- lv_dropdown_set_options(
- primary_dropdown,
- "Side buttons only\nButtons and touch\nD-Pad\nClickwheel");
- lv_group_add_obj(group_, primary_dropdown);
-
- lv_dropdown_set_selected(primary_dropdown,
- static_cast<uint16_t>(nvs.PrimaryInput()));
- themes::Theme::instance()->ApplyStyle(lv_dropdown_get_list(primary_dropdown),
- themes::Style::kPopup);
-
- lv_bind(primary_dropdown, LV_EVENT_VALUE_CHANGED, [this](lv_obj_t* obj) {
- drivers::NvsStorage::InputModes mode;
- switch (lv_dropdown_get_selected(obj)) {
- case 0:
- mode = drivers::NvsStorage::InputModes::kButtonsOnly;
- break;
- case 1:
- mode = drivers::NvsStorage::InputModes::kButtonsWithWheel;
- break;
- case 2:
- mode = drivers::NvsStorage::InputModes::kDirectionalWheel;
- break;
- case 3:
- mode = drivers::NvsStorage::InputModes::kRotatingWheel;
- break;
- default:
- return;
- }
- nvs_.PrimaryInput(mode);
- events::Ui().Dispatch(internal::ControlSchemeChanged{});
- });
-}
-
-Storage::Storage(models::TopBar& bar) : MenuScreen(bar, "Storage") {
- label_pair(content_, "Storage Capacity:", "32 GiB");
- label_pair(content_, "Currently Used:", "6 GiB");
- label_pair(content_, "DB Size:", "1.2 MiB");
-
- lv_obj_t* usage_bar = lv_bar_create(content_);
- lv_bar_set_range(usage_bar, 0, 32);
- lv_bar_set_value(usage_bar, 6, LV_ANIM_OFF);
-
- lv_obj_t* container = lv_obj_create(content_);
- lv_obj_set_size(container, lv_pct(100), 30);
- lv_obj_set_layout(container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(container, LV_FLEX_ALIGN_SPACE_EVENLY,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- lv_obj_t* reset_btn = lv_btn_create(container);
- lv_obj_t* reset_label = lv_label_create(reset_btn);
- lv_label_set_text(reset_label, "Update Database");
- lv_group_add_obj(group_, reset_btn);
- themes::Theme::instance()->ApplyStyle(reset_btn,
- themes::Style::kButtonPrimary);
-
- lv_bind(reset_btn, LV_EVENT_CLICKED, [&](lv_obj_t*) {
- events::Ui().Dispatch(internal::ReindexDatabase{});
- });
-}
-
-FirmwareUpdate::FirmwareUpdate(models::TopBar& bar, drivers::Samd& samd)
- : MenuScreen(bar, "Firmware Update") {
- lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER);
-
- auto samd_ver = samd.Version();
- label_pair(content_, "SAMD21 FW:", {samd_ver.data(), samd_ver.size()});
-
- lv_obj_t* spacer = lv_obj_create(content_);
- lv_obj_set_size(spacer, 1, 4);
-
- lv_obj_t* flash_esp_btn = lv_btn_create(content_);
- lv_obj_t* flash_esp_label = lv_label_create(flash_esp_btn);
- lv_label_set_text(flash_esp_label, "Update");
- lv_group_add_obj(group_, flash_esp_btn);
- themes::Theme::instance()->ApplyStyle(flash_esp_btn,
- themes::Style::kButtonPrimary);
-
- spacer = lv_obj_create(content_);
- lv_obj_set_size(spacer, 1, 8);
-
- auto desc = esp_app_get_description();
- label_pair(content_, "ESP32 FW:", desc->version);
-
- spacer = lv_obj_create(content_);
- lv_obj_set_size(spacer, 1, 4);
-
- lv_obj_t* flash_samd_btn = lv_btn_create(content_);
- lv_obj_t* flash_samd_label = lv_label_create(flash_samd_btn);
- lv_label_set_text(flash_samd_label, "Update");
- lv_group_add_obj(group_, flash_samd_btn);
- themes::Theme::instance()->ApplyStyle(flash_samd_btn,
- themes::Style::kButtonPrimary);
-}
-
-About::About(models::TopBar& bar) : MenuScreen(bar, "About") {
- lv_obj_t* label = lv_label_create(content_);
- lv_label_set_text(label, "Some licenses or whatever");
-}
-
-} // namespace screens
-} // namespace ui
diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp
index 75327a58..adda1c18 100644
--- a/src/ui/ui_fsm.cpp
+++ b/src/ui/ui_fsm.cpp
@@ -33,15 +33,11 @@
#include "event_queue.hpp"
#include "gpios.hpp"
#include "lvgl_task.hpp"
-#include "modal_confirm.hpp"
-#include "modal_progress.hpp"
-#include "model_playback.hpp"
#include "nvs.hpp"
#include "property.hpp"
#include "relative_wheel.hpp"
#include "screen.hpp"
#include "screen_lua.hpp"
-#include "screen_settings.hpp"
#include "screen_splash.hpp"
#include "spiffs.hpp"
#include "storage.hpp"
@@ -66,13 +62,6 @@ std::shared_ptr<Screen> UiState::sCurrentScreen;
std::shared_ptr<Modal> UiState::sCurrentModal;
std::shared_ptr<lua::LuaThread> UiState::sLua;
-std::weak_ptr<screens::Bluetooth> UiState::bluetooth_screen_;
-
-models::Playback UiState::sPlaybackModel;
-models::TopBar UiState::sTopBarModel{{},
- UiState::sPlaybackModel.is_playing,
- UiState::sPlaybackModel.current_track};
-
static TimerHandle_t sAlertTimer;
static lv_obj_t* sAlertContainer;
@@ -80,6 +69,70 @@ 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};
+lua::Property UiState::sBluetoothConnected{false};
+
+lua::Property UiState::sPlaybackPlaying{
+ false, [](const lua::LuaValue& val) {
+ bool current_val = std::get<bool>(sPlaybackPlaying.Get());
+ if (!std::holds_alternative<bool>(val)) {
+ return false;
+ }
+ bool new_val = std::get<bool>(val);
+ if (current_val != new_val) {
+ events::Audio().Dispatch(audio::TogglePlayPause{});
+ }
+ return true;
+ }};
+
+lua::Property UiState::sPlaybackTrack{0};
+lua::Property UiState::sPlaybackPosition{0};
+
+lua::Property UiState::sQueuePosition{0};
+lua::Property UiState::sQueueSize{0};
+lua::Property UiState::sQueueRepeat{false};
+lua::Property UiState::sQueueRandom{false};
+
+lua::Property UiState::sVolumeCurrentPct{
+ 0, [](const lua::LuaValue& val) {
+ events::Audio().Dispatch(audio::SetVolume{
+ .percent = {},
+ .db = 0,
+ });
+ return false;
+ }};
+lua::Property UiState::sVolumeCurrentDb{
+ 0, [](const lua::LuaValue& val) {
+ events::Audio().Dispatch(audio::SetVolume{
+ .percent = {},
+ .db = 0,
+ });
+ return false;
+ }};
+lua::Property UiState::sVolumeLeftBias{
+ 0, [](const lua::LuaValue& val) {
+ events::Audio().Dispatch(audio::SetVolumeBalance{
+ .left_bias = 0,
+ });
+ return false;
+ }};
+lua::Property UiState::sVolumeLimit{
+ 0, [](const lua::LuaValue& val) {
+ events::Audio().Dispatch(audio::SetVolumeLimit{
+ .new_limit = 0,
+ });
+ return false;
+ }};
+
+lua::Property UiState::sDisplayBrightness{0, [](const lua::LuaValue& val) {
+ sDisplay->SetBrightness(0);
+ return false;
+ }};
+
auto UiState::InitBootSplash(drivers::IGpios& gpios) -> bool {
// Init LVGL first, since the display driver registers itself with LVGL.
lv_init();
@@ -118,30 +171,59 @@ void UiState::react(const system_fsm::KeyLockChanged& ev) {
sInput->lock(ev.locking);
}
-void UiState::react(const system_fsm::BatteryStateChanged& ev) {
- sTopBarModel.battery_state.set(ev.new_state);
+void UiState::react(const internal::ControlSchemeChanged&) {
+ if (!sInput) {
+ return;
+ }
+ sInput->mode(sServices->nvs().PrimaryInput());
}
-void UiState::react(const audio::PlaybackStarted&) {}
-
-void UiState::react(const audio::PlaybackFinished&) {}
+void UiState::react(const internal::DismissAlerts&) {
+ lv_obj_clean(sAlertContainer);
+}
-void UiState::react(const audio::PlaybackUpdate& ev) {}
+void UiState::react(const system_fsm::BatteryStateChanged& ev) {
+ sBatteryPct.Update(static_cast<int>(ev.new_state.percent));
+ sBatteryMv.Update(static_cast<int>(ev.new_state.millivolts));
+}
void UiState::react(const audio::QueueUpdate&) {
auto& queue = sServices->track_queue();
- sPlaybackModel.current_track.set(queue.current());
-}
+ sQueueSize.Update(static_cast<int>(queue.totalSize()));
-void UiState::react(const internal::ControlSchemeChanged&) {
- if (!sInput) {
- return;
+ int current_pos = queue.currentPosition();
+ if (queue.current()) {
+ current_pos++;
}
- sInput->mode(sServices->nvs().PrimaryInput());
+ sQueuePosition.Update(current_pos);
+ sQueueRandom.Update(queue.random());
+ sQueueRepeat.Update(queue.repeat());
}
-void UiState::react(const internal::DismissAlerts&) {
- lv_obj_clean(sAlertContainer);
+void UiState::react(const audio::PlaybackStarted& ev) {
+ sPlaybackPlaying.Update(true);
+}
+
+void UiState::react(const audio::PlaybackUpdate& ev) {
+ sPlaybackTrack.Update(*ev.track);
+ sPlaybackPosition.Update(static_cast<int>(ev.seconds_elapsed));
+}
+
+void UiState::react(const audio::PlaybackFinished&) {
+ sPlaybackPlaying.Update(false);
+}
+
+void UiState::react(const audio::VolumeChanged& ev) {
+ sVolumeCurrentPct.Update(static_cast<int>(ev.percent));
+ sVolumeCurrentDb.Update(static_cast<int>(ev.db));
+}
+
+void UiState::react(const audio::VolumeBalanceChanged& ev) {
+ sVolumeLeftBias.Update(ev.left_bias);
+}
+
+void UiState::react(const audio::VolumeLimitChanged& ev) {
+ sVolumeLeftBias.Update(ev.new_limit);
}
namespace states {
@@ -185,57 +267,36 @@ void Lua::entry() {
alert_timer_callback);
sAlertContainer = lv_obj_create(sCurrentScreen->alert());
- auto bat =
- sServices->battery().State().value_or(battery::Battery::BatteryState{});
- battery_pct_ =
- std::make_shared<lua::Property>(static_cast<int>(bat.percent));
- battery_mv_ =
- std::make_shared<lua::Property>(static_cast<int>(bat.millivolts));
- battery_charging_ = std::make_shared<lua::Property>(bat.is_charging);
-
- bluetooth_en_ = std::make_shared<lua::Property>(false);
-
- queue_position_ = std::make_shared<lua::Property>(0);
- queue_size_ = std::make_shared<lua::Property>(0);
- queue_repeat_ = std::make_shared<lua::Property>(false);
- queue_random_ = std::make_shared<lua::Property>(false);
-
- playback_playing_ = std::make_shared<lua::Property>(
- false, [&](const lua::LuaValue& val) { return SetPlaying(val); });
- playback_track_ = std::make_shared<lua::Property>();
- playback_position_ = std::make_shared<lua::Property>();
-
- volume_current_pct_ = std::make_shared<lua::Property>(0);
- volume_current_db_ = std::make_shared<lua::Property>(0);
-
sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content()));
sLua->bridge().AddPropertyModule("power",
{
- {"battery_pct", battery_pct_},
- {"battery_millivolts", battery_mv_},
- {"plugged_in", battery_charging_},
+ {"battery_pct", &sBatteryPct},
+ {"battery_millivolts", &sBatteryMv},
+ {"plugged_in", &sBatteryCharging},
});
sLua->bridge().AddPropertyModule("bluetooth",
{
- {"enabled", bluetooth_en_},
- {"connected", bluetooth_en_},
+ {"enabled", &sBluetoothEnabled},
+ {"connected", &sBluetoothConnected},
});
sLua->bridge().AddPropertyModule("playback",
{
- {"playing", playback_playing_},
- {"track", playback_track_},
- {"position", playback_position_},
+ {"playing", &sPlaybackPlaying},
+ {"track", &sPlaybackTrack},
+ {"position", &sPlaybackPosition},
});
sLua->bridge().AddPropertyModule("queue", {
- {"position", queue_position_},
- {"size", queue_size_},
- {"replay", queue_repeat_},
- {"random", queue_random_},
+ {"position", &sQueuePosition},
+ {"size", &sQueueSize},
+ {"replay", &sQueueRepeat},
+ {"random", &sQueueRandom},
});
sLua->bridge().AddPropertyModule("volume",
{
- {"current_pct", volume_current_pct_},
- {"current_db", volume_current_db_},
+ {"current_pct", &sVolumeCurrentPct},
+ {"current_db", &sVolumeCurrentDb},
+ {"left_bias", &sVolumeLeftBias},
+ {"limit_db", &sVolumeLimit},
});
sLua->bridge().AddPropertyModule(
@@ -288,18 +349,6 @@ auto Lua::PopLuaScreen(lua_State* s) -> int {
return 0;
}
-auto Lua::SetPlaying(const lua::LuaValue& val) -> bool {
- bool current_val = std::get<bool>(playback_playing_->Get());
- if (!std::holds_alternative<bool>(val)) {
- return false;
- }
- bool new_val = std::get<bool>(val);
- if (current_val != new_val) {
- events::Audio().Dispatch(audio::TogglePlayPause{});
- }
- return true;
-}
-
auto Lua::ShowAlert(lua_State* s) -> int {
if (!sCurrentScreen) {
return 0;
@@ -358,150 +407,10 @@ void Lua::react(const OnLuaError& err) {
ESP_LOGE("lua", "%s", err.message.c_str());
}
-void Lua::react(const internal::ShowSettingsPage& ev) {
- PushScreen(std::shared_ptr<Screen>(new screens::Settings(sTopBarModel)));
- transit<Browse>();
-}
-
-void Lua::react(const system_fsm::BatteryStateChanged& ev) {
- battery_pct_->Update(static_cast<int>(ev.new_state.percent));
- battery_mv_->Update(static_cast<int>(ev.new_state.millivolts));
-}
-
-void Lua::react(const audio::QueueUpdate&) {
- auto& queue = sServices->track_queue();
- queue_size_->Update(static_cast<int>(queue.totalSize()));
-
- int current_pos = queue.currentPosition();
- if (queue.current()) {
- current_pos++;
- }
- queue_position_->Update(current_pos);
- queue_random_->Update(queue.random());
- queue_repeat_->Update(queue.repeat());
-}
-
-void Lua::react(const audio::PlaybackStarted& ev) {
- playback_playing_->Update(true);
-}
-
-void Lua::react(const audio::PlaybackUpdate& ev) {
- playback_track_->Update(*ev.track);
- playback_position_->Update(static_cast<int>(ev.seconds_elapsed));
-}
-
-void Lua::react(const audio::PlaybackFinished&) {
- playback_playing_->Update(false);
-}
-
-void Lua::react(const audio::VolumeChanged& ev) {
- volume_current_pct_->Update(static_cast<int>(ev.percent));
- volume_current_db_->Update(static_cast<int>(ev.db));
-}
-
void Lua::react(const internal::BackPressed& ev) {
PopLuaScreen(sLua->state());
}
-void Browse::entry() {}
-
-void Browse::react(const internal::ShowSettingsPage& ev) {
- std::shared_ptr<Screen> screen;
- std::shared_ptr<screens::Bluetooth> bt_screen;
- switch (ev.page) {
- case internal::ShowSettingsPage::Page::kRoot:
- screen.reset(new screens::Settings(sTopBarModel));
- break;
- case internal::ShowSettingsPage::Page::kBluetooth:
- bt_screen = std::make_shared<screens::Bluetooth>(
- sTopBarModel, sServices->bluetooth(), sServices->nvs());
- screen = bt_screen;
- bluetooth_screen_ = bt_screen;
- break;
- case internal::ShowSettingsPage::Page::kHeadphones:
- screen.reset(new screens::Headphones(sTopBarModel, sServices->nvs()));
- break;
- case internal::ShowSettingsPage::Page::kAppearance:
- screen.reset(
- new screens::Appearance(sTopBarModel, sServices->nvs(), *sDisplay));
- break;
- case internal::ShowSettingsPage::Page::kInput:
- screen.reset(new screens::InputMethod(sTopBarModel, sServices->nvs()));
- break;
- case internal::ShowSettingsPage::Page::kStorage:
- screen.reset(new screens::Storage(sTopBarModel));
- break;
- case internal::ShowSettingsPage::Page::kFirmwareUpdate:
- screen.reset(
- new screens::FirmwareUpdate(sTopBarModel, sServices->samd()));
- break;
- case internal::ShowSettingsPage::Page::kAbout:
- screen.reset(new screens::About(sTopBarModel));
- break;
- }
- if (screen) {
- PushScreen(screen);
- }
-}
-
-void Browse::react(const internal::BackPressed& ev) {
- if (PopScreen() == 0) {
- transit<Lua>();
- }
-}
-
-void Browse::react(const system_fsm::BluetoothDevicesChanged&) {
- auto bt = bluetooth_screen_.lock();
- if (bt) {
- bt->RefreshDevicesList();
- }
-}
-
-void Browse::react(const internal::ReindexDatabase& ev) {
- transit<Indexing>();
-}
-
-static std::shared_ptr<modals::Progress> sIndexProgress;
-
-void Indexing::entry() {
- sIndexProgress.reset(new modals::Progress(sCurrentScreen.get(), "Indexing",
- "Preparing database"));
- sCurrentModal = sIndexProgress;
- auto db = sServices->database().lock();
- if (!db) {
- // TODO: Hmm.
- return;
- }
- sServices->bg_worker().Dispatch<void>([=]() { db->updateIndexes(); });
-}
-
-void Indexing::exit() {
- sCurrentModal.reset();
- sIndexProgress.reset();
-}
-
-void Indexing::react(const database::event::UpdateStarted&) {}
-
-void Indexing::react(const database::event::UpdateProgress& ev) {
- std::ostringstream str;
- switch (ev.stage) {
- case database::event::UpdateProgress::Stage::kVerifyingExistingTracks:
- sIndexProgress->title("Verifying");
- str << "Tracks checked: " << ev.val;
- sIndexProgress->subtitle(str.str().c_str());
- break;
- case database::event::UpdateProgress::Stage::kScanningForNewTracks:
- sIndexProgress->title("Scanning");
- str << "Files checked: " << ev.val;
- sIndexProgress->subtitle(str.str().c_str());
- break;
- }
-}
-
-void Indexing::react(const database::event::UpdateFinished&) {
- transit<Browse>();
-}
-
} // namespace states
} // namespace ui
diff --git a/src/ui/widget_top_bar.cpp b/src/ui/widget_top_bar.cpp
deleted file mode 100644
index fbad5548..00000000
--- a/src/ui/widget_top_bar.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include "widget_top_bar.hpp"
-#include "battery.hpp"
-#include "core/lv_group.h"
-#include "core/lv_obj.h"
-#include "event_queue.hpp"
-#include "extra/layouts/flex/lv_flex.h"
-#include "font/lv_symbol_def.h"
-#include "model_top_bar.hpp"
-#include "themes.hpp"
-#include "track.hpp"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widgets/lv_img.h"
-#include "widgets/lv_label.h"
-
-namespace ui {
-namespace widgets {
-
-static void back_click_cb(lv_event_t* ev) {
- events::Ui().Dispatch(internal::BackPressed{});
-}
-
-TopBar::TopBar(lv_obj_t* parent,
- const Configuration& config,
- models::TopBar& model) {
- container_ = lv_obj_create(parent);
- lv_obj_set_size(container_, lv_pct(100), 20);
- lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(container_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_END);
- lv_obj_set_style_pad_column(container_, 5, LV_PART_MAIN);
-
- if (config.show_back_button) {
- back_button_ = lv_btn_create(container_);
- lv_obj_set_size(back_button_, LV_SIZE_CONTENT, 12);
- lv_obj_t* button_icon = lv_label_create(back_button_);
- lv_label_set_text(button_icon, "<");
- lv_obj_add_event_cb(back_button_, back_click_cb, LV_EVENT_CLICKED, NULL);
- lv_obj_center(button_icon);
- } else {
- back_button_ = nullptr;
- }
-
- lv_obj_t* title_ = lv_label_create(container_);
- lv_obj_set_height(title_, 17);
- lv_obj_set_flex_grow(title_, 1);
-
- lv_label_set_text(title_, config.title.c_str());
- lv_label_set_long_mode(title_, LV_LABEL_LONG_DOT);
-
- themes::Theme::instance()->ApplyStyle(container_, themes::Style::kTopBar);
-}
-
-} // namespace widgets
-} // namespace ui