From 1c067f32a44338bce81ca2992c1a599771bc0a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Oct 2026 13:44:43 +0100 Subject: [PATCH 1/4] UI: Add ScrollView::ScrollToCenter and GetEnclosingScrollView For bringing a given view into the middle of the scroll view it's in, rather than just into sight like focusing does. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/UI/ScrollView.cpp | 19 +++++++++++++++++++ Common/UI/ScrollView.h | 7 +++++++ 2 files changed, 26 insertions(+) diff --git a/Common/UI/ScrollView.cpp b/Common/UI/ScrollView.cpp index 67769db2b6..c3808eb792 100644 --- a/Common/UI/ScrollView.cpp +++ b/Common/UI/ScrollView.cpp @@ -490,6 +490,25 @@ void ScrollView::ScrollTo(float newScrollPos) { scrollToTarget_ = true; } +void ScrollView::ScrollToCenter(const View *view) { + const Bounds &vBounds = view->GetBounds(); + if (orientation_ == ORIENT_VERTICAL) { + ScrollTo(layoutScrollPos_ + vBounds.centerY() - bounds_.y - bounds_.h * 0.5f); + } else { + ScrollTo(layoutScrollPos_ + vBounds.centerX() - bounds_.x - bounds_.w * 0.5f); + } +} + +ScrollView *GetEnclosingScrollView(View *view) { + for (ViewGroup *group = view->Parent(); group; group = group->Parent()) { + ScrollView *scrollView = dynamic_cast(group); + if (scrollView) { + return scrollView; + } + } + return nullptr; +} + void ScrollView::ScrollRelative(float distance) { scrollTarget_ = scrollPos_ + distance; scrollToTarget_ = true; diff --git a/Common/UI/ScrollView.h b/Common/UI/ScrollView.h index b416dcb8e0..a054e3e752 100644 --- a/Common/UI/ScrollView.h +++ b/Common/UI/ScrollView.h @@ -5,6 +5,11 @@ namespace UI { +class ScrollView; + +// The nearest scroll view the view is in, or null. +ScrollView *GetEnclosingScrollView(View *view); + // A scrollview usually contains just a single child - a linear layout or similar. class ScrollView : public ViewGroup { public: @@ -21,6 +26,8 @@ public: void ScrollTo(float newScrollPos); void ScrollToBottom(); + // Scrolls so that the view (which must be laid out) ends up in the middle. + void ScrollToCenter(const View *view); void ScrollRelative(float distance); bool CanScroll() const; void Update() override; From 30c9dd3b4529f22d5e786b5c2f9d6bd76eed0dd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Oct 2026 13:44:44 +0100 Subject: [PATCH 2/4] UI: Let clickable views be highlighted without the focus SetHighlighted outlines the view, following its round corners, to point it out without giving it the focus, which would turn on keyboard navigation. Co-Authored-By: Claude Opus 5.5 (1M context) --- Common/UI/View.cpp | 3 +++ Common/UI/View.h | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Common/UI/View.cpp b/Common/UI/View.cpp index b14d87b7eb..28b01f8631 100644 --- a/Common/UI/View.cpp +++ b/Common/UI/View.cpp @@ -217,6 +217,9 @@ void Clickable::DrawBG(UIContext &dc, const Style &style) { } else { dc.FillRoundRect(style.background, bounds_, dc.GetTheme().choiceRoundRadius, roundCorners_); } + if (highlighted_) { + dc.Draw()->DrawRoundRect(bounds_, dc.GetTheme().choiceRoundRadius, 0xFFFFFFFF, RoundRectStyle::OutlineThin, roundCorners_); + } } void Clickable::ClickInternal() { diff --git a/Common/UI/View.h b/Common/UI/View.h index 768bd38414..0eef740735 100644 --- a/Common/UI/View.h +++ b/Common/UI/View.h @@ -565,6 +565,9 @@ public: void FocusChanged(FocusFlags focusFlags) override; + // Outlines it, to point it out without the keyboard focus (which would turn on keyboard navigation). + void SetHighlighted(bool highlighted) { highlighted_ = highlighted; } + Event OnClick; protected: @@ -579,6 +582,7 @@ protected: int downCountDown_ = 0; bool dragging_ = false; bool down_ = false; + bool highlighted_ = false; }; // TODO: Very similar to Choice, should probably merge them. From d887057b83c3ee0a438523b44d33c988b1514090 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sun, 25 Oct 2026 13:44:44 +0100 Subject: [PATCH 3/4] UI: Add a thin chevron image I_CHEVRON_RIGHT, a thin > for marking items that open another page. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/UIAtlas.cpp | 1 + assets/ui_images/images.svg | 3 +++ 2 files changed, 4 insertions(+) diff --git a/UI/UIAtlas.cpp b/UI/UIAtlas.cpp index e936252d76..f7ca4191a9 100644 --- a/UI/UIAtlas.cpp +++ b/UI/UIAtlas.cpp @@ -124,6 +124,7 @@ static const ImageMeta g_uiImageIDs[] = { {"I_ARROW_RIGHT", true}, {"I_ARROW_UP", true}, {"I_ARROW_DOWN", true}, + {"I_CHEVRON_RIGHT", false}, {"I_SLIDERS", false}, {"I_THREE_DOTS", true}, {"I_INFO", false}, diff --git a/assets/ui_images/images.svg b/assets/ui_images/images.svg index 7ac247ca9e..b6db9ec045 100644 --- a/assets/ui_images/images.svg +++ b/assets/ui_images/images.svg @@ -233,6 +233,9 @@ d="m 20.003686,15.425178 -4.588999,-4.577262 -0.0308,3.553627 -4.535971,3e-6 v 2.072375 l 4.535971,-2e-6 0.031,3.529868 z" style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.35133" sodipodi:nodetypes="cccccccc" /> Date: Sun, 25 Oct 2026 13:44:44 +0100 Subject: [PATCH 4/4] UI: Start normally when --start-screen names an unknown screen It left no screen at all, just a log full of "No current screen!". Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/NativeApp.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 3aac594438..8dc508d686 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -891,12 +891,14 @@ void NativeInit(int argc, const char *argv[], const CommandLineOptions &cmdLineO if (equals(cmdLineOptions.startScreen.value(), "touchscreentest")) { g_screenManager->switchScreen(new MainScreen()); g_screenManager->push(new TouchTestScreen(Path())); - } - if (equals(cmdLineOptions.startScreen.value(), "gamesettings")) { + } else if (equals(cmdLineOptions.startScreen.value(), "gamesettings")) { g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::TO_GAME_SETTINGS)); } else if (equals(cmdLineOptions.startScreen.value(), "developertools")) { g_screenManager->switchScreen(new MainScreen()); g_screenManager->push(new DeveloperToolsScreen(Path())); + } else { + WARN_LOG(Log::System, "Unknown start screen '%s'", cmdLineOptions.startScreen.value().c_str()); + g_screenManager->switchScreen(new LogoScreen(AfterLogoScreen::DEFAULT)); } } else if (skipLogo && !boot_filename.empty()) { INFO_LOG(Log::System, "Launching EmuScreen with boot filename '%s'", boot_filename.c_str());