From 48ba3d99d359c43073a779bbe4d733aa2eea1dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 5 Nov 2025 15:19:22 +0100 Subject: [PATCH] GameSettingsScreen: Add tab icons. In portrait mode, use only icons on the tabs. --- Common/UI/TabHolder.cpp | 20 +++- Common/UI/TabHolder.h | 4 + Common/UI/View.cpp | 36 +++--- Common/UI/View.h | 4 +- UI/GameSettingsScreen.cpp | 17 ++- UI/TabbedDialogScreen.cpp | 13 ++- UI/TabbedDialogScreen.h | 11 +- UI/UIAtlas.cpp | 5 +- assets/ui_images/images.svg | 212 ++++++++++++++++++------------------ 9 files changed, 182 insertions(+), 140 deletions(-) diff --git a/Common/UI/TabHolder.cpp b/Common/UI/TabHolder.cpp index 3099b025bb..2f1eafb1a7 100644 --- a/Common/UI/TabHolder.cpp +++ b/Common/UI/TabHolder.cpp @@ -9,7 +9,7 @@ namespace UI { TabHolder::TabHolder(Orientation orientation, float stripSize, TabHolderFlags flags, View *bannerView, LayoutParams *layoutParams) - : LinearLayout(Opposite(orientation), layoutParams) { + : LinearLayout(Opposite(orientation), layoutParams), orientation_(orientation), flags_(flags) { SetSpacing(0.0f); if (orientation == ORIENT_HORIZONTAL) { // This orientation supports adding a back button. @@ -34,6 +34,8 @@ TabHolder::TabHolder(Orientation orientation, float stripSize, TabHolderFlags fl } } else { tabContainer_ = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(stripSize, FILL_PARENT)); + tabContainer_->Add(new Spacer(8.0f)); + tabContainer_->SetSpacing(0.0f); tabStrip_ = new ChoiceStrip(orientation, new LayoutParams(FILL_PARENT, FILL_PARENT)); tabStrip_->SetTopTabs(true); tabScroll_ = new ScrollView(orientation, new LinearLayoutParams(1.0f)); @@ -64,7 +66,13 @@ void TabHolder::AddBack(UIScreen *parent) { void TabHolder::AddTabContents(std::string_view title, ImageID imageId, ViewGroup *tabContents) { tabs_.push_back(tabContents); - tabStrip_->AddChoice(title, imageId); + if (orientation_ == ORIENT_HORIZONTAL && (flags_ & TabHolderFlags::HorizontalOnlyIcons) && imageId.isValid()) { + tabStrip_->AddChoice(imageId); + } else if (orientation_ == ORIENT_VERTICAL && (flags_ & TabHolderFlags::VerticalShowIcons) && imageId.isValid()) { + tabStrip_->AddChoice(title, imageId); + } else { + tabStrip_->AddChoice(title); + } contents_->Add(tabContents); if (tabs_.size() > 1) tabContents->SetVisibility(V_GONE); @@ -78,7 +86,13 @@ void TabHolder::AddTabContents(std::string_view title, ImageID imageId, ViewGrou void TabHolder::AddTabDeferred(std::string_view title, ImageID imageId, std::function createCb) { tabs_.push_back(nullptr); // marker - tabStrip_->AddChoice(title, imageId); + if (orientation_ == ORIENT_HORIZONTAL && (flags_ & TabHolderFlags::HorizontalOnlyIcons) && imageId.isValid()) { + tabStrip_->AddChoice(imageId); + } else if (orientation_ == ORIENT_VERTICAL && (flags_ & TabHolderFlags::VerticalShowIcons) && imageId.isValid()) { + tabStrip_->AddChoice(title, imageId); + } else { + tabStrip_->AddChoice(title); + } tabTweens_.push_back(nullptr); createFuncs_.push_back(createCb); diff --git a/Common/UI/TabHolder.h b/Common/UI/TabHolder.h index f8b42d9961..99bd84b32c 100644 --- a/Common/UI/TabHolder.h +++ b/Common/UI/TabHolder.h @@ -14,6 +14,8 @@ class ScrollView; enum class TabHolderFlags { Default = 0, BackButton = 1, + HorizontalOnlyIcons = 2, + VerticalShowIcons = 4, }; ENUM_CLASS_BITOPS(TabHolderFlags); @@ -57,7 +59,9 @@ private: ChoiceStrip *tabStrip_ = nullptr; ScrollView *tabScroll_ = nullptr; ViewGroup *contents_ = nullptr; + Orientation orientation_ = ORIENT_HORIZONTAL; + TabHolderFlags flags_ = TabHolderFlags::Default; int currentTab_ = 0; std::vector tabs_; std::vector tabTweens_; diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 30ffc0da73..1c8c63c8bc 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -468,11 +468,6 @@ void Choice::ClickInternal() { void Choice::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert, float &w, float &h) const { float totalW = 0.0f; float totalH = 0.0f; - if (image_.isValid()) { - dc.Draw()->GetAtlas()->measureImage(image_, &w, &h); - totalW = w * imgScale_ + 6; - totalH = h * imgScale_; - } if (!text_.empty()) { const int paddingX = 12; float availWidth = horiz.size - paddingX * 2 - textPadding_.horiz() - totalW; @@ -488,6 +483,16 @@ void Choice::GetContentDimensionsBySpec(const UIContext &dc, MeasureSpec horiz, dc.MeasureTextRect(dc.GetTheme().uiFont, scale, scale, text_, availBounds, &textW, &textH, FLAG_WRAP_TEXT); totalH = std::max(totalH, textH); totalW += textW; + if (image_.isValid()) { + totalW += 12; + totalW += totalH; + } + } else { + if (image_.isValid()) { + dc.Draw()->GetAtlas()->measureImage(image_, &w, &h); + totalW = w * imgScale_ + 6; + totalH = h * imgScale_; + } } w = totalW + (text_.empty() ? 16 : 24); @@ -508,17 +513,20 @@ void Choice::Draw(UIContext &dc) { } else if (!text_.empty() && !hideTitle_) { dc.SetFontStyle(dc.GetTheme().uiFont); - int paddingX = 12; - float availWidth = bounds_.w - paddingX * 2 - textPadding_.horiz(); + int paddingLeft = 12; + int paddingRight = 12; + float availWidth = bounds_.w - (paddingLeft + paddingRight + textPadding_.horiz()); if (image_.isValid()) { + paddingLeft = 0; const AtlasImage *image = dc.Draw()->GetAtlas()->getImage(image_); if (image) { _dbg_assert_(image); - paddingX += image->w + 6; - availWidth -= image->w + 6; // TODO: Use scale rotation and flip here as well (DrawImageRotated is always ALIGN_CENTER for now) - dc.Draw()->DrawImage(image_, bounds_.x + 6, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_LEFT | ALIGN_VCENTER); + dc.Draw()->DrawImage(image_, bounds_.x + bounds_.h * 0.5f + paddingLeft, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER); + + paddingLeft += bounds_.h; + availWidth -= bounds_.h; } } @@ -528,12 +536,12 @@ void Choice::Draw(UIContext &dc) { if (rightIconImage_.isValid()) { uint32_t col = rightIconKeepColor_ ? 0xffffffff : style.fgColor; // Don't apply theme to gold icon if (shine_) { - Bounds b = Bounds::FromCenter(bounds_.x2() - 32 - paddingX, bounds_.centerY(), bounds_.h * 0.4f); + Bounds b = Bounds::FromCenter(bounds_.x2() - 32 - paddingRight, bounds_.centerY(), bounds_.h * 0.4f); DrawIconShine(dc, b.Inset(5.0f, 5.0f), 0.65f, false); } - dc.Draw()->DrawImageRotated(rightIconImage_, bounds_.x2() - 32 - paddingX, bounds_.centerY(), rightIconScale_, rightIconRot_, col, rightIconFlipH_); + dc.Draw()->DrawImageRotated(rightIconImage_, bounds_.x2() - 32 - paddingRight, bounds_.centerY(), rightIconScale_, rightIconRot_, col, rightIconFlipH_); } - Bounds textBounds(bounds_.x + paddingX + textPadding_.left, bounds_.y, availWidth, bounds_.h); + Bounds textBounds(bounds_.x + paddingLeft + textPadding_.left, bounds_.y, availWidth, bounds_.h); dc.DrawTextRectSqueeze(text_, textBounds, style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT | drawTextFlags_); } dc.SetFontScale(1.0f, 1.0f); @@ -629,7 +637,7 @@ void CollapsibleHeader::Draw(UIContext &dc) { float xoff = 37.0f; dc.SetFontStyle(dc.GetTheme().uiFontSmall); - dc.DrawText(text_, bounds_.x + 4 + xoff, bounds_.centerY(), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER); + dc.DrawText(text_, bounds_.x + 6 + xoff, bounds_.centerY(), style.fgColor, ALIGN_LEFT | ALIGN_VCENTER); dc.Draw()->DrawImageCenterTexel(dc.GetTheme().whiteImage, bounds_.x, bounds_.y2() - 2, bounds_.x2(), bounds_.y2(), style.fgColor); if (hasSubItems_) { dc.Draw()->DrawImageRotated(ImageID("I_ARROW"), bounds_.x + 20.0f, bounds_.y + 20.0f, 1.0f, *toggle_ ? -M_PI / 2 : M_PI, style.fgColor); diff --git a/Common/UI/View.h b/Common/UI/View.h index e3057e4554..5cc1b51ab1 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -1033,8 +1033,8 @@ public: Event OnClick; private: - bool down_; - bool dragging_; + bool down_ = false; + bool dragging_ = false; }; class TextEdit : public View { diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 49f66e84cc..4a476064af 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -117,7 +117,7 @@ void SetMemStickDirDarwin(int requesterToken) { #endif GameSettingsScreen::GameSettingsScreen(const Path &gamePath, std::string gameID, bool editThenRestore) - : UITabbedBaseDialogScreen(gamePath), gameID_(gameID), editThenRestore_(editThenRestore) { + : UITabbedBaseDialogScreen(gamePath, TabDialogFlags::HorizontalOnlyIcons | TabDialogFlags::VerticalShowIcons), gameID_(gameID), editThenRestore_(editThenRestore) { prevInflightFrames_ = g_Config.iInflightFrames; analogSpeedMapped_ = KeyMap::InputMappingsFromPspButton(VIRTKEY_SPEED_ANALOG, nullptr, true); } @@ -221,35 +221,34 @@ void GameSettingsScreen::CreateTabs() { using namespace UI; auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); - AddTab("GameSettingsGraphics", ms->T("Graphics"), [this](UI::LinearLayout *parent) { + AddTab("GameSettingsGraphics", ms->T("Graphics"), ImageID("I_DISPLAY"), [this](UI::LinearLayout *parent) { CreateGraphicsSettings(parent); }); - AddTab("GameSettingsControls", ms->T("Controls"), [this](UI::LinearLayout *parent) { + AddTab("GameSettingsControls", ms->T("Controls"), ImageID("I_CONTROLLER"), [this](UI::LinearLayout *parent) { CreateControlsSettings(parent); }); - AddTab("GameSettingsAudio", ms->T("Audio"), [this](UI::LinearLayout *parent) { + AddTab("GameSettingsAudio", ms->T("Audio"), ImageID("I_SPEAKER_MAX"), [this](UI::LinearLayout *parent) { CreateAudioSettings(parent); }); - AddTab("GameSettingsNetworking", ms->T("Networking"), [this](UI::LinearLayout *parent) { + AddTab("GameSettingsNetworking", ms->T("Networking"), ImageID("I_WIFI"), [this](UI::LinearLayout *parent) { CreateNetworkingSettings(parent); }); - AddTab("GameSettingsTools", ms->T("Tools"), [this](UI::LinearLayout *parent) { + AddTab("GameSettingsTools", ms->T("Tools"), ImageID("I_DEVMENU"), [this](UI::LinearLayout *parent) { CreateToolsSettings(parent); }); - AddTab("GameSettingsSystem", ms->T("System"), [this](UI::LinearLayout *parent) { + AddTab("GameSettingsSystem", ms->T("System"), ImageID("I_GEAR"), [this](UI::LinearLayout *parent) { parent->SetSpacing(0); CreateSystemSettings(parent); }); - int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); if ((deviceType == DEVICE_TYPE_VR) || g_Config.bForceVR) { - AddTab("GameSettingsVR", ms->T("VR"), [this](UI::LinearLayout *parent) { + AddTab("GameSettingsVR", ms->T("VR"), ImageID::invalid(), [this](UI::LinearLayout *parent) { CreateVRSettings(parent); }); } diff --git a/UI/TabbedDialogScreen.cpp b/UI/TabbedDialogScreen.cpp index d8fbdb43cc..6b35730003 100644 --- a/UI/TabbedDialogScreen.cpp +++ b/UI/TabbedDialogScreen.cpp @@ -53,12 +53,21 @@ void UITabbedBaseDialogScreen::CreateViews() { if (portrait) { auto di = GetI18NCategory(I18NCat::DIALOG); LinearLayout *verticalLayout = new LinearLayout(ORIENT_VERTICAL, new LayoutParams(FILL_PARENT, FILL_PARENT)); - tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, TabHolderFlags::BackButton, filterNotice_, new LinearLayoutParams(1.0f)); + + TabHolderFlags tabHolderFlags = TabHolderFlags::BackButton; + if (flags_ & TabDialogFlags::HorizontalOnlyIcons) { + tabHolderFlags |= TabHolderFlags::HorizontalOnlyIcons; + } + tabHolder_ = new TabHolder(ORIENT_HORIZONTAL, 200, tabHolderFlags, filterNotice_, new LinearLayoutParams(1.0f)); verticalLayout->Add(tabHolder_); CreateExtraButtons(verticalLayout, 0); root_->Add(verticalLayout); } else { - tabHolder_ = new TabHolder(ORIENT_VERTICAL, 200, TabHolderFlags::Default, filterNotice_, new AnchorLayoutParams(10, 0, 10, 0, false)); + TabHolderFlags tabHolderFlags = TabHolderFlags::Default; + if (flags_ & TabDialogFlags::VerticalShowIcons) { + tabHolderFlags |= TabHolderFlags::VerticalShowIcons; + } + tabHolder_ = new TabHolder(ORIENT_VERTICAL, 300, tabHolderFlags, filterNotice_, new AnchorLayoutParams(10, 0, 10, 0, false)); CreateExtraButtons(tabHolder_->Container(), 10); tabHolder_->AddBack(this); root_->Add(tabHolder_); diff --git a/UI/TabbedDialogScreen.h b/UI/TabbedDialogScreen.h index b78d4f7e9d..42e53921c0 100644 --- a/UI/TabbedDialogScreen.h +++ b/UI/TabbedDialogScreen.h @@ -15,6 +15,13 @@ namespace UI { class TabHolder; } +enum class TabDialogFlags { + Default = 0, + HorizontalOnlyIcons = 1, + VerticalShowIcons = 2, +}; +ENUM_CLASS_BITOPS(TabDialogFlags); + enum class TabFlags { Default = 0, NonScrollable = 1, @@ -23,7 +30,7 @@ ENUM_CLASS_BITOPS(TabFlags); class UITabbedBaseDialogScreen : public UIBaseDialogScreen { public: - UITabbedBaseDialogScreen(const Path &gamePath) : UIBaseDialogScreen(gamePath) { + UITabbedBaseDialogScreen(const Path &gamePath, TabDialogFlags flags = TabDialogFlags::Default) : UIBaseDialogScreen(gamePath), flags_(flags) { ignoreBottomInset_ = true; } @@ -61,4 +68,6 @@ private: // If we recreate the views while this is active we show it again std::string oldSettingInfo_; std::string searchFilter_; + + TabDialogFlags flags_ = TabDialogFlags::Default; }; diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp index 1d53c8b9fa..394fed4e8c 100644 --- a/UI/UIAtlas.cpp +++ b/UI/UIAtlas.cpp @@ -135,7 +135,7 @@ static const ImageMeta imageIDs[] = { {"I_FILLED_CIRCLE_3", false}, {"I_FILLED_CIRCLE_4", false}, {"I_FILLED_CIRCLE_5", false}, - {"I_SETTINGS_DISPLAY", false}, + {"I_DISPLAY", false}, {"I_NAVIGATE_BACK", false}, {"I_NAVIGATE_FORWARD", false}, {"I_FOLDER_UPLOAD", false}, @@ -150,6 +150,9 @@ static const ImageMeta imageIDs[] = { {"I_LOGO_PLAY_STORE", false}, {"I_LOGO_APP_STORE", false}, {"I_SEARCH", false}, + {"I_DEVMENU", false}, + {"I_CONTROLLER", false}, + {"I_DEBUGGER", false}, }; static std::string PNGNameFromID(std::string_view id) { diff --git a/assets/ui_images/images.svg b/assets/ui_images/images.svg index a5dddc85b1..f778fc3f75 100644 --- a/assets/ui_images/images.svg +++ b/assets/ui_images/images.svg @@ -24,15 +24,15 @@ inkscape:pagecheckerboard="true" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" - inkscape:zoom="2.0000001" - inkscape:cx="520.74999" - inkscape:cy="253.74999" + inkscape:zoom="4.0000002" + inkscape:cx="95.374995" + inkscape:cy="277.37499" inkscape:window-width="3379" inkscape:window-height="1941" inkscape:window-x="-9" inkscape:window-y="0" inkscape:window-maximized="0" - inkscape:current-layer="layer1" + inkscape:current-layer="I_FOLDER_PINNED" showgrid="false" /> + + + + + + .st0{stroke:#FFFFFF;stroke-miterlimit:10;} .st1{fill:#FFFFFF;} +