feat: add UI size slider to settings#3524
Conversation
Add a UI scale slider (50%-200%) to the in-game settings panel, allowing players to adjust the interface size regardless of their OS scaling, browser zoom, or screen type (mobile, ultrawide, etc.). Implementation: - UserSettings: uiScale()/setUiScale()/applyUiScale() with localStorage persistence and clamping to 50-200 range - SettingsModal: range input slider following the existing volume slider pattern, placed after the audio settings - Main.ts: apply saved scale on page load - en.json: translation key for the label Uses CSS zoom on the root element for proper layout reflow. Closes openfrontio#3067
WalkthroughA UI scaling feature is introduced, allowing users to adjust interface size from 50% to 200% via a settings slider. The implementation includes persistent storage, localized UI labels, initialization during app startup, and DOM zoom manipulation to scale the entire interface. Changes
Sequence DiagramssequenceDiagram
participant Client
participant UserSettings
participant Browser
Client->>UserSettings: applyUiScale()
activate UserSettings
UserSettings->>UserSettings: read uiScale (default 100)
UserSettings->>Browser: document.documentElement.style.zoom = "1.0"
deactivate UserSettings
Note over Browser: UI renders at 100% scale
sequenceDiagram
participant User
participant SettingsModal
participant UserSettings
participant Storage
participant Browser
User->>SettingsModal: adjust range slider
SettingsModal->>SettingsModal: onUiScaleChange()
SettingsModal->>UserSettings: setUiScale(75)
activate UserSettings
UserSettings->>UserSettings: clamp to [50, 200]
UserSettings->>Storage: persist value
UserSettings->>Browser: apply zoom = "0.75"
deactivate UserSettings
Note over Browser: UI scales to 75%
SettingsModal->>SettingsModal: requestUpdate()
SettingsModal->>User: display "75%"
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/core/game/UserSettings.ts`:
- Around line 269-284: Normalize UI scale in one place and ensure both setting
and applying use it: create or use a single normalization step (e.g.,
normalizeUiScale(value?: number)) that ensures the value is finite, defaults to
the stored uiScale(), clamps to [50,200], and snaps to 10% steps (round to
nearest 10), then use that normalized value in setUiScale (before
setFloat/applyUiScale) and in applyUiScale (call normalizeUiScale() instead of
using raw scale or uiScale()), and ensure getFloat/setFloat still receive only
the normalized number for "settings.uiScale".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc210491-5b9a-462b-8791-f52c1212e273
📒 Files selected for processing (4)
resources/lang/en.jsonsrc/client/Main.tssrc/client/graphics/layers/SettingsModal.tssrc/core/game/UserSettings.ts
| uiScale(): number { | ||
| return this.getFloat("settings.uiScale", 100); | ||
| } | ||
|
|
||
| setUiScale(scale: number): void { | ||
| const clamped = Math.max(50, Math.min(200, scale)); | ||
| this.setFloat("settings.uiScale", clamped); | ||
| this.applyUiScale(clamped); | ||
| } | ||
|
|
||
| applyUiScale(scale?: number): void { | ||
| const value = scale ?? this.uiScale(); | ||
| if (typeof document !== "undefined") { | ||
| document.documentElement.style.zoom = (value / 100).toString(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Enforce UI scale normalization in one place (finite + step + clamp).
At Line 274, Math.max/Math.min does not protect against NaN, and step normalization (10%) is not enforced. At Line 280-283, unnormalized values are applied directly to zoom. This can persist/apply invalid values and break scaling state.
💡 Proposed fix
+ private normalizeUiScale(scale: number): number {
+ if (!Number.isFinite(scale)) return 100;
+ const stepped = Math.round(scale / 10) * 10;
+ return Math.max(50, Math.min(200, stepped));
+ }
+
uiScale(): number {
- return this.getFloat("settings.uiScale", 100);
+ return this.normalizeUiScale(this.getFloat("settings.uiScale", 100));
}
setUiScale(scale: number): void {
- const clamped = Math.max(50, Math.min(200, scale));
- this.setFloat("settings.uiScale", clamped);
- this.applyUiScale(clamped);
+ const normalized = this.normalizeUiScale(scale);
+ this.setFloat("settings.uiScale", normalized);
+ this.applyUiScale(normalized);
}
applyUiScale(scale?: number): void {
- const value = scale ?? this.uiScale();
+ const value = this.normalizeUiScale(scale ?? this.uiScale());
if (typeof document !== "undefined") {
document.documentElement.style.zoom = (value / 100).toString();
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/core/game/UserSettings.ts` around lines 269 - 284, Normalize UI scale in
one place and ensure both setting and applying use it: create or use a single
normalization step (e.g., normalizeUiScale(value?: number)) that ensures the
value is finite, defaults to the stored uiScale(), clamps to [50,200], and snaps
to 10% steps (round to nearest 10), then use that normalized value in setUiScale
(before setFloat/applyUiScale) and in applyUiScale (call normalizeUiScale()
instead of using raw scale or uiScale()), and ensure getFloat/setFloat still
receive only the normalized number for "settings.uiScale".
Summary
Adds a UI scale slider (50%-200%) to the in-game settings panel, allowing players to adjust the interface size. Addresses the issue where UI looks bad with OS scaling at 125% or on mobile/ultrawide screens.
How It Works
zoomondocument.documentElement— reflows layout correctly (unliketransform: scale)localStorage— setting survives page refreshFiles Changed
UserSettings.tsuiScale(),setUiScale(),applyUiScale()methodsSettingsModal.tsMain.tsen.jsonScreenshots
The slider follows the exact same pattern as the existing Music/SFX volume sliders — consistent UX, no new dependencies.
Test Plan
Closes #3067