Add basic browsing and playing ui
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
idf_component_register(
|
||||
SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "screen_menu.cpp" "wheel_encoder.cpp"
|
||||
SRCS "lvgl_task.cpp" "ui_fsm.cpp" "screen_splash.cpp" "screen_menu.cpp" "wheel_encoder.cpp" "screen_track_browser.cpp" "screen_playing.cpp"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer")
|
||||
target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS})
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "core/lv_group.h"
|
||||
#include "core/lv_obj.h"
|
||||
#include "core/lv_obj_tree.h"
|
||||
#include "lvgl.h"
|
||||
@@ -16,13 +17,20 @@ namespace ui {
|
||||
|
||||
class Screen {
|
||||
public:
|
||||
Screen() : root_(lv_obj_create(NULL)) {}
|
||||
virtual ~Screen() { lv_obj_del(root_); }
|
||||
Screen() : root_(lv_obj_create(NULL)), group_(lv_group_create()) {}
|
||||
virtual ~Screen() {
|
||||
lv_obj_del(root_);
|
||||
lv_group_del(group_);
|
||||
}
|
||||
|
||||
virtual auto Tick() -> void {}
|
||||
|
||||
auto root() -> lv_obj_t* { return root_; }
|
||||
auto group() -> lv_group_t* { return group_; }
|
||||
|
||||
protected:
|
||||
lv_obj_t* const root_;
|
||||
lv_group_t* const group_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "index.hpp"
|
||||
#include "lvgl.h"
|
||||
|
||||
#include "screen.hpp"
|
||||
@@ -17,10 +19,11 @@ namespace screens {
|
||||
|
||||
class Menu : public Screen {
|
||||
public:
|
||||
Menu();
|
||||
explicit Menu(std::vector<database::IndexInfo> indexes);
|
||||
~Menu();
|
||||
|
||||
private:
|
||||
std::vector<database::IndexInfo> indexes_;
|
||||
lv_obj_t* container_;
|
||||
lv_obj_t* label_;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database.hpp"
|
||||
#include "index.hpp"
|
||||
#include "tinyfsm.hpp"
|
||||
|
||||
namespace ui {
|
||||
@@ -20,4 +22,16 @@ struct OnStorageChange : tinyfsm::Event {
|
||||
|
||||
struct OnSystemError : tinyfsm::Event {};
|
||||
|
||||
namespace internal {
|
||||
|
||||
struct RecordSelected : tinyfsm::Event {
|
||||
database::IndexRecord record;
|
||||
};
|
||||
|
||||
struct IndexSelected : tinyfsm::Event {
|
||||
database::IndexInfo index;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
} // namespace ui
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <stack>
|
||||
|
||||
#include "relative_wheel.hpp"
|
||||
#include "tinyfsm.hpp"
|
||||
@@ -16,6 +17,7 @@
|
||||
#include "storage.hpp"
|
||||
#include "system_events.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
#include "ui_events.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
@@ -37,15 +39,23 @@ class UiState : public tinyfsm::Fsm<UiState> {
|
||||
|
||||
virtual void react(const system_fsm::KeyLockChanged&){};
|
||||
|
||||
virtual void react(const internal::RecordSelected&){};
|
||||
virtual void react(const internal::IndexSelected&){};
|
||||
|
||||
virtual void react(const system_fsm::DisplayReady&) {}
|
||||
virtual void react(const system_fsm::BootComplete&) {}
|
||||
virtual void react(const system_fsm::StorageMounted&) {}
|
||||
|
||||
protected:
|
||||
void PushScreen(std::shared_ptr<Screen>);
|
||||
|
||||
static drivers::IGpios* sIGpios;
|
||||
static std::shared_ptr<drivers::TouchWheel> sTouchWheel;
|
||||
static std::shared_ptr<drivers::RelativeWheel> sRelativeWheel;
|
||||
static std::shared_ptr<drivers::Display> sDisplay;
|
||||
static std::weak_ptr<database::Database> sDb;
|
||||
|
||||
static std::stack<std::shared_ptr<Screen>> sScreens;
|
||||
static std::shared_ptr<Screen> sCurrentScreen;
|
||||
};
|
||||
|
||||
@@ -61,7 +71,11 @@ class Splash : public UiState {
|
||||
class Interactive : public UiState {
|
||||
void entry() override;
|
||||
|
||||
void react(const internal::RecordSelected&) override;
|
||||
void react(const internal::IndexSelected&) override;
|
||||
|
||||
void react(const system_fsm::KeyLockChanged&) override;
|
||||
void react(const system_fsm::StorageMounted&) override;
|
||||
};
|
||||
|
||||
class FatalError : public UiState {};
|
||||
|
||||
@@ -54,9 +54,6 @@ void LvglMain(std::weak_ptr<drivers::RelativeWheel> weak_touch_wheel,
|
||||
lv_init();
|
||||
|
||||
TouchWheelEncoder encoder(weak_touch_wheel);
|
||||
lv_group_t* nav_group = lv_group_create();
|
||||
lv_group_set_default(nav_group);
|
||||
lv_indev_set_group(encoder.registration(), nav_group);
|
||||
|
||||
std::shared_ptr<Screen> current_screen;
|
||||
auto& events = events::EventQueue::GetInstance();
|
||||
@@ -68,9 +65,14 @@ void LvglMain(std::weak_ptr<drivers::RelativeWheel> weak_touch_wheel,
|
||||
if (screen != current_screen && screen != nullptr) {
|
||||
// TODO(jacqueline): animate this sometimes
|
||||
lv_scr_load(screen->root());
|
||||
lv_indev_set_group(encoder.registration(), screen->group());
|
||||
current_screen = screen;
|
||||
}
|
||||
|
||||
if (current_screen) {
|
||||
current_screen->Tick();
|
||||
}
|
||||
|
||||
lv_task_handler();
|
||||
// 30 FPS
|
||||
// TODO(jacqueline): make this dynamic
|
||||
|
||||
+18
-12
@@ -11,35 +11,41 @@
|
||||
|
||||
#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 "widgets/lv_label.h"
|
||||
|
||||
namespace ui {
|
||||
namespace screens {
|
||||
|
||||
static void item_click_cb(lv_event_t* ev) {
|
||||
ESP_LOGI("menu", "clicked!");
|
||||
if (ev->user_data == NULL) {
|
||||
return;
|
||||
}
|
||||
database::IndexInfo* index =
|
||||
reinterpret_cast<database::IndexInfo*>(ev->user_data);
|
||||
|
||||
events::Dispatch<internal::IndexSelected, UiState>(
|
||||
internal::IndexSelected{.index = *index});
|
||||
}
|
||||
|
||||
Menu::Menu() {
|
||||
Menu::Menu(std::vector<database::IndexInfo> indexes) : indexes_(indexes) {
|
||||
lv_obj_t* list = lv_list_create(root_);
|
||||
lv_obj_set_size(list, lv_disp_get_hor_res(NULL), lv_disp_get_ver_res(NULL));
|
||||
lv_obj_center(list);
|
||||
|
||||
lv_obj_t* button;
|
||||
|
||||
button = lv_list_add_btn(list, NULL, "hi");
|
||||
lv_obj_add_event_cb(button, item_click_cb, LV_EVENT_CLICKED, NULL);
|
||||
|
||||
button = lv_list_add_btn(list, NULL, "second");
|
||||
lv_obj_add_event_cb(button, item_click_cb, LV_EVENT_CLICKED, NULL);
|
||||
|
||||
button = lv_list_add_btn(list, NULL, "third");
|
||||
lv_obj_add_event_cb(button, item_click_cb, LV_EVENT_CLICKED, NULL);
|
||||
for (database::IndexInfo& index : indexes_) {
|
||||
lv_obj_t* item = lv_list_add_btn(list, NULL, index.name.c_str());
|
||||
lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_CLICKED, &index);
|
||||
lv_group_add_obj(group_, item);
|
||||
}
|
||||
}
|
||||
|
||||
Menu::~Menu() {}
|
||||
|
||||
+68
-3
@@ -6,23 +6,33 @@
|
||||
|
||||
#include "ui_fsm.hpp"
|
||||
#include <memory>
|
||||
#include "audio_events.hpp"
|
||||
#include "core/lv_obj.h"
|
||||
#include "display.hpp"
|
||||
#include "event_queue.hpp"
|
||||
#include "lvgl_task.hpp"
|
||||
#include "relative_wheel.hpp"
|
||||
#include "screen.hpp"
|
||||
#include "screen_menu.hpp"
|
||||
#include "screen_playing.hpp"
|
||||
#include "screen_splash.hpp"
|
||||
#include "screen_track_browser.hpp"
|
||||
#include "system_events.hpp"
|
||||
#include "touchwheel.hpp"
|
||||
|
||||
namespace ui {
|
||||
|
||||
static constexpr char kTag[] = "ui_fsm";
|
||||
|
||||
static const std::size_t kRecordsPerPage = 10;
|
||||
|
||||
drivers::IGpios* UiState::sIGpios;
|
||||
std::shared_ptr<drivers::TouchWheel> UiState::sTouchWheel;
|
||||
std::shared_ptr<drivers::RelativeWheel> UiState::sRelativeWheel;
|
||||
std::shared_ptr<drivers::Display> UiState::sDisplay;
|
||||
std::weak_ptr<database::Database> UiState::sDb;
|
||||
|
||||
std::stack<std::shared_ptr<Screen>> UiState::sScreens;
|
||||
std::shared_ptr<Screen> UiState::sCurrentScreen;
|
||||
|
||||
auto UiState::Init(drivers::IGpios* gpio_expander) -> bool {
|
||||
@@ -52,6 +62,13 @@ auto UiState::Init(drivers::IGpios* gpio_expander) -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UiState::PushScreen(std::shared_ptr<Screen> screen) {
|
||||
if (sCurrentScreen) {
|
||||
sScreens.push(sCurrentScreen);
|
||||
}
|
||||
sCurrentScreen = screen;
|
||||
}
|
||||
|
||||
namespace states {
|
||||
|
||||
void Splash::exit() {
|
||||
@@ -64,14 +81,62 @@ void Splash::react(const system_fsm::BootComplete& ev) {
|
||||
transit<Interactive>();
|
||||
}
|
||||
|
||||
void Interactive::entry() {
|
||||
sCurrentScreen.reset(new screens::Menu());
|
||||
}
|
||||
void Interactive::entry() {}
|
||||
|
||||
void Interactive::react(const system_fsm::KeyLockChanged& ev) {
|
||||
sDisplay->SetDisplayOn(ev.falling);
|
||||
}
|
||||
|
||||
void Interactive::react(const system_fsm::StorageMounted& ev) {
|
||||
sDb = ev.db;
|
||||
auto db = ev.db.lock();
|
||||
if (!db) {
|
||||
// TODO(jacqueline): Hmm.
|
||||
return;
|
||||
}
|
||||
PushScreen(std::make_shared<screens::Menu>(db->GetIndexes()));
|
||||
}
|
||||
|
||||
void Interactive::react(const internal::RecordSelected& ev) {
|
||||
auto db = sDb.lock();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.record.track()) {
|
||||
ESP_LOGI(kTag, "selected track '%s'", ev.record.text()->c_str());
|
||||
// TODO(jacqueline): We should also send some kind of playlist info here.
|
||||
auto track = ev.record.track().value();
|
||||
events::Dispatch<audio::PlayTrack, audio::AudioState>(audio::PlayTrack{
|
||||
.id = track.data().id(),
|
||||
.data = track.data(),
|
||||
});
|
||||
PushScreen(std::make_shared<screens::Playing>(track));
|
||||
} else {
|
||||
ESP_LOGI(kTag, "selected record '%s'", ev.record.text()->c_str());
|
||||
auto cont = ev.record.Expand(kRecordsPerPage);
|
||||
if (!cont) {
|
||||
return;
|
||||
}
|
||||
auto query = db->GetPage(&cont.value());
|
||||
std::string title = ev.record.text().value_or("TODO");
|
||||
PushScreen(
|
||||
std::make_shared<screens::TrackBrowser>(sDb, title, std::move(query)));
|
||||
}
|
||||
}
|
||||
|
||||
void Interactive::react(const internal::IndexSelected& ev) {
|
||||
auto db = sDb.lock();
|
||||
if (!db) {
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(kTag, "selected index %s", ev.index.name.c_str());
|
||||
auto query = db->GetTracksByIndex(ev.index, kRecordsPerPage);
|
||||
PushScreen(std::make_shared<screens::TrackBrowser>(sDb, ev.index.name,
|
||||
std::move(query)));
|
||||
}
|
||||
|
||||
} // namespace states
|
||||
} // namespace ui
|
||||
|
||||
|
||||
Reference in New Issue
Block a user