diff --git a/include/layered_error_frame.hpp b/include/layered_error_frame.hpp index f8f1f1a..0ae4670 100644 --- a/include/layered_error_frame.hpp +++ b/include/layered_error_frame.hpp @@ -31,14 +31,24 @@ namespace nxdt::views /* Extended class to switch between ErrorFrame and List views on demand. */ class LayeredErrorFrame: public brls::LayerView { + private: + brls::SidebarItem *sidebar_item = nullptr; + protected: ErrorFrame *error_frame = nullptr; brls::List *list = nullptr; - void SwitchLayerView(bool use_error_frame); + bool IsListItemFocused(void); + + int GetFocusStackViewIndex(void); + bool UpdateFocusStackViewAtIndex(int index, brls::View *view); + + void SwitchLayerView(bool use_error_frame, bool update_focused_view = false, bool update_focus_stack = true); public: LayeredErrorFrame(std::string msg = ""); + + void SetParentSidebarItem(brls::SidebarItem *sidebar_item); }; } diff --git a/libs/borealis b/libs/borealis index 7de1624..7f70ea3 160000 --- a/libs/borealis +++ b/libs/borealis @@ -1 +1 @@ -Subproject commit 7de16249a693e187e7249404d41e0da8a5572bf6 +Subproject commit 7f70ea3dd36dcdc6ca6c01e67632452a4da6d253 diff --git a/source/layered_error_frame.cpp b/source/layered_error_frame.cpp index 18672aa..d35a887 100644 --- a/source/layered_error_frame.cpp +++ b/source/layered_error_frame.cpp @@ -34,24 +34,110 @@ namespace nxdt::views this->addLayer(this->list); } - void LayeredErrorFrame::SwitchLayerView(bool use_error_frame) + void LayeredErrorFrame::SetParentSidebarItem(brls::SidebarItem *sidebar_item) { - int index = this->getLayerIndex(); - int new_index = (index ^ 1); - brls::View *cur_focus = brls::Application::getCurrentFocus(); + if (sidebar_item) this->sidebar_item = sidebar_item; + } + + bool LayeredErrorFrame::IsListItemFocused(void) + { + brls::View *cur_view = brls::Application::getCurrentFocus(); + size_t cur_list_count = this->list->getViewsCount(); - /* Don't proceed if we're already at the desired view layer. */ - if (index < 0 || index > 1 || (use_error_frame && index == 0) || (!use_error_frame && index == 1)) return; - - /* Focus the sidebar if we're currently focusing an element from our List and we're about to switch to the error frame. */ - if (use_error_frame && cur_focus && cur_focus->hasParent()) + if (cur_list_count) { - brls::View *cur_focus_parent = cur_focus->getParent(); - if (cur_focus_parent && cur_focus_parent->hasParent() && cur_focus_parent->getParent() == this->list) brls::Application::onGamepadButtonPressed(GLFW_GAMEPAD_BUTTON_DPAD_LEFT, false); + while(cur_view) + { + if (cur_view == this->list) return true; + cur_view = cur_view->getParent(); + } } - /* Change layer view. */ - this->changeLayer(new_index); - this->invalidate(true); + return false; + } + + int LayeredErrorFrame::GetFocusStackViewIndex(void) + { + size_t cur_list_count = this->list->getViewsCount(); + std::vector *focus_stack = brls::Application::getFocusStack(); + + if (cur_list_count && focus_stack) + { + size_t focus_stack_size = focus_stack->size(); + + for(size_t i = 0; i < focus_stack_size; i++) + { + for(size_t j = 0; j < cur_list_count; j++) + { + if (this->list->getChild(j) == focus_stack->at(i)) return static_cast(i); + } + } + } + + return -1; + } + + bool LayeredErrorFrame::UpdateFocusStackViewAtIndex(int index, brls::View *view) + { + std::vector *focus_stack = brls::Application::getFocusStack(); + if (!focus_stack || index < 0) return false; + + size_t focus_stack_size = focus_stack->size(); + if (index >= static_cast(focus_stack_size)) return false; + + focus_stack->at(index) = view; + brls::Logger::debug("Focus stack updated"); + + return true; + } + + void LayeredErrorFrame::SwitchLayerView(bool use_error_frame, bool update_focused_view, bool update_focus_stack) + { + int cur_index = this->getLayerIndex(); + int new_index = (use_error_frame ? 0 : 1); + + size_t cur_list_count = this->list->getViewsCount(); + brls::View *first_child = nullptr; + + int focus_stack_index = this->GetFocusStackViewIndex(); + bool focus_stack_updated = false; + + if (cur_list_count) + { + /* Get pointer to the first list item. */ + first_child = this->list->getChild(0); + + /* Update focus stack information, if needed. */ + if (update_focus_stack && focus_stack_index > -1) focus_stack_updated = this->UpdateFocusStackViewAtIndex(focus_stack_index, use_error_frame ? this->sidebar_item : first_child); + } + + if (!focus_stack_updated) + { + /* Check if the user is currently focusing a list item. */ + if (!update_focused_view) update_focused_view = this->IsListItemFocused(); + + if (update_focused_view) + { + /* Update focused view. */ + if (use_error_frame || !cur_list_count) + { + /* Move focus to the sidebar item. */ + brls::Application::giveFocus(this->sidebar_item); + } else { + /* Move focus to the first list item. */ + brls::Application::giveFocus(first_child); + + /* Make sure to call willAppear() on our list to update the scrolling accordingly. */ + this->list->willAppear(true); + } + } + } + + /* Change layer view only if the new index is different. */ + if (cur_index != new_index) + { + this->changeLayer(new_index); + this->invalidate(true); + } } } diff --git a/source/root_view.cpp b/source/root_view.cpp index 73dcac2..540c85d 100644 --- a/source/root_view.cpp +++ b/source/root_view.cpp @@ -33,10 +33,12 @@ namespace nxdt::views { RootView::RootView(void) : brls::TabFrame() { + int material = brls::Application::getFontStash()->material; + /* Set UI properties. */ this->setTitle(APP_TITLE); this->setIcon(BOREALIS_ASSET("icon/" APP_TITLE ".jpg")); - this->setFooterText("v" APP_VERSION); + this->setFooterText("v" APP_VERSION " (" GIT_REV ")"); /* Check if we're running under applet mode. */ this->applet_mode = utilsAppletModeCheck(); @@ -44,20 +46,21 @@ namespace nxdt::views /* Create labels. */ this->applet_mode_lbl = new brls::Label(brls::LabelStyle::HINT, "root_view/applet_mode"_i18n); this->applet_mode_lbl->setColor(nvgRGB(255, 0, 0)); + this->applet_mode_lbl->setFontSize(brls::Application::getStyle()->AppletFrame.titleSize); this->applet_mode_lbl->setParent(this); this->time_lbl = new brls::Label(brls::LabelStyle::SMALL, ""); this->time_lbl->setParent(this); this->battery_icon = new brls::Label(brls::LabelStyle::SMALL, ""); - this->battery_icon->setFont(brls::Application::getFontStash()->material); + this->battery_icon->setFont(material); this->battery_icon->setParent(this); this->battery_percentage = new brls::Label(brls::LabelStyle::SMALL, ""); this->battery_percentage->setParent(this); this->connection_icon = new brls::Label(brls::LabelStyle::SMALL, ""); - this->connection_icon->setFont(brls::Application::getFontStash()->material); + this->connection_icon->setFont(material); this->connection_icon->setParent(this); this->connection_status_lbl = new brls::Label(brls::LabelStyle::SMALL, ""); @@ -71,21 +74,40 @@ namespace nxdt::views this->usb_host_task = new nxdt::tasks::UsbHostTask(); /* Add tabs. */ - this->addTab("root_view/tabs/gamecard"_i18n, new GameCardTab(this->gc_status_task)); + GameCardTab *gamecard_tab = new GameCardTab(this->gc_status_task); + this->addTab("root_view/tabs/gamecard"_i18n, gamecard_tab); + gamecard_tab->SetParentSidebarItem(static_cast(this->sidebar->getChild(this->sidebar->getViewsCount() - 1))); + this->addSeparator(); - this->addTab("root_view/tabs/user_titles"_i18n, new TitlesTab(this->title_task, false)); - this->addTab("root_view/tabs/system_titles"_i18n, new TitlesTab(this->title_task, true)); + + TitlesTab *user_titles_tab = new TitlesTab(this->title_task, false); + this->addTab("root_view/tabs/user_titles"_i18n, user_titles_tab); + user_titles_tab->SetParentSidebarItem(static_cast(this->sidebar->getChild(this->sidebar->getViewsCount() - 1))); + + TitlesTab *system_titles_tab = new TitlesTab(this->title_task, true); + this->addTab("root_view/tabs/system_titles"_i18n, system_titles_tab); + system_titles_tab->SetParentSidebarItem(static_cast(this->sidebar->getChild(this->sidebar->getViewsCount() - 1))); + this->addSeparator(); + this->addTab("root_view/tabs/options"_i18n, new brls::Rectangle(nvgRGB(255, 255, 0))); + this->addSeparator(); + this->addTab("root_view/tabs/about"_i18n, new AboutTab()); /* Subscribe to status info event. */ this->status_info_task_sub = this->status_info_task->RegisterListener([this](const nxdt::tasks::StatusInfoData *status_info_data) { - /* Update time label. */ bool is_am = true; struct tm *timeinfo = status_info_data->timeinfo; + u32 charge_percentage = status_info_data->charge_percentage; + PsmChargerType charger_type = status_info_data->charger_type; + + NifmInternetConnectionType connection_type = status_info_data->connection_type; + char *ip_addr = status_info_data->ip_addr; + + /* Update time label. */ timeinfo->tm_mon++; timeinfo->tm_year += 1900; @@ -107,18 +129,12 @@ namespace nxdt::views is_am ? "AM" : "PM")); /* Update battery labels. */ - u32 charge_percentage = status_info_data->charge_percentage; - PsmChargerType charger_type = status_info_data->charger_type; - this->battery_icon->setText(charger_type != PsmChargerType_Unconnected ? "\uE1A3" : (charge_percentage <= 15 ? "\uE19C" : "\uE1A4")); this->battery_icon->setColor(charger_type != PsmChargerType_Unconnected ? nvgRGB(0, 255, 0) : (charge_percentage <= 15 ? nvgRGB(255, 0, 0) : brls::Application::getTheme()->textColor)); this->battery_percentage->setText(fmt::format("{}%", charge_percentage)); - /* Update network label. */ - NifmInternetConnectionType connection_type = status_info_data->connection_type; - char *ip_addr = status_info_data->ip_addr; - + /* Update network labels. */ this->connection_icon->setText(!connection_type ? "\uE195" : (connection_type == NifmInternetConnectionType_WiFi ? "\uE63E" : "\uE8BE")); this->connection_status_lbl->setText(ip_addr ? std::string(ip_addr) : "root_view/not_connected"_i18n); @@ -173,8 +189,8 @@ namespace nxdt::views /* Applet mode label. */ this->applet_mode_lbl->invalidate(true); this->applet_mode_lbl->setBoundaries( - this->x + (this->width / 4) - (this->applet_mode_lbl->getWidth() / 2), - this->y + this->height - (style->AppletFrame.footerHeight / 2), + this->x + (this->width - this->applet_mode_lbl->getWidth()) / 2, + this->y + (style->AppletFrame.headerHeightRegular / 2) + style->AppletFrame.titleOffset, this->applet_mode_lbl->getWidth(), this->applet_mode_lbl->getHeight()); } diff --git a/source/tasks.cpp b/source/tasks.cpp index c46f474..2d0a93b 100644 --- a/source/tasks.cpp +++ b/source/tasks.cpp @@ -56,7 +56,7 @@ namespace nxdt::tasks /* Get network connection status. */ u32 signal_strength = 0; - NifmInternetConnectionStatus connection_status = (NifmInternetConnectionStatus)0; + NifmInternetConnectionStatus connection_status = static_cast(0); Result rc = nifmGetInternetConnectionStatus(&(status_info_data->connection_type), &signal_strength, &connection_status); if (R_SUCCEEDED(rc)) @@ -70,7 +70,7 @@ namespace nxdt::tasks status_info_data->ip_addr = NULL; } } else { - status_info_data->connection_type = (NifmInternetConnectionType)0; + status_info_data->connection_type = static_cast(0); status_info_data->ip_addr = NULL; } diff --git a/source/titles_tab.cpp b/source/titles_tab.cpp index a9a5876..694f11d 100644 --- a/source/titles_tab.cpp +++ b/source/titles_tab.cpp @@ -65,14 +65,18 @@ namespace nxdt::views app_metadata(app_metadata), is_system(is_system) { - brls::Style* style = brls::Application::getStyle(); - /* Set sublabel. */ - this->subLabel = (!this->is_system ? std::string(app_metadata->lang_entry.author) : fmt::format("{:016X}", this->app_metadata->title_id)); - this->setHeight(style->List.Item.heightWithSubLabel); + if (!this->is_system) + { + this->subLabel = std::string(app_metadata->lang_entry.author); + this->setHeight(brls::Application::getStyle()->List.Item.heightWithSubLabel); + } /* Set thumbnail (if needed). */ if (app_metadata->icon && app_metadata->icon_size) this->setThumbnail(app_metadata->icon, app_metadata->icon_size); + + /* Set value. */ + this->setValue(fmt::format("{:016X}", this->app_metadata->title_id), false, false); } TitlesTab::TitlesTab(nxdt::tasks::TitleTask *title_task, bool is_system) : LayeredErrorFrame("titles_tab/no_titles_available"_i18n), title_task(title_task), is_system(is_system) @@ -100,39 +104,25 @@ namespace nxdt::views void TitlesTab::PopulateList(const nxdt::tasks::TitleApplicationMetadataVector* app_metadata) { + if (!app_metadata) return; + /* Block inputs while we're doing our thing. */ brls::Application::blockInputs(); ON_SCOPE_EXIT { brls::Application::unblockInputs(); }; - if (!app_metadata) return; - - bool refocus = false; + /* Populate variables. */ size_t app_metadata_count = app_metadata->size(); + bool update_focused_view = this->IsListItemFocused(); + int focus_stack_index = this->GetFocusStackViewIndex(); - if (app_metadata_count) - { - /* Determine if we need to refocus after updating the list. */ - brls::View *cur_view = brls::Application::getCurrentFocus(); - while(cur_view) - { - if (cur_view == this->list) - { - refocus = true; - break; - } - - cur_view = cur_view->getParent(); - } - } else { - /* If we need to, switch to the error frame *before* cleaning up our list. */ - this->SwitchLayerView(true); - } + /* If needed, switch to the error frame *before* cleaning up our list. */ + if (!app_metadata_count) this->SwitchLayerView(true); /* Clear list. */ this->list->clear(); this->list->invalidate(true); - /* Immediately return if we have no user application metadata. */ + /* Return immediately if we have no user application metadata. */ if (!app_metadata_count) return; /* Populate list. */ @@ -176,15 +166,11 @@ namespace nxdt::views this->list->addView(title); } + /* Update focus stack, if needed. */ + if (focus_stack_index > -1) this->UpdateFocusStackViewAtIndex(focus_stack_index, this->list->getChild(0)); + /* Switch to the list. */ this->list->invalidate(true); - this->SwitchLayerView(false); - - /* Refocus, if needed. */ - if (refocus) - { - brls::Application::giveFocus(this->list->getChild(0)); - this->list->willAppear(true); - } + this->SwitchLayerView(false, update_focused_view, focus_stack_index < 0); } }