mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-08-07 19:21:30 +00:00
Add structure icon size graphics override (#4270)
## Summary Adds a new **Structure icon size** option to `GraphicsOverrides`, exposed as a slider in the Graphics Settings modal. Players can now scale how large structure icons are drawn on the map. ## Changes - **`GraphicsOverrides.ts`** — add `iconSize: z.number()` to the `structure` override schema. - **`RenderOverrides.ts`** — apply the override onto `settings.structure.iconSize` (consumed by `StructurePass`/`StructureLevelPass` shaders). - **`GraphicsSettingsModal.ts`** — add a slider (range 20–120, step 5) in the "Structure Icons" section, with getter/handler following the existing pattern. Falls back to the `render-settings.json` default of 60 when unset. - **`resources/lang/en.json`** — add `icon_size_label` / `icon_size_desc` (English only, per i18n rules). - **`tests/GraphicsOverrides.test.ts`** — schema-validation cases plus application tests (override sets the value; absence keeps the default). The setting persists via the existing `userSettings.graphicsOverrides()` localStorage flow and takes effect live through the existing `regenerateRenderSettings` wiring. ## Testing - `npx vitest tests/GraphicsOverrides.test.ts --run` — 35 passed - `tsc --noEmit` — no new type errors 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -535,6 +535,8 @@
|
|||||||
"hover_glow_alpha_label": "Hover glow strength",
|
"hover_glow_alpha_label": "Hover glow strength",
|
||||||
"hover_glow_width_desc": "How far the white glow extends behind the hovered player's name",
|
"hover_glow_width_desc": "How far the white glow extends behind the hovered player's name",
|
||||||
"hover_glow_width_label": "Hover glow size",
|
"hover_glow_width_label": "Hover glow size",
|
||||||
|
"icon_size_desc": "How large structure icons are drawn on the map",
|
||||||
|
"icon_size_label": "Structure icon size",
|
||||||
"name_cull_desc": "Hide names smaller than this size",
|
"name_cull_desc": "Hide names smaller than this size",
|
||||||
"name_cull_label": "Minimum name size",
|
"name_cull_label": "Minimum name size",
|
||||||
"name_scale_label": "Name Scale",
|
"name_scale_label": "Name Scale",
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ const HOVER_GLOW_ALPHA_MIN = 0;
|
|||||||
const HOVER_GLOW_ALPHA_MAX = 1;
|
const HOVER_GLOW_ALPHA_MAX = 1;
|
||||||
const HOVER_GLOW_ALPHA_STEP = 0.05;
|
const HOVER_GLOW_ALPHA_STEP = 0.05;
|
||||||
|
|
||||||
|
const ICON_SIZE_MIN = 40;
|
||||||
|
const ICON_SIZE_MAX = 70;
|
||||||
|
const ICON_SIZE_STEP = 5;
|
||||||
|
|
||||||
const HIGHLIGHT_FILL_MIN = 0;
|
const HIGHLIGHT_FILL_MIN = 0;
|
||||||
const HIGHLIGHT_FILL_MAX = 1;
|
const HIGHLIGHT_FILL_MAX = 1;
|
||||||
const HIGHLIGHT_FILL_STEP = 0.01;
|
const HIGHLIGHT_FILL_STEP = 0.01;
|
||||||
@@ -318,6 +322,18 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
|||||||
this.patchRailroad({ railThickness: value });
|
this.patchRailroad({ railThickness: value });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private currentIconSize(): number {
|
||||||
|
return (
|
||||||
|
this.userSettings.graphicsOverrides().structure?.iconSize ??
|
||||||
|
renderDefaults.structure.iconSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onIconSizeChange(event: Event) {
|
||||||
|
const value = parseFloat((event.target as HTMLInputElement).value);
|
||||||
|
this.patchStructure({ iconSize: value });
|
||||||
|
}
|
||||||
|
|
||||||
private currentClassicIcons(): boolean {
|
private currentClassicIcons(): boolean {
|
||||||
return (
|
return (
|
||||||
this.userSettings.graphicsOverrides().structure?.classicIcons ?? true
|
this.userSettings.graphicsOverrides().structure?.classicIcons ?? true
|
||||||
@@ -432,6 +448,7 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
|||||||
const hoverGlowWidth = this.currentHoverGlowWidth();
|
const hoverGlowWidth = this.currentHoverGlowWidth();
|
||||||
const hoverGlowAlpha = this.currentHoverGlowAlpha();
|
const hoverGlowAlpha = this.currentHoverGlowAlpha();
|
||||||
const namesColored = !this.currentDarkNames();
|
const namesColored = !this.currentDarkNames();
|
||||||
|
const iconSize = this.currentIconSize();
|
||||||
const classicIcons = this.currentClassicIcons();
|
const classicIcons = this.currentClassicIcons();
|
||||||
const classicNumbers = this.currentClassicNumbers();
|
const classicNumbers = this.currentClassicNumbers();
|
||||||
const highlightFill = this.currentHighlightFill();
|
const highlightFill = this.currentHighlightFill();
|
||||||
@@ -629,6 +646,31 @@ export class GraphicsSettingsModal extends LitElement implements Controller {
|
|||||||
${translateText("graphics_setting.section_structure_icons")}
|
${translateText("graphics_setting.section_structure_icons")}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||||
|
>
|
||||||
|
<div class="flex-1">
|
||||||
|
<div class="font-medium">
|
||||||
|
${translateText("graphics_setting.icon_size_label")}
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-slate-400">
|
||||||
|
${translateText("graphics_setting.icon_size_desc")}
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
type="range"
|
||||||
|
min=${ICON_SIZE_MIN}
|
||||||
|
max=${ICON_SIZE_MAX}
|
||||||
|
step=${ICON_SIZE_STEP}
|
||||||
|
.value=${String(iconSize)}
|
||||||
|
@input=${this.onIconSizeChange}
|
||||||
|
class="w-full border border-slate-500 rounded-lg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-slate-400 w-12 text-right">
|
||||||
|
${iconSize.toFixed(0)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
class="flex gap-3 items-center w-full text-left p-3 hover:bg-slate-700 rounded-sm text-white transition-colors"
|
||||||
@click=${this.onToggleClassicIcons}
|
@click=${this.onToggleClassicIcons}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export const GraphicsOverridesSchema = z
|
|||||||
.partial(),
|
.partial(),
|
||||||
structure: z
|
structure: z
|
||||||
.object({
|
.object({
|
||||||
|
iconSize: z.number(),
|
||||||
classicIcons: z.boolean(),
|
classicIcons: z.boolean(),
|
||||||
classicNumbers: z.boolean(),
|
classicNumbers: z.boolean(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ export function applyGraphicsOverrides(
|
|||||||
if (overrides.name?.hoverGlowAlpha !== undefined) {
|
if (overrides.name?.hoverGlowAlpha !== undefined) {
|
||||||
settings.name.hoverGlowAlpha = overrides.name.hoverGlowAlpha;
|
settings.name.hoverGlowAlpha = overrides.name.hoverGlowAlpha;
|
||||||
}
|
}
|
||||||
|
if (overrides.structure?.iconSize !== undefined) {
|
||||||
|
settings.structure.iconSize = overrides.structure.iconSize;
|
||||||
|
}
|
||||||
if (overrides.structure?.classicIcons ?? true) {
|
if (overrides.structure?.classicIcons ?? true) {
|
||||||
// Classic look (default): lighter player-colored shape behind a darkened
|
// Classic look (default): lighter player-colored shape behind a darkened
|
||||||
// player-colored icon glyph (matching the old canvas renderer's
|
// player-colored icon glyph (matching the old canvas renderer's
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ describe("GraphicsOverridesSchema", () => {
|
|||||||
{ structure: { classicNumbers: true } },
|
{ structure: { classicNumbers: true } },
|
||||||
{ structure: { classicNumbers: false } },
|
{ structure: { classicNumbers: false } },
|
||||||
{ structure: { classicIcons: true, classicNumbers: false } },
|
{ structure: { classicIcons: true, classicNumbers: false } },
|
||||||
|
{ structure: { iconSize: 80 } },
|
||||||
|
{ structure: { iconSize: 40, classicIcons: false } },
|
||||||
{ name: { darkNames: true }, structure: { classicIcons: true } },
|
{ name: { darkNames: true }, structure: { classicIcons: true } },
|
||||||
];
|
];
|
||||||
for (const c of cases) {
|
for (const c of cases) {
|
||||||
@@ -262,6 +264,16 @@ describe("applyGraphicsOverrides", () => {
|
|||||||
expect(absent.iconAlpha).toBe(0.9);
|
expect(absent.iconAlpha).toBe(0.9);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("iconSize override sets structure.iconSize", () => {
|
||||||
|
expect(gen({ structure: { iconSize: 90 } }).structure.iconSize).toBe(90);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("iconSize absent → keeps render-settings.json default", () => {
|
||||||
|
const def = createRenderSettings().structure.iconSize;
|
||||||
|
expect(gen({ structure: {} }).structure.iconSize).toBe(def);
|
||||||
|
expect(gen({}).structure.iconSize).toBe(def);
|
||||||
|
});
|
||||||
|
|
||||||
test("classicNumbers=true → classic bitmap font", () => {
|
test("classicNumbers=true → classic bitmap font", () => {
|
||||||
expect(
|
expect(
|
||||||
gen({ structure: { classicNumbers: true } }).structureLevel.classicFont,
|
gen({ structure: { classicNumbers: true } }).structureLevel.classicFont,
|
||||||
|
|||||||
Reference in New Issue
Block a user