fix etag cache not being returned upon the 2nd request, add jump page support for all menus.

- all menus feature page jumping, using L2/R2 (or DPAD_LEFT/DPAD_RIGHT in list menus)
- successive calls to fetch the etag would fail, this was seen in themezer and github menus.
- add limit the number of icons loaded per frame in homebrew menu.
- display default icon the image is not ready to be loaded / invalid.

fixes #53
This commit is contained in:
ITotalJustice
2024-12-31 03:57:08 +00:00
parent 588eb01379
commit e452615c77
13 changed files with 227 additions and 228 deletions

View File

@@ -57,6 +57,8 @@ public:
static void SetTheme(u64 theme_index); static void SetTheme(u64 theme_index);
static auto GetThemeIndex() -> u64; static auto GetThemeIndex() -> u64;
static auto GetDefaultImage(int* w = nullptr, int* h = nullptr) -> int;
// returns argv[0] // returns argv[0]
static auto GetExePath() -> fs::FsPath; static auto GetExePath() -> fs::FsPath;
// returns true if we are hbmenu. // returns true if we are hbmenu.
@@ -119,6 +121,7 @@ public:
u64 m_start_timestamp{}; u64 m_start_timestamp{};
u64 m_prev_timestamp{}; u64 m_prev_timestamp{};
fs::FsPath m_prev_last_launch{}; fs::FsPath m_prev_last_launch{};
int m_default_image{};
bool m_is_launched_via_sphaira_forwader{}; bool m_is_launched_via_sphaira_forwader{};

View File

@@ -19,7 +19,6 @@ struct NroEntry {
s64 size{}; s64 size{};
NacpStruct nacp{}; NacpStruct nacp{};
std::vector<u8> icon{};
u64 icon_size{}; u64 icon_size{};
u64 icon_offset{}; u64 icon_offset{};

View File

@@ -21,6 +21,9 @@ struct MenuBase : Widget {
void SetTitleSubHeading(std::string sub_heading); void SetTitleSubHeading(std::string sub_heading);
void SetSubHeading(std::string sub_heading); void SetSubHeading(std::string sub_heading);
static auto ScrollHelperDown(u64& index, u64& start, u64 step, s64 row, s64 page, u64 size) -> bool;
static auto ScrollHelperUp(u64& index, u64& start, s64 step, s64 row, s64 page, s64 size) -> bool;
private: private:
void UpdateVars(); void UpdateVars();

View File

@@ -359,7 +359,7 @@ struct Controller {
m_kup = 0; m_kup = 0;
} }
void UpdateButtonHeld(HidNpadButton buttons) { void UpdateButtonHeld(u64 buttons) {
if (m_kdown & buttons) { if (m_kdown & buttons) {
m_step = 50; m_step = 50;
m_counter = 0; m_counter = 0;
@@ -367,7 +367,7 @@ struct Controller {
m_counter += m_step; m_counter += m_step;
if (m_counter >= m_MAX) { if (m_counter >= m_MAX) {
m_kdown |= buttons; m_kdown |= m_kheld & buttons;
m_counter = 0; m_counter = 0;
m_step = std::min(m_step + 50, m_MAX_STEP); m_step = std::min(m_step + 50, m_MAX_STEP);
} }

View File

@@ -389,6 +389,10 @@ auto App::GetThemeIndex() -> u64 {
return g_app->m_theme_index; return g_app->m_theme_index;
} }
auto App::GetDefaultImage(int* w, int* h) -> int {
return g_app->m_default_image;
}
auto App::GetExePath() -> fs::FsPath { auto App::GetExePath() -> fs::FsPath {
return g_app->m_app_path; return g_app->m_app_path;
} }
@@ -593,24 +597,7 @@ void App::Poll() {
m_controller.m_kdown = padGetButtonsDown(&m_pad); m_controller.m_kdown = padGetButtonsDown(&m_pad);
m_controller.m_kheld = padGetButtons(&m_pad); m_controller.m_kheld = padGetButtons(&m_pad);
m_controller.m_kup = padGetButtonsUp(&m_pad); m_controller.m_kup = padGetButtonsUp(&m_pad);
m_controller.UpdateButtonHeld(static_cast<u64>(Button::ANY_DIRECTION));
// dpad
m_controller.UpdateButtonHeld(HidNpadButton_Left);
m_controller.UpdateButtonHeld(HidNpadButton_Right);
m_controller.UpdateButtonHeld(HidNpadButton_Down);
m_controller.UpdateButtonHeld(HidNpadButton_Up);
// ls
m_controller.UpdateButtonHeld(HidNpadButton_StickLLeft);
m_controller.UpdateButtonHeld(HidNpadButton_StickLRight);
m_controller.UpdateButtonHeld(HidNpadButton_StickLDown);
m_controller.UpdateButtonHeld(HidNpadButton_StickLUp);
// rs
m_controller.UpdateButtonHeld(HidNpadButton_StickRLeft);
m_controller.UpdateButtonHeld(HidNpadButton_StickRRight);
m_controller.UpdateButtonHeld(HidNpadButton_StickRDown);
m_controller.UpdateButtonHeld(HidNpadButton_StickRUp);
HidTouchScreenState touch_state{}; HidTouchScreenState touch_state{};
hidGetTouchScreenStates(&touch_state, 1); hidGetTouchScreenStates(&touch_state, 1);
@@ -1107,6 +1094,15 @@ App::App(const char* argv0) {
log_write("sd_total_space: %zd\n", sd_total_space); log_write("sd_total_space: %zd\n", sd_total_space);
} }
// load default image
if (R_SUCCEEDED(romfsInit())) {
ON_SCOPE_EXIT(romfsExit());
const auto image = ImageLoadFromFile("romfs:/default.png");
if (!image.data.empty()) {
m_default_image = nvgCreateImageRGBA(vg, image.w, image.h, 0, image.data.data());
}
}
App::Push(std::make_shared<ui::menu::main::MainMenu>()); App::Push(std::make_shared<ui::menu::main::MainMenu>());
log_write("finished app constructor\n"); log_write("finished app constructor\n");
} }
@@ -1133,6 +1129,7 @@ App::~App() {
// this has to be called before any cleanup to ensure the lifetime of // this has to be called before any cleanup to ensure the lifetime of
// nvg is still active as some widgets may need to free images. // nvg is still active as some widgets may need to free images.
m_widgets.clear(); m_widgets.clear();
nvgDeleteImage(vg, m_default_image);
appletUnhook(&m_appletHookCookie); appletUnhook(&m_appletHookCookie);

View File

@@ -93,48 +93,14 @@ struct Cache {
} }
void get(const fs::FsPath& path, curl::Header& header) { void get(const fs::FsPath& path, curl::Header& header) {
mutexLock(&m_mutex); const auto [etag, last_modified] = get_internal(path);
ON_SCOPE_EXIT(mutexUnlock(&m_mutex)); if (!etag.empty()) {
header.m_map.emplace("if-none-match", etag);
if (!fs::FsNativeSd().FileExists(path)) {
return;
} }
const auto kkey = generate_key_from_path(path); if (!last_modified.empty()) {
const auto it = m_cache.find(kkey); header.m_map.emplace("if-modified-since", last_modified);
if (it != m_cache.end()) {
return;
} }
auto hash_key = yyjson_mut_obj_getn(m_root, kkey.c_str(), kkey.length());
if (!hash_key) {
return;
}
auto etag_key = yyjson_mut_obj_get(hash_key, ETAG_STR);
auto last_modified_key = yyjson_mut_obj_get(hash_key, LAST_MODIFIED_STR);
const auto etag_value = yyjson_mut_get_str(etag_key);
const auto etag_value_len = yyjson_mut_get_len(etag_key);
const auto last_modified_value = yyjson_mut_get_str(last_modified_key);
const auto last_modified_value_len = yyjson_mut_get_len(last_modified_key);
if ((!etag_value || !etag_value_len) && (!last_modified_value || !last_modified_value_len)) {
return;
}
std::string etag;
std::string last_modified;
if (etag_value && etag_value_len) {
etag.assign(etag_value, etag_value_len);
}
if (last_modified_value && last_modified_value_len) {
last_modified.assign(last_modified_value, last_modified_value_len);
}
m_cache.insert_or_assign(it, kkey, Value{etag, last_modified});
header.m_map.emplace("if-none-match", etag);
header.m_map.emplace("if-modified-since", last_modified);
} }
void set(const fs::FsPath& path, const curl::Header& value) { void set(const fs::FsPath& path, const curl::Header& value) {
@@ -157,6 +123,48 @@ struct Cache {
} }
private: private:
auto get_internal(const fs::FsPath& path) -> Value {
if (!fs::FsNativeSd().FileExists(path)) {
return {};
}
const auto kkey = generate_key_from_path(path);
const auto it = m_cache.find(kkey);
if (it != m_cache.end()) {
return it->second;
}
auto hash_key = yyjson_mut_obj_getn(m_root, kkey.c_str(), kkey.length());
if (!hash_key) {
return {};
}
auto etag_key = yyjson_mut_obj_get(hash_key, ETAG_STR);
auto last_modified_key = yyjson_mut_obj_get(hash_key, LAST_MODIFIED_STR);
const auto etag_value = yyjson_mut_get_str(etag_key);
const auto etag_value_len = yyjson_mut_get_len(etag_key);
const auto last_modified_value = yyjson_mut_get_str(last_modified_key);
const auto last_modified_value_len = yyjson_mut_get_len(last_modified_key);
if ((!etag_value || !etag_value_len) && (!last_modified_value || !last_modified_value_len)) {
return {};
}
std::string etag;
std::string last_modified;
if (etag_value && etag_value_len) {
etag.assign(etag_value, etag_value_len);
}
if (last_modified_value && last_modified_value_len) {
last_modified.assign(last_modified_value, last_modified_value_len);
}
const Value ret{etag, last_modified};
m_cache.insert_or_assign(it, kkey, ret);
return ret;
}
void set_internal(const fs::FsPath& path, const Value& value) { void set_internal(const fs::FsPath& path, const Value& value) {
const auto kkey = generate_key_from_path(path); const auto kkey = generate_key_from_path(path);
@@ -536,15 +544,20 @@ auto DownloadInternal(CURL* curl, const Api& e) -> ApiResult {
fsFileClose(&chunk.f); fsFileClose(&chunk.f);
if (res == CURLE_OK && http_code != 304) { if (res == CURLE_OK) {
if (e.m_flags.m_flags & Flag_Cache) { if (http_code == 304) {
g_cache.set(e.m_path, header_out); log_write("cached download: %s\n", e.m_url.m_str.c_str());
} } else {
log_write("un-cached download: %s code: %u\n", e.m_url.m_str.c_str(), http_code);
if (e.m_flags.m_flags & Flag_Cache) {
g_cache.set(e.m_path, header_out);
}
fs.DeleteFile(e.m_path, true); fs.DeleteFile(e.m_path, true);
fs.CreateDirectoryRecursivelyWithPath(e.m_path, true); fs.CreateDirectoryRecursivelyWithPath(e.m_path, true);
if (R_FAILED(fs.RenameFile(tmp_buf, e.m_path, true))) { if (R_FAILED(fs.RenameFile(tmp_buf, e.m_path, true))) {
success = false; success = false;
}
} }
} }
chunk.data.clear(); chunk.data.clear();

View File

@@ -290,26 +290,6 @@ void DrawIcon(NVGcontext* vg, const LazyImage& l, const LazyImage& d, Vec4 vec,
DrawIcon(vg, l, d, vec.x, vec.y, vec.w, vec.h, rounded, scale); DrawIcon(vg, l, d, vec.x, vec.y, vec.w, vec.h, rounded, scale);
} }
auto ScrollHelperDown(u64& index, u64& start, u64 step, u64 max, u64 size) -> bool {
if (size && index < (size - 1)) {
if (index < (size - step)) {
index = index + step;
App::PlaySoundEffect(SoundEffect_Scroll);
} else {
index = size - 1;
App::PlaySoundEffect(SoundEffect_Scroll);
}
if (index - start >= max) {
log_write("moved down\n");
start += step;
}
return true;
}
return false;
}
auto AppDlToStr(u32 value) -> std::string { auto AppDlToStr(u32 value) -> std::string {
auto str = std::to_string(value); auto str = std::to_string(value);
u32 inc = 3; u32 inc = 3;
@@ -826,6 +806,7 @@ void EntryMenu::UpdateOptions() {
return InstallApp(pbox, m_entry); return InstallApp(pbox, m_entry);
}, [this](bool success){ }, [this](bool success){
if (success) { if (success) {
App::Notify("Downloaded "_i18n + m_entry.title);
m_entry.status = EntryStatus::Installed; m_entry.status = EntryStatus::Installed;
m_menu.SetDirty(); m_menu.SetDirty();
UpdateOptions(); UpdateOptions();
@@ -838,6 +819,7 @@ void EntryMenu::UpdateOptions() {
return UninstallApp(pbox, m_entry); return UninstallApp(pbox, m_entry);
}, [this](bool success){ }, [this](bool success){
if (success) { if (success) {
App::Notify("Removed "_i18n + m_entry.title);
m_entry.status = EntryStatus::Get; m_entry.status = EntryStatus::Get;
m_menu.SetDirty(); m_menu.SetDirty();
UpdateOptions(); UpdateOptions();
@@ -930,41 +912,23 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
} }
}}), }}),
std::make_pair(Button::DOWN, Action{[this](){ std::make_pair(Button::DOWN, Action{[this](){
if (ScrollHelperDown(m_index, m_start, 3, 9, m_entries_current.size())) { if (ScrollHelperDown(m_index, m_start, 3, 3, 9, m_entries_current.size())) {
SetIndex(m_index); SetIndex(m_index);
} }
}}), }}),
std::make_pair(Button::UP, Action{[this](){ std::make_pair(Button::UP, Action{[this](){
if (m_entries_current.empty()) { if (ScrollHelperUp(m_index, m_start, 3, 3, 9, m_entries_current.size())) {
return;
}
if (m_index >= 3) {
SetIndex(m_index - 3);
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index < m_start ) {
// log_write("moved up\n");
m_start -= 3;
}
}
}}),
std::make_pair(Button::R2, Action{(u8)ActionType::HELD, [this](){
if (ScrollHelperDown(m_index, m_start, 9, 9, m_entries_current.size())) {
SetIndex(m_index); SetIndex(m_index);
} }
}}), }}),
std::make_pair(Button::L2, Action{(u8)ActionType::HELD, [this](){ std::make_pair(Button::R2, Action{[this](){
if (m_entries.empty()) { if (ScrollHelperDown(m_index, m_start, 9, 3, 9, m_entries_current.size())) {
return; SetIndex(m_index);
} }
}}),
if (m_index >= 9) { std::make_pair(Button::L2, Action{[this](){
SetIndex(m_index - 9); if (ScrollHelperUp(m_index, m_start, 9, 3, 9, m_entries_current.size())) {
App::PlaySoundEffect(SoundEffect_Scroll); SetIndex(m_index);
while (m_index < m_start) {
// log_write("moved up\n");
m_start -= 3;
}
} }
}}), }}),
std::make_pair(Button::A, Action{"Info"_i18n, [this](){ std::make_pair(Button::A, Action{"Info"_i18n, [this](){
@@ -1027,12 +991,6 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"AppStore"_i18n}
curl::Flags{curl::Flag_Cache}, curl::Flags{curl::Flag_Cache},
curl::OnComplete{[this](auto& result){ curl::OnComplete{[this](auto& result){
if (result.success) { if (result.success) {
if (result.code == 304) {
log_write("appstore json not updated\n");
} else {
log_write("appstore json updated\n");
}
m_repo_download_state = ImageDownloadState::Done; m_repo_download_state = ImageDownloadState::Done;
if (HasFocus()) { if (HasFocus()) {
ScanHomebrew(); ScanHomebrew();
@@ -1120,7 +1078,6 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
image.state = ImageDownloadState::Done; image.state = ImageDownloadState::Done;
// data hasn't changed // data hasn't changed
if (result.code == 304) { if (result.code == 304) {
log_write("downloaded appstore image, was cached\n");
image.cached = false; image.cached = false;
} }
} else { } else {

View File

@@ -322,23 +322,23 @@ Menu::Menu(const std::vector<NroEntry>& nro_entries) : MenuBase{"FileBrowser"_i1
} }
}}), }}),
std::make_pair(Button::DOWN, Action{[this](){ std::make_pair(Button::DOWN, Action{[this](){
if (m_index < (m_entries_current.size() - 1)) { if (ScrollHelperDown(m_index, m_index_offset, 1, 1, 8, m_entries_current.size())) {
SetIndex(m_index + 1); SetIndex(m_index);
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index - m_index_offset >= 8) {
log_write("moved down\n");
m_index_offset++;
}
} }
}}), }}),
std::make_pair(Button::UP, Action{[this](){ std::make_pair(Button::UP, Action{[this](){
if (m_index != 0) { if (ScrollHelperUp(m_index, m_index_offset, 1, 1, 8, m_entries_current.size())) {
SetIndex(m_index - 1); SetIndex(m_index);
App::PlaySoundEffect(SoundEffect_Scroll); }
if (m_index < m_index_offset ) { }}),
log_write("moved up\n"); std::make_pair(Button::DPAD_RIGHT, Action{[this](){
m_index_offset--; if (ScrollHelperDown(m_index, m_index_offset, 8, 1, 8, m_entries_current.size())) {
} SetIndex(m_index);
}
}}),
std::make_pair(Button::DPAD_LEFT, Action{[this](){
if (ScrollHelperUp(m_index, m_index_offset, 8, 1, 8, m_entries_current.size())) {
SetIndex(m_index);
} }
}}), }}),
std::make_pair(Button::A, Action{"Open"_i18n, [this](){ std::make_pair(Button::A, Action{"Open"_i18n, [this](){

View File

@@ -233,28 +233,24 @@ Menu::Menu() : MenuBase{"GitHub"_i18n} {
fs::FsNativeSd().CreateDirectoryRecursively(CACHE_PATH); fs::FsNativeSd().CreateDirectoryRecursively(CACHE_PATH);
this->SetActions( this->SetActions(
std::make_pair(Button::R2, Action{[this](){
}}),
std::make_pair(Button::DOWN, Action{[this](){ std::make_pair(Button::DOWN, Action{[this](){
if (m_index < (m_entries.size() - 1)) { if (ScrollHelperDown(m_index, m_index_offset, 1, 1, 8, m_entries.size())) {
SetIndex(m_index + 1); SetIndex(m_index);
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index - m_index_offset >= 8) {
log_write("moved down\n");
m_index_offset++;
}
} }
}}), }}),
std::make_pair(Button::UP, Action{[this](){ std::make_pair(Button::UP, Action{[this](){
if (m_index != 0) { if (ScrollHelperUp(m_index, m_index_offset, 1, 1, 8, m_entries.size())) {
SetIndex(m_index - 1); SetIndex(m_index);
App::PlaySoundEffect(SoundEffect_Scroll); }
if (m_index < m_index_offset ) { }}),
log_write("moved up\n"); std::make_pair(Button::DPAD_RIGHT, Action{[this](){
m_index_offset--; if (ScrollHelperDown(m_index, m_index_offset, 8, 1, 8, m_entries.size())) {
} SetIndex(m_index);
}
}}),
std::make_pair(Button::DPAD_LEFT, Action{[this](){
if (ScrollHelperUp(m_index, m_index_offset, 8, 1, 8, m_entries.size())) {
SetIndex(m_index);
} }
}}), }}),

View File

@@ -43,27 +43,23 @@ Menu::Menu() : MenuBase{"Homebrew"_i18n} {
} }
}}), }}),
std::make_pair(Button::DOWN, Action{[this](){ std::make_pair(Button::DOWN, Action{[this](){
if (m_index < (m_entries.size() - 1)) { if (ScrollHelperDown(m_index, m_start, 3, 3, 9, m_entries.size())) {
if (m_index < (m_entries.size() - 3)) { SetIndex(m_index);
SetIndex(m_index + 3);
} else {
SetIndex(m_entries.size() - 1);
}
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index - m_start >= 9) {
log_write("moved down\n");
m_start += 3;
}
} }
}}), }}),
std::make_pair(Button::UP, Action{[this](){ std::make_pair(Button::UP, Action{[this](){
if (m_index >= 3) { if (ScrollHelperUp(m_index, m_start, 3, 3, 9, m_entries.size())) {
SetIndex(m_index - 3); SetIndex(m_index);
App::PlaySoundEffect(SoundEffect_Scroll); }
if (m_index < m_start ) { }}),
// log_write("moved up\n"); std::make_pair(Button::R2, Action{[this](){
m_start -= 3; if (ScrollHelperDown(m_index, m_start, 9, 3, 9, m_entries.size())) {
} SetIndex(m_index);
}
}}),
std::make_pair(Button::L2, Action{[this](){
if (ScrollHelperUp(m_index, m_start, 9, 3, 9, m_entries.size())) {
SetIndex(m_index);
} }
}}), }}),
std::make_pair(Button::A, Action{"Launch"_i18n, [this](){ std::make_pair(Button::A, Action{"Launch"_i18n, [this](){
@@ -177,15 +173,26 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour); gfx::drawRect(vg, SCREEN_WIDTH - 50+2, 102 + sb_h * sb_y, 10-4, sb_h + (sb_h * 2) - 4, theme->elements[ThemeEntryID_TEXT_SELECTED].colour);
} }
// max images per frame, in order to not hit io / gpu too hard.
const int image_load_max = 2;
int image_load_count = 0;
for (u64 i = 0, pos = SCROLL, y = 110, w = 370, h = 155; pos < nro_total && i < max_entry_display; y += h + 10) { for (u64 i = 0, pos = SCROLL, y = 110, w = 370, h = 155; pos < nro_total && i < max_entry_display; y += h + 10) {
for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) { for (u64 j = 0, x = 75; j < 3 && pos < nro_total && i < max_entry_display; j++, i++, pos++, x += w + 10) {
auto& e = m_entries[pos]; auto& e = m_entries[pos];
// lazy load image // lazy load image
if (!e.image && e.icon.empty() && e.icon_size && e.icon_offset) { if (image_load_count < image_load_max) {
e.icon = nro_get_icon(e.path, e.icon_size, e.icon_offset); if (!e.image && e.icon_size && e.icon_offset) {
if (!e.icon.empty()) { // NOTE: it seems that images can be any size. SuperTux uses a 1024x1024
e.image = nvgCreateImageMem(vg, 0, e.icon.data(), e.icon.size()); // ~300Kb image, which takes a few frames to completely load.
// really, switch-tools should handle this by resizing the image before
// adding it to the nro, as well as validate its a valid jpeg.
const auto icon = nro_get_icon(e.path, e.icon_size, e.icon_offset);
if (!icon.empty()) {
e.image = nvgCreateImageMem(vg, 0, icon.data(), icon.size());
image_load_count++;
}
} }
} }
@@ -198,7 +205,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
const float image_size = 115; const float image_size = 115;
gfx::drawImageRounded(vg, x + 20, y + 20, image_size, image_size, e.image); gfx::drawImageRounded(vg, x + 20, y + 20, image_size, image_size, e.image ? e.image : App::GetDefaultImage());
nvgSave(vg); nvgSave(vg);
nvgScissor(vg, x, y, w - 30.f, h); // clip nvgScissor(vg, x, y, w - 30.f, h); // clip
@@ -261,12 +268,12 @@ void Menu::SetIndex(std::size_t index) {
// log_write("name: %s hbini.ts: %lu file.ts: %lu smaller: %s\n", e.GetName(), e.hbini.timestamp, e.timestamp.modified, e.hbini.timestamp < e.timestamp.modified ? "true" : "false"); // log_write("name: %s hbini.ts: %lu file.ts: %lu smaller: %s\n", e.GetName(), e.hbini.timestamp, e.timestamp.modified, e.hbini.timestamp < e.timestamp.modified ? "true" : "false");
SetTitleSubHeading(m_entries[m_index].path); SetTitleSubHeading(m_entries[m_index].path);
this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries.size())); this->SetSubHeading(std::to_string(m_index + 1) + " / " + std::to_string(m_entries.size()) + " " + std::to_string(m_start));
} }
void Menu::InstallHomebrew() { void Menu::InstallHomebrew() {
const auto& nro = m_entries[m_index]; const auto& nro = m_entries[m_index];
InstallHomebrew(nro.path, nro.nacp, nro.icon); InstallHomebrew(nro.path, nro.nacp, nro_get_icon(nro.path, nro.size, nro.icon_offset));
} }
void Menu::ScanHomebrew() { void Menu::ScanHomebrew() {

View File

@@ -158,12 +158,6 @@ MainMenu::MainMenu() {
return false; return false;
} }
if (result.code == 304) {
log_write("data hasn't changed\n");
} else {
log_write("etag changed\n");
}
auto json = yyjson_read_file(CACHE_PATH, YYJSON_READ_NOFLAG, nullptr, nullptr); auto json = yyjson_read_file(CACHE_PATH, YYJSON_READ_NOFLAG, nullptr, nullptr);
R_UNLESS(json, false); R_UNLESS(json, false);
ON_SCOPE_EXIT(yyjson_doc_free(json)); ON_SCOPE_EXIT(yyjson_doc_free(json));

View File

@@ -103,4 +103,53 @@ void MenuBase::UpdateVars() {
m_poll_timestamp.Update(); m_poll_timestamp.Update();
} }
auto MenuBase::ScrollHelperDown(u64& index, u64& start, u64 step, s64 row, s64 page, u64 size) -> bool {
const auto old_index = index;
if (index < (size - step)) {
index += step;
} else {
index = size - 1;
}
if (index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
s64 delta = index - old_index;
if (index - start >= page) {
do {
start += row;
delta -= row;
} while (delta > 0 && start + page < size);
}
return true;
}
return false;
}
auto MenuBase::ScrollHelperUp(u64& index, u64& start, s64 step, s64 row, s64 page, s64 size) -> bool {
const auto old_index = index;
if (index >= step) {
index -= step;
} else {
index = 0;
}
if (index != old_index) {
App::PlaySoundEffect(SoundEffect_Scroll);
// if ()
while (index < start) {
// log_write("moved up\n");
start -= row;
}
return true;
}
return false;
}
} // namespace sphaira::ui::menu } // namespace sphaira::ui::menu

View File

@@ -179,26 +179,6 @@ auto loadThemeImage(ThemeEntry& e) -> bool {
} }
} }
auto ScrollHelperDown(u64& index, u64& start, u64 step, u64 max, u64 size) -> bool {
if (size && index < (size - 1)) {
if (index < (size - step)) {
index = index + step;
App::PlaySoundEffect(SoundEffect_Scroll);
} else {
index = size - 1;
App::PlaySoundEffect(SoundEffect_Scroll);
}
if (index - start >= max) {
log_write("moved down\n");
start += step;
}
return true;
}
return false;
}
void from_json(yyjson_val* json, Creator& e) { void from_json(yyjson_val* json, Creator& e) {
JSON_OBJ_ITR( JSON_OBJ_ITR(
JSON_SET_STR(id); JSON_SET_STR(id);
@@ -449,7 +429,25 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
}}), }}),
std::make_pair(Button::DOWN, Action{[this](){ std::make_pair(Button::DOWN, Action{[this](){
const auto& page = m_pages[m_page_index]; const auto& page = m_pages[m_page_index];
if (ScrollHelperDown(m_index, m_start, 3, 6, page.m_packList.size())) { if (ScrollHelperDown(m_index, m_start, 3, 3, 6, page.m_packList.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::UP, Action{[this](){
const auto& page = m_pages[m_page_index];
if (ScrollHelperUp(m_index, m_start, 3, 3, 6, page.m_packList.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::R2, Action{[this](){
const auto& page = m_pages[m_page_index];
if (ScrollHelperDown(m_index, m_start, 6, 3, 6, page.m_packList.size())) {
SetIndex(m_index);
}
}}),
std::make_pair(Button::L2, Action{[this](){
const auto& page = m_pages[m_page_index];
if (ScrollHelperUp(m_index, m_start, 6, 3, 6, page.m_packList.size())) {
SetIndex(m_index); SetIndex(m_index);
} }
}}), }}),
@@ -465,12 +463,10 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
App::Push(std::make_shared<ProgressBox>("Installing "_i18n + entry.details.name, [this, &entry](auto pbox){ App::Push(std::make_shared<ProgressBox>("Installing "_i18n + entry.details.name, [this, &entry](auto pbox){
return InstallTheme(pbox, entry); return InstallTheme(pbox, entry);
}, [this](bool success){ }, [this, &entry](bool success){
// if (success) { if (success) {
// m_entry.status = EntryStatus::Installed; App::Notify("Downloaded "_i18n + entry.details.name);
// m_menu.SetDirty(); }
// UpdateOptions();
// }
}, 2)); }, 2));
} }
} }
@@ -530,16 +526,6 @@ Menu::Menu() : MenuBase{"Themezer"_i18n} {
} }
})); }));
}}), }}),
std::make_pair(Button::UP, Action{[this](){
if (m_index >= 3) {
SetIndex(m_index - 3);
App::PlaySoundEffect(SoundEffect_Scroll);
if (m_index < m_start ) {
// log_write("moved up\n");
m_start -= 3;
}
}
}}),
std::make_pair(Button::R, Action{"Next Page"_i18n, [this](){ std::make_pair(Button::R, Action{"Next Page"_i18n, [this](){
m_page_index++; m_page_index++;
if (m_page_index >= m_page_index_max) { if (m_page_index >= m_page_index_max) {
@@ -662,10 +648,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
image.state = ImageDownloadState::Done; image.state = ImageDownloadState::Done;
// data hasn't changed // data hasn't changed
if (result.code == 304) { if (result.code == 304) {
log_write("downloaded themezer image, was cached\n");
image.cached = false; image.cached = false;
} else {
log_write("downloaded new themezer image\n");
} }
} else { } else {
image.state = ImageDownloadState::Failed; image.state = ImageDownloadState::Failed;
@@ -690,9 +673,7 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
} }
} }
if (image.image) { gfx::drawImageRounded(vg, x + xoff, y, 320, 180, image.image ? image.image : App::GetDefaultImage());
gfx::drawImageRounded(vg, x + xoff, y, 320, 180, image.image);
}
} }
gfx::drawTextArgs(vg, x + xoff, y + 180 + 20, 18, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.details.name.c_str()); gfx::drawTextArgs(vg, x + xoff, y + 180 + 20, 18, NVG_ALIGN_LEFT, theme->elements[text_id].colour, "%s", e.details.name.c_str());