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>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-24 16:12:01 +02:00
1 parent 53a7127618
commit 58fa522d48
12 files changed
+73 -9

No files matched your search

+38 -2
View File
@@ -58,7 +58,7 @@ void UIScreen::DoRecreateViews() {
// Update layout and refocus so things scroll into view.
// This is for resizing down, when focused on something now offscreen.
UI::LayoutViewHierarchy(*screenManager()->getUIContext(), RootMargins(), root_, LayoutMode(), UseImmersiveMode());
UI::LayoutViewHierarchy(*screenManager()->getUIContext(), GetContentBounds(*screenManager()->getUIContext()), root_);
UI::View *focused = UI::GetFocusedView();
if (focused) {
root_->SubviewFocused(focused);
@@ -128,6 +128,42 @@ Bounds UIScreen::GetLayoutBounds(UIContext &dc) const {
return dc.GetLayoutBounds(LayoutMode(), UseImmersiveMode());
}
// On TVs and big monitors, controls out at the edges are hard to see and far apart. Grows with the
// screen, from nothing at 600 high (or wide, if smaller) to the full inset at 1000, so resizing a window
// doesn't make it jump.
static float LargeScreenInset() {
const float fullInset = 40.0f;
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) {
return fullInset;
}
const float size = (float)std::min(g_display.dp_xres, g_display.dp_yres);
return fullInset * std::clamp((size - 600.0f) / 400.0f, 0.0f, 1.0f);
}
Bounds UIScreen::GetContentBounds(const UIContext &dc) const {
const UI::Margins margins = RootMargins();
Bounds bounds = dc.GetLayoutBounds(LayoutMode(), UseImmersiveMode());
bounds.x += margins.left;
bounds.y += margins.top;
bounds.w -= margins.horiz();
bounds.h -= margins.vert();
if (!LimitContentArea()) {
return bounds;
}
const float inset = LargeScreenInset();
bounds.x += inset;
bounds.y += inset;
bounds.w -= inset * 2.0f;
bounds.h -= inset * 2.0f;
const float maxWidth = MaxContentWidth();
if (maxWidth > 0.0f && bounds.w > maxWidth) {
bounds.x += (bounds.w - maxWidth) * 0.5f;
bounds.w = maxWidth;
}
return bounds;
}
ScreenRenderFlags UIScreen::render(ScreenRenderMode mode) {
// A screen rendered under a popup must keep its views: the popup's callbacks point into them (the choice
// that opened it, the value it writes to). update() recreates them once the screen is on top again.
@@ -137,7 +173,7 @@ ScreenRenderFlags UIScreen::render(ScreenRenderMode mode) {
UIContext &uiContext = *screenManager()->getUIContext();
if (root_) {
UI::LayoutViewHierarchy(uiContext, RootMargins(), root_, LayoutMode(), UseImmersiveMode());
UI::LayoutViewHierarchy(uiContext, GetContentBounds(uiContext), root_);
}
uiContext.PushTransform({translation_, scale_, alpha_});