summaryrefslogtreecommitdiff
path: root/src/ui/screen_track_browser.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-11-24 15:13:10 +1100
committerjacqueline <me@jacqueline.id.au>2023-11-24 15:13:10 +1100
commit7c6eb2997cbba350e7384151e13659271508e08f (patch)
treeb6f95a4843521e69b24cbf4c126d84442d19fc23 /src/ui/screen_track_browser.cpp
parent230721cd6271f3239b42e1d2471f8db15bebd712 (diff)
downloadtangara-fw-7c6eb2997cbba350e7384151e13659271508e08f.tar.gz
Migrate 'now playing' screen to lua
Diffstat (limited to 'src/ui/screen_track_browser.cpp')
-rw-r--r--src/ui/screen_track_browser.cpp431
1 files changed, 0 insertions, 431 deletions
diff --git a/src/ui/screen_track_browser.cpp b/src/ui/screen_track_browser.cpp
deleted file mode 100644
index c7b035ad..00000000
--- a/src/ui/screen_track_browser.cpp
+++ /dev/null
@@ -1,431 +0,0 @@
-/*
- * Copyright 2023 jacqueline <me@jacqueline.id.au>
- *
- * SPDX-License-Identifier: GPL-3.0-only
- */
-
-#include <algorithm>
-#include <memory>
-
-#include "core/lv_obj.h"
-#include "core/lv_obj_scroll.h"
-#include "core/lv_obj_tree.h"
-#include "database.hpp"
-#include "event_queue.hpp"
-#include "extra/layouts/flex/lv_flex.h"
-#include "font/lv_symbol_def.h"
-#include "lvgl.h"
-#include "misc/lv_anim.h"
-#include "misc/lv_color.h"
-#include "model_top_bar.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 "source.hpp"
-#include "themes.hpp"
-#include "track_queue.hpp"
-#include "ui_events.hpp"
-#include "ui_fsm.hpp"
-#include "widget_top_bar.hpp"
-#include "widgets/lv_label.h"
-
-[[maybe_unused]] static constexpr char kTag[] = "browser";
-
-static constexpr int kMaxPages = 4;
-static constexpr int kPageBuffer = 6;
-
-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(
- models::TopBar& top_bar_model,
- audio::TrackQueue& queue,
- std::weak_ptr<database::Database> db,
- const std::pmr::vector<std::pmr::string>& crumbs,
- std::future<database::Result<database::IndexRecord>*>&& initial_page)
- : queue_(queue),
- db_(db),
- play_button_(nullptr),
- enqueue_button_(nullptr),
- list_(nullptr),
- loading_indicator_(nullptr),
- breadcrumbs_(crumbs),
- loading_pos_(END),
- loading_page_(move(initial_page)),
- initial_page_(),
- current_pages_() {
- 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_CENTER, LV_FLEX_ALIGN_CENTER,
- LV_FLEX_ALIGN_CENTER);
-
- widgets::TopBar::Configuration config{
- .show_back_button = true,
- .title = breadcrumbs_[0],
- };
- auto top_bar = CreateTopBar(content_, config, top_bar_model);
- back_button_ = top_bar->button();
-
- lv_obj_t* scrollable = lv_obj_create(content_);
- lv_obj_set_width(scrollable, lv_pct(100));
- lv_obj_set_flex_grow(scrollable, 1);
- lv_obj_set_layout(scrollable, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(scrollable, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(scrollable, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_START);
-
- if (crumbs.size() > 1) {
- lv_obj_t* header = lv_obj_create(scrollable);
- lv_obj_set_size(header, lv_pct(100), LV_SIZE_CONTENT);
- lv_obj_set_layout(header, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(header, LV_FLEX_FLOW_COLUMN);
- lv_obj_set_flex_align(header, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START,
- LV_FLEX_ALIGN_START);
-
- lv_obj_set_style_pad_left(header, 4, LV_PART_MAIN);
- lv_obj_set_style_pad_right(header, 4, LV_PART_MAIN);
-
- lv_obj_t* spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 2);
-
- for (size_t i = 1; i < crumbs.size(); i++) {
- lv_obj_t* crumb = lv_label_create(header);
- lv_label_set_text(crumb, crumbs[i].c_str());
-
- spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 2);
- }
-
- spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 2);
-
- lv_obj_t* buttons_container = lv_obj_create(header);
- lv_obj_set_width(buttons_container, lv_pct(100));
- lv_obj_set_height(buttons_container, LV_SIZE_CONTENT);
- lv_obj_set_layout(buttons_container, LV_LAYOUT_FLEX);
- lv_obj_set_flex_flow(buttons_container, LV_FLEX_FLOW_ROW);
- lv_obj_set_flex_align(buttons_container, LV_FLEX_ALIGN_END,
- LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
-
- lv_obj_t* label;
-
- play_button_ = lv_btn_create(buttons_container);
- label = lv_label_create(play_button_);
- lv_label_set_text(label, "Play all");
- lv_group_add_obj(group_, play_button_);
- themes::Theme::instance()->ApplyStyle(play_button_,
- themes::Style::kButtonPrimary);
-
- lv_bind(play_button_, LV_EVENT_CLICKED, [&](lv_obj_t*) {
- if (!initial_page_) {
- return;
- }
- queue_.Clear();
- queue_.IncludeNext(playlist::CreateSourceFromResults(db_, initial_page_));
- events::Ui().Dispatch(internal::ShowNowPlaying{});
- });
-
- if (queue_.GetCurrent()) {
- spacer = lv_obj_create(buttons_container);
- lv_obj_set_size(spacer, 4, 1);
-
- enqueue_button_ = lv_btn_create(buttons_container);
- label = lv_label_create(enqueue_button_);
- lv_label_set_text(label, "Enqueue");
- lv_group_add_obj(group_, enqueue_button_);
- themes::Theme::instance()->ApplyStyle(enqueue_button_,
- themes::Style::kButtonPrimary);
-
- lv_bind(enqueue_button_, LV_EVENT_CLICKED, [&](lv_obj_t*) {
- if (!initial_page_) {
- return;
- }
- queue_.IncludeNext(
- playlist::CreateSourceFromResults(db_, initial_page_));
- });
- }
-
- lv_obj_set_style_border_width(header, 1, LV_PART_MAIN);
- lv_obj_set_style_border_color(header, lv_color_black(), LV_PART_MAIN);
- lv_obj_set_style_border_side(header, LV_BORDER_SIDE_BOTTOM, LV_PART_MAIN);
-
- spacer = lv_obj_create(header);
- lv_obj_set_size(spacer, 1, 4);
-
- lv_obj_set_style_border_width(header, 1, LV_PART_MAIN);
- lv_obj_set_style_border_color(
- header, lv_palette_lighten(LV_PALETTE_GREY, 3), LV_PART_MAIN);
- }
-
- list_ = lv_list_create(scrollable);
- lv_obj_set_size(list_, lv_pct(100), LV_SIZE_CONTENT);
-
- // The default scrollbar is deceptive because we load in items progressively.
- // TODO/FIXME: this doesn't actually turn off the scrollbar, it seems.
- lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
- // Wrapping behaves in surprising ways, again due to progressing loading.
- lv_group_set_wrap(group_, false);
-}
-
-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) {
- std::shared_ptr<database::Result<database::IndexRecord>> 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 res = GetItemIndex(lv_event_get_target(ev));
- if (!res) {
- return;
- }
-
- auto index = *res;
- for (const auto& page : current_pages_) {
- for (std::size_t i = 0; i < page->values().size(); i++) {
- if (index == 0) {
- auto text = page->values()[i]->text();
- auto crumbs = breadcrumbs_;
- crumbs.push_back(text.value());
- events::Ui().Dispatch(internal::RecordSelected{
- .show_menu = ev->code == LV_EVENT_LONG_PRESSED,
- .new_crumbs = crumbs,
- .initial_page = initial_page_,
- .page = page,
- .record = i,
- });
- return;
- }
- index--;
- }
- }
-}
-
-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,
- std::shared_ptr<database::Result<database::IndexRecord>> results) -> void {
- if (loading_indicator_ != nullptr) {
- lv_obj_del(loading_indicator_);
- loading_indicator_ = nullptr;
- }
-
- if (initial_page_ == nullptr) {
- initial_page_ = results;
- }
-
- auto fn = [&](const std::shared_ptr<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_label_set_long_mode(lv_obj_get_child(item, -1), LV_LABEL_LONG_DOT);
- lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_CLICKED, this);
- lv_obj_add_event_cb(item, item_click_cb, LV_EVENT_LONG_PRESSED, this);
- lv_obj_add_event_cb(item, item_select_cb, LV_EVENT_FOCUSED, this);
-
- if (pos == START) {
- lv_obj_move_to_index(item, 0);
- }
- };
-
- lv_obj_t* focused = lv_group_get_focused(group_);
-
- // Adding objects at the start of the list will artificially scroll the list
- // up. Scroll it down by the height we're adding so that the user doesn't
- // notice any jank.
- if (pos == START) {
- int num_to_add = results->values().size();
- // Assuming that all items are the same height, this item's y pos should be
- // exactly the height of the new items.
- lv_obj_t* representative_item = lv_obj_get_child(list_, num_to_add);
- if (representative_item != nullptr) {
- int scroll_adjustment = lv_obj_get_y(representative_item);
- lv_obj_scroll_by(list_, 0, -scroll_adjustment, LV_ANIM_OFF);
- }
- }
-
- switch (pos) {
- case START:
- std::for_each(results->values().rbegin(), results->values().rend(), fn);
- current_pages_.push_front(results);
- break;
- case END:
- std::for_each(results->values().begin(), results->values().end(), fn);
- current_pages_.push_back(results);
- break;
- }
-
- lv_group_remove_all_objs(group_);
- lv_group_add_obj(group_, back_button_);
- if (play_button_) {
- lv_group_add_obj(group_, play_button_);
- }
- if (enqueue_button_) {
- lv_group_add_obj(group_, enqueue_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));
- }
- lv_group_focus_obj(focused);
-}
-
-auto TrackBrowser::DropPage(Position pos) -> void {
- if (pos == START) {
- // Removing objects from the start of the list will artificially scroll the
- // list down. Scroll it up by the height we're removing so that the user
- // doesn't notice any jank.
- int num_to_remove = current_pages_.front()->values().size();
- lv_obj_t* new_top_obj = lv_obj_get_child(list_, num_to_remove);
- if (new_top_obj != nullptr) {
- int scroll_adjustment = lv_obj_get_y(new_top_obj);
- lv_obj_scroll_by(list_, 0, scroll_adjustment, LV_ANIM_OFF);
- }
-
- 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;
- }
-
- std::optional<database::Continuation> cont;
- switch (pos) {
- case START:
- cont = current_pages_.front()->prev_page();
- break;
- case END:
- cont = current_pages_.back()->next_page();
- break;
- }
- if (!cont) {
- 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;
- }
- }
-
- loading_pos_ = pos;
- loading_page_ = db->GetPage<database::IndexRecord>(&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 {};
-}
-
-} // namespace screens
-} // namespace ui