diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index 1b4a34aa..c69d1484 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -4,6 +4,7 @@ idf_component_register( SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "screen_menu.cpp" "wheel_encoder.cpp" "screen_track_browser.cpp" "screen_playing.cpp" "themes.cpp" + "widget_top_bar.cpp" "splash.c" "font_fusion.c" "font_symbols.c" INCLUDE_DIRS "include" REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer") diff --git a/src/ui/include/screen_track_browser.hpp b/src/ui/include/screen_track_browser.hpp index 95fb80e2..af80f29c 100644 --- a/src/ui/include/screen_track_browser.hpp +++ b/src/ui/include/screen_track_browser.hpp @@ -49,6 +49,7 @@ class TrackBrowser : public Screen { -> std::optional; std::weak_ptr db_; + lv_obj_t* back_button_; lv_obj_t* list_; lv_obj_t* loading_indicator_; diff --git a/src/ui/include/ui_events.hpp b/src/ui/include/ui_events.hpp index fabc56fc..cc7db349 100644 --- a/src/ui/include/ui_events.hpp +++ b/src/ui/include/ui_events.hpp @@ -32,6 +32,8 @@ struct IndexSelected : tinyfsm::Event { database::IndexInfo index; }; +struct BackPressed : tinyfsm::Event {}; + } // namespace internal } // namespace ui diff --git a/src/ui/include/ui_fsm.hpp b/src/ui/include/ui_fsm.hpp index 2fc6db4e..20e5beb1 100644 --- a/src/ui/include/ui_fsm.hpp +++ b/src/ui/include/ui_fsm.hpp @@ -48,6 +48,7 @@ class UiState : public tinyfsm::Fsm { virtual void react(const internal::RecordSelected&) {} virtual void react(const internal::IndexSelected&) {} + virtual void react(const internal::BackPressed&) {} virtual void react(const system_fsm::DisplayReady&) {} virtual void react(const system_fsm::BootComplete&) {} @@ -83,6 +84,7 @@ class Browse : public UiState { void react(const internal::RecordSelected&) override; void react(const internal::IndexSelected&) override; + void react(const internal::BackPressed&) override; void react(const system_fsm::KeyLockChanged&) override; void react(const system_fsm::StorageMounted&) override; @@ -93,6 +95,8 @@ class Playing : public UiState { void entry() override; void exit() override; + void react(const internal::BackPressed&) override; + void react(const audio::PlaybackStarted&) override; void react(const audio::PlaybackUpdate&) override; void react(const audio::QueueUpdate&) override; diff --git a/src/ui/include/widget_top_bar.hpp b/src/ui/include/widget_top_bar.hpp new file mode 100644 index 00000000..9019807f --- /dev/null +++ b/src/ui/include/widget_top_bar.hpp @@ -0,0 +1,30 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include + +#include "lvgl.h" + +namespace ui { + +namespace widgets { + +class TopBar { + public: + TopBar(lv_obj_t* parent, lv_group_t* group); + + auto set_title(const std::string&) -> void; + + lv_obj_t* container_; + lv_obj_t* title_; + lv_obj_t* back_button_; +}; + +} // namespace widgets + +} // namespace ui diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp index 6414f2f8..85200c4d 100644 --- a/src/ui/screen_playing.cpp +++ b/src/ui/screen_playing.cpp @@ -37,6 +37,7 @@ #include "track.hpp" #include "ui_events.hpp" #include "ui_fsm.hpp" +#include "widget_top_bar.hpp" #include "widgets/lv_btn.h" #include "widgets/lv_img.h" #include "widgets/lv_label.h" @@ -114,6 +115,8 @@ Playing::Playing(std::weak_ptr db, audio::TrackQueue* queue) lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); + lv_obj_set_scrollbar_mode(root_, LV_SCROLLBAR_MODE_OFF); + lv_obj_t* above_fold_container = lv_obj_create(root_); lv_obj_set_layout(above_fold_container, LV_LAYOUT_FLEX); lv_obj_set_size(above_fold_container, lv_pct(100), lv_disp_get_ver_res(NULL)); @@ -121,11 +124,8 @@ Playing::Playing(std::weak_ptr db, audio::TrackQueue* queue) lv_obj_set_flex_align(above_fold_container, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START); - lv_obj_t* back_button = lv_btn_create(above_fold_container); - lv_obj_set_size(back_button, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_t* back_label = lv_label_create(back_button); - lv_label_set_text(back_label, "<"); - lv_group_add_obj(group_, back_button); + widgets::TopBar top_bar(above_fold_container, group_); + top_bar.set_title("Now Playing"); lv_obj_t* info_container = lv_obj_create(above_fold_container); lv_obj_set_layout(info_container, LV_LAYOUT_FLEX); diff --git a/src/ui/screen_track_browser.cpp b/src/ui/screen_track_browser.cpp index 4154e7c1..8f8321d7 100644 --- a/src/ui/screen_track_browser.cpp +++ b/src/ui/screen_track_browser.cpp @@ -31,6 +31,7 @@ #include "screen_track_browser.hpp" #include "ui_events.hpp" #include "ui_fsm.hpp" +#include "widget_top_bar.hpp" #include "widgets/lv_label.h" static constexpr char kTag[] = "browser"; @@ -70,29 +71,17 @@ TrackBrowser::TrackBrowser( lv_obj_set_layout(root_, LV_LAYOUT_FLEX); lv_obj_set_size(root_, lv_pct(100), lv_pct(100)); lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, - LV_FLEX_ALIGN_START); + lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, + LV_FLEX_ALIGN_CENTER); // The default scrollbar is deceptive because we load in items progressively. lv_obj_set_scrollbar_mode(root_, LV_SCROLLBAR_MODE_OFF); // Wrapping behaves in surprising ways, again due to progressing loading. lv_group_set_wrap(group_, false); - lv_obj_t* header = lv_obj_create(root_); - lv_obj_set_size(header, lv_pct(100), 15); - lv_obj_set_flex_flow(header, LV_FLEX_FLOW_ROW); - lv_obj_set_flex_align(header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, - LV_FLEX_ALIGN_CENTER); - - lv_obj_t* title_label = lv_label_create(header); - lv_label_set_text(title_label, title.c_str()); - lv_obj_set_flex_grow(title_label, 1); - - lv_obj_t* playback_label = lv_label_create(header); - lv_label_set_text(playback_label, LV_SYMBOL_PAUSE); - - lv_obj_t* battery_label = lv_label_create(header); - lv_label_set_text(battery_label, LV_SYMBOL_BATTERY_2); + widgets::TopBar top_bar(root_, group_); + top_bar.set_title(title); + back_button_ = top_bar.back_button_; list_ = lv_list_create(root_); lv_obj_set_width(list_, lv_pct(100)); @@ -209,6 +198,7 @@ auto TrackBrowser::AddResults(Position pos, } lv_group_remove_all_objs(group_); + lv_group_add_obj(group_, back_button_); int num_children = lv_obj_get_child_cnt(list_); for (int i = 0; i < num_children; i++) { lv_group_add_obj(group_, lv_obj_get_child(list_, i)); diff --git a/src/ui/ui_fsm.cpp b/src/ui/ui_fsm.cpp index d2d0ad59..96949cd0 100644 --- a/src/ui/ui_fsm.cpp +++ b/src/ui/ui_fsm.cpp @@ -20,6 +20,7 @@ #include "system_events.hpp" #include "touchwheel.hpp" #include "track_queue.hpp" +#include "ui_events.hpp" namespace ui { @@ -147,6 +148,10 @@ void Browse::react(const internal::IndexSelected& ev) { std::move(query))); } +void Browse::react(const internal::BackPressed& ev) { + PopScreen(); +} + static std::shared_ptr sPlayingScreen; void Playing::entry() { @@ -171,6 +176,10 @@ void Playing::react(const audio::QueueUpdate& ev) { sPlayingScreen->OnQueueUpdate(); } +void Playing::react(const internal::BackPressed& ev) { + transit(); +} + } // namespace states } // namespace ui diff --git a/src/ui/widget_top_bar.cpp b/src/ui/widget_top_bar.cpp new file mode 100644 index 00000000..62c05a25 --- /dev/null +++ b/src/ui/widget_top_bar.cpp @@ -0,0 +1,54 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "widget_top_bar.hpp" +#include "core/lv_group.h" +#include "core/lv_obj.h" +#include "event_queue.hpp" +#include "extra/layouts/flex/lv_flex.h" +#include "ui_events.hpp" +#include "ui_fsm.hpp" +#include "widgets/lv_img.h" + +namespace ui { +namespace widgets { + +static void back_click_cb(lv_event_t* ev) { + events::Dispatch({}); +} + +TopBar::TopBar(lv_obj_t* parent, lv_group_t* group) { + container_ = lv_obj_create(parent); + lv_obj_set_size(container_, lv_pct(100), 14); + lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); + lv_obj_set_flex_align(container_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, + LV_FLEX_ALIGN_END); + + back_button_ = lv_btn_create(container_); + lv_obj_set_size(back_button_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); + lv_obj_t* button_icon = lv_label_create(back_button_); + lv_label_set_text(button_icon, "<"); + + lv_group_add_obj(group, back_button_); + lv_obj_add_event_cb(back_button_, back_click_cb, LV_EVENT_CLICKED, NULL); + + title_ = lv_label_create(container_); + lv_label_set_text(title_, ""); + lv_obj_set_flex_grow(title_, 1); + + lv_obj_t* playback_label = lv_label_create(container_); + lv_label_set_text(playback_label, LV_SYMBOL_PAUSE); + + lv_obj_t* battery_label = lv_label_create(container_); + lv_label_set_text(battery_label, LV_SYMBOL_BATTERY_2); +} + +auto TopBar::set_title(const std::string& new_title) -> void { + lv_label_set_text(title_, new_title.c_str()); +} + +} // namespace widgets +} // namespace ui