diff --git a/library/include/borealis/views/applet_frame.hpp b/library/include/borealis/views/applet_frame.hpp index 1402928..beacd1a 100644 --- a/library/include/borealis/views/applet_frame.hpp +++ b/library/include/borealis/views/applet_frame.hpp @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -30,6 +31,7 @@ class AppletFrame : public Box { public: AppletFrame(); + ~AppletFrame() override; void handleXMLElement(tinyxml2::XMLElement* element) override; @@ -48,8 +50,20 @@ class AppletFrame : public Box static View* create(); private: + void updateFooterHints(); + void clearHintBox(Box* box); + void addHintItem(Box* box, const Action& action, float spacingAfter); + BRLS_BIND(Label, title, "brls/applet_frame/title_label"); BRLS_BIND(Image, icon, "brls/applet_frame/title_icon"); + BRLS_BIND(Box, footerHintRight, "brls/applet_frame/footer_hint_right"); + BRLS_BIND(Box, footerHintLeft, "brls/applet_frame/footer_hint_left"); + + GenericEvent::Subscription focusSubscription; + VoidEvent::Subscription hintsSubscription; + + int switchFont = FONT_INVALID; + int regularFont = FONT_INVALID; protected: View* contentView = nullptr; diff --git a/library/include/borealis/views/label.hpp b/library/include/borealis/views/label.hpp index 3e1ccef..797d328 100644 --- a/library/include/borealis/views/label.hpp +++ b/library/include/borealis/views/label.hpp @@ -85,6 +85,7 @@ class Label : public View void setVerticalAlign(VerticalAlign align); void setFontSize(float value); + void setFontFace(int fontFace); void setLineHeight(float value); void setTextColor(NVGcolor color); diff --git a/library/lib/views/applet_frame.cpp b/library/lib/views/applet_frame.cpp index f9f47dc..11b8cd6 100644 --- a/library/lib/views/applet_frame.cpp +++ b/library/lib/views/applet_frame.cpp @@ -15,6 +15,11 @@ limitations under the License. */ +#include +#include + +#include +#include #include #include #include @@ -22,6 +27,73 @@ namespace brls { +namespace +{ + +constexpr float HINT_FONT_SIZE = 22.0f; +constexpr float HINT_SPACING = 30.0f; +constexpr float HINT_ICON_GAP = 4.0f; + +const char* getButtonIcon(ControllerButton button) +{ + switch (button) + { + case BUTTON_A: + return "\uE0E0"; + case BUTTON_B: + return "\uE0E1"; + case BUTTON_X: + return "\uE0E2"; + case BUTTON_Y: + return "\uE0E3"; + case BUTTON_START: + return "\uE0EF"; + case BUTTON_BACK: + return "\uE0F0"; + case BUTTON_UP: + return "\uE0EB"; + case BUTTON_DOWN: + return "\uE0EC"; + case BUTTON_LEFT: + return "\uE0ED"; + case BUTTON_RIGHT: + return "\uE0EE"; + default: + return "\uE152"; + } +} + +std::string getButtonHintText(const Action& action) +{ + if (!action.hintText.empty()) + return action.hintText; + + switch (action.button) + { + case BUTTON_A: + return "Ok"; + case BUTTON_B: + return "Zurück"; + case BUTTON_START: + return "Beenden"; + default: + return ""; + } +} + +bool actionsSort(const Action& a, const Action& b) +{ + if (a.button == BUTTON_START) + return true; + if (b.button == BUTTON_A) + return true; + if (b.button == BUTTON_B && a.button != BUTTON_A) + return true; + return false; +} + +} // namespace + const std::string appletFrameXML = R"xml( @@ -61,16 +133,12 @@ const std::string appletFrameXML = R"xml( - + - + axis="row" + direction="leftToRight" + justifyContent="flexStart" + alignItems="center" + visibility="gone" /> - + grow="1.0" + axis="row" + direction="leftToRight" + justifyContent="flexEnd" + alignItems="center" /> @@ -109,6 +187,141 @@ AppletFrame::AppletFrame() }); this->forwardXMLAttribute("iconInterpolation", this->icon, "interpolation"); + + this->switchFont = Application::getFont(FONT_SWITCH_ICONS); + this->regularFont = Application::getFont(FONT_REGULAR); + + this->focusSubscription = Application::getGlobalFocusChangeEvent()->subscribe([this](View*) { + this->updateFooterHints(); + }); + + this->hintsSubscription = Application::getGlobalHintsUpdateEvent()->subscribe([this]() { + this->updateFooterHints(); + }); + + this->updateFooterHints(); +} + +AppletFrame::~AppletFrame() +{ + Application::getGlobalFocusChangeEvent()->unsubscribe(this->focusSubscription); + Application::getGlobalHintsUpdateEvent()->unsubscribe(this->hintsSubscription); +} + +void AppletFrame::clearHintBox(Box* box) +{ + if (!box) + return; + + std::vector children = box->getChildren(); + while (!children.empty()) + { + View* child = children.back(); + box->removeView(child); + children.pop_back(); + } +} + +void AppletFrame::addHintItem(Box* box, const Action& action, float spacingAfter) +{ + const std::string hintText = getButtonHintText(action); + if (hintText.empty()) + return; + + Box* item = new Box(Axis::ROW); + item->setDirection(Direction::LEFT_TO_RIGHT); + item->setAlignItems(AlignItems::CENTER); + if (spacingAfter > 0.0f) + item->setMarginRight(spacingAfter); + + Label* icon = new Label(); + icon->setText(getButtonIcon(action.button)); + icon->setFontSize(HINT_FONT_SIZE); + icon->setSingleLine(true); + if (this->switchFont != FONT_INVALID) + icon->setFontFace(this->switchFont); + + Label* text = new Label(); + text->setText(hintText); + text->setFontSize(HINT_FONT_SIZE); + text->setSingleLine(true); + text->setMarginLeft(HINT_ICON_GAP); + if (this->regularFont != FONT_INVALID) + text->setFontFace(this->regularFont); + + item->addView(icon); + item->addView(text); + box->addView(item); +} + +void AppletFrame::updateFooterHints() +{ + this->clearHintBox(this->footerHintRight); + this->clearHintBox(this->footerHintLeft); + + std::set addedKeys; + std::vector rightActions; + std::vector leftActions; + + View* focusParent = Application::getCurrentFocus(); + if (!focusParent) + focusParent = this->contentView; + + while (focusParent) + { + for (const Action& action : focusParent->getActions()) + { + if (action.hidden || !action.available) + continue; + + if (addedKeys.find(action.button) != addedKeys.end()) + continue; + + addedKeys.insert(action.button); + + if (action.button == BUTTON_START) + leftActions.push_back(action); + else + rightActions.push_back(action); + } + + focusParent = focusParent->getParent(); + } + + std::stable_sort(rightActions.begin(), rightActions.end(), actionsSort); + std::stable_sort(leftActions.begin(), leftActions.end(), actionsSort); + + for (size_t i = 0; i < rightActions.size(); i++) + { + float spacing = (i + 1 < rightActions.size()) ? HINT_SPACING : 0.0f; + this->addHintItem(this->footerHintRight, rightActions[i], spacing); + } + + for (size_t i = 0; i < leftActions.size(); i++) + { + float spacing = (i + 1 < leftActions.size()) ? HINT_SPACING : 0.0f; + this->addHintItem(this->footerHintLeft, leftActions[i], spacing); + } + + std::string* commonFooter = Application::getCommonFooter(); + if (commonFooter && !commonFooter->empty()) + { + Label* text = new Label(); + text->setText(*commonFooter); + text->setFontSize(HINT_FONT_SIZE); + text->setSingleLine(true); + if (!leftActions.empty()) + text->setMarginLeft(HINT_SPACING); + if (this->regularFont != FONT_INVALID) + text->setFontFace(this->regularFont); + this->footerHintLeft->addView(text); + } + + const bool hasRight = !this->footerHintRight->getChildren().empty(); + const bool hasLeft = !this->footerHintLeft->getChildren().empty(); + + this->footerHintRight->setVisibility(hasRight ? Visibility::VISIBLE : Visibility::GONE); + this->footerHintLeft->setVisibility(hasLeft ? Visibility::VISIBLE : Visibility::GONE); } void AppletFrame::setIconFromRes(std::string name) @@ -132,7 +345,6 @@ void AppletFrame::setContentView(View* view) { if (this->contentView) { - // Remove the node this->removeView(this->contentView); this->contentView = nullptr; } @@ -146,6 +358,7 @@ void AppletFrame::setContentView(View* view) this->contentView->setGrow(1.0f); this->addView(this->contentView, 1); + this->updateFooterHints(); } void AppletFrame::handleXMLElement(tinyxml2::XMLElement* element) diff --git a/library/lib/views/label.cpp b/library/lib/views/label.cpp index c31d904..25e4bdf 100644 --- a/library/lib/views/label.cpp +++ b/library/lib/views/label.cpp @@ -316,6 +316,14 @@ void Label::setFontSize(float value) this->invalidate(); } +void Label::setFontFace(int fontFace) +{ + if (fontFace != FONT_INVALID) + this->font = fontFace; + + this->invalidate(); +} + void Label::setLineHeight(float value) { this->lineHeight = value;