diff options
Diffstat (limited to 'src/ui/screen_settings.cpp')
| -rw-r--r-- | src/ui/screen_settings.cpp | 575 |
1 files changed, 0 insertions, 575 deletions
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 |
