Commit Graph
695 Commits
Author SHA1 Message Date
Henrik Rydgård f9baffccb6 Setting reorganization, some changed strings, soft drop shadows for text on game screen 2026-10-31 01:22:27 +01:00
Henrik Rydgård 67763e48a1 Add an "info textview" with a border. 2026-10-31 00:27:10 +01:00
Henrik Rydgård d09a9ffa11 Correct corner radius of popups to match inner rounded things 2026-10-29 15:12:55 +01:00
Henrik Rydgård 0a2568f4ef Round more things. Remove the underline from popup titles, cleaner look 2026-10-27 21:58:32 +01:00
Henrik RydgårdandClaude Opus 5.5 6ea7cf7be2 OSD: Round the corners of on-screen notifications
Half the radius of a Choice. Covers notices, progress bars, the
leaderboard tracker and RetroAchievements popups (not the listed
achievements on the achievement screens).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:49:06 +01:00
Henrik RydgårdandClaude Opus 5.5 e67543d604 UI: Pad the inside of popups
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:47:06 +01:00
Henrik RydgårdandClaude Opus 5.5 6efdad7cea UI: Round the popup buttons
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:47:06 +01:00
Henrik RydgårdandClaude Opus 5.5 916dfab834 UI: Round the corners of popups
FillRoundRect falls back to a square FillRect for drawables it can't
round, and a ViewGroup with round corners set rounds its background.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 21:47:06 +01:00
Henrik RydgårdandClaude Opus 5.5 a941ad634d NoticeView: Round corners like a Choice, pad the contents, fill the width
RenderNotice and MeasureNotice take padding (added inside the background)
and the corners to round. The drop shadow moves out of RenderNotice to the
floating OSD notices, since views don't have one. Without layout params, a
NoticeView is FILL_PARENT wide like a Choice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:17:31 +01:00
Henrik RydgårdandClaude Opus 5.5 4c7dd44351 TextDrawer: Remove the current font and scale
Drawing now takes the style and scale too, like measuring, so the text
drawers have no font state left. UIContext keeps its current style and
passes it, and fills it in from the theme if nothing has set one yet.
PPGe passes its one font.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:54:10 +01:00
Henrik RydgårdandClaude Opus 5.5 dc1e9e50fc TextDrawer: Measure in a given style without switching fonts
Measuring took the current font and scale, so UIContext switched to the
style being measured and back. On first draw the font to switch back to
could be an empty one, which the UWP drawer asserts on.

MeasureString and MeasureStringRect now take the style and scale, and the
backends' MeasureStringInternal measures in the style it's given.
SetOrCreateFont, which sets the font drawing uses, is shared, and the
backends only implement EnsureFont, which creates a font if needed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:45:04 +01:00
Henrik Rydgård b4c096d13c Tweaks on DisplayLayoutScreen.cpp 2026-10-26 23:42:20 +01:00
Henrik Rydgård 4d26021a9b More round stuff 2026-10-26 23:08:25 +01:00
Henrik Rydgård 5c2a17f120 Give InfoItem backgrounds 2026-10-26 23:01:21 +01:00
Henrik Rydgård acc9fe9283 Somewhat better organization of UI settings 2026-10-26 23:01:21 +01:00
Henrik Rydgård 734d56a1d3 Tweaks to RetroAchievements screen 2026-10-26 23:01:21 +01:00
Henrik RydgårdandClaude Opus 5.5 f364f297f1 Touch controls: Use the full width when the side insets are only for a top corner
On the iPhone Duo's cover screen, the camera sits in a box in the top right
corner, which gives a side inset down the whole right edge in portrait. The
controls are far below it, so they lose width for nothing. iOS 27.1 reports
the box as an occlusion region, and from that we report how far down the side
insets are needed (SYSPROP_DISPLAY_SIDE_INSETS_BOTTOM). When that's within the
top third, the game screen and the touch layout editor skip the side insets.
The rest of the UI, including the FPS counter, still keeps them.

The regions can show up after the size and insets have settled, so the insets
are now recomputed on every layout, and a screen recreates its views when its
layout mode changes, since the touch controls are placed for the bounds they
were created in.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-26 09:39:49 +01:00
Henrik RydgårdandClaude Opus 5.5 cccb296050 UI: Only even out the side insets in landscape
In portrait, a camera cutout is at the top, so a one-sided side inset is a
real edge and mirroring it just wastes space. The iPhone Duo's cover screen
has its sensor bar on the right in portrait, which left an equally wide
empty strip on the left of the main screen.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 23:21:12 +01:00
Henrik RydgårdandClaude Opus 5.5 70f205f2ff Take output screenshots after the ImGui debugger has drawn
The screenshot readback ran in ScreenManager's post-render callback, and
on Vulkan it ends the backbuffer render step. renderImDebugger() draws
after that, so taking a screenshot with the debugger open made its first
SetViewport() write through a null render step (an assert in debug
builds, a Play crash report in release). Do the readback at the end of
the frame in NativeFrame instead, which also puts the debugger in the
screenshot, and remove the now unused callback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 19:44:21 +01:00
Henrik Rydgård 518795e35f Cut down on outdated/useless REPORTing and logging 2026-10-25 17:33:44 +01:00
Henrik RydgårdandClaude Opus 5.5 b91bd6af86 Settings: Build the settings screen from pages described as data
Each tab is now a SettingsPage: a list of items (headers, settings, buttons,
hints, links to other pages) that a function builds and views are created
from. Settings are added with page->Setting(&g_Config.x), which takes
the label, the kind of view, the range or choices, units, labels for special
values and the default from the config. The pages keep what depends on the
UI (which settings to show and when, enable rules, and behavior that needs the
screen), and reach the screen through the page's SettingsPageHost.

A page can link to other pages, which open on top of it in the same tab,
under a bar showing where you are, like Tools > Developer tools > Graphics,
where the earlier pages can be clicked to go back to them. The bar has back,
search and help buttons, and the back key also goes back a page (or out of a
search) before leaving the screen. Each page remembers its scroll position.
In portrait there's no tab strip: a page links to the tabs, and the bar shows
just the current page's title. Rotating keeps showing the same page. Moving
between pages only focuses an item when navigating with a keyboard or
controller.

The developer tools are now pages linked from the Tools tab, instead of a
screen of their own. The in-game developer menu and
--start-screen=developertools open the settings on that page. The toggles of
live state (the remote debugger and the MIPS tracer) show and change it
through functions, and pretending to be in-game for the notification tests is
a flag on the OSD. The memory map and memstick tests, only reachable from
commented-out code, are removed.

Search lists the matching settings from all tabs and the pages they link to,
grouped by where they are. Each result goes to the setting on its page,
scrolled to and highlighted (outlined, when picked with a mouse or touch), or
for a page, opens it. This replaces the search tab and the old filter that
hid the views that didn't match, and with them ShowSearchControls. Changing
tabs leaves the search.

The pages, the bar and search are in SettingsPagesDialogScreen, between the
tabbed dialog screen and the settings screen, so the other tabbed screens
keep plain tabs.

Other visible changes:
- Reset buttons that went to the wrong value now go to the real default: the
  touch control auto-hide delay (was 20 seconds instead of 8), the mouse
  wheel delay (1 ms, below its own range, instead of 80) and the UI size (0
  instead of -1 on desktop). The alternative speeds get a reset button.
- Units are shown on more sliders (frames, MHz, seconds, Hz), and popups no
  longer spell out what 0 means in the units, since the zero label does.
  Units of up to three characters (%, x, ms, MHz) aren't translated.
- The Android HW scaler hides the scales the display is too small for,
  instead of cutting the list short.
- Choices are translated in the setting's own section.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 15:26:58 +01:00
Henrik RydgårdandClaude Opus 5.5 30c9dd3b45 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) <noreply@anthropic.com>
2026-10-25 13:44:44 +01:00
Henrik RydgårdandClaude Opus 5.5 1c067f32a4 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) <noreply@anthropic.com>
2026-10-25 13:44:43 +01:00
Henrik Rydgård 965b07c527 A lot more round corner fixes and layout fixes. 2026-10-25 10:28:40 +01:00
Henrik RydgårdandClaude Opus 5.5 5567f51eb8 UI: Don't apply the large-screen inset on phones and tablets
They're held close and have their own system insets.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 09:41:38 +01:00
Henrik RydgårdandClaude Opus 5.5 8e353761c7 UI: Let scroll views extend under the bottom inset when they don't quite reach the edge
A vertical ScrollView that extends into the bottom inset (like a navigation bar) adds
that much space after its contents, so the end can be scrolled out from under it.
It only did that when the view reached the very bottom of the screen, and then added
the whole inset. Since 58fa522d48 inset the content area of dialog screens on
screens over 600 dp, a phone like the S21 Ultra (617 dp wide) got a 1.7 dp inset,
which turned the extra space off. Add exactly the part of the view that lies in the
inset instead, and skip the bottom shadow in that case.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 09:41:38 +01:00
Henrik RydgårdandClaude Opus 5.5 58fa522d48 UI: Limit the width of dialog screens, and keep content off the edges of large screens
Dialog screens (tabbed, two-pane, simple and the others built on
UIBaseDialogScreen) center their content at up to 1400 wide, instead of
stretching settings rows and buttons across very wide windows.

On large screens, those screens and the main screen also get an inset on all
sides, since controls right at the edge are hard to see there. It grows with
the screen's smaller dimension, from nothing at 600 to the full inset at
1000, and is always full on TVs. The game, popups, and the screens that
position things to match the game's screen (display layout, touch control
layout and tests, custom button and visual mapping) are left as they were.

The root's bounds are computed by UIScreen::GetContentBounds, since
UI::Margins can't hold widths this large.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 16:12:01 +02:00
Henrik Rydgård 695fc865ab Fix some look problems on TabbedDialogScreen 2026-10-24 14:12:53 +02:00
Henrik RydgårdandClaude Opus 5.5 b54056685e 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) <noreply@anthropic.com>
2026-10-24 14:01:58 +02:00
Henrik Rydgård 505ae1c965 Cleanup 2026-10-24 12:54:49 +02:00
Henrik RydgårdandClaude Opus 5.5 8b1ffb9b39 UI: Apply GridLayout children's margins, drop the unused grid orientation
Grid children are given LinearLayoutParams almost everywhere, so their margins are
taken from those as well as from GridLayoutParams, which gains margins.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:49:45 +02:00
Henrik RydgårdandClaude Opus 5.5 7ddd0ce33b UI: Skip hidden views in AnchorLayout
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:49:44 +02:00
Henrik RydgårdandClaude Opus 5.5 5c4b1be7f5 UI: Let LinearLayout place its children as a group along its direction
SetContentGravity centers the children, or moves them to the end, when they don't
fill the layout.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:49:44 +02:00
Henrik RydgårdandClaude Opus 5.5 bff97cbb09 UI: Fix two LinearLayout measuring and layout bugs
Lay out children across the layout direction within the layout's bounds rather than
its measured size, which differ when a parent clamps it. And count weighted
children's margins when growing to fit them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:49:32 +02:00
Henrik RydgårdandClaude Opus 5.5 e5b1760bff UI: Add padding to GridLayout
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:36:17 +02:00
Henrik RydgårdandClaude Opus 5.5 d9126ad685 UI: Remove the unused FrameLayout
Nothing used it, and it didn't work: it never set its own size, centered vertically
using the width, and didn't lay out its children.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:36:16 +02:00
Henrik RydgårdandClaude Opus 5.5 7fa69ac27f UI: Center views inside their margins
Centering gravity centered in the whole cell, ignoring the margins, so a view with
uneven margins ended up off center.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:36:07 +02:00
Henrik RydgårdandClaude Opus 5.5 81d078efc4 UI: Fix ScrollView's handling of its child's margins
The child was measured inside its margins, then had them subtracted from its bounds
again, and the leading margin along the scroll direction was never applied, so the
end of the content couldn't be scrolled to. Also include the margins when wrapping
the content, and page horizontally by the width rather than the x position.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:35:56 +02:00
Henrik RydgårdandClaude Opus 5.5 cfb0b1131b UI: Clamp fixed-size views to an AT_MOST spec
MeasureBySpec compared the spec against the view's previous measurement instead of
its fixed size, so a fixed-size view larger than the space offered wasn't clamped
unless an earlier measure happened to be larger.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:35:55 +02:00
Henrik RydgårdandClaude Opus 5.5 e30c8f7141 UI: Give LinearLayout a working padding setting
LinearLayout had a public padding member that only DisplayLayoutScreen set. Make it
private with a setter, and fix the measuring: the vertical retry used the horizontal
padding, the AT_MOST retries and the weighted children ignored it, and growing to fit
weighted children counted it twice.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 12:24:21 +02:00
Henrik Rydgård ad132d8f1b Apply rounded corners to all Choices by default, apply proper group rounding in many places. 2026-10-24 11:21:40 +02:00
Henrik Rydgård 1a2d140652 Fix corner rounding bug when there's only one item 2026-10-24 01:52:34 +02:00
Henrik Rydgård c44a12363f Prototype the new ChoiceGroup component 2026-10-24 01:52:34 +02:00
Henrik RydgårdandClaude Opus 5.5 2ed5aa264c UI: Let round rects keep some corners square
A RoundCorners mask picks the corners to round. A square corner is drawn
from the corner image's edge profiles, split along the diagonal, so its
antialiasing matches the straight edges and it needs no extra draw call.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 01:52:34 +02:00
Henrik RydgårdandClaude Opus 5.5 1fb8c3090b UI: Fade out the scroll bob when the scroll view is left alone
It stays for a second after the last activity, then fades out over half
a second. Any scrolling, touch, bob drag, or the mouse over the bob's
strip brings it back within a tenth of a second. It also shows briefly
when the view appears, so it's clear that it scrolls.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 01:52:34 +02:00
Henrik RydgårdandClaude Opus 5.5 f795f7e07f UI: A press and release in one event is a press only for the top screen
Key ups go to every screen, so keys don't get stuck, and that included
events that are a press and a release at once, like iOS's back swipe. So
with settings open from the pause menu, a back swipe made the pause
screen try to close too. It isn't on top, so finishDialog refused, but
the screen had already stopped taking input, and once settings closed it
was left on top, ignoring everything.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 23:16:47 +02:00
Henrik RydgårdandClaude Opus 5.5 5fbd134a7d Android: Show the game on a second display, with the controls on the phone
A presentation display (HDMI or USB-C video out, some wireless displays,
or the simulated ones in Developer options) gets a Presentation with a
SurfaceView, whose surface becomes a Game output, when the backend is
Vulkan. Otherwise Android keeps mirroring.

The surface mustn't be touched after surfaceDestroyed returns, so the
output is removed with the new Native_RemoveOutputAndWait(), which
returns once the render thread has let go of it (or right away if the
graphics are down). The UI drawn on an output now leaves out the main
swapchain's pre-rotation, as the game already did.

"In menus on %1" gets the device name from Settings.Global.DEVICE_NAME.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 22:31:07 +02:00
Henrik RydgårdandClaude Opus 5.5 3b1432d851 Outputs: Begin the UI frame after binding, and scissor to the output
UIContext::BeginFrame sets the scissor, which on Vulkan needs a bound
render target, and sets it to the primary's size. In portrait, that cut
the TV's menu message off past the phone's width.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 3ca76c674c Outputs: Say the menus are on the device, instead of showing black
When no screen on the stack shows anything on an output (we're in the
menus), the background screen draws "Menus are on your device" there.
UI drawn to an output gets the primary's DPI scale, over the whole output.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00
Henrik RydgårdandClaude Opus 5.5 d491fc90cf Secondary outputs, and --game-window on Mac to show the game in a second window
The ScreenManager keeps a list of secondary outputs, each with its own
swapchain, recreated after a lost device. Platform code adds, resizes and
removes them from any thread through Native_AddOutput() and friends, which
take effect at the start of the next frame.

Each frame, every output is drawn by the topmost screen that claims it
(Screen::renderOutput). EmuScreen claims Game outputs: it presents the
game there, laid out for that output, and leaves it out of the backbuffer,
where the touch controls and the menus stay. Not in non-buffered mode,
which draws straight into the backbuffer.

--game-window opens such an output on Mac, with Metal. It's a smoke test
for multiple swapchains, and a stand-in for an AirPlay display.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 18:42:53 +02:00