|
|
@ -9,11 +9,13 @@ |
|
|
|
|
|
|
|
|
|
|
|
#include "core/lv_obj.h" |
|
|
|
#include "core/lv_obj.h" |
|
|
|
#include "core/lv_obj_scroll.h" |
|
|
|
#include "core/lv_obj_scroll.h" |
|
|
|
|
|
|
|
#include "core/lv_obj_tree.h" |
|
|
|
#include "database.hpp" |
|
|
|
#include "database.hpp" |
|
|
|
#include "event_queue.hpp" |
|
|
|
#include "event_queue.hpp" |
|
|
|
#include "extra/layouts/flex/lv_flex.h" |
|
|
|
#include "extra/layouts/flex/lv_flex.h" |
|
|
|
#include "font/lv_symbol_def.h" |
|
|
|
#include "font/lv_symbol_def.h" |
|
|
|
#include "lvgl.h" |
|
|
|
#include "lvgl.h" |
|
|
|
|
|
|
|
#include "misc/lv_anim.h" |
|
|
|
#include "screen_menu.hpp" |
|
|
|
#include "screen_menu.hpp" |
|
|
|
|
|
|
|
|
|
|
|
#include "core/lv_event.h" |
|
|
|
#include "core/lv_event.h" |
|
|
@ -33,8 +35,8 @@ |
|
|
|
|
|
|
|
|
|
|
|
static constexpr char kTag[] = "browser"; |
|
|
|
static constexpr char kTag[] = "browser"; |
|
|
|
|
|
|
|
|
|
|
|
static constexpr int kMaxPages = 3; |
|
|
|
static constexpr int kMaxPages = 4; |
|
|
|
static constexpr int kPageBuffer = 5; |
|
|
|
static constexpr int kPageBuffer = 6; |
|
|
|
|
|
|
|
|
|
|
|
namespace ui { |
|
|
|
namespace ui { |
|
|
|
namespace screens { |
|
|
|
namespace screens { |
|
|
@ -70,7 +72,11 @@ TrackBrowser::TrackBrowser( |
|
|
|
lv_obj_set_flex_flow(root_, LV_FLEX_FLOW_COLUMN); |
|
|
|
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_obj_set_flex_align(root_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_START, |
|
|
|
LV_FLEX_ALIGN_START); |
|
|
|
LV_FLEX_ALIGN_START); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// The default scrollbar is deceptive because we load in items progressively.
|
|
|
|
lv_obj_set_scrollbar_mode(root_, LV_SCROLLBAR_MODE_OFF); |
|
|
|
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_t* header = lv_obj_create(root_); |
|
|
|
lv_obj_set_size(header, lv_pct(100), 15); |
|
|
|
lv_obj_set_size(header, lv_pct(100), 15); |
|
|
@ -104,6 +110,7 @@ auto TrackBrowser::Tick() -> void { |
|
|
|
} |
|
|
|
} |
|
|
|
if (loading_page_->wait_for(std::chrono::seconds(0)) == |
|
|
|
if (loading_page_->wait_for(std::chrono::seconds(0)) == |
|
|
|
std::future_status::ready) { |
|
|
|
std::future_status::ready) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "load finished. adding to page."); |
|
|
|
auto result = loading_page_->get(); |
|
|
|
auto result = loading_page_->get(); |
|
|
|
AddResults(loading_pos_.value_or(END), result); |
|
|
|
AddResults(loading_pos_.value_or(END), result); |
|
|
|
|
|
|
|
|
|
|
@ -118,10 +125,12 @@ auto TrackBrowser::OnItemSelected(lv_event_t* ev) -> void { |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
if (index < kPageBuffer) { |
|
|
|
if (index < kPageBuffer) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "fetch page at start"); |
|
|
|
FetchNewPage(START); |
|
|
|
FetchNewPage(START); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
if (index > GetNumRecords() - kPageBuffer) { |
|
|
|
if (index > GetNumRecords() - kPageBuffer) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "fetch page at end"); |
|
|
|
FetchNewPage(END); |
|
|
|
FetchNewPage(END); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
@ -169,12 +178,28 @@ auto TrackBrowser::AddResults(Position pos, |
|
|
|
lv_obj_t* item = lv_list_add_btn(list_, NULL, text->c_str()); |
|
|
|
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_click_cb, LV_EVENT_CLICKED, this); |
|
|
|
lv_obj_add_event_cb(item, item_select_cb, LV_EVENT_FOCUSED, this); |
|
|
|
lv_obj_add_event_cb(item, item_select_cb, LV_EVENT_FOCUSED, this); |
|
|
|
lv_group_add_obj(group_, item); |
|
|
|
|
|
|
|
if (pos == START) { |
|
|
|
if (pos == START) { |
|
|
|
lv_obj_move_to_index(item, 0); |
|
|
|
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) { |
|
|
|
switch (pos) { |
|
|
|
case START: |
|
|
|
case START: |
|
|
|
std::for_each(results->values().rbegin(), results->values().rend(), fn); |
|
|
|
std::for_each(results->values().rbegin(), results->values().rend(), fn); |
|
|
@ -185,10 +210,27 @@ auto TrackBrowser::AddResults(Position pos, |
|
|
|
current_pages_.emplace_back(results); |
|
|
|
current_pages_.emplace_back(results); |
|
|
|
break; |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
lv_group_remove_all_objs(group_); |
|
|
|
|
|
|
|
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 { |
|
|
|
auto TrackBrowser::DropPage(Position pos) -> void { |
|
|
|
if (pos == START) { |
|
|
|
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++) { |
|
|
|
for (int i = 0; i < current_pages_.front()->values().size(); i++) { |
|
|
|
lv_obj_t* item = lv_obj_get_child(list_, 0); |
|
|
|
lv_obj_t* item = lv_obj_get_child(list_, 0); |
|
|
|
if (item == NULL) { |
|
|
|
if (item == NULL) { |
|
|
@ -212,8 +254,24 @@ auto TrackBrowser::DropPage(Position pos) -> void { |
|
|
|
|
|
|
|
|
|
|
|
auto TrackBrowser::FetchNewPage(Position pos) -> void { |
|
|
|
auto TrackBrowser::FetchNewPage(Position pos) -> void { |
|
|
|
if (loading_page_) { |
|
|
|
if (loading_page_) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "already loading; giving up"); |
|
|
|
|
|
|
|
return; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "out of pages; giving up"); |
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
auto db = db_.lock(); |
|
|
|
auto db = db_.lock(); |
|
|
|
if (!db) { |
|
|
|
if (!db) { |
|
|
|
return; |
|
|
|
return; |
|
|
@ -224,27 +282,16 @@ auto TrackBrowser::FetchNewPage(Position pos) -> void { |
|
|
|
if (current_pages_.size() >= kMaxPages) { |
|
|
|
if (current_pages_.size() >= kMaxPages) { |
|
|
|
switch (pos) { |
|
|
|
switch (pos) { |
|
|
|
case START: |
|
|
|
case START: |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "dropping end page"); |
|
|
|
DropPage(END); |
|
|
|
DropPage(END); |
|
|
|
break; |
|
|
|
break; |
|
|
|
case END: |
|
|
|
case END: |
|
|
|
|
|
|
|
ESP_LOGI(kTag, "dropping start page"); |
|
|
|
DropPage(START); |
|
|
|
DropPage(START); |
|
|
|
break; |
|
|
|
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_pos_ = pos; |
|
|
|
loading_page_ = db->GetPage(&cont.value()); |
|
|
|
loading_page_ = db->GetPage(&cont.value()); |
|
|
|
} |
|
|
|
} |
|
|
|