Add better controls for queue manipulation
This commit is contained in:
@@ -73,6 +73,11 @@ class IResetableSource : public ISource {
|
||||
virtual auto Reset() -> void = 0;
|
||||
};
|
||||
|
||||
auto CreateSourceFromResults(
|
||||
std::weak_ptr<database::Database>,
|
||||
std::shared_ptr<database::Result<database::IndexRecord>>)
|
||||
-> std::shared_ptr<IResetableSource>;
|
||||
|
||||
class IndexRecordSource : public IResetableSource {
|
||||
public:
|
||||
IndexRecordSource(std::weak_ptr<database::Database> db,
|
||||
@@ -102,4 +107,32 @@ class IndexRecordSource : public IResetableSource {
|
||||
ssize_t current_item_;
|
||||
};
|
||||
|
||||
class NestedSource : public IResetableSource {
|
||||
public:
|
||||
NestedSource(std::weak_ptr<database::Database> db,
|
||||
std::shared_ptr<database::Result<database::IndexRecord>>);
|
||||
|
||||
auto Current() -> std::optional<database::TrackId> override;
|
||||
auto Advance() -> std::optional<database::TrackId> override;
|
||||
auto Peek(std::size_t n, std::vector<database::TrackId>*)
|
||||
-> std::size_t override;
|
||||
|
||||
auto Previous() -> std::optional<database::TrackId> override;
|
||||
auto Reset() -> void override;
|
||||
|
||||
private:
|
||||
auto CreateChild(std::shared_ptr<database::IndexRecord> page)
|
||||
-> std::shared_ptr<IResetableSource>;
|
||||
|
||||
std::weak_ptr<database::Database> db_;
|
||||
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> initial_page_;
|
||||
ssize_t initial_item_;
|
||||
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> current_page_;
|
||||
ssize_t current_item_;
|
||||
|
||||
std::shared_ptr<IResetableSource> current_child_;
|
||||
};
|
||||
|
||||
} // namespace playlist
|
||||
|
||||
@@ -22,6 +22,17 @@
|
||||
|
||||
namespace playlist {
|
||||
|
||||
auto CreateSourceFromResults(
|
||||
std::weak_ptr<database::Database> db,
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> results)
|
||||
-> std::shared_ptr<IResetableSource> {
|
||||
if (results->values()[0]->track()) {
|
||||
return std::make_shared<IndexRecordSource>(db, results);
|
||||
} else {
|
||||
return std::make_shared<NestedSource>(db, results);
|
||||
}
|
||||
}
|
||||
|
||||
IndexRecordSource::IndexRecordSource(
|
||||
std::weak_ptr<database::Database> db,
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> initial)
|
||||
@@ -142,4 +153,156 @@ auto IndexRecordSource::Reset() -> void {
|
||||
current_item_ = initial_item_;
|
||||
}
|
||||
|
||||
NestedSource::NestedSource(
|
||||
std::weak_ptr<database::Database> db,
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> initial)
|
||||
: db_(db),
|
||||
initial_page_(initial),
|
||||
initial_item_(0),
|
||||
current_page_(initial_page_),
|
||||
current_item_(initial_item_),
|
||||
current_child_(CreateChild(initial->values()[0])) {}
|
||||
|
||||
auto NestedSource::Current() -> std::optional<database::TrackId> {
|
||||
if (current_child_) {
|
||||
return current_child_->Current();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
auto NestedSource::Advance() -> std::optional<database::TrackId> {
|
||||
if (!current_child_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto child_next = current_child_->Advance();
|
||||
if (child_next) {
|
||||
return child_next;
|
||||
}
|
||||
// Our current child has run out of tracks. Move on to the next child.
|
||||
current_item_++;
|
||||
current_child_.reset();
|
||||
|
||||
if (current_item_ >= current_page_->values().size()) {
|
||||
// We're even out of items in this page!
|
||||
auto next_page = current_page_->next_page();
|
||||
if (!next_page) {
|
||||
current_item_--;
|
||||
return {};
|
||||
}
|
||||
|
||||
auto db = db_.lock();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
|
||||
current_page_.reset(db->GetPage<database::IndexRecord>(&*next_page).get());
|
||||
current_item_ = 0;
|
||||
}
|
||||
current_child_ = CreateChild(current_page_->values()[current_item_]);
|
||||
|
||||
return Current();
|
||||
}
|
||||
|
||||
auto NestedSource::Previous() -> std::optional<database::TrackId> {
|
||||
if (current_page_ == initial_page_ && current_item_ <= initial_item_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
current_item_--;
|
||||
current_child_.reset();
|
||||
|
||||
if (current_item_ < 0) {
|
||||
auto prev_page = current_page_->prev_page();
|
||||
if (!prev_page) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto db = db_.lock();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
|
||||
current_page_.reset(db->GetPage<database::IndexRecord>(&*prev_page).get());
|
||||
current_item_ = current_page_->values().size() - 1;
|
||||
}
|
||||
current_child_ = CreateChild(current_page_->values()[current_item_]);
|
||||
|
||||
return Current();
|
||||
}
|
||||
|
||||
auto NestedSource::Peek(std::size_t n, std::vector<database::TrackId>* out)
|
||||
-> std::size_t {
|
||||
if (current_page_->values().size() <= current_item_) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto db = db_.lock();
|
||||
if (!db) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::size_t items_added = 0;
|
||||
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> working_page =
|
||||
current_page_;
|
||||
std::size_t working_item = current_item_;
|
||||
std::shared_ptr<IResetableSource> working_child = current_child_;
|
||||
|
||||
while (working_child) {
|
||||
auto res = working_child->Peek(n, out);
|
||||
n -= res;
|
||||
items_added += res;
|
||||
|
||||
if (n == 0) {
|
||||
break;
|
||||
} else {
|
||||
working_item++;
|
||||
if (working_item < working_page->values().size()) {
|
||||
working_child = CreateChild(working_page->values()[working_item]);
|
||||
} else {
|
||||
auto next_page = current_page_->next_page();
|
||||
if (!next_page) {
|
||||
break;
|
||||
}
|
||||
working_page.reset(
|
||||
db->GetPage<database::IndexRecord>(&*next_page).get());
|
||||
working_item = 0;
|
||||
working_child = CreateChild(working_page->values()[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items_added;
|
||||
}
|
||||
|
||||
auto NestedSource::Reset() -> void {
|
||||
current_page_ = initial_page_;
|
||||
current_item_ = initial_item_;
|
||||
current_child_ = CreateChild(initial_page_->values()[initial_item_]);
|
||||
}
|
||||
|
||||
auto NestedSource::CreateChild(std::shared_ptr<database::IndexRecord> record)
|
||||
-> std::shared_ptr<IResetableSource> {
|
||||
auto cont = record->Expand(10);
|
||||
if (!cont) {
|
||||
return {};
|
||||
}
|
||||
auto db = db_.lock();
|
||||
if (!db) {
|
||||
return {};
|
||||
}
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> next_level{
|
||||
db->GetPage<database::IndexRecord>(&*cont).get()};
|
||||
if (!next_level) {
|
||||
return {};
|
||||
}
|
||||
auto next_level_record = next_level->values()[0];
|
||||
if (next_level_record->track()) {
|
||||
return std::make_shared<IndexRecordSource>(db_, next_level);
|
||||
} else {
|
||||
return std::make_shared<NestedSource>(db_, next_level);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace playlist
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
#include <sys/_stdint.h>
|
||||
#include <memory>
|
||||
|
||||
#include "lvgl.h"
|
||||
|
||||
#include "audio_events.hpp"
|
||||
#include "core/lv_event.h"
|
||||
#include "core/lv_group.h"
|
||||
#include "esp_timer.h"
|
||||
#include "event_queue.hpp"
|
||||
@@ -20,6 +23,8 @@
|
||||
#include "touchwheel.hpp"
|
||||
#include "ui_events.hpp"
|
||||
|
||||
static constexpr char kTag[] = "input";
|
||||
|
||||
constexpr int kDPadAngleThreshold = 20;
|
||||
constexpr int kLongPressDelayMs = 500;
|
||||
constexpr int kRepeatDelayMs = 250;
|
||||
@@ -58,6 +63,11 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void {
|
||||
return;
|
||||
}
|
||||
|
||||
lv_obj_t* active_object = nullptr;
|
||||
if (registration_ && registration_->group) {
|
||||
active_object = lv_group_get_focused(registration_->group);
|
||||
}
|
||||
|
||||
raw_wheel_.Update();
|
||||
relative_wheel_->Update();
|
||||
// GPIOs updating is handled by system_fsm.
|
||||
@@ -226,8 +236,9 @@ auto EncoderInput::Read(lv_indev_data_t* data) -> void {
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
break;
|
||||
case Trigger::kLongPress:
|
||||
// TODO: ???
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
if (active_object) {
|
||||
lv_event_send(active_object, LV_EVENT_LONG_PRESSED, NULL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,11 +24,12 @@ class AddToQueue : public Modal {
|
||||
public:
|
||||
AddToQueue(Screen*,
|
||||
audio::TrackQueue&,
|
||||
std::shared_ptr<playlist::IndexRecordSource>);
|
||||
std::shared_ptr<playlist::IResetableSource>,
|
||||
bool all_tracks_only = false);
|
||||
|
||||
private:
|
||||
audio::TrackQueue& queue_;
|
||||
std::shared_ptr<playlist::IndexRecordSource> item_;
|
||||
std::shared_ptr<playlist::IResetableSource> item_;
|
||||
lv_obj_t* container_;
|
||||
|
||||
lv_obj_t* selected_track_btn_;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
#include "database.hpp"
|
||||
#include "model_top_bar.hpp"
|
||||
#include "screen.hpp"
|
||||
#include "track_queue.hpp"
|
||||
|
||||
namespace ui {
|
||||
namespace screens {
|
||||
@@ -23,9 +24,10 @@ namespace screens {
|
||||
class TrackBrowser : public Screen {
|
||||
public:
|
||||
TrackBrowser(
|
||||
models::TopBar&,
|
||||
models::TopBar& top_bar,
|
||||
audio::TrackQueue& queue,
|
||||
std::weak_ptr<database::Database> db,
|
||||
const std::pmr::string& title,
|
||||
const std::pmr::vector<std::pmr::string>& breadcrumbs,
|
||||
std::future<database::Result<database::IndexRecord>*>&& initial_page);
|
||||
~TrackBrowser() {}
|
||||
|
||||
@@ -49,11 +51,16 @@ class TrackBrowser : public Screen {
|
||||
auto GetNumRecords() -> std::size_t;
|
||||
auto GetItemIndex(lv_obj_t* obj) -> std::optional<std::size_t>;
|
||||
|
||||
audio::TrackQueue& queue_;
|
||||
std::weak_ptr<database::Database> db_;
|
||||
lv_obj_t* back_button_;
|
||||
lv_obj_t* play_button_;
|
||||
lv_obj_t* enqueue_button_;
|
||||
lv_obj_t* list_;
|
||||
lv_obj_t* loading_indicator_;
|
||||
|
||||
std::pmr::vector<std::pmr::string> breadcrumbs_;
|
||||
|
||||
std::optional<Position> loading_pos_;
|
||||
std::optional<std::future<database::Result<database::IndexRecord>*>>
|
||||
loading_page_;
|
||||
|
||||
@@ -27,6 +27,8 @@ struct OnSystemError : tinyfsm::Event {};
|
||||
namespace internal {
|
||||
|
||||
struct RecordSelected : tinyfsm::Event {
|
||||
bool show_menu;
|
||||
std::pmr::vector<std::pmr::string> new_crumbs;
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> initial_page;
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> page;
|
||||
std::size_t record;
|
||||
|
||||
@@ -37,13 +37,54 @@ namespace modals {
|
||||
|
||||
AddToQueue::AddToQueue(Screen* host,
|
||||
audio::TrackQueue& queue,
|
||||
std::shared_ptr<playlist::IndexRecordSource> item)
|
||||
std::shared_ptr<playlist::IResetableSource> item,
|
||||
bool all_tracks_only)
|
||||
: Modal(host), queue_(queue), item_(item), all_tracks_(0) {
|
||||
lv_obj_set_layout(root_, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN);
|
||||
lv_obj_set_flex_align(root_, LV_FLEX_ALIGN_SPACE_EVENLY, LV_FLEX_ALIGN_START,
|
||||
LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
if (all_tracks_only) {
|
||||
all_tracks_ = true;
|
||||
} else {
|
||||
lv_obj_t* button_container = lv_obj_create(root_);
|
||||
lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
selected_track_btn_ = lv_btn_create(button_container);
|
||||
lv_obj_t* label = lv_label_create(selected_track_btn_);
|
||||
lv_label_set_text(label, "Selected");
|
||||
lv_group_add_obj(group_, selected_track_btn_);
|
||||
lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED);
|
||||
themes::Theme::instance()->ApplyStyle(selected_track_btn_,
|
||||
themes::Style::kTab);
|
||||
|
||||
lv_bind(selected_track_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) {
|
||||
lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED);
|
||||
lv_obj_clear_state(all_tracks_btn_, LV_STATE_CHECKED);
|
||||
all_tracks_ = false;
|
||||
});
|
||||
|
||||
all_tracks_btn_ = lv_btn_create(button_container);
|
||||
label = lv_label_create(all_tracks_btn_);
|
||||
lv_label_set_text(label, "From here");
|
||||
lv_group_add_obj(group_, all_tracks_btn_);
|
||||
themes::Theme::instance()->ApplyStyle(all_tracks_btn_, themes::Style::kTab);
|
||||
|
||||
lv_bind(all_tracks_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) {
|
||||
lv_obj_clear_state(selected_track_btn_, LV_STATE_CHECKED);
|
||||
lv_obj_add_state(all_tracks_btn_, LV_STATE_CHECKED);
|
||||
all_tracks_ = true;
|
||||
});
|
||||
|
||||
lv_obj_t* spacer = lv_obj_create(root_);
|
||||
lv_obj_set_size(spacer, 1, 4);
|
||||
}
|
||||
|
||||
lv_obj_t* button_container = lv_obj_create(root_);
|
||||
lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
|
||||
@@ -51,44 +92,8 @@ AddToQueue::AddToQueue(Screen* host,
|
||||
lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
selected_track_btn_ = lv_btn_create(button_container);
|
||||
lv_obj_t* label = lv_label_create(selected_track_btn_);
|
||||
lv_label_set_text(label, "Selected");
|
||||
lv_group_add_obj(group_, selected_track_btn_);
|
||||
lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED);
|
||||
themes::Theme::instance()->ApplyStyle(selected_track_btn_,
|
||||
themes::Style::kTab);
|
||||
|
||||
lv_bind(selected_track_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) {
|
||||
lv_obj_add_state(selected_track_btn_, LV_STATE_CHECKED);
|
||||
lv_obj_clear_state(all_tracks_btn_, LV_STATE_CHECKED);
|
||||
all_tracks_ = false;
|
||||
});
|
||||
|
||||
all_tracks_btn_ = lv_btn_create(button_container);
|
||||
label = lv_label_create(all_tracks_btn_);
|
||||
lv_label_set_text(label, "All tracks");
|
||||
lv_group_add_obj(group_, all_tracks_btn_);
|
||||
themes::Theme::instance()->ApplyStyle(all_tracks_btn_, themes::Style::kTab);
|
||||
|
||||
lv_bind(all_tracks_btn_, LV_EVENT_CLICKED, [this](lv_obj_t*) {
|
||||
lv_obj_clear_state(selected_track_btn_, LV_STATE_CHECKED);
|
||||
lv_obj_add_state(all_tracks_btn_, LV_STATE_CHECKED);
|
||||
all_tracks_ = true;
|
||||
});
|
||||
|
||||
lv_obj_t* spacer = lv_obj_create(root_);
|
||||
lv_obj_set_size(spacer, 1, 4);
|
||||
|
||||
button_container = lv_obj_create(root_);
|
||||
lv_obj_set_size(button_container, lv_pct(100), LV_SIZE_CONTENT);
|
||||
lv_obj_set_layout(button_container, LV_LAYOUT_FLEX);
|
||||
lv_obj_set_flex_flow(button_container, LV_FLEX_FLOW_ROW);
|
||||
lv_obj_set_flex_align(button_container, LV_FLEX_ALIGN_SPACE_EVENLY,
|
||||
LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
|
||||
lv_obj_t* btn = lv_btn_create(button_container);
|
||||
label = lv_label_create(btn);
|
||||
lv_obj_t* label = lv_label_create(btn);
|
||||
lv_label_set_text(label, "Play now");
|
||||
lv_group_add_obj(group_, btn);
|
||||
|
||||
@@ -112,7 +117,7 @@ AddToQueue::AddToQueue(Screen* host,
|
||||
label = lv_label_create(root_);
|
||||
lv_label_set_text(label, "Enqueue");
|
||||
|
||||
spacer = lv_obj_create(root_);
|
||||
lv_obj_t* spacer = lv_obj_create(root_);
|
||||
lv_obj_set_size(spacer, 1, 4);
|
||||
|
||||
button_container = lv_obj_create(root_);
|
||||
@@ -151,7 +156,7 @@ AddToQueue::AddToQueue(Screen* host,
|
||||
});
|
||||
}
|
||||
|
||||
spacer = lv_obj_create(root_);
|
||||
lv_obj_t* spacer = lv_obj_create(root_);
|
||||
lv_obj_set_size(spacer, 1, 4);
|
||||
|
||||
button_container = lv_obj_create(root_);
|
||||
|
||||
+122
-12
@@ -16,6 +16,7 @@
|
||||
#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 "screen_menu.hpp"
|
||||
|
||||
@@ -30,6 +31,9 @@
|
||||
#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"
|
||||
@@ -61,12 +65,17 @@ static void item_select_cb(lv_event_t* ev) {
|
||||
|
||||
TrackBrowser::TrackBrowser(
|
||||
models::TopBar& top_bar_model,
|
||||
audio::TrackQueue& queue,
|
||||
std::weak_ptr<database::Database> db,
|
||||
const std::pmr::string& title,
|
||||
const std::pmr::vector<std::pmr::string>& crumbs,
|
||||
std::future<database::Result<database::IndexRecord>*>&& initial_page)
|
||||
: db_(db),
|
||||
: 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_(),
|
||||
@@ -76,22 +85,111 @@ TrackBrowser::TrackBrowser(
|
||||
lv_obj_set_flex_align(content_, 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(content_, LV_SCROLLBAR_MODE_OFF);
|
||||
// Wrapping behaves in surprising ways, again due to progressing loading.
|
||||
lv_group_set_wrap(group_, false);
|
||||
|
||||
widgets::TopBar::Configuration config{
|
||||
.show_back_button = true,
|
||||
.title = title,
|
||||
.title = breadcrumbs_[0],
|
||||
};
|
||||
auto top_bar = CreateTopBar(content_, config, top_bar_model);
|
||||
back_button_ = top_bar->button();
|
||||
|
||||
list_ = lv_list_create(content_);
|
||||
lv_obj_set_width(list_, lv_pct(100));
|
||||
lv_obj_set_flex_grow(list_, 1);
|
||||
lv_obj_center(list_);
|
||||
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.
|
||||
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 {
|
||||
@@ -138,7 +236,12 @@ auto TrackBrowser::OnItemClicked(lv_event_t* ev) -> void {
|
||||
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,
|
||||
@@ -181,6 +284,7 @@ auto TrackBrowser::AddResults(
|
||||
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) {
|
||||
@@ -217,6 +321,12 @@ auto TrackBrowser::AddResults(
|
||||
|
||||
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));
|
||||
|
||||
+28
-7
@@ -11,6 +11,7 @@
|
||||
#include "audio_fsm.hpp"
|
||||
#include "battery.hpp"
|
||||
#include "core/lv_obj.h"
|
||||
#include "database.hpp"
|
||||
#include "misc/lv_gc.h"
|
||||
|
||||
#include "audio_events.hpp"
|
||||
@@ -112,8 +113,12 @@ void UiState::react(const audio::PlaybackUpdate& ev) {
|
||||
|
||||
void UiState::react(const audio::QueueUpdate&) {
|
||||
auto& queue = sServices->track_queue();
|
||||
bool had_queue = sPlaybackModel.current_track.get().has_value();
|
||||
sPlaybackModel.current_track.set(queue.GetCurrent());
|
||||
sPlaybackModel.upcoming_tracks.set(queue.GetUpcoming(10));
|
||||
if (!had_queue) {
|
||||
transit<states::Playing>();
|
||||
}
|
||||
}
|
||||
|
||||
void UiState::react(const internal::ControlSchemeChanged&) {
|
||||
@@ -285,14 +290,20 @@ void Browse::react(const internal::RecordSelected& ev) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& queue = sServices->track_queue();
|
||||
auto record = ev.page->values().at(ev.record);
|
||||
if (record->track()) {
|
||||
ESP_LOGI(kTag, "selected track '%s'", record->text()->c_str());
|
||||
auto& queue = sServices->track_queue();
|
||||
auto source = std::make_shared<playlist::IndexRecordSource>(
|
||||
sServices->database(), ev.initial_page, 0, ev.page, ev.record);
|
||||
sCurrentModal.reset(
|
||||
new modals::AddToQueue(sCurrentScreen.get(), queue, source));
|
||||
if (ev.show_menu) {
|
||||
sCurrentModal.reset(
|
||||
new modals::AddToQueue(sCurrentScreen.get(), queue, source));
|
||||
} else {
|
||||
queue.Clear();
|
||||
queue.AddNext(source);
|
||||
transit<Playing>();
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(kTag, "selected record '%s'", record->text()->c_str());
|
||||
auto cont = record->Expand(kRecordsPerPage);
|
||||
@@ -300,9 +311,17 @@ void Browse::react(const internal::RecordSelected& ev) {
|
||||
return;
|
||||
}
|
||||
auto query = db->GetPage<database::IndexRecord>(&cont.value());
|
||||
std::pmr::string title = record->text().value_or("TODO");
|
||||
PushScreen(std::make_shared<screens::TrackBrowser>(
|
||||
sTopBarModel, sServices->database(), title, std::move(query)));
|
||||
if (ev.show_menu) {
|
||||
std::shared_ptr<database::Result<database::IndexRecord>> res{query.get()};
|
||||
auto source = playlist::CreateSourceFromResults(db, res);
|
||||
sCurrentModal.reset(
|
||||
new modals::AddToQueue(sCurrentScreen.get(), queue, source, true));
|
||||
} else {
|
||||
std::pmr::string title = record->text().value_or("");
|
||||
PushScreen(std::make_shared<screens::TrackBrowser>(
|
||||
sTopBarModel, sServices->track_queue(), sServices->database(),
|
||||
ev.new_crumbs, std::move(query)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,8 +333,10 @@ void Browse::react(const internal::IndexSelected& ev) {
|
||||
|
||||
ESP_LOGI(kTag, "selected index %s", ev.index.name.c_str());
|
||||
auto query = db->GetTracksByIndex(ev.index, kRecordsPerPage);
|
||||
std::pmr::vector<std::pmr::string> crumbs = {ev.index.name};
|
||||
PushScreen(std::make_shared<screens::TrackBrowser>(
|
||||
sTopBarModel, sServices->database(), ev.index.name, std::move(query)));
|
||||
sTopBarModel, sServices->track_queue(), sServices->database(), crumbs,
|
||||
std::move(query)));
|
||||
}
|
||||
|
||||
void Browse::react(const internal::BackPressed& ev) {
|
||||
|
||||
Reference in New Issue
Block a user