From 7cdcd44e0ca10ebdc796638190ed1d9b45d99ef0 Mon Sep 17 00:00:00 2001 From: jacqueline Date: Mon, 15 Jan 2024 12:31:20 +1100 Subject: Begin migration of remaining screens to Lua --- src/ui/CMakeLists.txt | 6 +- src/ui/event_binding.cpp | 23 -- src/ui/include/event_binding.hpp | 30 -- src/ui/include/modal.hpp | 10 - src/ui/include/modal_confirm.hpp | 29 -- src/ui/include/modal_progress.hpp | 34 --- src/ui/include/model_playback.hpp | 26 -- src/ui/include/model_top_bar.hpp | 26 -- src/ui/include/screen.hpp | 35 --- src/ui/include/screen_settings.hpp | 116 -------- src/ui/include/ui_events.hpp | 15 - src/ui/include/ui_fsm.hpp | 104 +++---- src/ui/include/widget_top_bar.hpp | 45 --- src/ui/lvgl_task.cpp | 4 - src/ui/modal.cpp | 4 +- src/ui/modal_confirm.cpp | 76 ----- src/ui/modal_progress.cpp | 67 ----- src/ui/screen.cpp | 41 --- src/ui/screen_settings.cpp | 575 ------------------------------------- src/ui/ui_fsm.cpp | 337 ++++++++-------------- src/ui/widget_top_bar.cpp | 61 ---- 21 files changed, 160 insertions(+), 1504 deletions(-) delete mode 100644 src/ui/event_binding.cpp delete mode 100644 src/ui/include/event_binding.hpp delete mode 100644 src/ui/include/modal_confirm.hpp delete mode 100644 src/ui/include/modal_progress.hpp delete mode 100644 src/ui/include/model_playback.hpp delete mode 100644 src/ui/include/model_top_bar.hpp delete mode 100644 src/ui/include/screen_settings.hpp delete mode 100644 src/ui/include/widget_top_bar.hpp delete mode 100644 src/ui/modal_confirm.cpp delete mode 100644 src/ui/modal_progress.cpp delete mode 100644 src/ui/screen_settings.cpp delete mode 100644 src/ui/widget_top_bar.cpp (limited to 'src/ui') 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 - * - * 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(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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include - -#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& { return signal_; } - - private: - lv_obj_t* obj_; - nod::signal 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> event_bindings_; - - template - auto lv_bind(lv_obj_t* obj, lv_event_code_t ev, T fn) -> void { - auto binding = std::make_unique(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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include - -#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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include - -#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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "bindey/property.h" - -#include "track.hpp" - -namespace ui { -namespace models { - -struct Playback { - bindey::property is_playing; - bindey::property> current_track; - bindey::property> upcoming_tracks; - - bindey::property current_track_position; - bindey::property 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 - * - * 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_state; - - // Shared with the Playback model - bindey::property& is_playing; - bindey::property>& 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 #include -#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 data_bindings_; - std::pmr::vector> event_bindings_; - - template - auto lv_bind(lv_obj_t* obj, lv_event_code_t ev, T fn) -> void { - auto binding = std::make_unique(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 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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include -#include -#include -#include - -#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 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 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 { 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 { static std::shared_ptr sCurrentModal; static std::shared_ptr sLua; - static std::weak_ptr 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 battery_pct_; - std::shared_ptr battery_mv_; - std::shared_ptr battery_charging_; - - std::shared_ptr bluetooth_en_; - - std::shared_ptr playback_playing_; - std::shared_ptr playback_track_; - std::shared_ptr playback_position_; - - std::shared_ptr queue_position_; - std::shared_ptr queue_size_; - std::shared_ptr queue_repeat_; - std::shared_ptr queue_random_; - - std::shared_ptr volume_current_pct_; - std::shared_ptr 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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include -#include - -#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 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(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 - * - * 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 - * - * 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(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 - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "screen_settings.hpp" -#include -#include - -#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(reinterpret_cast(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(static_cast(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(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(ev->user_data); - instance->OnDeviceSelected(lv_obj_get_index(ev->target)); -} - -static auto remove_preferred_cb(lv_event_t* ev) { - Bluetooth* instance = reinterpret_cast(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 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(ev->user_data); - instance->ChangeMaxVolume(selected_index); -} - -static void increase_vol_limit_cb(lv_event_t* ev) { - Headphones* instance = reinterpret_cast(ev->user_data); - instance->ChangeCustomVolume(2); -} - -static void decrease_vol_limit_cb(lv_event_t* ev) { - Headphones* instance = reinterpret_cast(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(level) - - drivers::wm8523::kLineLevelReferenceVolume) / - 4; - int16_t db_parts = (static_cast(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(ev->user_data); - instance->ChangeBrightness(lv_slider_get_value(ev->target)); -} - -static void release_brightness_cb(lv_event_t* ev) { - Appearance* instance = reinterpret_cast(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(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 UiState::sCurrentScreen; std::shared_ptr UiState::sCurrentModal; std::shared_ptr UiState::sLua; -std::weak_ptr 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(sPlaybackPlaying.Get()); + if (!std::holds_alternative(val)) { + return false; + } + bool new_val = std::get(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(ev.new_state.percent)); + sBatteryMv.Update(static_cast(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(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(ev.seconds_elapsed)); +} + +void UiState::react(const audio::PlaybackFinished&) { + sPlaybackPlaying.Update(false); +} + +void UiState::react(const audio::VolumeChanged& ev) { + sVolumeCurrentPct.Update(static_cast(ev.percent)); + sVolumeCurrentDb.Update(static_cast(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(static_cast(bat.percent)); - battery_mv_ = - std::make_shared(static_cast(bat.millivolts)); - battery_charging_ = std::make_shared(bat.is_charging); - - bluetooth_en_ = std::make_shared(false); - - queue_position_ = std::make_shared(0); - queue_size_ = std::make_shared(0); - queue_repeat_ = std::make_shared(false); - queue_random_ = std::make_shared(false); - - playback_playing_ = std::make_shared( - false, [&](const lua::LuaValue& val) { return SetPlaying(val); }); - playback_track_ = std::make_shared(); - playback_position_ = std::make_shared(); - - volume_current_pct_ = std::make_shared(0); - volume_current_db_ = std::make_shared(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(playback_playing_->Get()); - if (!std::holds_alternative(val)) { - return false; - } - bool new_val = std::get(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(new screens::Settings(sTopBarModel))); - transit(); -} - -void Lua::react(const system_fsm::BatteryStateChanged& ev) { - battery_pct_->Update(static_cast(ev.new_state.percent)); - battery_mv_->Update(static_cast(ev.new_state.millivolts)); -} - -void Lua::react(const audio::QueueUpdate&) { - auto& queue = sServices->track_queue(); - queue_size_->Update(static_cast(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(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(ev.percent)); - volume_current_db_->Update(static_cast(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; - std::shared_ptr 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( - 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(); - } -} - -void Browse::react(const system_fsm::BluetoothDevicesChanged&) { - auto bt = bluetooth_screen_.lock(); - if (bt) { - bt->RefreshDevicesList(); - } -} - -void Browse::react(const internal::ReindexDatabase& ev) { - transit(); -} - -static std::shared_ptr 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([=]() { 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(); -} - } // 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 - * - * 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 -- cgit v1.2.3