From 672eba3625c02c873f9cf2950528620dfc9a492c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Mar 2026 14:14:36 +0100 Subject: [PATCH 1/4] Adhoc: Remove outdated entries from custom lists. Start refactoring to allow full keyboard control --- UI/AdhocServerScreen.cpp | 65 ++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/UI/AdhocServerScreen.cpp b/UI/AdhocServerScreen.cpp index ed9b172124..3106842127 100644 --- a/UI/AdhocServerScreen.cpp +++ b/UI/AdhocServerScreen.cpp @@ -191,6 +191,26 @@ private: AdhocServerListEntry entry_; }; +void AddDeleteButton(std::string *editValue, ScreenManager *screenManager, UI::ViewGroup *viewGroup, const AdhocServerListEntry &entry) { + using namespace UI; + Choice *deleteButton = viewGroup->Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(0, 0, 10, 0)))); + deleteButton->OnClick.Add([host = entry.host, screenManager, editValue](UI::EventParams &e) { + auto di = GetI18NCategory(I18NCat::DIALOG); + const std::string quotedHost = "\"" + host + "\""; + const std::string message = ApplySafeSubstitutions(di->T("Are you sure you want to delete %1?"), quotedHost); + screenManager->push(new UI::MessagePopupScreen(di->T("Delete"), message, di->T("Delete"), di->T("Cancel"), [host, editValue](bool confirmed) { + if (confirmed) { + RemoveNoCase(g_Config.vCustomAdhocServerList, host); + RemoveNoCase(g_Config.vCustomAdhocServerListWithRelay, host); + if (*editValue == host) { + // Reset to socom.cc, which will always be in a list. + *editValue = DefaultProAdhocServer(); + } + } + })); + }); +} + AdhocServerRow::AdhocServerRow(std::string *editValue, const AdhocServerListEntry &entry, bool showDeleteButton, ScreenManager *screenManager, UI::LayoutParams *layoutParams) : UI::LinearLayout(ORIENT_HORIZONTAL, new UI::LinearLayoutParams(UI::FILL_PARENT, UI::WRAP_CONTENT, UI::Margins(5.0f, 0.0f))), value_(editValue), entry_(entry) { using namespace UI; @@ -223,22 +243,7 @@ AdhocServerRow::AdhocServerRow(std::string *editValue, const AdhocServerListEntr TextView *relay = Add(new TextView("Relay", new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(10.0)))); } if (showDeleteButton) { - Choice *deleteButton = Add(new Choice(ImageID("I_TRASHCAN"), new LinearLayoutParams(WRAP_CONTENT, WRAP_CONTENT, Gravity::G_VCENTER, Margins(0, 0, 10, 0)))); - deleteButton->OnClick.Add([host = entry.host, screenManager, editValue](UI::EventParams &e) { - auto di = GetI18NCategory(I18NCat::DIALOG); - const std::string quotedHost = "\"" + host + "\""; - const std::string message = ApplySafeSubstitutions(di->T("Are you sure you want to delete %1?"), quotedHost); - screenManager->push(new UI::MessagePopupScreen(di->T("Delete"), message, di->T("Delete"), di->T("Cancel"), [host, editValue](bool confirmed) { - if (confirmed) { - RemoveNoCase(g_Config.vCustomAdhocServerList, host); - RemoveNoCase(g_Config.vCustomAdhocServerListWithRelay, host); - if (*editValue == host) { - // Reset to socom.cc, which will always be in a list. - *editValue = DefaultProAdhocServer(); - } - } - })); - }); + AddDeleteButton(editValue, screenManager, this, entry); } if (!entry.description.empty()) { @@ -342,36 +347,44 @@ void AdhocServerScreen::CreatePopupContents(UI::ViewGroup *parent) { return false; }; - for (const auto &host : g_Config.vCustomAdhocServerListWithRelay) { + for (auto iter = g_Config.vCustomAdhocServerListWithRelay.begin(); iter != g_Config.vCustomAdhocServerListWithRelay.end();) { // If the host is already in the public list, skip it. We don't want duplicates. - if (hostInEntries(host) || host.empty()) { + if (hostInEntries(*iter) || iter->empty()) { + // Remove things that duplicate the public list, or that are empty (probably added by mistake). + iter = g_Config.vCustomAdhocServerListWithRelay.erase(iter); + recreateParent_ = true; continue; } AdhocServerListEntry entry; - entry.name = host; - entry.host = host; + entry.name = *iter; + entry.host = *iter; entry.mode = AdhocDataMode::AemuPostoffice; customEntries.push_back(entry); - if (host == editValue_) { + if (*iter == editValue_) { currentServerFound = true; } + iter++; } - for (const auto &host : g_Config.vCustomAdhocServerList) { + for (auto iter = g_Config.vCustomAdhocServerList.begin(); iter != g_Config.vCustomAdhocServerList.end();) { // If the host is already in the public list, skip it. We don't want duplicates. - if (hostInEntries(host) || host.empty()) { + if (hostInEntries(*iter) || iter->empty()) { + // Remove things that duplicate the public list, or that are empty (probably added by mistake). + iter = g_Config.vCustomAdhocServerList.erase(iter); + recreateParent_ = true; continue; } AdhocServerListEntry entry; - entry.name = host; - entry.host = host; + entry.name = *iter; + entry.host = *iter; entry.mode = AdhocDataMode::P2P; customEntries.push_back(entry); - if (host == editValue_) { + if (*iter == editValue_) { currentServerFound = true; } + iter++; } ScrollView *scrollView = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(1.0f)); From 3352df3ae7221ed49619dbf3383c21469dc0f119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Mar 2026 14:43:28 +0100 Subject: [PATCH 2/4] Not super pretty, but implements keyboard navigation for the adhoc server list --- Common/UI/View.cpp | 30 ++++++++++++++++++++++++++++++ Common/UI/View.h | 2 ++ UI/AdhocServerScreen.cpp | 8 +++++++- 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index 2bbf8bcbd6..9ddd6a1490 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -1201,6 +1201,36 @@ bool ClickableTextView::Touch(const TouchInput &input) { return contains; } +bool ClickableTextView::Key(const KeyInput &key) { + if (!HasFocus() && key.deviceId != DEVICE_ID_MOUSE) { + down_ = false; + return false; + } + // TODO: Replace most of Update with this. + + bool ret = false; + if (key.flags & KeyInputFlags::DOWN) { + if (IsAcceptKey(key)) { + down_ = true; + ret = true; + } + } + if (key.flags & KeyInputFlags::UP) { + if (IsAcceptKey(key)) { + if (down_) { + EventParams e{}; + e.v = this; + OnClick.Trigger(e); + down_ = false; + ret = true; + } + } else if (down_ && IsEscapeKey(key)) { + down_ = false; + } + } + return ret; +} + TextEdit::TextEdit(std::string_view text, std::string_view title, std::string_view placeholderText, LayoutParams *layoutParams) : View(layoutParams), text_(text), title_(title), undo_(text), placeholderText_(placeholderText), textColor_(0xFFFFFFFF), maxLen_(255) { diff --git a/Common/UI/View.h b/Common/UI/View.h index b9ccd91fd5..1d9b038f2d 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -1086,6 +1086,8 @@ public: ClickableTextView(std::string_view text, LayoutParams *layoutParams = 0) : TextView(text, layoutParams) {} bool Touch(const TouchInput &input) override; + bool Key(const KeyInput &input) override; + Event OnClick; private: diff --git a/UI/AdhocServerScreen.cpp b/UI/AdhocServerScreen.cpp index 3106842127..4324ff8241 100644 --- a/UI/AdhocServerScreen.cpp +++ b/UI/AdhocServerScreen.cpp @@ -223,7 +223,13 @@ AdhocServerRow::AdhocServerRow(std::string *editValue, const AdhocServerListEntr LinearLayout *lines = Add(new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(Margins(5, 5)))); lines->SetSpacing(0.0f); - lines->Add(new TextView(entry.name)); + ClickableTextView *name = lines->Add(new ClickableTextView(entry.name)); + name->SetFocusable(true); + name->OnClick.Add([this](UI::EventParams &e) { + EventParams e2; + e2.v = this; + OnSelected.Trigger(e2); + }); std::string secondLine = entry.host; if (entry.host == "localhost") { From 6f57a49f07955c74dc94dd2e8c89beeef297a76c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Mar 2026 14:59:03 +0100 Subject: [PATCH 3/4] Default touch control layout improvement in portrait, fix insets on pause screen in portrait --- UI/GamepadEmu.cpp | 5 ++++- UI/PauseScreen.h | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/UI/GamepadEmu.cpp b/UI/GamepadEmu.cpp index 6bcf4442ac..fd37328a12 100644 --- a/UI/GamepadEmu.cpp +++ b/UI/GamepadEmu.cpp @@ -824,7 +824,8 @@ void InitPadLayout(TouchControlConfig *config, DeviceOrientation orientation, fl const float scale = globalScale; const int halfW = xres / 2; - const float screenBottom = orientation == DeviceOrientation::Portrait ? (yres - yres * 0.13f) : yres; + const bool portrait = orientation == DeviceOrientation::Portrait; + const float screenBottom = portrait ? (yres - yres * 0.13f) : yres; auto initTouchPos = [=](ConfigTouchPos *touch, float x, float y, float extraScale = 1.0f) { if (touch->x == -1.0f || touch->y == -1.0f) { @@ -863,6 +864,8 @@ void InitPadLayout(TouchControlConfig *config, DeviceOrientation orientation, fl int Action_button_center_Y = screenBottom - Action_button_spacing * 2; if (config->touchRightAnalogStick.show) { Action_button_center_Y -= 150 * scale; + } else if (portrait) { + Action_button_center_Y -= 120 * scale; } initTouchPos(&config->touchActionButtonCenter, Action_button_center_X, Action_button_center_Y); diff --git a/UI/PauseScreen.h b/UI/PauseScreen.h index cc94f34680..5cf57ac5f3 100644 --- a/UI/PauseScreen.h +++ b/UI/PauseScreen.h @@ -41,7 +41,9 @@ protected: void CreateViews() override; void update() override; UI::Margins RootMargins() const override; - + ViewLayoutMode LayoutMode() const override { + return ViewLayoutMode::ApplyInsets; + } // For processing of certain mapped keys. bool UnsyncKey(const KeyInput &key) override; void UnsyncAxis(const AxisInput *axes, size_t count) override; From e1ee96676737b79df7e541cf16cb366481de3480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 15 Mar 2026 15:21:28 +0100 Subject: [PATCH 4/4] Fix some more minor inset-related layout issues --- UI/CustomButtonMappingScreen.h | 4 ++++ UI/SimpleDialogScreen.cpp | 8 ++++---- UI/TiltAnalogSettingsScreen.cpp | 8 +++----- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/UI/CustomButtonMappingScreen.h b/UI/CustomButtonMappingScreen.h index f5af27f51f..9f6f2b5a00 100644 --- a/UI/CustomButtonMappingScreen.h +++ b/UI/CustomButtonMappingScreen.h @@ -38,6 +38,10 @@ protected: void dialogFinished(const Screen *dialog, DialogResult result) override; std::string_view GetTitle() const override; + ViewLayoutMode LayoutMode() const override { + return ViewLayoutMode::IgnoreBottomInset; + } + private: void saveArray(); diff --git a/UI/SimpleDialogScreen.cpp b/UI/SimpleDialogScreen.cpp index 9eb1122824..2088f8648e 100644 --- a/UI/SimpleDialogScreen.cpp +++ b/UI/SimpleDialogScreen.cpp @@ -32,7 +32,7 @@ void UISimpleBaseDialogScreen::CreateViews() { if (flags_ & SimpleDialogFlags::ContentsCanScroll) { ScrollView *scroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); - LinearLayout *contents = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(Margins(0, 0, 8, 0))); + LinearLayout *contents = new LinearLayoutList(ORIENT_VERTICAL, new LinearLayoutParams(Margins(0, 0, portrait ? 0 : 8, 0))); contents->SetSpacing(0); CreateDialogViews(contents); scroll->Add(contents); @@ -45,7 +45,7 @@ void UISimpleBaseDialogScreen::CreateViews() { ViewLayoutMode UITwoPaneBaseDialogScreen::LayoutMode() const { const bool portrait = GetDeviceOrientation() == DeviceOrientation::Portrait; if (portrait) { - if (flags_ & TwoPaneFlags::SettingsCanScroll) { + if ((flags_ & TwoPaneFlags::SettingsCanScroll) || (flags_ & TwoPaneFlags::ContentsCanScroll)) { return ViewLayoutMode::IgnoreBottomInset; } else { return ViewLayoutMode::ApplyInsets; @@ -111,9 +111,9 @@ void UITwoPaneBaseDialogScreen::CreateViews() { if (!(flags_ & TwoPaneFlags::SettingsInContextMenu)) { LinearLayout *settingsPane; if (flags_ & TwoPaneFlags::SettingsCanScroll) { - settingsPane = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT)); + settingsPane = new LinearLayout(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, WRAP_CONTENT, Margins(8))); settingsPane->SetSpacing(0.0f); - ScrollView *settingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f, Margins(8))); + ScrollView *settingsScroll = new ScrollView(ORIENT_VERTICAL, new LinearLayoutParams(FILL_PARENT, FILL_PARENT, 1.0f)); settingsScroll->Add(settingsPane); root->Add(settingsScroll); } else { diff --git a/UI/TiltAnalogSettingsScreen.cpp b/UI/TiltAnalogSettingsScreen.cpp index 7f035afc9c..09a064593a 100644 --- a/UI/TiltAnalogSettingsScreen.cpp +++ b/UI/TiltAnalogSettingsScreen.cpp @@ -29,6 +29,7 @@ #include "UI/JoystickHistoryView.h" #include "UI/GamepadEmu.h" #include "UI/TiltAnalogSettingsScreen.h" +#include "UI/MiscViews.h" const char *g_tiltTypes[] = { "None (Disabled)", "Analog Stick", "D-PAD", "PSP Action Buttons", "L/R Trigger Buttons" }; const size_t g_numTiltTypes = ARRAY_SIZE(g_tiltTypes); @@ -113,15 +114,12 @@ void TiltAnalogSettingsScreen::CreateSettingsViews(UI::ViewGroup *settings) { }); typeChoice->SetEnabledPtr(&g_Config.bTiltInputEnabled); settings->Add(new ItemHeader(co->T("Calibration"))); - TextView *calibrationInfo = new TextView(co->T("To Calibrate", "Hold device at your preferred angle and press Calibrate.")); - calibrationInfo->SetSmall(true); - calibrationInfo->SetPadding(Margins(5)); - calibrationInfo->SetAlign(FLAG_WRAP_TEXT); - settings->Add(calibrationInfo); Choice *calibrate = new Choice(co->T("Calibrate")); calibrate->OnClick.Handle(this, &TiltAnalogSettingsScreen::OnCalibrate); calibrate->SetEnabledPtr(&g_Config.bTiltInputEnabled); settings->Add(calibrate); + SettingHint *calibrationInfo = new SettingHint(co->T("To Calibrate", "Hold device at your preferred angle and press Calibrate."), calibrate); + settings->Add(calibrationInfo); settings->Add(new ItemHeader(co->T("Sensitivity"))); if (g_Config.iTiltInputType == 1) {