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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
The backbuffer is now a Swapchain, from GetPrimarySwapchain(), and
callers bind, measure and read it back through that instead of nullptr.
nullptr still means it too. Backends with one swapchain map the primary
to their old backbuffer path.
On Metal, CreateSwapchain() adds more, each with its own layer, depth
buffer and drawable. Passes merge per swapchain, and Present() presents
every drawable acquired that frame. The backbuffer is just the first
of them. Nothing creates a second one yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Set by ViewGroup when a view is added, inserted or swapped in. Lets a view
find the scroll view it's in, for example.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The saved tab was selected before the Search tab was added, so the index
was out of range and no tab was created. Select it after adding Search, and
create the current tab anyway if the saved one no longer exists (Search is
hidden on small screens).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MeasureString was slow mostly because of wrapping: DrawStringRect and
MeasureStringRect re-ran the word wrapper every frame, and it measures
every prefix of every word. Wrapped strings are now cached. The caches
are unordered_maps with an xxhash of the key, and lookups reuse a key
buffer so a hit doesn't allocate. UIContext also skips switching fonts
when measuring in the current style. In a Mac debug build, 200 cached
measures go from 0.26 to 0.06 ms and 10 wrapped paragraphs from 2.1 to
0.01 ms per frame.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The volume key filter in NativeKey left unmapped volume keys to the OS, so
they never reached the dialog and couldn't be mapped. A new InputMode::AllKeys
lets a screen ask for them; the dialog consumes them so the volume doesn't
change while mapping.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NativeKey calls KeyEventToFocusMoves directly on the input thread, while
ProcessHeldKeys iterates heldKeys on the UI thread, which tripped an
iterator assert in debug builds. ProcessHeldKeys also dispatched key
events mid-iteration, where a handler calling EnableFocusMovement(false)
would clear the set under it. Now it collects due repeats under the lock
and dispatches them after.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With no type-to-search active, Backspace goes to the parent directory. Never
on a repeat, so holding it doesn't climb to the root. Not every platform
flags repeats, so the browser also tracks whether it's held.
Windows raw input now flags auto-repeated key downs as IS_REPEAT, like
Android and UWP, and forgets the held keys it releases on losing focus. The
UI drops OS repeats of a key it's already repeating itself, so held arrows
don't double up (they were dropped before too, a step later).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Refresh already focused a game it was asked to. Now it does the same for
directories, and keeps the request until a listing that was still loading
has arrived. UI::ScrollFocusedViewIntoView scrolls the focused view into
sight once it's been laid out, since SetFocus alone doesn't scroll.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On Windows, a key released while Alt is held comes as WM_SYSKEYUP, which
raw input dropped, so after Alt+Up the UI kept the arrow held and the focus
kept moving. Handle it like WM_KEYUP. Alt+arrows also no longer move the
focus, since they're shortcuts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ScrollView dropped every mouse event without the left button, so views inside
one never saw hovers or right clicks. This kept nested scroll views from
tracking hover for the mouse wheel, and GameButton's right-click info only
worked once a left click had set its hover flag.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Fixes#17608
Text fields took Ctrl or Cmd for copy/paste/undo; now it's Cmd on Apple
platforms and Ctrl elsewhere, as is Ctrl+F (Cmd+F) for game search. Word
delete in the search filter is Option+Backspace on Apple.
The ImGui debugger never got Super, so with ImGui's Mac Cmd/Ctrl swap,
its paste and other Ctrl shortcuts could not be reached at all on Mac.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tvOS only does that with a Back press it gets to see, so the view controller
lets controller events through to UIKit while the top screen is top level
(the new TOP_LEVEL_CHANGED notification), and passes presses that aren't
keyboard keys on to super. Elsewhere Back stays ours.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replaces the center-to-center scoring, which skipped a wide view between narrow
ones, since its center is far away even when its edge is right there. Now it
works like Android's FocusFinder: only views in the direction of the move are
candidates, ones in the origin's beam beat ones off to the side (going up or
down, unless those are in a nearer row), and otherwise the nearest wins, with
distance along the move weighted 13 times distance across it. Unlike Android,
the distance across is to the candidate's nearest edge rather than its center.
First/last and page moves keep their scoring.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>