UI: Draw setting hints inside their Choice or CheckBox

ClickableItem gets a hint text, drawn small under the item's own content,
so it highlights and clicks along with it instead of being a separate
SettingHint view below. Settings pages attach it with .Hint() on the item
it belongs to, and search matches it as part of that item.

SettingHint stays for the few hints that have no item to go in: custom
settings views that aren't ClickableItems, and cheat comments under a title.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-11-05 10:35:23 +01:00
1 parent 9f5c502d8f
commit ef3c7985ee
12 files changed
+130 -85

No files matched your search

+7 -5
View File
@@ -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);
}
}
+49 -13
View File
@@ -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);
}
+22
View File
@@ -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.
+12 -2
View File
@@ -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.
+3 -6
View File
@@ -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);
+3 -6
View File
@@ -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);
+7 -14
View File
@@ -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<PopupMultiChoice *>(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");
+6 -12
View File
@@ -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);
+12 -14
View File
@@ -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<void(UI::EventParams &)> 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<UI::ClickableItem *>(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<UI::View *> *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<UI::ViewGroup *>(view);
} else if (view) {
prevView = view;
}
}
}
+4 -3
View File
@@ -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<void(UI::EventParams &)> func) { onChange = std::move(func); return *this; }
SettingItem &OnClick(std::function<void(UI::EventParams &)> func) { onChange = std::move(func); return *this; }
SettingItem &Setup(std::function<void(UI::View *)> 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<UI::View *(UI::ViewGroup *parent)> 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<void(SettingsPage *)> build);
+4 -8
View File
@@ -409,10 +409,9 @@ bool SettingsPagesDialogScreen::SearchPage(UI::ViewGroup *parent, int settingsTa
const I18NCat cat = page.DefaultCat();
const std::deque<SettingItem> &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<bool> 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;
}
+1 -2
View File
@@ -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)) {