diff --git a/Common/UI/PopupScreens.cpp b/Common/UI/PopupScreens.cpp index e7a301ebb8..65e5406ebb 100644 --- a/Common/UI/PopupScreens.cpp +++ b/Common/UI/PopupScreens.cpp @@ -1088,26 +1088,28 @@ void AbstractChoiceWithValueDisplay::Draw(UIContext &dc) { textPadding_.right = w + paddingX; Choice::Draw(dc); + const Bounds bounds = ContentBounds(); int imagePadding = 0; if (rightIconImage_.isValid()) { - imagePadding = bounds_.h; + imagePadding = bounds.h; } dc.SetFontScale(scale, scale); - Bounds valueBounds(bounds_.x2() - textPadding_.right - imagePadding, bounds_.y, w, bounds_.h); + Bounds valueBounds(bounds.x2() - textPadding_.right - imagePadding, bounds.y, w, bounds.h); dc.DrawTextRect(valueText, valueBounds, color, ALIGN_RIGHT | ALIGN_VCENTER | FLAG_WRAP_TEXT); dc.SetFontScale(1.0f, 1.0f); } else { Choice::Draw(dc); + const Bounds bounds = ContentBounds(); if (iconOnly_) { // In this case we only display the image of the choice. Useful for small buttons spawning a popup. - dc.Draw()->DrawImageRotated(ValueImage(), bounds_.centerX(), bounds_.centerY(), imgScale_, imgRot_, style.fgColor, imgFlipH_); + dc.Draw()->DrawImageRotated(ValueImage(), bounds.centerX(), bounds.centerY(), imgScale_, imgRot_, style.fgColor, imgFlipH_); return; } - float scale = CalculateValueScale(dc, valueText, bounds_.w); + float scale = CalculateValueScale(dc, valueText, bounds.w); dc.SetFontScale(scale, scale); - dc.DrawTextRect(valueText, bounds_.Expand(-paddingX, 0.0f), color, ALIGN_LEFT | ALIGN_VCENTER | FLAG_WRAP_TEXT); + dc.DrawTextRect(valueText, bounds.Expand(-paddingX, 0.0f), color, ALIGN_LEFT | ALIGN_VCENTER | FLAG_WRAP_TEXT); dc.SetFontScale(1.0f, 1.0f); } } diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 0f7b8140a6..1c9920aade 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -439,6 +439,36 @@ void ClickableItem::Draw(UIContext &dc) { DrawBG(dc, style, dc.GetTheme().choiceRoundRadius); } +static constexpr float HINT_PADDING_X = 12.0f; +static constexpr float HINT_PADDING_BOTTOM = 8.0f; + +void ClickableItem::Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) { + Clickable::Measure(dc, horiz, vert); + hintHeight_ = 0.0f; + if (hintText_.empty()) { + return; + } + float availWidth = measuredWidth_ - HINT_PADDING_X * 2; + if (availWidth <= 0.0f) { + availWidth = MAX_ITEM_SIZE; + } + float w, h; + dc.MeasureTextRect(dc.GetTheme().uiFontTiny, 1.0f, 1.0f, hintText_, availWidth, &w, &h, FLAG_WRAP_TEXT); + hintHeight_ = h + HINT_PADDING_BOTTOM; + measuredHeight_ += hintHeight_; +} + +void ClickableItem::DrawHint(UIContext &dc, const Style &style) const { + if (hintText_.empty()) { + return; + } + dc.SetFontStyle(dc.GetTheme().uiFontTiny); + dc.SetFontScale(1.0f, 1.0f); + Bounds hintBounds(bounds_.x + HINT_PADDING_X, bounds_.y2() - hintHeight_, bounds_.w - HINT_PADDING_X * 2, hintHeight_ - HINT_PADDING_BOTTOM); + dc.DrawTextRect(hintText_, hintBounds, style.fgColor, ALIGN_TOPLEFT | FLAG_WRAP_TEXT); + dc.SetFontStyle(dc.GetTheme().uiFont); +} + void Choice::ClickInternal() { ClickableItem::ClickInternal(); UI::PlayUISound(UI::UISound::CONFIRM); @@ -494,10 +524,12 @@ void Choice::Draw(UIContext &dc) { DrawBG(dc, style, dc.GetTheme().choiceRoundRadius); + const Bounds bounds = ContentBounds(); + if (image_.isValid() && text_.empty()) { - dc.Draw()->DrawImageRotated(image_, bounds_.centerX(), bounds_.centerY(), imgScale_, imgRot_, style.fgColor, imgFlipH_); + dc.Draw()->DrawImageRotated(image_, bounds.centerX(), bounds.centerY(), imgScale_, imgRot_, style.fgColor, imgFlipH_); if (shine_) { - Bounds b = Bounds::FromCenter(bounds_.centerX(), bounds_.centerY(), bounds_.h * 0.4f); + Bounds b = Bounds::FromCenter(bounds.centerX(), bounds.centerY(), bounds.h * 0.4f); DrawIconShine(dc, b.Inset(5.0f, 5.0f), 0.65f, false); } } else if (!text_.empty() && !hideTitle_) { @@ -512,9 +544,9 @@ void Choice::Draw(UIContext &dc) { if (image) { _dbg_assert_(image); // TODO: Use scale rotation and flip here as well (DrawImageRotated is always ALIGN_CENTER for now) - dc.Draw()->DrawImage(image_, bounds_.x + bounds_.h * 0.5f + paddingLeft, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER); + dc.Draw()->DrawImage(image_, bounds.x + bounds.h * 0.5f + paddingLeft, bounds.centerY(), 1.0f, style.fgColor, ALIGN_CENTER); - paddingLeft += bounds_.h; + paddingLeft += bounds.h; } } @@ -524,30 +556,32 @@ void Choice::Draw(UIContext &dc) { paddingRight = std::max(ITEM_HEIGHT, imgW); } - float availWidth = bounds_.w - (paddingLeft + paddingRight + textPadding_.horiz()); + float availWidth = bounds.w - (paddingLeft + paddingRight + textPadding_.horiz()); if (centered_) { - dc.DrawTextRectSqueeze(text_, bounds_, style.fgColor, ALIGN_CENTER | FLAG_WRAP_TEXT | drawTextFlags_); + dc.DrawTextRectSqueeze(text_, bounds, style.fgColor, ALIGN_CENTER | FLAG_WRAP_TEXT | drawTextFlags_); } else { if (rightIconImage_.isValid()) { uint32_t col = rightIconKeepColor_ ? 0xffffffff : style.fgColor; // Don't apply theme to gold icon - float iconX = bounds_.x2() - paddingRight + ITEM_HEIGHT * 0.5f; + float iconX = bounds.x2() - paddingRight + ITEM_HEIGHT * 0.5f; if (shine_) { - Bounds b = Bounds::FromCenter(iconX, bounds_.centerY(), bounds_.h * 0.4f); + Bounds b = Bounds::FromCenter(iconX, bounds.centerY(), bounds.h * 0.4f); DrawIconShine(dc, b.Inset(5.0f, 5.0f), 0.65f, false); } - dc.Draw()->DrawImageRotated(rightIconImage_, iconX, bounds_.centerY(), rightIconScale_, rightIconRot_, col, rightIconFlipH_); + dc.Draw()->DrawImageRotated(rightIconImage_, iconX, bounds.centerY(), rightIconScale_, rightIconRot_, col, rightIconFlipH_); } - Bounds textBounds(bounds_.x + paddingLeft + 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); } + DrawHint(dc, style); + if (selected_) { - dc.Draw()->DrawImage(ImageID("I_CHECKMARK"), bounds_.x2() - 30, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_CENTER); + dc.Draw()->DrawImage(ImageID("I_CHECKMARK"), bounds.x2() - 30, bounds.centerY(), 1.0f, style.fgColor, ALIGN_CENTER); } } @@ -832,11 +866,13 @@ void CheckBox::Draw(UIContext &dc) { // Padding right of the checkbox image too. const float availWidth = bounds_.w - paddingX * 2 - imageW - paddingX; + const Bounds bounds = ContentBounds(); if (!text_.empty()) { - Bounds textBounds(bounds_.x + paddingX, bounds_.y, availWidth, bounds_.h); + Bounds textBounds(bounds.x + paddingX, bounds.y, availWidth, bounds.h); dc.DrawTextRectSqueeze(text_, textBounds, style.fgColor, ALIGN_VCENTER | FLAG_WRAP_TEXT); } - dc.Draw()->DrawImage(image, bounds_.x2() - paddingX, bounds_.centerY(), 1.0f, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); + dc.Draw()->DrawImage(image, bounds.x2() - paddingX, bounds.centerY(), 1.0f, style.fgColor, ALIGN_RIGHT | ALIGN_VCENTER); + DrawHint(dc, style); dc.SetFontScale(1.0f, 1.0f); } diff --git a/Common/UI/View.h b/Common/UI/View.h index 70fc2508d9..d9a552b852 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -750,9 +750,31 @@ class ClickableItem : public Clickable { public: ClickableItem(LayoutParams *layoutParams); void GetContentDimensions(const UIContext &dc, float &w, float &h) const override; + void Measure(const UIContext &dc, MeasureSpec horiz, MeasureSpec vert) override; // Draws the item background. void Draw(UIContext &dc) override; + + // Small explanatory text under the item, part of it (highlighted and clicked along with it). + // Drawn by Choice and CheckBox. + void SetHintText(std::string_view text) { + hintText_ = text; + } + const std::string &GetHintText() const { + return hintText_; + } + +protected: + // The item's bounds without the hint below. + Bounds ContentBounds() const { + Bounds b = bounds_; + b.h -= hintHeight_; + return b; + } + void DrawHint(UIContext &dc, const Style &style) const; + + std::string hintText_; + float hintHeight_ = 0.0f; }; // Use to trigger something or open a submenu screen. diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index b98f92a0bc..b48004d8b3 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -307,7 +307,7 @@ void CwCheatScreen::CreateContentViews(UI::ViewGroup *parent) { rightColumn->Add(new ItemHeader(cw->T("Cheats"))); bool prevIsTitle = false; - View *prev = nullptr; + CheckBox *prev = nullptr; for (size_t i = 0; i < fileInfo_.size(); ++i) { std::string_view text; if (fileInfo_[i].IsTitle(&text)) { @@ -318,7 +318,17 @@ void CwCheatScreen::CreateContentViews(UI::ViewGroup *parent) { prevIsTitle = true; prev = nullptr; } else if (fileInfo_[i].IsPostComment(&text)) { - rightColumn->Add(new SettingHint(text, prev)); + if (prev) { + // A cheat's comment lines go in its checkbox. + std::string hint = prev->GetHintText(); + if (!hint.empty()) { + hint += '\n'; + } + hint += text; + prev->SetHintText(hint); + } else { + rightColumn->Add(new SettingHint(text, nullptr)); + } prevIsTitle = false; } else { // Regular cheat code. diff --git a/UI/Settings/AudioPage.cpp b/UI/Settings/AudioPage.cpp index 7b16ac84be..9206301cea 100644 --- a/UI/Settings/AudioPage.cpp +++ b/UI/Settings/AudioPage.cpp @@ -103,8 +103,7 @@ void BuildAudioSettings(SettingsPage *page) { sdlAudio = true; static const int bufferSizes[] = {128, 256, 512, 1024, 2048}; - page->IntSlider(&g_Config.iSDLAudioBufferSize, 0, 2048, "Buffer size").Default(256).RestrictTo(bufferSizes, ARRAY_SIZE(bufferSizes)); - page->Hint({ I18NCat::DIALOG, "This change will not take effect until PPSSPP is restarted." }); + page->IntSlider(&g_Config.iSDLAudioBufferSize, 0, 2048, "Buffer size").Default(256).RestrictTo(bufferSizes, ARRAY_SIZE(bufferSizes)).Hint({ I18NCat::DIALOG, "This change will not take effect until PPSSPP is restarted." }); #endif #if PPSSPP_PLATFORM(WINDOWS) @@ -155,13 +154,11 @@ void BuildAudioSettings(SettingsPage *page) { }; // AAudio isn't stable before Android 8.1, so there it's always OpenSL ES. if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 27) { - page->Setting(&g_Config.iAndroidAudioApi).OnChange(restartAudio); - page->Hint({ I18NCat::AUDIO, "AudioApiHint", "Try the other one if you have no sound or other audio problems." }); + page->Setting(&g_Config.iAndroidAudioApi).OnChange(restartAudio).Hint({ I18NCat::AUDIO, "AudioApiHint", "Try the other one if you have no sound or other audio problems." }); page->Setting(&g_Config.bAndroidAudioMMap).OnChange(restartAudio).EnabledFunc([]() { return g_Config.iAndroidAudioApi == (int)AndroidAudioApi::AAUDIO; - }); - page->Hint({ I18NCat::AUDIO, "AudioMMapHint", "Lowest audio delay. On some devices, it causes silence over Bluetooth or other connected audio devices. Turn it off if you get no sound." }); + }).Hint({ I18NCat::AUDIO, "AudioMMapHint", "Lowest audio delay. On some devices, it causes silence over Bluetooth or other connected audio devices. Turn it off if you get no sound." }); } // Show Oboe debug info const std::string audioErrorStr = AndroidAudio_GetErrorString(g_audioState); diff --git a/UI/Settings/ControlsPage.cpp b/UI/Settings/ControlsPage.cpp index 7c68dc3bbd..0fe7a8edd2 100644 --- a/UI/Settings/ControlsPage.cpp +++ b/UI/Settings/ControlsPage.cpp @@ -103,8 +103,7 @@ void BuildControlsSettings(SettingsPage *page) { page->Setting(&touch.bHideStickBackground).Enabled(&g_Config.bShowTouchControls); // Sticky D-pad. - page->Setting(&g_Config.bStickyTouchDPad).Enabled(&g_Config.bShowTouchControls); - page->Hint("Easier sweeping movements"); + page->Setting(&g_Config.bStickyTouchDPad).Enabled(&g_Config.bShowTouchControls).Hint("Easier sweeping movements"); // Re-centers itself to the touch location on touch-down. page->Setting(&g_Config.bAutoCenterTouchAnalog).Enabled(&g_Config.bShowTouchControls); @@ -130,8 +129,7 @@ void BuildControlsSettings(SettingsPage *page) { #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP) page->Setting(&g_Config.bIgnoreWindowsKey); #endif - page->Setting(&g_Config.fAnalogLimiterDeadzone); - page->Hint({ I18NCat::CONTROLS, "AnalogLimiter Tip", "When the analog limiter button is pressed" }); + page->Setting(&g_Config.fAnalogLimiterDeadzone).Hint({ I18NCat::CONTROLS, "AnalogLimiter Tip", "When the analog limiter button is pressed" }); page->Setting(&g_Config.iRapidFireInterval); #if PPSSPP_PLATFORM(WINDOWS) || defined(SDL) || PPSSPP_PLATFORM(ANDROID) bool enableMouseSettings = true; @@ -148,8 +146,7 @@ void BuildControlsSettings(SettingsPage *page) { page->Header({ I18NCat::CONTROLS, "Mouse", "Mouse settings" }); page->Setting(&g_Config.iMouseWheelUpDelayMs); - page->Setting(&g_Config.bMouseControl); - page->Hint({ I18NCat::CONTROLS, "MouseControl Tip", "You can now map mouse in control mapping screen by pressing the 'M' icon." }); + page->Setting(&g_Config.bMouseControl).Hint({ I18NCat::CONTROLS, "MouseControl Tip", "You can now map mouse in control mapping screen by pressing the 'M' icon." }); #if !PPSSPP_PLATFORM(ANDROID) page->Setting(&g_Config.bMouseConfine).Enabled(&g_Config.bMouseControl); diff --git a/UI/Settings/GraphicsPage.cpp b/UI/Settings/GraphicsPage.cpp index 428f87fa37..9fed98791e 100644 --- a/UI/Settings/GraphicsPage.cpp +++ b/UI/Settings/GraphicsPage.cpp @@ -113,8 +113,7 @@ static void BuildSpeedHacksPage(SettingsPage *page) { page->Header(); - page->Setting(&g_Config.bSkipBufferEffects).Disabled(&g_Config.bSoftwareRendering); - page->Hint({I18NCat::GRAPHICS, "RenderingMode NonBuffered Tip", "Faster, but graphics may be missing in some games"}); + page->Setting(&g_Config.bSkipBufferEffects).Disabled(&g_Config.bSoftwareRendering).Hint({I18NCat::GRAPHICS, "RenderingMode NonBuffered Tip", "Faster, but graphics may be missing in some games"}); page->Setting(&g_Config.iSkipGPUReadbackMode).Disabled(&g_Config.bSoftwareRendering).Setup([](View *view) { static_cast(view)->SetDefault(0); @@ -302,8 +301,7 @@ void BuildGraphicsSettings(SettingsPage *page) { } page->Setting(&g_Config.bRenderDuplicateFrames).EnabledFunc([] { return !g_Config.bSkipBufferEffects && g_Config.iFrameSkip == 0; - }); - page->Hint({I18NCat::GRAPHICS, "RenderDuplicateFrames Tip", "Can make framerate smoother in games that run at lower framerates"}); + }).Hint({I18NCat::GRAPHICS, "RenderDuplicateFrames Tip", "Can make framerate smoother in games that run at lower framerates"}); page->Header("Frame Rate Control"); @@ -314,14 +312,12 @@ void BuildGraphicsSettings(SettingsPage *page) { } page->Header(); - page->Link({I18NCat::GRAPHICS, "Speed Hacks"}, I18NCat::GRAPHICS, &BuildSpeedHacksPage); - page->Hint({I18NCat::GRAPHICS, "SpeedHacksHint", "These settings can cause rendering errors"}); + page->Link({I18NCat::GRAPHICS, "Speed Hacks"}, I18NCat::GRAPHICS, &BuildSpeedHacksPage).Hint({I18NCat::GRAPHICS, "SpeedHacksHint", "These settings can cause rendering errors"}); page->Header(); page->Setting(&g_Config.iBloomHack).EnabledFunc([] { return !g_Config.bSoftwareRendering && g_Config.iInternalResolution != 1; - }); - page->Hint("Reduces artifacts"); + }).Hint("Reduces artifacts"); page->Header("Latency"); @@ -333,8 +329,7 @@ void BuildGraphicsSettings(SettingsPage *page) { g_Config.iInflightFrames = previous; }); } - }); - page->Hint("Faster, input lag"); // TODO: This hint could use improvement. + }).Hint("Faster, input lag"); // TODO: This hint could use improvement. } page->Header("Texture upscaling"); @@ -364,13 +359,11 @@ void BuildGraphicsSettings(SettingsPage *page) { }); page->Setting(&g_Config.iTexScalingLevel).EnabledFunc([]() { return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); - }); - page->Hint({ I18NCat::GRAPHICS, "UpscaleLevel Tip", "CPU heavy - some scaling may be delayed to avoid stutter" }); + }).Hint({ I18NCat::GRAPHICS, "UpscaleLevel Tip", "CPU heavy - some scaling may be delayed to avoid stutter" }); page->Setting(&g_Config.bTexDeposterize).EnabledFunc([]() { return !g_Config.bSoftwareRendering && !UsingHardwareTextureScaling(); - }); - page->Hint({ I18NCat::GRAPHICS, "Deposterize Tip", "Fixes visual banding glitches in upscaled textures" }); + }).Hint({ I18NCat::GRAPHICS, "Deposterize Tip", "Fixes visual banding glitches in upscaled textures" }); page->Header("Texture Filtering"); diff --git a/UI/Settings/NetworkingPage.cpp b/UI/Settings/NetworkingPage.cpp index 82786adf06..89e8b602ca 100644 --- a/UI/Settings/NetworkingPage.cpp +++ b/UI/Settings/NetworkingPage.cpp @@ -208,25 +208,21 @@ void BuildNetworkingSettings(SettingsPage *page) { page->Setting(&g_Config.iAdhocServerRelayMode).EnabledFunc([] { return !PSP_IsInited(); - }); - page->Hint({ I18NCat::NETWORKING, "PacketRelayHint", "Available on servers that provide 'aemu_postoffice' packet relay, like socom.cc. Disable this for LAN or VPN play. Can be more reliable, but sometimes slower." }); + }).Hint({ I18NCat::NETWORKING, "PacketRelayHint", "Available on servers that provide 'aemu_postoffice' packet relay, like socom.cc. Disable this for LAN or VPN play. Can be more reliable, but sometimes slower." }); page->Custom({ I18NCat::SYSTEM, "Nickname" }, [host](UI::ViewGroup *parent) { return AddNicknameChoice(host, parent); - }); - page->Hint("Try to pick a unique nickname for ad hoc play"); + }).Hint("Try to pick a unique nickname for ad hoc play"); page->Setting(&g_Config.bEnableAdhocServer).OnChange(requestLocalNetwork); page->Setting(&g_Config.bAdhocUseUdp).EnabledFunc([] { return !PSP_IsInited(); - }); - page->Hint("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP."); + }).Hint("Try switching between TCP/UDP if you suffer frequent adhoc server disconnects. Some network environments favor TCP and some UDP."); page->Setting(&g_Config.bAdhocRelayModeEnetPdpReliable).EnabledFunc([] { return !PSP_IsInited() && g_Config.bAdhocUseUdp; - }); - page->Hint("When disabled, packet loss on datagram adhoc sockets will be exposed to the game's netcode. Keep this enabled if you don't know what that means."); + }).Hint("When disabled, packet loss on datagram adhoc sockets will be exposed to the game's netcode. Keep this enabled if you don't know what that means."); page->Header("Infrastructure"); if (g_Config.sInfrastructureUsername.empty()) { @@ -261,8 +257,7 @@ void BuildNetworkingSettings(SettingsPage *page) { // away, instead of waiting for a port request that may never come. UPnP_Notify(); }); - page->Setting(&g_Config.bUPnPUseOriginalPort).Enabled(&g_Config.bEnableUPnP); - page->Hint({ I18NCat::NETWORKING, "UseOriginalPort Tip", "May not work for all devices or games, see wiki." }); + page->Setting(&g_Config.bUPnPUseOriginalPort).Enabled(&g_Config.bEnableUPnP).Hint({ I18NCat::NETWORKING, "UseOriginalPort Tip", "May not work for all devices or games, see wiki." }); page->Header(); page->Link({I18NCat::NETWORKING, "Chat"}, I18NCat::NETWORKING, &BuildChatSettingsPage); @@ -282,8 +277,7 @@ void BuildNetworkingSettings(SettingsPage *page) { wlanChannelChoice->HideChoice(i + 7); } }); - page->Setting(&g_Config.iPortOffset); - page->Hint("PortOffsetHint"); + page->Setting(&g_Config.iPortOffset).Hint("PortOffsetHint"); page->Setting(&g_Config.iMinTimeout); page->Setting(&g_Config.bForcedFirstConnect); page->Setting(&g_Config.bAllowSpeedControlWhileConnected); diff --git a/UI/SettingsPage.cpp b/UI/SettingsPage.cpp index ad35cc9ca9..883b4547a2 100644 --- a/UI/SettingsPage.cpp +++ b/UI/SettingsPage.cpp @@ -181,11 +181,6 @@ SettingItem &SettingsPage::Info(SettingText text, std::string_view rightText) { return item; } -SettingItem &SettingsPage::Hint(SettingText text) { - _dbg_assert_(!items_.empty()); - return Add(SettingType::Hint, text); -} - SettingItem &SettingsPage::Description(SettingText text) { return Add(SettingType::Description, text); } @@ -279,7 +274,7 @@ static std::function ChangeHandler(const SettingItem &i }; } -static UI::View *CreateSettingViewInner(const SettingItem &item, I18NCat pageCat, UI::View *prevView, UI::ViewGroup *parent, ScreenManager *screenManager) { +static UI::View *CreateSettingViewInner(const SettingItem &item, I18NCat pageCat, UI::ViewGroup *parent, ScreenManager *screenManager) { using namespace UI; std::string_view text = item.text.Translate(pageCat); @@ -363,8 +358,6 @@ static UI::View *CreateSettingViewInner(const SettingItem &item, I18NCat pageCat } case SettingType::Info: return parent->Add(new InfoItem(text, item.rightText)); - case SettingType::Hint: - return parent->Add(new SettingHint(text, prevView)); case SettingType::Description: { TextView *description = parent->Add(new TextView(text, FLAG_WRAP_TEXT, false, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(0, 4)))); @@ -386,8 +379,8 @@ static UI::View *CreateSettingViewInner(const SettingItem &item, I18NCat pageCat return nullptr; } -static UI::View *CreateSettingView(const SettingItem &item, I18NCat pageCat, UI::View *prevView, UI::ViewGroup *parent, ScreenManager *screenManager) { - UI::View *view = CreateSettingViewInner(item, pageCat, prevView, parent, screenManager); +static UI::View *CreateSettingView(const SettingItem &item, I18NCat pageCat, UI::ViewGroup *parent, ScreenManager *screenManager) { + UI::View *view = CreateSettingViewInner(item, pageCat, parent, screenManager); if (!view) { return nullptr; } @@ -403,6 +396,14 @@ static UI::View *CreateSettingView(const SettingItem &item, I18NCat pageCat, UI: choice->SetIconRight(ImageID("I_CHEVRON_RIGHT")); } } + if (!item.hint.empty()) { + if (UI::ClickableItem *clickable = dynamic_cast(view)) { + clickable->SetHintText(item.hint.Translate(pageCat)); + } else { + // Custom views that can't show a hint get it below them. + parent->Add(new SettingHint(item.hint.Translate(pageCat), view)); + } + } if (item.setup) { item.setup(view); } @@ -410,7 +411,6 @@ static UI::View *CreateSettingView(const SettingItem &item, I18NCat pageCat, UI: } void CreateSettingViews(const SettingsPage &page, UI::ViewGroup *parent, ScreenManager *screenManager, std::vector *views) { - UI::View *prevView = nullptr; // Each header is a group, which the items after it go in. Items before the first get one without a title. UI::ViewGroup *group = nullptr; for (const SettingItem &item : page.Items()) { @@ -419,7 +419,7 @@ void CreateSettingViews(const SettingsPage &page, UI::ViewGroup *parent, ScreenM if (!group && !outsideGroups) { group = parent->Add(new UI::ChoiceGroup()); } - UI::View *view = CreateSettingView(item, page.DefaultCat(), prevView, outsideGroups ? parent : group, screenManager); + UI::View *view = CreateSettingView(item, page.DefaultCat(), outsideGroups ? parent : group, screenManager); if (views) { views->push_back(view); } @@ -430,8 +430,6 @@ void CreateSettingViews(const SettingsPage &page, UI::ViewGroup *parent, ScreenM group = nullptr; } else if (view && item.type == SettingType::Header) { group = static_cast(view); - } else if (view) { - prevView = view; } } } diff --git a/UI/SettingsPage.h b/UI/SettingsPage.h index 54a1fbfdee..8655895ea9 100644 --- a/UI/SettingsPage.h +++ b/UI/SettingsPage.h @@ -86,7 +86,6 @@ enum class SettingType { FloatSlider, Choice, // A button. onChange is called on click. Info, - Hint, // Explanatory text attached to the previous item. Description, // Text about the page or a part of it, outside the groups. Custom, Link, // Opens another page. @@ -97,6 +96,8 @@ enum class SettingType { struct SettingItem { SettingType type; SettingText text; + // Explanatory text, shown under the item. Searched along with the text. + SettingText hint; void *ptr = nullptr; // bool *, int * or std::string *, depending on type. // IntChoice. The choices are translated in the same category as the text. @@ -169,6 +170,7 @@ struct SettingItem { SettingItem &OnChange(std::function func) { onChange = std::move(func); return *this; } SettingItem &OnClick(std::function func) { onChange = std::move(func); return *this; } SettingItem &Setup(std::function func) { setup = std::move(func); return *this; } + SettingItem &Hint(SettingText text) { hint = text; return *this; } SettingItem &OpensScreen() { opensScreen = true; return *this; } SettingItem &Default(int value) { defaultValue = value; hasDefault = true; return *this; } SettingItem &Step(int value) { step = value; return *this; } @@ -197,11 +199,10 @@ public: SettingItem &IntSlider(int *value, int minValue, int maxValue, SettingText text); SettingItem &Choice(SettingText text, ImageID image = ImageID::invalid()); SettingItem &Info(SettingText text, std::string_view rightText); - SettingItem &Hint(SettingText text); // The items after it start a new group. Not searched. SettingItem &Description(SettingText text); // For anything the other types can't describe. The text is only used for search. The view - // returned is the one a following Hint is attached to, and the one search results lead to. + // returned is the one its Hint is shown in, and the one search results lead to. SettingItem &Custom(SettingText text, std::function create); // Opens another page, built by build, on top of this one. The text is the new page's title. SettingItem &Link(SettingText text, I18NCat cat, std::function build); diff --git a/UI/SettingsPagesDialogScreen.cpp b/UI/SettingsPagesDialogScreen.cpp index 4e3b7f53ff..f723c7b4fa 100644 --- a/UI/SettingsPagesDialogScreen.cpp +++ b/UI/SettingsPagesDialogScreen.cpp @@ -409,10 +409,9 @@ bool SettingsPagesDialogScreen::SearchPage(UI::ViewGroup *parent, int settingsTa const I18NCat cat = page.DefaultCat(); const std::deque &items = page.Items(); - // A matching header shows its whole section, and a matching hint the setting it belongs to. + // A matching header shows its whole section. std::vector show(items.size()); bool sectionMatches = false; - int lastSetting = -1; for (int i = 0; i < (int)items.size(); i++) { const SettingItem &item = items[i]; bool matches = SettingTextMatches(item.text, cat, searchText_); @@ -420,15 +419,12 @@ bool SettingsPagesDialogScreen::SearchPage(UI::ViewGroup *parent, int settingsTa case SettingType::Header: sectionMatches = matches; break; - case SettingType::Hint: - if (matches && lastSetting >= 0) { - show[lastSetting] = true; - } - break; case SettingType::Description: break; default: - lastSetting = i; + if (!item.hint.empty() && SettingTextMatches(item.hint, cat, searchText_)) { + matches = true; + } show[i] = matches || sectionMatches; break; } diff --git a/UI/TiltAnalogSettingsScreen.cpp b/UI/TiltAnalogSettingsScreen.cpp index 1f4584e852..59d5cda727 100644 --- a/UI/TiltAnalogSettingsScreen.cpp +++ b/UI/TiltAnalogSettingsScreen.cpp @@ -124,9 +124,8 @@ void TiltAnalogSettingsScreen::CreateSettingsViews(UI::ViewGroup *settings) { Choice *calibrate = new Choice(co->T("Calibrate")); calibrate->OnClick.Handle(this, &TiltAnalogSettingsScreen::OnCalibrate); calibrate->SetEnabledPtr(&g_Config.bTiltInputEnabled); + calibrate->SetHintText(co->T("To Calibrate", "Hold device at your preferred angle and press Calibrate.")); calibrationSettings->Add(calibrate); - SettingHint *calibrationInfo = new SettingHint(co->T("To Calibrate", "Hold device at your preferred angle and press Calibrate."), calibrate); - calibrationSettings->Add(calibrationInfo); ChoiceGroup *sensitivitySettings = groups->Add(new ChoiceGroup(co->T("Sensitivity"))); if (TiltTypeIsAnalog(g_Config.iTiltInputType)) {