From b54056685e693a0a176a37116e69f7a99289a61c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 24 Oct 2026 14:01:58 +0200 Subject: [PATCH] UI: Space ChoiceGroups from their container instead of their own margins ChoiceGroups no longer have hardcoded margins. A new ChoiceGroupList holds them, with padding and spacing that give the same look as before. Settings tabs, developer tools, the two-pane settings columns, the pause menu and the rest put their groups in one, and a few screens adjust its padding and spacing to their layout. Also add the two groups in the remote disc streaming settings that were created but never added, and make the settings search look inside groups: it only checked a tab's direct children, so with ChoiceGroups a filter hid everything. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/UI/ViewGroup.cpp | 8 ++++- Common/UI/ViewGroup.h | 7 +++++ UI/ControlMappingScreen.cpp | 17 ++++++++--- UI/CustomButtonMappingScreen.cpp | 2 +- UI/CwCheatScreen.cpp | 5 +-- UI/DevScreens.cpp | 4 +-- UI/DeveloperToolsScreen.cpp | 22 +++++++------- UI/DriverManagerScreen.cpp | 2 +- UI/FirmwareScreen.cpp | 11 ++++--- UI/GameSettingsScreen.cpp | 14 ++++----- UI/MainScreen.cpp | 8 +++-- UI/PauseScreen.cpp | 4 +-- UI/RemoteISOScreen.cpp | 6 ++-- UI/RetroAchievementScreens.cpp | 12 +++++--- UI/TabbedDialogScreen.cpp | 52 ++++++++++++++++++++------------ UI/TiltAnalogSettingsScreen.cpp | 9 +++--- 16 files changed, 112 insertions(+), 71 deletions(-) diff --git a/Common/UI/ViewGroup.cpp b/Common/UI/ViewGroup.cpp index 4b3fc81dc7..390277aa69 100644 --- a/Common/UI/ViewGroup.cpp +++ b/Common/UI/ViewGroup.cpp @@ -862,13 +862,19 @@ std::string LinearLayoutList::DescribeText() const { } ChoiceGroup::ChoiceGroup(std::string_view title, LayoutParams *layoutParams) - : LinearLayout(ORIENT_VERTICAL, layoutParams ? layoutParams : new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(12, 12, 8, 12))), title_(title) { + : LinearLayout(ORIENT_VERTICAL, layoutParams ? layoutParams : new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)), title_(title) { if (!title.empty()) { Add(new ItemHeader(title)); } SetSpacing(0.0f); } +ChoiceGroupList::ChoiceGroupList(LayoutParams *layoutParams) + : LinearLayoutList(ORIENT_VERTICAL, layoutParams ? layoutParams : new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)) { + SetPadding(Margins(12, 12, 8, 12)); + SetSpacing(24.0f); +} + void ChoiceGroup::Draw(UIContext &dc) { if (views_.empty()) { LinearLayout::Draw(dc); diff --git a/Common/UI/ViewGroup.h b/Common/UI/ViewGroup.h index 5cb9affc2f..5c6144c679 100644 --- a/Common/UI/ViewGroup.h +++ b/Common/UI/ViewGroup.h @@ -255,6 +255,7 @@ public: }; // These are pretty vertical LinearLayout, with a title and a roundrect background. +// They have no margins: put them in a ChoiceGroupList, or another layout that spaces them out. class ChoiceGroup : public LinearLayout { public: ChoiceGroup(std::string_view title, LayoutParams *layoutParams = nullptr); @@ -265,6 +266,12 @@ private: std::string title_; }; +// A vertical list that spaces out the ChoiceGroups in it and keeps them off its edges. +class ChoiceGroupList : public LinearLayoutList { +public: + ChoiceGroupList(LayoutParams *layoutParams = nullptr); +}; + // GridLayout is a little different from the Android layout. This one has fixed size // rows and columns, filled row by row. Items are not allowed to deviate from the set sizes. struct GridLayoutSettings { diff --git a/UI/ControlMappingScreen.cpp b/UI/ControlMappingScreen.cpp index a8e33aa725..80d9bcd55b 100644 --- a/UI/ControlMappingScreen.cpp +++ b/UI/ControlMappingScreen.cpp @@ -231,7 +231,12 @@ void ControlMappingScreen::CreateSettingsViews(UI::ViewGroup *parent) { using namespace UI; auto km = GetI18NCategory(I18NCat::KEYMAPPING); - ChoiceGroup *resetChoices = parent->Add(new ChoiceGroup("", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(0, 12)))); + // Use the whole width of the narrow settings column. + ChoiceGroupList *groups = parent->Add(new ChoiceGroupList()); + groups->SetPadding(Margins(0, 12, 0, 0)); + groups->SetSpacing(12.0f); + + ChoiceGroup *resetChoices = groups->Add(new ChoiceGroup("")); resetChoices->Add(new Choice(km->T("Clear All")))->OnClick.Add([](UI::EventParams &) { KeyMap::ClearAllMappings(); @@ -240,7 +245,7 @@ void ControlMappingScreen::CreateSettingsViews(UI::ViewGroup *parent) { KeyMap::RestoreDefault(); }); - ChoiceGroup *moreChoices = parent->Add(new ChoiceGroup("", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); + ChoiceGroup *moreChoices = groups->Add(new ChoiceGroup("")); std::string sysName = System_GetProperty(SYSPROP_NAME); // If there's a builtin controller, restore to default should suffice. No need to conf the controller on top. @@ -533,7 +538,9 @@ void AnalogCalibrationScreen::CreateSettingsViews(UI::ViewGroup *scrollContents) using namespace UI; auto co = GetI18NCategory(I18NCat::CONTROLS); - ChoiceGroup *analogSettings = scrollContents->Add(new ChoiceGroup(co->T("Analog Settings"))); + ChoiceGroupList *groups = scrollContents->Add(new ChoiceGroupList()); + + ChoiceGroup *analogSettings = groups->Add(new ChoiceGroup(co->T("Analog Settings"))); // TODO: Would be nicer if these didn't pop up... analogSettings->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogDeadzone, 0.0f, 0.5f, 0.15f, co->T("Deadzone radius"), 0.01f, screenManager(), "/ 1.0")); @@ -547,7 +554,7 @@ void AnalogCalibrationScreen::CreateSettingsViews(UI::ViewGroup *scrollContents) analogSettings->Add(new PopupSliderChoiceFloat(&g_Config.fAnalogAutoRotSpeed, 0.1f, 20.0f, 8.0f, co->T("Auto-rotation speed"), 1.0f, screenManager())); // Advanced deadzone settings (Steam Input-style). - ChoiceGroup *deadzoneSettings = scrollContents->Add(new ChoiceGroup(co->T("Advanced Deadzone Settings"))); + ChoiceGroup *deadzoneSettings = groups->Add(new ChoiceGroup(co->T("Advanced Deadzone Settings"))); static const char *deadzoneShapes[] = { "Circle", "Square", "Cross" }; deadzoneSettings->Add(new PopupMultiChoice(&g_Config.iAnalogDeadzoneShape, co->T("Deadzone shape"), deadzoneShapes, 0, ARRAY_SIZE(deadzoneShapes), I18NCat::CONTROLS, screenManager())); @@ -560,7 +567,7 @@ void AnalogCalibrationScreen::CreateSettingsViews(UI::ViewGroup *scrollContents) static const char *responseCurves[] = { "Linear", "Aggressive", "Relaxed", "Wide" }; deadzoneSettings->Add(new PopupMultiChoice(&g_Config.iAnalogResponseCurve, co->T("Response curve"), responseCurves, 0, ARRAY_SIZE(responseCurves), I18NCat::CONTROLS, screenManager())); - scrollContents->Add(new ChoiceGroup(""))->Add(new Choice(co->T("Reset to defaults")))->OnClick.Handle(this, &AnalogCalibrationScreen::OnResetToDefaults); + groups->Add(new ChoiceGroup(""))->Add(new Choice(co->T("Reset to defaults")))->OnClick.Handle(this, &AnalogCalibrationScreen::OnResetToDefaults); } void AnalogCalibrationScreen::CreateContentViews(UI::ViewGroup *parent) { diff --git a/UI/CustomButtonMappingScreen.cpp b/UI/CustomButtonMappingScreen.cpp index 3abef446c1..8813f25416 100644 --- a/UI/CustomButtonMappingScreen.cpp +++ b/UI/CustomButtonMappingScreen.cpp @@ -159,7 +159,7 @@ void CustomButtonMappingScreen::CreateDialogViews(UI::ViewGroup *parent) { ScrollView *rightScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 1.0f)); root__->Add(rightScroll); - LinearLayout *vertLayout = new LinearLayout(ORIENT_VERTICAL); + LinearLayout *vertLayout = new ChoiceGroupList(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); rightScroll->Add(vertLayout); ChoiceGroup *styleSettings = vertLayout->Add(new ChoiceGroup(co->T("Button style"))); diff --git a/UI/CwCheatScreen.cpp b/UI/CwCheatScreen.cpp index 4ccb296ea2..b98f92a0bc 100644 --- a/UI/CwCheatScreen.cpp +++ b/UI/CwCheatScreen.cpp @@ -222,8 +222,9 @@ void CwCheatScreen::CreateSettingsViews(UI::ViewGroup *leftColumn) { auto di = GetI18NCategory(I18NCat::DIALOG); auto mm = GetI18NCategory(I18NCat::MAINMENU); + ChoiceGroupList *groups = leftColumn->Add(new ChoiceGroupList()); //leftColumn->Add(new Choice(cw->T("Add Cheat")))->OnClick.Handle(this, &CwCheatScreen::OnAddCheat); - ChoiceGroup *importGroup = leftColumn->Add(new ChoiceGroup(cw->T("Import Cheats"))); + ChoiceGroup *importGroup = groups->Add(new ChoiceGroup(cw->T("Import Cheats"))); importGroup->Add(new Choice(cw->T("Download cheat database")))->OnClick.Add([this](UI::EventParams &) { screenManager()->push(new CwCheatDownloadPopupScreen()); @@ -238,7 +239,7 @@ void CwCheatScreen::CreateSettingsViews(UI::ViewGroup *leftColumn) { importGroup->Add(new Choice(title))->OnClick.Handle(this, &CwCheatScreen::OnImportCheat); importGroup->Add(new Choice(mm->T("Browse"), ImageID("I_FOLDER_OPEN")))->OnClick.Handle(this, &CwCheatScreen::OnImportBrowse); - ChoiceGroup *optionsGroup = leftColumn->Add(new ChoiceGroup(di->T("Options"))); + ChoiceGroup *optionsGroup = groups->Add(new ChoiceGroup(di->T("Options"))); Choice *searchChoice = optionsGroup->Add(new Choice(di->T("Search"), ImageID("I_SEARCH"))); searchChoice->OnClick.Add([this, searchChoice, screenManager = screenManager(), token = GetRequesterToken()](UI::EventParams &) { auto di = GetI18NCategory(I18NCat::DIALOG); diff --git a/UI/DevScreens.cpp b/UI/DevScreens.cpp index d8bcef71af..67f4b7b67e 100644 --- a/UI/DevScreens.cpp +++ b/UI/DevScreens.cpp @@ -456,7 +456,7 @@ void JitDebugScreen::CreateViews() { topbar->Add(new Choice(di->T("Enable All")))->OnClick.Handle(this, &JitDebugScreen::OnEnableAll); vert->Add(topbar); - ChoiceGroup *disabledFlags = vert->Add(new ChoiceGroup(dev->T("Disabled JIT functionality"))); + ChoiceGroup *disabledFlags = vert->Add(new ChoiceGroupList())->Add(new ChoiceGroup(dev->T("Disabled JIT functionality"))); for (auto flag : jitDisableFlags) { // Do not add translation of these. @@ -639,7 +639,7 @@ void FrameDumpTestScreen::CreateTabs() { auto dev = GetI18NCategory(I18NCat::DEVELOPER); AddTab("General", dev->T("Dumps"), [this](UI::LinearLayout *parent) { - ChoiceGroup *dumps = parent->Add(new ChoiceGroup("GE Frame Dumps")); + ChoiceGroup *dumps = parent->Add(new ChoiceGroupList())->Add(new ChoiceGroup("GE Frame Dumps")); for (auto &file : files_) { std::string url = framedumpsBaseUrl + file; diff --git a/UI/DeveloperToolsScreen.cpp b/UI/DeveloperToolsScreen.cpp index db1c5dbc49..8a36a73f3e 100644 --- a/UI/DeveloperToolsScreen.cpp +++ b/UI/DeveloperToolsScreen.cpp @@ -649,41 +649,41 @@ void DeveloperToolsScreen::CreateTabs() { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); AddTab("General", sy->T("General"), [this](UI::LinearLayout *parent) { - CreateGeneralTab(parent); + CreateGeneralTab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("TextureReplacement", dev->T("Texture Replacement"), [this](UI::LinearLayout *parent) { - CreateTextureReplacementTab(parent); + CreateTextureReplacementTab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("Graphics", ms->T("Graphics"), [this](UI::LinearLayout *parent) { - CreateGraphicsTab(parent); + CreateGraphicsTab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("Networking", ms->T("Networking"), [this](UI::LinearLayout *parent) { - CreateNetworkTab(parent); + CreateNetworkTab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("Audio", ms->T("Audio"), [this](UI::LinearLayout *parent) { - CreateAudioTab(parent); + CreateAudioTab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("Tests", dev->T("Tests"), [this](UI::LinearLayout *parent) { - CreateTestsTab(parent); + CreateTestsTab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("UI", dev->T("UI"), [this](UI::LinearLayout *parent) { - CreateUITab(parent); + CreateUITab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("DumpFiles", dev->T("Dump files"), [this](UI::LinearLayout *parent) { - CreateDumpFileTab(parent); + CreateDumpFileTab(parent->Add(new UI::ChoiceGroupList())); }); AddTab("HLE", dev->T("HLE"), [this](UI::LinearLayout *parent) { - CreateHLETab(parent); + CreateHLETab(parent->Add(new UI::ChoiceGroupList())); }); #if !PPSSPP_PLATFORM(ANDROID) && !PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(SWITCH) AddTab("MIPSTracer", dev->T("MIPSTracer"), [this](UI::LinearLayout *parent) { - CreateMIPSTracerTab(parent); + CreateMIPSTracerTab(parent->Add(new UI::ChoiceGroupList())); }); #endif //#if PPSSPP_PLATFORM(ANDROID) if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= 30) { AddTab("Crash history", dev->T("Crash history"), [this](UI::LinearLayout *parent) { - CreateCrashHistoryTab(parent); + CreateCrashHistoryTab(parent->Add(new UI::ChoiceGroupList())); }); } //#endif diff --git a/UI/DriverManagerScreen.cpp b/UI/DriverManagerScreen.cpp index b42868fcd3..51ae7a6950 100644 --- a/UI/DriverManagerScreen.cpp +++ b/UI/DriverManagerScreen.cpp @@ -165,7 +165,7 @@ void DriverManagerScreen::CreateDriverTab(UI::ViewGroup *drivers) { auto di = GetI18NCategory(I18NCat::DIALOG); auto gr = GetI18NCategory(I18NCat::GRAPHICS); - ChoiceGroup *driverManagerGroup = drivers->Add(new ChoiceGroup(gr->T("AdrenoTools driver manager"))); + ChoiceGroup *driverManagerGroup = drivers->Add(new ChoiceGroupList())->Add(new ChoiceGroup(gr->T("AdrenoTools driver manager"))); auto customDriverInstallChoice = driverManagerGroup->Add(new Choice(gr->T("Install custom driver..."))); driverManagerGroup->Add(new Choice(di->T("More info")))->OnClick.Add([=](UI::EventParams &e) { System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.ppsspp.org/docs/reference/custom-drivers/"); diff --git a/UI/FirmwareScreen.cpp b/UI/FirmwareScreen.cpp index 0eae28db27..5dc760f947 100644 --- a/UI/FirmwareScreen.cpp +++ b/UI/FirmwareScreen.cpp @@ -113,19 +113,20 @@ void FirmwareScreen::CreateSettingsViews(UI::ViewGroup *parent) { // that has flash0 mounted is worse - so while a game is running, this screen is read-only. const bool gameRunning = PSP_IsInited(); + ChoiceGroupList *groups = parent->Add(new ChoiceGroupList()); if (info_.hasVsh && !FirmwareVersionSupportsVSH(info_.version)) { - parent->Add(new NoticeView(NoticeLevel::WARN, sy->T("XMB unsupported"), + groups->Add(new NoticeView(NoticeLevel::WARN, sy->T("XMB unsupported"), sy->T("XMBUnsupportedVersion", "Only firmware 6.61 can boot the XMB so far"))); } - ChoiceGroup *launchGroup = parent->Add(new ChoiceGroup("")); + ChoiceGroup *launchGroup = groups->Add(new ChoiceGroup("")); Choice *launch = launchGroup->Add(new Choice(sy->T("Launch XMB"), ImageID("I_PLAY"))); - ChoiceGroup *settings = parent->Add(new ChoiceGroup("")); + ChoiceGroup *settings = groups->Add(new ChoiceGroup("")); settings->Add(new CheckBox(&g_Config.bAutoUpgradeFirmware, sy->T("Install or upgrade firmware from game discs"), sy->T("AutoUpgradeFirmwareDesc", "Most game discs carry a firmware updater. Install it when there's no firmware yet, or when the disc's is newer."))); - ChoiceGroup *actions = parent->Add(new ChoiceGroup("")); + ChoiceGroup *actions = groups->Add(new ChoiceGroup("")); launch->OnClick.Add([this](UI::EventParams &) { LaunchVSH(); }); @@ -154,7 +155,7 @@ void FirmwareScreen::CreateSettingsViews(UI::ViewGroup *parent) { } if (gameRunning) { - parent->Add(new NoticeView(NoticeLevel::INFO, + groups->Add(new NoticeView(NoticeLevel::INFO, sy->T("FirmwareNeedsNoGame", "Stop the game to change the installed firmware"), "")); } } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 42cfd23290..4a01b83634 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -246,43 +246,43 @@ void GameSettingsScreen::CreateTabs() { AddTab("GameSettingsGraphics", ms->T("Graphics"), ImageID("I_DISPLAY"), [this](UI::LinearLayout *parent) { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); parent->Add(new PaneTitleBar(gamePath_, ms->T("Graphics"), "graphics", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT))); - CreateGraphicsSettings(parent); + CreateGraphicsSettings(parent->Add(new ChoiceGroupList())); }); AddTab("GameSettingsControls", ms->T("Controls"), ImageID("I_CONTROLLER"), [this](UI::LinearLayout *parent) { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); parent->Add(new PaneTitleBar(gamePath_, ms->T("Controls"), "controls")); - CreateControlsSettings(parent); + CreateControlsSettings(parent->Add(new ChoiceGroupList())); }); AddTab("GameSettingsAudio", ms->T("Audio"), ImageID("I_SPEAKER_MAX"), [this](UI::LinearLayout *parent) { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); parent->Add(new PaneTitleBar(gamePath_, ms->T("Audio"), "audio")); - CreateAudioSettings(parent); + CreateAudioSettings(parent->Add(new ChoiceGroupList())); }); AddTab("GameSettingsNetworking", ms->T("Networking"), ImageID("I_WIFI"), [this](UI::LinearLayout *parent) { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); parent->Add(new PaneTitleBar(gamePath_, ms->T("Networking"), "network")); - CreateNetworkingSettings(parent); + CreateNetworkingSettings(parent->Add(new ChoiceGroupList())); }); AddTab("GameSettingsTools", ms->T("Tools"), ImageID("I_TOOLS"), [this](UI::LinearLayout *parent) { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); parent->Add(new PaneTitleBar(gamePath_, ms->T("Tools"), "tools")); - CreateToolsSettings(parent); + CreateToolsSettings(parent->Add(new ChoiceGroupList())); }); AddTab("GameSettingsSystem", ms->T("System"), ImageID("I_PSP"), [this](UI::LinearLayout *parent) { auto ms = GetI18NCategory(I18NCat::MAINSETTINGS); parent->Add(new PaneTitleBar(gamePath_, ms->T("System"), "system")); - CreateSystemSettings(parent); + CreateSystemSettings(parent->Add(new ChoiceGroupList())); }); int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE); if ((deviceType == DEVICE_TYPE_VR) || g_Config.bForceVR) { AddTab("GameSettingsVR", ms->T("VR"), ImageID::invalid(), [this](UI::LinearLayout *parent) { - CreateVRSettings(parent); + CreateVRSettings(parent->Add(new ChoiceGroupList())); }); } } diff --git a/UI/MainScreen.cpp b/UI/MainScreen.cpp index 5812310634..da8f804543 100644 --- a/UI/MainScreen.cpp +++ b/UI/MainScreen.cpp @@ -370,7 +370,11 @@ void MainScreen::CreateMainButtons(UI::ViewGroup *parent, bool portrait) { } return; } else { - ChoiceGroup *mainButtons = parent->Add(new ChoiceGroup("", new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(0, 0, 0, 12)))); + ChoiceGroupList *buttons = parent->Add(new ChoiceGroupList()); + buttons->SetPadding(Margins(0)); + buttons->SetSpacing(12.0f); + + ChoiceGroup *mainButtons = buttons->Add(new ChoiceGroup("")); if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { mainButtons->Add(new Choice(mm->T("Load", "Load...")))->OnClick.Handle(this, &MainScreen::OnLoadFile); @@ -394,7 +398,7 @@ void MainScreen::CreateMainButtons(UI::ViewGroup *parent, bool portrait) { // Officially, iOS apps should not have exit buttons. Remove it to maximize app store review chances. // (The exit button previously created problems on Android, but now we allow it in landscape mode.) #if !PPSSPP_PLATFORM(IOS_APP_STORE) - Choice *exitButton = parent->Add(new Choice(di->T("Exit"))); + Choice *exitButton = buttons->Add(new Choice(di->T("Exit"))); exitButton->OnClick.Add([](UI::EventParams &e) { // Let's make sure the config was saved, since it may not have been. if (!g_Config.Save("MainScreen::OnExit")) { diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index e8542136ce..56da81bb44 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -711,10 +711,9 @@ void GamePauseScreen::CreateViews() { root_->Add(buttonColumnScroll); } - LinearLayout *rightColumnItems = new LinearLayout(ORIENT_VERTICAL); + LinearLayout *rightColumnItems = new ChoiceGroupList(new LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); buttonColumn->Add(rightColumnItems); - rightColumnItems->SetSpacing(0.0f); ChoiceGroup *topGroup = rightColumnItems->Add(new ChoiceGroup("")); if (__KernelIsRunningVSH()) { // The XMB notices discs coming and going by itself, like a PSP's. @@ -795,7 +794,6 @@ void GamePauseScreen::CreateViews() { } if (portrait) { - rightColumnItems->Add(new Spacer(20.0)); UI::LinearLayout *exitRow = new UI::LinearLayout(ORIENT_HORIZONTAL, new UI::LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(0, 0, 0, 0))); rightColumnItems->Add(exitRow); exitRow->Add(exit); diff --git a/UI/RemoteISOScreen.cpp b/UI/RemoteISOScreen.cpp index fe0f0958b8..c28d4f0291 100644 --- a/UI/RemoteISOScreen.cpp +++ b/UI/RemoteISOScreen.cpp @@ -285,7 +285,7 @@ void RemoteISOScreen::CreateTabs() { }); AddTab("Settings", ri->T("Settings"), [this](UI::LinearLayout *settings) { - CreateSettingsTab(settings); + CreateSettingsTab(settings->Add(new UI::ChoiceGroupList())); }); } @@ -403,7 +403,7 @@ void RemoteISOScreen::CreateSettingsTab(UI::ViewGroup *remoteisoSettings) { g_Config.iRemoteISOShareType = (int)RemoteISOShareType::RECENT; } - ChoiceGroup *remoteSettings = new ChoiceGroup(""); + ChoiceGroup *remoteSettings = remoteisoSettings->Add(new ChoiceGroup("")); UI::Choice *remoteServer = new PopupTextInputChoice(GetRequesterToken(), &g_Config.sLastRemoteISOServer, ri->T("Remote Server"), "", 255, screenManager()); remoteSettings->Add(remoteServer); @@ -421,7 +421,7 @@ void RemoteISOScreen::CreateSettingsTab(UI::ViewGroup *remoteisoSettings) { remoteSettings->Add(remoteSubdir); remoteSubdir->SetEnabledPtr(&g_Config.bRemoteISOManual); - ChoiceGroup *serverSettings = new ChoiceGroup(""); + ChoiceGroup *serverSettings = remoteisoSettings->Add(new ChoiceGroup("")); PopupSliderChoice *portChoice = new PopupSliderChoice(&g_Config.iRemoteISOPort, 0, 65535, 0, ri->T("Local Server Port", "Local Server Port"), 100, screenManager()); serverSettings->Add(portChoice); diff --git a/UI/RetroAchievementScreens.cpp b/UI/RetroAchievementScreens.cpp index c83d721c9b..8bf03d1cec 100644 --- a/UI/RetroAchievementScreens.cpp +++ b/UI/RetroAchievementScreens.cpp @@ -442,7 +442,8 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup) } using namespace UI; - ChoiceGroup *settingsGroup = viewGroup->Add(new ChoiceGroup(di->T("Settings"))); + ChoiceGroupList *groups = viewGroup->Add(new ChoiceGroupList()); + ChoiceGroup *settingsGroup = groups->Add(new ChoiceGroup(di->T("Settings"))); settingsGroup->Add(new CheckBox(&g_Config.bAchievementsEnable, ac->T("Achievements enabled")))->OnClick.Add([this](UI::EventParams &e) -> void { Achievements::UpdateSettings(); RecreateViews(); @@ -450,7 +451,7 @@ void RetroAchievementsSettingsScreen::CreateAccountTab(UI::ViewGroup *viewGroup) settingsGroup->Add(new CheckBox(&g_Config.bAchievementsHardcoreMode, ac->T("Hardcore Mode (no savestates)")))->SetEnabledPtr(&g_Config.bAchievementsEnable); settingsGroup->Add(new CheckBox(&g_Config.bAchievementsSoundEffects, ac->T("Sound Effects")))->SetEnabledPtr(&g_Config.bAchievementsEnable); - ChoiceGroup *linksGroup = viewGroup->Add(new ChoiceGroup(di->T("Links"))); + ChoiceGroup *linksGroup = groups->Add(new ChoiceGroup(di->T("Links"))); linksGroup->Add(new Choice(ac->T("RetroAchievements website"), ImageID("I_LINK_OUT")))->OnClick.Add([](UI::EventParams &) -> void { System_LaunchUrl(LaunchUrlType::BROWSER_URL, "https://www.retroachievements.org/"); }); @@ -466,7 +467,8 @@ void RetroAchievementsSettingsScreen::CreateCustomizeTab(UI::ViewGroup *viewGrou viewGroup->Add(new PaneTitleBar(Path(), ac->T("Customize"), "/docs/reference/retro-achievements/")); using namespace UI; - ChoiceGroup *soundSettings = viewGroup->Add(new ChoiceGroup(ac->T("Sound Effects"))); + ChoiceGroupList *groups = viewGroup->Add(new ChoiceGroupList()); + ChoiceGroup *soundSettings = groups->Add(new ChoiceGroup(ac->T("Sound Effects"))); if (System_GetPropertyBool(SYSPROP_HAS_FILE_BROWSER)) { soundSettings->Add(new AudioFileChooser(GetRequesterToken(), &g_Config.sAchievementsUnlockAudioFile, ac->T("Achievement unlocked"), UISound::ACHIEVEMENT_UNLOCKED)); soundSettings->Add(new AudioFileChooser(GetRequesterToken(), &g_Config.sAchievementsLeaderboardSubmitAudioFile, ac->T("Leaderboard score submission"), UISound::LEADERBOARD_SUBMITTED)); @@ -478,7 +480,7 @@ void RetroAchievementsSettingsScreen::CreateCustomizeTab(UI::ViewGroup *viewGrou static const char *positions[] = { "None", "Bottom Left", "Bottom Center", "Bottom Right", "Top Left", "Top Center", "Top Right", "Center Left", "Center Right" }; - ChoiceGroup *notificationSettings = viewGroup->Add(new ChoiceGroup(ac->T("Notifications"))); + ChoiceGroup *notificationSettings = groups->Add(new ChoiceGroup(ac->T("Notifications"))); notificationSettings->Add(new PopupMultiChoice(&g_Config.iAchievementsLeaderboardStartedOrFailedPos, ac->T("Leaderboard attempt started or failed"), positions, -1, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bAchievementsEnable); notificationSettings->Add(new PopupMultiChoice(&g_Config.iAchievementsLeaderboardSubmittedPos, ac->T("Leaderboard result submitted"), positions, -1, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bAchievementsEnable); notificationSettings->Add(new PopupMultiChoice(&g_Config.iAchievementsLeaderboardTrackerPos, ac->T("Leaderboard tracker"), positions, -1, ARRAY_SIZE(positions), I18NCat::DIALOG, screenManager()))->SetEnabledPtr(&g_Config.bAchievementsEnable); @@ -494,7 +496,7 @@ void RetroAchievementsSettingsScreen::CreateDeveloperToolsTab(UI::ViewGroup *vie using namespace UI; viewGroup->Add(new PaneTitleBar(Path(), sy->T("Developer Tools"), "/docs/reference/retro-achievements/")); - ChoiceGroup *devSettings = viewGroup->Add(new ChoiceGroup("")); + ChoiceGroup *devSettings = viewGroup->Add(new ChoiceGroupList())->Add(new ChoiceGroup("")); #ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION devSettings->Add(new CheckBox(&g_Config.bAchievementsEnableRAIntegration, ac->T("Enable RAIntegration (for achievement development)")))->SetEnabledPtr(&g_Config.bAchievementsEnable); #endif diff --git a/UI/TabbedDialogScreen.cpp b/UI/TabbedDialogScreen.cpp index 649e76f946..4c785b4b86 100644 --- a/UI/TabbedDialogScreen.cpp +++ b/UI/TabbedDialogScreen.cpp @@ -130,7 +130,7 @@ void UITabbedBaseDialogScreen::CreateViews() { AddTab("GameSettingsSearch", ms->T("Search"), ImageID("I_SEARCH"), [this](UI::LinearLayout *searchSettings) { auto se = GetI18NCategory(I18NCat::SEARCH); - ChoiceGroup *searchGroup = searchSettings->Add(new ChoiceGroup(se->T("Find settings"))); + ChoiceGroup *searchGroup = searchSettings->Add(new ChoiceGroupList())->Add(new ChoiceGroup(se->T("Find settings"))); searchGroup->Add(new PopupTextInputChoice(GetRequesterToken(), &searchFilter_, se->T("Filter"), "", 64, screenManager()))->OnChange.Add([this](UI::EventParams &e) { System_PostUIMessage(UIMessage::GAMESETTINGS_SEARCH, StripSpaces(searchFilter_)); }); @@ -185,6 +185,36 @@ void UITabbedBaseDialogScreen::SetCurrentTab(int tab) { tabHolder_->SetCurrentTab(tab); } +// Hides the views in the group that don't match the filter, and returns whether any did. Groups that can't +// be focused (like ChoiceGroups) are searched recursively and stay visible if anything in them matches. +// Other views that can't be focused (headings, notices) are shown only when something after them matches. +static bool ApplySearchFilterToGroup(UI::ViewGroup *group, int firstIndex, const std::string &filter) { + using namespace UI; + bool anyMatch = false; + View *lastHeading = nullptr; + for (int i = firstIndex; i < group->GetNumSubviews(); ++i) { + View *v = group->GetViewByIndex(i); + bool match; + if (v->IsViewGroup() && !v->CanBeFocused()) { + match = ApplySearchFilterToGroup((ViewGroup *)v, 0, filter); + } else if (!v->CanBeFocused()) { + lastHeading = v; + v->SetVisibility(filter.empty() ? V_VISIBLE : V_GONE); + continue; + } else { + std::string label = v->DescribeText(); + std::transform(label.begin(), label.end(), label.begin(), tolower); + match = label.find(filter) != label.npos; + } + if (match && lastHeading) { + lastHeading->SetVisibility(V_VISIBLE); + } + v->SetVisibility((filter.empty() || match) ? V_VISIBLE : V_GONE); + anyMatch = anyMatch || match; + } + return anyMatch; +} + void UITabbedBaseDialogScreen::ApplySearchFilter() { using namespace UI; // GAMESETTINGS_SEARCH is broadcast to every screen, including ones whose views don't exist yet. @@ -222,24 +252,8 @@ void UITabbedBaseDialogScreen::ApplySearchFilter() { continue; } - bool tabMatches = searchFilter_.empty(); - - UI::View *lastHeading = nullptr; - for (int i = 1; i < tabContents->GetNumSubviews(); ++i) { - UI::View *v = tabContents->GetViewByIndex(i); - if (!v->CanBeFocused()) { - lastHeading = v; - } - - std::string label = v->DescribeText(); - std::transform(label.begin(), label.end(), label.begin(), tolower); - bool match = v->CanBeFocused() && label.find(searchFilter_) != label.npos; - tabMatches = tabMatches || match; - - if (match && lastHeading) - lastHeading->SetVisibility(UI::V_VISIBLE); - v->SetVisibility((searchFilter_.empty() || match) ? UI::V_VISIBLE : UI::V_GONE); - } + // The first view is the tab's title. + const bool tabMatches = ApplySearchFilterToGroup((ViewGroup *)tabContents, 1, searchFilter_) || searchFilter_.empty(); tabHolder_->EnableTab(t, tabMatches); matches = matches || tabMatches; } diff --git a/UI/TiltAnalogSettingsScreen.cpp b/UI/TiltAnalogSettingsScreen.cpp index 846ebed8ff..e84234529c 100644 --- a/UI/TiltAnalogSettingsScreen.cpp +++ b/UI/TiltAnalogSettingsScreen.cpp @@ -96,11 +96,12 @@ void TiltAnalogSettingsScreen::CreateSettingsViews(UI::ViewGroup *settings) { GamepadUpdateOpacity(1.0f); + ChoiceGroupList *groups = settings->Add(new ChoiceGroupList()); auto enabledFunc = [=]() -> bool { return g_Config.iTiltInputType != 0; }; - ChoiceGroup *setupSettings = settings->Add(new ChoiceGroup(co->T("Tilt control setup"))); + ChoiceGroup *setupSettings = groups->Add(new ChoiceGroup(co->T("Tilt control setup"))); CheckBox *enabled = setupSettings->Add(new CheckBox(&g_Config.bTiltInputEnabled, co->T("Enabled"))); PopupMultiChoice *typeChoice = setupSettings->Add(new PopupMultiChoice(&g_Config.iTiltInputType, co->T("Tilt Input Type"), g_tiltTypes, 0, g_numTiltTypes, I18NCat::CONTROLS, screenManager())); if (g_Config.iTiltInputType != 0) { @@ -117,7 +118,7 @@ void TiltAnalogSettingsScreen::CreateSettingsViews(UI::ViewGroup *settings) { if (System_GetPropertyBool(SYSPROP_HAS_GYRO)) { setupSettings->Add(new CheckBox(&g_Config.bTiltUseGyro, co->T("Use gyroscope")))->SetEnabledPtr(&g_Config.bTiltInputEnabled); } - ChoiceGroup *calibrationSettings = settings->Add(new ChoiceGroup(co->T("Calibration"))); + ChoiceGroup *calibrationSettings = groups->Add(new ChoiceGroup(co->T("Calibration"))); Choice *calibrate = new Choice(co->T("Calibrate")); calibrate->OnClick.Handle(this, &TiltAnalogSettingsScreen::OnCalibrate); calibrate->SetEnabledPtr(&g_Config.bTiltInputEnabled); @@ -125,7 +126,7 @@ void TiltAnalogSettingsScreen::CreateSettingsViews(UI::ViewGroup *settings) { SettingHint *calibrationInfo = new SettingHint(co->T("To Calibrate", "Hold device at your preferred angle and press Calibrate."), calibrate); calibrationSettings->Add(calibrationInfo); - ChoiceGroup *sensitivitySettings = settings->Add(new ChoiceGroup(co->T("Sensitivity"))); + ChoiceGroup *sensitivitySettings = groups->Add(new ChoiceGroup(co->T("Sensitivity"))); if (TiltTypeIsAnalog(g_Config.iTiltInputType)) { sensitivitySettings->Add(new PopupSliderChoiceFloat(&g_Config.fTiltAnalogDeadzoneRadius, 0.0f, 0.8f, 0.0f, co->T("Deadzone radius"), 0.02f, screenManager(), "/ 1.0"))->SetEnabledPtr(&g_Config.bTiltInputEnabled); sensitivitySettings->Add(new PopupSliderChoiceFloat(&g_Config.fTiltInverseDeadzone, 0.0f, 0.8f, 0.0f, co->T("Low end radius"), 0.02f, screenManager(), "/ 1.0"))->SetEnabledPtr(&g_Config.bTiltInputEnabled); @@ -134,7 +135,7 @@ void TiltAnalogSettingsScreen::CreateSettingsViews(UI::ViewGroup *settings) { sensitivitySettings->Add(new PopupSliderChoice(&g_Config.iTiltSensitivityX, 0, 100, 60, co->T("Tilt Sensitivity along X axis"), screenManager(), "%"))->SetEnabledPtr(&g_Config.bTiltInputEnabled); sensitivitySettings->Add(new PopupSliderChoice(&g_Config.iTiltSensitivityY, 0, 100, 60, co->T("Tilt Sensitivity along Y axis"), screenManager(), "%"))->SetEnabledPtr(&g_Config.bTiltInputEnabled); - ChoiceGroup *invertSettings = settings->Add(new ChoiceGroup(co->T("Invert Axes"))); + ChoiceGroup *invertSettings = groups->Add(new ChoiceGroup(co->T("Invert Axes"))); invertSettings->Add(new CheckBox(&g_Config.bInvertTiltX, co->T("Invert Tilt along X axis")))->SetEnabledPtr(&g_Config.bTiltInputEnabled); invertSettings->Add(new CheckBox(&g_Config.bInvertTiltY, co->T("Invert Tilt along Y axis")))->SetEnabledPtr(&g_Config.bTiltInputEnabled); }