summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-07-04 14:39:22 +1000
committerjacqueline <me@jacqueline.id.au>2023-07-04 14:39:22 +1000
commit191441ebe2ecc654fee7d9cbfc536df4212117c8 (patch)
tree0e580f4738358ccd079fb66441b71e4a4093bf8a /src
parent80170642ea1d8bfc9703af217993ae29e6ee81d6 (diff)
downloadtangara-fw-191441ebe2ecc654fee7d9cbfc536df4212117c8.tar.gz
Add missing files >.<
Diffstat (limited to 'src')
-rw-r--r--src/ui/include/screen_playing.hpp29
-rw-r--r--src/ui/include/screen_track_browser.hpp64
-rw-r--r--src/ui/screen_playing.cpp43
-rw-r--r--src/ui/screen_track_browser.cpp266
4 files changed, 402 insertions, 0 deletions
diff --git a/src/ui/include/screen_playing.hpp b/src/ui/include/screen_playing.hpp
new file mode 100644
index 00000000..cf1ddaa2
--- /dev/null
+++ b/src/ui/include/screen_playing.hpp
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <memory>
+
+#include "lvgl.h"
+
+#include "database.hpp"
+#include "screen.hpp"
+
+namespace ui {
+namespace screens {
+
+class Playing : public Screen {
+ public:
+ explicit Playing(database::Track t);
+ ~Playing();
+
+ private:
+ database::Track track_;
+};
+
+} // namespace screens
+} // namespace ui
diff --git a/src/ui/include/screen_track_browser.hpp b/src/ui/include/screen_track_browser.hpp
new file mode 100644
index 00000000..95fb80e2
--- /dev/null
+++ b/src/ui/include/screen_track_browser.hpp
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#pragma once
+
+#include <deque>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "lvgl.h"
+
+#include "database.hpp"
+#include "screen.hpp"
+
+namespace ui {
+namespace screens {
+
+class TrackBrowser : public Screen {
+ public:
+ TrackBrowser(
+ std::weak_ptr<database::Database> db,
+ const std::string& title,
+ std::future<database::Result<database::IndexRecord>*>&& initial_page);
+ ~TrackBrowser() {}
+
+ auto Tick() -> void override;
+
+ auto OnItemSelected(lv_event_t* ev) -> void;
+ auto OnItemClicked(lv_event_t* ev) -> void;
+
+ private:
+ enum Position {
+ START = 0,
+ END = 1,
+ };
+ auto AddLoadingIndictor(Position pos) -> void;
+ auto AddResults(Position pos, database::Result<database::IndexRecord>*)
+ -> void;
+ auto DropPage(Position pos) -> void;
+ auto FetchNewPage(Position pos) -> void;
+
+ auto GetNumRecords() -> std::size_t;
+ auto GetItemIndex(lv_obj_t* obj) -> std::optional<std::size_t>;
+ auto GetRecordByIndex(std::size_t index)
+ -> std::optional<database::IndexRecord>;
+
+ std::weak_ptr<database::Database> db_;
+ lv_obj_t* list_;
+ lv_obj_t* loading_indicator_;
+
+ std::optional<Position> loading_pos_;
+ std::optional<std::future<database::Result<database::IndexRecord>*>>
+ loading_page_;
+
+ std::deque<std::unique_ptr<database::Result<database::IndexRecord>>>
+ current_pages_;
+};
+
+} // namespace screens
+} // namespace ui
diff --git a/src/ui/screen_playing.cpp b/src/ui/screen_playing.cpp
new file mode 100644
index 00000000..053f324c
--- /dev/null
+++ b/src/ui/screen_playing.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "screen_playing.hpp"
+
+#include "esp_log.h"
+#include "lvgl.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 "track.hpp"
+#include "ui_events.hpp"
+#include "ui_fsm.hpp"
+#include "widgets/lv_label.h"
+
+namespace ui {
+namespace screens {
+
+Playing::Playing(database::Track track) : track_(track) {
+ lv_obj_t* container = lv_obj_create(root_);
+ lv_obj_set_align(container, LV_ALIGN_CENTER);
+ lv_obj_set_size(container, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
+
+ // bro idk
+ lv_obj_t* label = lv_label_create(container);
+ lv_label_set_text_static(label, track.TitleOrFilename().c_str());
+ lv_obj_set_align(label, LV_ALIGN_CENTER);
+}
+
+Playing::~Playing() {}
+
+} // namespace screens
+} // namespace ui
diff --git a/src/ui/screen_track_browser.cpp b/src/ui/screen_track_browser.cpp
new file mode 100644
index 00000000..17b7dc46
--- /dev/null
+++ b/src/ui/screen_track_browser.cpp
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <algorithm>
+#include <memory>
+
+#include "database.hpp"
+#include "event_queue.hpp"
+#include "lvgl.h"
+#include "screen_menu.hpp"
+
+#include "core/lv_event.h"
+#include "esp_log.h"
+
+#include "core/lv_group.h"
+#include "core/lv_obj_pos.h"
+#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 "misc/lv_area.h"
+#include "screen_track_browser.hpp"
+#include "ui_events.hpp"
+#include "ui_fsm.hpp"
+#include "widgets/lv_label.h"
+
+static constexpr char kTag[] = "browser";
+
+static constexpr int kMaxPages = 3;
+static constexpr int kPageBuffer = 5;
+
+namespace ui {
+namespace screens {
+
+static void item_click_cb(lv_event_t* ev) {
+ if (ev->user_data == NULL) {
+ return;
+ }
+ TrackBrowser* instance = reinterpret_cast<TrackBrowser*>(ev->user_data);
+ instance->OnItemClicked(ev);
+}
+
+static void item_select_cb(lv_event_t* ev) {
+ if (ev->user_data == NULL) {
+ return;
+ }
+ TrackBrowser* instance = reinterpret_cast<TrackBrowser*>(ev->user_data);
+ instance->OnItemSelected(ev);
+}
+
+TrackBrowser::TrackBrowser(
+ std::weak_ptr<database::Database> db,
+ const std::string& title,
+ std::future<database::Result<database::IndexRecord>*>&& initial_page)
+ : db_(db),
+ list_(nullptr),
+ loading_indicator_(nullptr),
+ loading_pos_(END),
+ loading_page_(std::move(initial_page)),
+ current_pages_() {
+ lv_obj_t* title_obj = lv_label_create(root_);
+ lv_label_set_text(title_obj, title.c_str());
+
+ 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_);
+}
+
+auto TrackBrowser::Tick() -> void {
+ if (!loading_page_) {
+ return;
+ }
+ if (!loading_page_->valid()) {
+ // TODO(jacqueline): error case.
+ return;
+ }
+ if (loading_page_->wait_for(std::chrono::seconds(0)) ==
+ std::future_status::ready) {
+ auto result = loading_page_->get();
+ AddResults(loading_pos_.value_or(END), result);
+
+ loading_page_.reset();
+ loading_pos_.reset();
+ }
+}
+
+auto TrackBrowser::OnItemSelected(lv_event_t* ev) -> void {
+ auto index = GetItemIndex(lv_event_get_target(ev));
+ if (!index) {
+ return;
+ }
+ if (index < kPageBuffer) {
+ FetchNewPage(START);
+ return;
+ }
+ if (index > GetNumRecords() - kPageBuffer) {
+ FetchNewPage(END);
+ return;
+ }
+}
+
+auto TrackBrowser::OnItemClicked(lv_event_t* ev) -> void {
+ auto index = GetItemIndex(lv_event_get_target(ev));
+ if (!index) {
+ return;
+ }
+ auto record = GetRecordByIndex(*index);
+ if (!record) {
+ return;
+ }
+ ESP_LOGI(kTag, "clicked item %u (%s)", *index,
+ record->text().value_or("[nil]").c_str());
+ events::Dispatch<internal::RecordSelected, UiState>(
+ internal::RecordSelected{.record = *record});
+}
+
+auto TrackBrowser::AddLoadingIndictor(Position pos) -> void {
+ if (loading_indicator_) {
+ return;
+ }
+ loading_indicator_ = lv_list_add_text(list_, "Loading...");
+ if (pos == START) {
+ lv_obj_move_to_index(loading_indicator_, 0);
+ }
+}
+
+auto TrackBrowser::AddResults(Position pos,
+ database::Result<database::IndexRecord>* results)
+ -> void {
+ if (loading_indicator_ != nullptr) {
+ lv_obj_del(loading_indicator_);
+ loading_indicator_ = nullptr;
+ }
+
+ auto fn = [&](const database::IndexRecord& record) {
+ auto text = record.text();
+ if (!text) {
+ // TODO(jacqueline): Display category-specific text.
+ text = "[ no data ]";
+ }
+ lv_obj_t* item = lv_list_add_btn(list_, NULL, text->c_str());
+ lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_CLICKED, this);
+ lv_obj_add_event_cb(item, item_select_cb, LV_EVENT_FOCUSED, this);
+ lv_group_add_obj(group_, item);
+ if (pos == START) {
+ lv_obj_move_to_index(item, 0);
+ }
+ };
+
+ switch (pos) {
+ case START:
+ std::for_each(results->values().rbegin(), results->values().rend(), fn);
+ current_pages_.emplace_front(results);
+ break;
+ case END:
+ std::for_each(results->values().begin(), results->values().end(), fn);
+ current_pages_.emplace_back(results);
+ break;
+ }
+}
+
+auto TrackBrowser::DropPage(Position pos) -> void {
+ if (pos == START) {
+ for (int i = 0; i < current_pages_.front()->values().size(); i++) {
+ lv_obj_t* item = lv_obj_get_child(list_, 0);
+ if (item == NULL) {
+ continue;
+ }
+ lv_obj_del(item);
+ }
+ current_pages_.pop_front();
+ } else if (pos == END) {
+ for (int i = 0; i < current_pages_.back()->values().size(); i++) {
+ lv_obj_t* item = lv_obj_get_child(list_, lv_obj_get_child_cnt(list_) - 1);
+ if (item == NULL) {
+ continue;
+ }
+ lv_group_remove_obj(item);
+ lv_obj_del(item);
+ }
+ current_pages_.pop_back();
+ }
+}
+
+auto TrackBrowser::FetchNewPage(Position pos) -> void {
+ if (loading_page_) {
+ return;
+ }
+ auto db = db_.lock();
+ if (!db) {
+ return;
+ }
+
+ // If we already have a complete set of pages, drop the page that's furthest
+ // away.
+ if (current_pages_.size() >= kMaxPages) {
+ switch (pos) {
+ case START:
+ DropPage(END);
+ break;
+ case END:
+ DropPage(START);
+ break;
+ }
+ }
+
+ std::optional<database::Continuation<database::IndexRecord>> cont;
+ switch (pos) {
+ case START:
+ cont = current_pages_.front()->prev_page();
+ break;
+ case END:
+ cont = current_pages_.back()->next_page();
+ break;
+ }
+ if (!cont) {
+ return;
+ }
+
+ loading_pos_ = pos;
+ loading_page_ = db->GetPage(&cont.value());
+}
+
+auto TrackBrowser::GetNumRecords() -> std::size_t {
+ return lv_obj_get_child_cnt(list_) - (loading_indicator_ != nullptr ? 1 : 0);
+}
+
+auto TrackBrowser::GetItemIndex(lv_obj_t* obj) -> std::optional<std::size_t> {
+ std::size_t child_count = lv_obj_get_child_cnt(list_);
+ std::size_t index = 0;
+ for (int i = 0; i < child_count; i++) {
+ lv_obj_t* child = lv_obj_get_child(list_, i);
+ if (child == loading_indicator_) {
+ continue;
+ }
+ if (child == obj) {
+ return index;
+ }
+ index++;
+ }
+ return {};
+}
+
+auto TrackBrowser::GetRecordByIndex(std::size_t index)
+ -> std::optional<database::IndexRecord> {
+ std::size_t current_index = 0;
+ for (const auto& page : current_pages_) {
+ if (index > current_index + page->values().size()) {
+ current_index += page->values().size();
+ continue;
+ }
+ if (index < current_index) {
+ // uhhh
+ break;
+ }
+ std::size_t index_in_page = index - current_index;
+ return page->values().at(index_in_page);
+ }
+ return {};
+}
+
+} // namespace screens
+} // namespace ui