From e0edffe03f0a46399b89d9d68f094481beb37fde Mon Sep 17 00:00:00 2001 From: Kunal Siyag Date: Tue, 20 Oct 2026 20:59:07 +0530 Subject: [PATCH] feat(fp): crisp, near-square HUD look Sharpen the on-foot HUD toward a tactical-shooter look without changing behaviour. - hud_style: RADIUS (2) and RADIUS_PANEL (3) tokens, a CHIP_LINE hairline, display() (heavy tracked sans), caps_label(), caps_width() and stripe(). - Chips: square backing with a bottom hairline that takes the warn or fail colour. - Toasts get a 2 px left stripe in their kind colour; the incident banner is caps with a red stripe. - District caption: tracked caps in display(), cyan rule, caps mayor line. - Prompt, drive chip, speedometer, phone, place card, pause menu and map card use the new radii; the speedometer and phone title use display(). - The chat panel and ticker keep their gold look. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- docs/api/scripts.fp.ui.md | 15 ++++-- scripts/fp/drive/drive_hud.gd | 6 +-- scripts/fp/phone/phone_ui.gd | 7 +-- scripts/fp/ui/hud_caption.gd | 15 ++++-- scripts/fp/ui/hud_chips.gd | 36 +++++++++++--- scripts/fp/ui/hud_prompt.gd | 6 ++- scripts/fp/ui/hud_style.gd | 92 +++++++++++++++++++++++++++-------- scripts/fp/ui/hud_toasts.gd | 49 +++++++++++++------ scripts/fp/ui/map_screen.gd | 3 +- scripts/fp/ui/pause_menu.gd | 7 +-- scripts/fp/ui/place_card.gd | 4 +- 11 files changed, 177 insertions(+), 63 deletions(-) diff --git a/docs/api/scripts.fp.ui.md b/docs/api/scripts.fp.ui.md index f437a6ca..019864d7 100644 --- a/docs/api/scripts.fp.ui.md +++ b/docs/api/scripts.fp.ui.md @@ -416,22 +416,29 @@ Shared look of the on-foot HUD (slice B): colour tokens, fonts with tabular figu - `SHADOW = Color(0, 0, 0, 0.45)` - `BRAND_RE = "(?i)(Argon|DeepSeek|Kimi|Qwen|Grok|GMI|gemini|MiniMax|model|latency|cache|heuristic)"` - `TITLE_FONT_PATH = "res://fonts/lilita_one_regular.ttf"` +- `RADIUS = 2`: Corner radius of chips, toasts, prompts and buttons, and of panels: near-square, crisp edges. +- `RADIUS_PANEL = 3` +- `CHIP_LINE = Color(1, 1, 1, 0.16)`: The hairline under a chip; a chip in a warn or fail state tints it. ### Functions - `static func font() -> Font`: The UI font (the project's fallback sans) with tabular figures, so numbers do not jitter. - `static func bold() -> Font`: A heavier cut of font() for values and titles. - `static func caps() -> Font`: Tracked capitals for small headings ("SETTINGS", "CAMERA"). -- `static func title() -> Font`: The game's display face (pause title only). +- `static func title() -> Font`: The game's display face (pause and map titles only). +- `static func display() -> Font`: A heavy, tightly tracked cut for HUD headlines and big numbers (district caption, speedometer). - `static func own(n: Node) -> Node`: Marks n as created by slice B (meta OWNER_META), for the branding test and smoke. - `static func label(t: String, size_px: int, col: Color = TEXT, f: Font = null, outline: int = 3) -> Label`: A passive label: B's font, an outline and a soft shadow so it reads on sky and asphalt. - `static func chip_label(t: String, size_px: int, col: Color = TEXT, f: Font = null) -> Label`: A label on a chip's backing: a 2 px outline, no shadow pass. - `static func plain(t: String, size_px: int, col: Color = TEXT, f: Font = null) -> Label`: A label for text inside a solid panel (no outline needed). +- `static func caps_label(t: String, size_px: int, col: Color = TEXT, outline: int = 2) -> Label`: Tracked capitals drawn from t; the Label keeps t as its text (only the drawing is upper-cased). +- `static func caps_width(t: String, size_px: int) -> float`: Width of t drawn by caps_label at size_px. +- `static func stripe(sb: StyleBoxFlat, col: Color, w: int = 3) -> StyleBoxFlat`: Turns sb's border into a single w px accent stripe down its left edge. - `static func box(bg: Color, radius: int, h: int, v: int, border: Color = Color(0, 0, 0, 0), bw: int = 0) -> StyleBoxFlat`: A StyleBoxFlat with one corner radius and content margins (h, v). -- `static func chip_box(h: int = 11, v: int = 4) -> StyleBoxFlat`: The pill behind a passive indicator chip: at most CHIP_BG, fully rounded (own instance, so a chip can pulse its own backing). -- `static func panel(bg: Color = PANEL_BG, radius: int = 10, h: int = 14, v: int = 10, border: Color = PANEL_LINE) -> PanelContainer`: A PanelContainer with a rounded, optionally bordered background (restyled CityHud._panel). +- `static func chip_box(h: int = 11, v: int = 4) -> StyleBoxFlat`: The backing behind a passive indicator chip: at most CHIP_BG, near-square, with a hairline along its bottom edge (own instance, so a chip can pulse and tint its own backing). +- `static func panel(bg: Color = PANEL_BG, radius: int = RADIUS_PANEL, h: int = 14, v: int = 10, border: Color = PANEL_LINE) -> PanelContainer`: A PanelContainer with a crisp, optionally bordered background (restyled CityHud._panel). - `static func menu_button(t: String, size_px: int = 19) -> Button`: A menu button: flat text on idle, a light bar when focused or hovered. -- `static func pill_button(t: String, size_px: int = 15) -> Button`: A compact pill button (card actions, segmented choices). +- `static func pill_button(t: String, size_px: int = 15) -> Button`: A compact button (card actions, segmented choices). - `static func key_cap(k: String, size_px: int = 13) -> PanelContainer`: A key cap ("E", "M", "Esc") for prompts and hints. - `static func fmt(n: int) -> String`: Thousands separators: 1168 -> "1,168", -3222 -> "−3,222" (true minus sign). Restyled CityHud._fmt. - `static func money(n: int) -> String`: Compact money: "$950", "$3.2k", "$124k", "$1.2M"; a deficit stays exact so it reads at a glance ("−$3,222", true minus sign) until it passes 100k ("−$124k"). diff --git a/scripts/fp/drive/drive_hud.gd b/scripts/fp/drive/drive_hud.gd index de20c336..b30c47bb 100644 --- a/scripts/fp/drive/drive_hud.gd +++ b/scripts/fp/drive/drive_hud.gd @@ -24,7 +24,7 @@ func setup() -> void: _chip = PanelContainer.new() _chip.name = "DriveChip" _chip.mouse_filter = Control.MOUSE_FILTER_IGNORE - _chip.add_theme_stylebox_override("panel", S.box(Color(0.04, 0.05, 0.08, 0.6), 8, 10, 6)) + _chip.add_theme_stylebox_override("panel", S.box(Color(0.04, 0.05, 0.08, 0.6), S.RADIUS, 10, 6)) var h := HBoxContainer.new() h.mouse_filter = Control.MOUSE_FILTER_IGNORE h.add_theme_constant_override("separation", 9) @@ -41,7 +41,7 @@ func setup() -> void: func _build_speedo() -> void: - _speedo = S.panel(S.PANEL_BG, 10, 16, 8) + _speedo = S.panel(S.PANEL_BG, S.RADIUS_PANEL, 16, 8) _speedo.name = "Speedometer" var v := VBoxContainer.new() v.mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -51,7 +51,7 @@ func _build_speedo() -> void: row.mouse_filter = Control.MOUSE_FILTER_IGNORE row.add_theme_constant_override("separation", 8) v.add_child(row) - _speed = S.plain("0", 40, S.WHITE, S.title()) + _speed = S.plain("0", 40, S.WHITE, S.display()) _speed.name = "Speed" _speed.custom_minimum_size = Vector2(84, 0) _speed.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT diff --git a/scripts/fp/phone/phone_ui.gd b/scripts/fp/phone/phone_ui.gd index abe92f8d..154e4582 100644 --- a/scripts/fp/phone/phone_ui.gd +++ b/scripts/fp/phone/phone_ui.gd @@ -27,7 +27,7 @@ func setup() -> void: name = "BuildPhone" mouse_filter = Control.MOUSE_FILTER_IGNORE set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - _panel = S.panel(Color(0.03, 0.04, 0.07, 0.9), 22, 16, 14, Color(1, 1, 1, 0.14)) + _panel = S.panel(Color(0.03, 0.04, 0.07, 0.9), S.RADIUS_PANEL, 16, 14, Color(1, 1, 1, 0.14)) _panel.name = "Phone" _panel.custom_minimum_size = Vector2(WIDTH, 0) var v := VBoxContainer.new() @@ -44,8 +44,9 @@ func setup() -> void: _treasury = S.plain("", 15, S.GOOD, S.bold()) _treasury.name = "Treasury" top.add_child(_treasury) - _district = S.plain("", 18, S.WHITE, S.title()) + _district = S.plain("", 15, S.WHITE, S.display()) _district.name = "District" + _district.uppercase = true _district.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART v.add_child(_district) _mayor = S.plain("", 13, S.TEXT_DIM, S.font()) @@ -126,7 +127,7 @@ func show_state(st: Dictionary) -> void: var bg := Color(0.0, 0.88, 1.0, 0.16) if on else Color(1, 1, 1, 0.05) var line := S.ACCENT if on else Color(0, 0, 0, 0) (_tools[id] as PanelContainer).add_theme_stylebox_override( - "panel", S.box(bg, 8, 8, 5, line, 1 if on else 0) + "panel", S.box(bg, S.RADIUS, 8, 5, line, 1 if on else 0) ) _building.text = str(st.get("building", "")) _building.visible = not _building.text.is_empty() diff --git a/scripts/fp/ui/hud_caption.gd b/scripts/fp/ui/hud_caption.gd index d7845de9..7de5f195 100644 --- a/scripts/fp/ui/hud_caption.gd +++ b/scripts/fp/ui/hud_caption.gd @@ -48,8 +48,9 @@ func setup(p_ctx: FpContext) -> void: _box.offset_top = -SAFE - 90.0 _box.offset_bottom = -SAFE add_child(_box) - _title = S.label("", 34, S.WHITE, S.title(), 0) + _title = S.label("", 30, S.WHITE, S.display(), 0) _title.name = "District" + _title.uppercase = true _title.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT _title.add_theme_color_override("font_shadow_color", Color(0, 0, 0, 0.5)) _title.add_theme_constant_override("shadow_offset_y", 2) @@ -57,11 +58,11 @@ func setup(p_ctx: FpContext) -> void: _box.add_child(_title) _rule = ColorRect.new() _rule.mouse_filter = Control.MOUSE_FILTER_IGNORE - _rule.color = Color(1, 1, 1, 0.55) + _rule.color = S.ACCENT _rule.custom_minimum_size = Vector2(56, 2) _rule.size_flags_horizontal = Control.SIZE_SHRINK_END _box.add_child(_rule) - _sub = S.label("", 16, S.TEXT, S.font(), 3) + _sub = S.caps_label("", 13, S.TEXT, 3) _sub.name = "Mayor" _sub.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT _box.add_child(_sub) @@ -70,7 +71,9 @@ func setup(p_ctx: FpContext) -> void: if ctx.bus != null: ctx.bus.player_tile_changed.connect(func(_x: int, _y: int) -> void: check()) ctx.bus.world_ready.connect(arrive) - ctx.bus.conversation_changed.connect(func(_id: String, on: bool) -> void: _in_conversation = on) + ctx.bus.conversation_changed.connect( + func(_id: String, on: bool) -> void: _in_conversation = on + ) arrive.call_deferred() @@ -127,7 +130,9 @@ func _process(delta: float) -> void: _age = INF _box.visible = false return - var blocked := ctx.is_modal() or _in_conversation or (ctx.player != null and ctx.player.photo_mode) + var blocked := ( + ctx.is_modal() or _in_conversation or (ctx.player != null and ctx.player.photo_mode) + ) var k_in := clampf(_age / FADE_IN, 0.0, 1.0) var k_out := clampf((life - _age) / FADE_OUT, 0.0, 1.0) var ease_in := 1.0 - (1.0 - k_in) * (1.0 - k_in) * (1.0 - k_in) diff --git a/scripts/fp/ui/hud_chips.gd b/scripts/fp/ui/hud_chips.gd index ef1e7d18..a161cbed 100644 --- a/scripts/fp/ui/hud_chips.gd +++ b/scripts/fp/ui/hud_chips.gd @@ -19,7 +19,8 @@ const IDS := ["pop", "mood", "money", "power", "water", "time", "alerts", "sessi const STATE_COLORS := {"ok": S.TEXT, "good": S.GOOD, "warn": S.AMBER, "fail": S.RED} var ctx: FpContext -## id -> {"panel": PanelContainer, "box": StyleBoxFlat, "icon": HudIcon, "value": Label, "state": String, "pulse": float} +## id -> {"panel": PanelContainer, "box": StyleBoxFlat, "icon": HudIcon, "value": Label, +## "state": String, "pulse": float, "line": Color} var _chips: Dictionary = {} var _day: Label var _speed: Label @@ -85,13 +86,25 @@ func refresh() -> void: var st := sim.stats _put("pop", S.fmt(int(st.get("population", 0))), "ok") var mood := mood_value() - var mood_state := "good" if mood >= 6.5 else ("ok" if mood >= 4.5 else ("warn" if mood >= 3.0 else "fail")) + var mood_state := ( + "good" if mood >= 6.5 else ("ok" if mood >= 4.5 else ("warn" if mood >= 3.0 else "fail")) + ) _put("mood", "%.1f" % mood, mood_state) (_chips["mood"]["icon"] as HudIcon).mood = clampf(mood / 10.0, 0.0, 1.0) var funds := sim.total_funds() _put("money", S.money(funds), "warn" if funds < 0 else "ok") - _util("power", float(st.get("power_need", 0.0)), float(st.get("power_cap", 0.0)), int(st.get("unpowered", 0))) - _util("water", float(st.get("water_need", 0.0)), float(st.get("water_cap", 0.0)), int(st.get("unwatered", 0))) + _util( + "power", + float(st.get("power_need", 0.0)), + float(st.get("power_cap", 0.0)), + int(st.get("unpowered", 0)) + ) + _util( + "water", + float(st.get("water_need", 0.0)), + float(st.get("water_cap", 0.0)), + int(st.get("unwatered", 0)) + ) _refresh_clock() var n := sim.events.size() var alerts: Dictionary = _chips["alerts"] @@ -150,7 +163,7 @@ func _process(delta: float) -> void: var k := sin(p / PULSE * PI) var sb: StyleBoxFlat = c["box"] sb.bg_color = S.CHIP_BG.lerp(Color(0.12, 0.14, 0.2, 0.55), k) - sb.border_color = Color(1, 1, 1, 0.22 * k) + sb.border_color = (c["line"] as Color).lerp(Color(1, 1, 1, 0.5), k) if _blink > 0.0: _blink = maxf(0.0, _blink - delta) var a := 1.0 - 0.75 * absf(sin((BLINK - _blink) / BLINK * TAU)) @@ -202,6 +215,9 @@ func _put(id: String, value: String, state: String, pulse: bool = true) -> void: # "good" tints only the icon: values stay white unless something needs attention. l.add_theme_color_override("font_color", S.TEXT if state == "good" else col) (c["icon"] as HudIcon).color = col if state != "ok" else S.TEXT + # A chip that needs attention carries its colour on the hairline too. + c["line"] = Color(col, 0.9) if state in ["warn", "fail"] else S.CHIP_LINE + (c["box"] as StyleBoxFlat).border_color = c["line"] func _group(row: HBoxContainer, align: BoxContainer.AlignmentMode, expand: bool) -> HBoxContainer: @@ -236,7 +252,15 @@ func _chip(parent: Control, id: String, icon: String) -> Dictionary: v.vertical_alignment = VERTICAL_ALIGNMENT_CENTER h.add_child(v) parent.add_child(p) - var c := {"panel": p, "box": sb, "icon": ic, "value": v, "state": "", "pulse": 0.0} + var c := { + "panel": p, + "box": sb, + "icon": ic, + "value": v, + "state": "", + "pulse": 0.0, + "line": S.CHIP_LINE + } _chips[id] = c return c diff --git a/scripts/fp/ui/hud_prompt.gd b/scripts/fp/ui/hud_prompt.gd index 39e872ad..39b71e57 100644 --- a/scripts/fp/ui/hud_prompt.gd +++ b/scripts/fp/ui/hud_prompt.gd @@ -49,7 +49,7 @@ func setup(p_ctx: FpContext) -> void: _chip = PanelContainer.new() _chip.name = "Chip" _chip.mouse_filter = Control.MOUSE_FILTER_IGNORE - _chip.add_theme_stylebox_override("panel", S.box(Color(0.04, 0.05, 0.08, 0.55), 8, 8, 5)) + _chip.add_theme_stylebox_override("panel", S.box(Color(0.04, 0.05, 0.08, 0.6), S.RADIUS, 8, 5)) var h := HBoxContainer.new() h.mouse_filter = Control.MOUSE_FILTER_IGNORE h.add_theme_constant_override("separation", 9) @@ -121,7 +121,9 @@ func _process(delta: float) -> void: _mode = mode _snap = true var first := mode == "first" - var blocked := ctx.is_modal() or _in_conversation or (ctx.player != null and ctx.player.photo_mode) + var blocked := ( + ctx.is_modal() or _in_conversation or (ctx.player != null and ctx.player.photo_mode) + ) var view := size if size.x > 0.0 and size.y > 0.0 else get_viewport_rect().size var centre := (view * 0.5).round() # Crosshair: first person only. diff --git a/scripts/fp/ui/hud_style.gd b/scripts/fp/ui/hud_style.gd index 88aa84b7..df2aed42 100644 --- a/scripts/fp/ui/hud_style.gd +++ b/scripts/fp/ui/hud_style.gd @@ -26,10 +26,16 @@ const OUTLINE := Color(0.01, 0.02, 0.04, 0.62) const SHADOW := Color(0, 0, 0, 0.45) const BRAND_RE := "(?i)(Argon|DeepSeek|Kimi|Qwen|Grok|GMI|gemini|MiniMax|model|latency|cache|heuristic)" const TITLE_FONT_PATH := "res://fonts/lilita_one_regular.ttf" +## Corner radius of chips, toasts, prompts and buttons, and of panels: near-square, crisp edges. +const RADIUS := 2 +const RADIUS_PANEL := 3 +## The hairline under a chip; a chip in a warn or fail state tints it. +const CHIP_LINE := Color(1, 1, 1, 0.16) static var _font: FontVariation static var _bold: FontVariation static var _caps: FontVariation +static var _display: FontVariation static var _title: Font @@ -62,13 +68,24 @@ static func caps() -> Font: return _caps -## The game's display face (pause title only). +## The game's display face (pause and map titles only). static func title() -> Font: if _title == null: _title = load(TITLE_FONT_PATH) if ResourceLoader.exists(TITLE_FONT_PATH) else bold() return _title +## A heavy, tightly tracked cut for HUD headlines and big numbers (district caption, speedometer). +static func display() -> Font: + if _display == null: + _display = FontVariation.new() + _display.base_font = ThemeDB.fallback_font + _display.opentype_features = {_tag("tnum"): 1, _tag("lnum"): 1} + _display.variation_embolden = 0.9 + _display.spacing_glyph = 1 + return _display + + ## Marks n as created by slice B (meta OWNER_META), for the branding test and smoke. static func own(n: Node) -> Node: n.set_meta(OWNER_META, OWNER) @@ -76,7 +93,9 @@ static func own(n: Node) -> Node: ## A passive label: B's font, an outline and a soft shadow so it reads on sky and asphalt. -static func label(t: String, size_px: int, col: Color = TEXT, f: Font = null, outline: int = 3) -> Label: +static func label( + t: String, size_px: int, col: Color = TEXT, f: Font = null, outline: int = 3 +) -> Label: var l := Label.new() l.text = t l.mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -107,8 +126,30 @@ static func plain(t: String, size_px: int, col: Color = TEXT, f: Font = null) -> return l +## Tracked capitals drawn from t; the Label keeps t as its text (only the drawing is upper-cased). +static func caps_label(t: String, size_px: int, col: Color = TEXT, outline: int = 2) -> Label: + var l := label(t, size_px, col, caps(), outline) + l.uppercase = true + return l + + +## Width of t drawn by caps_label at size_px. +static func caps_width(t: String, size_px: int) -> float: + return caps().get_string_size(t.to_upper(), HORIZONTAL_ALIGNMENT_LEFT, -1, size_px).x + + +## Turns sb's border into a single w px accent stripe down its left edge. +static func stripe(sb: StyleBoxFlat, col: Color, w: int = 3) -> StyleBoxFlat: + sb.border_color = col + sb.set_border_width_all(0) + sb.border_width_left = w + return sb + + ## A StyleBoxFlat with one corner radius and content margins (h, v). -static func box(bg: Color, radius: int, h: int, v: int, border: Color = Color(0, 0, 0, 0), bw: int = 0) -> StyleBoxFlat: +static func box( + bg: Color, radius: int, h: int, v: int, border: Color = Color(0, 0, 0, 0), bw: int = 0 +) -> StyleBoxFlat: var sb := StyleBoxFlat.new() sb.bg_color = bg sb.set_corner_radius_all(radius) @@ -123,16 +164,23 @@ static func box(bg: Color, radius: int, h: int, v: int, border: Color = Color(0, return sb -## The pill behind a passive indicator chip: at most CHIP_BG, fully rounded (own instance, so a -## chip can pulse its own backing). +## The backing behind a passive indicator chip: at most CHIP_BG, near-square, with a hairline +## along its bottom edge (own instance, so a chip can pulse and tint its own backing). static func chip_box(h: int = 11, v: int = 4) -> StyleBoxFlat: - var sb := box(CHIP_BG, 15, h, v) - sb.corner_detail = 10 + var sb := box(CHIP_BG, RADIUS, h, v) + sb.border_color = CHIP_LINE + sb.border_width_bottom = 1 return sb -## A PanelContainer with a rounded, optionally bordered background (restyled CityHud._panel). -static func panel(bg: Color = PANEL_BG, radius: int = 10, h: int = 14, v: int = 10, border: Color = PANEL_LINE) -> PanelContainer: +## A PanelContainer with a crisp, optionally bordered background (restyled CityHud._panel). +static func panel( + bg: Color = PANEL_BG, + radius: int = RADIUS_PANEL, + h: int = 14, + v: int = 10, + border: Color = PANEL_LINE +) -> PanelContainer: var p := PanelContainer.new() p.mouse_filter = Control.MOUSE_FILTER_IGNORE var sb := box(bg, radius, h, v, border, 1 if border.a > 0.0 else 0) @@ -156,9 +204,9 @@ static func menu_button(t: String, size_px: int = 19) -> Button: b.add_theme_color_override("font_focus_color", Color(0.04, 0.05, 0.08)) b.add_theme_color_override("font_pressed_color", Color(0.04, 0.05, 0.08)) b.add_theme_color_override("font_hover_pressed_color", Color(0.04, 0.05, 0.08)) - var idle := box(Color(1, 1, 1, 0.0), 6, 16, 9) - var hover := box(Color(1, 1, 1, 0.1), 6, 16, 9) - var focus := box(Color(1, 1, 1, 0.92), 6, 16, 9) + var idle := box(Color(1, 1, 1, 0.0), RADIUS, 16, 9) + var hover := box(Color(1, 1, 1, 0.1), RADIUS, 16, 9) + var focus := box(Color(1, 1, 1, 0.92), RADIUS, 16, 9) b.add_theme_stylebox_override("normal", idle) b.add_theme_stylebox_override("hover", hover) b.add_theme_stylebox_override("pressed", focus) @@ -168,7 +216,7 @@ static func menu_button(t: String, size_px: int = 19) -> Button: return b -## A compact pill button (card actions, segmented choices). +## A compact button (card actions, segmented choices). static func pill_button(t: String, size_px: int = 15) -> Button: var b := Button.new() b.text = t @@ -181,11 +229,17 @@ static func pill_button(t: String, size_px: int = 15) -> Button: b.add_theme_color_override("font_pressed_color", Color(0.04, 0.05, 0.08)) b.add_theme_color_override("font_hover_pressed_color", Color(0.04, 0.05, 0.08)) b.add_theme_color_override("font_focus_color", WHITE) - b.add_theme_stylebox_override("normal", box(Color(1, 1, 1, 0.06), 6, 12, 6, Color(1, 1, 1, 0.12), 1)) - b.add_theme_stylebox_override("hover", box(Color(1, 1, 1, 0.14), 6, 12, 6, Color(1, 1, 1, 0.2), 1)) - b.add_theme_stylebox_override("pressed", box(Color(1, 1, 1, 0.92), 6, 12, 6)) - b.add_theme_stylebox_override("hover_pressed", box(Color(1, 1, 1, 0.92), 6, 12, 6)) - b.add_theme_stylebox_override("focus", box(Color(0, 0, 0, 0), 6, 12, 6, Color(1, 1, 1, 0.85), 2)) + b.add_theme_stylebox_override( + "normal", box(Color(1, 1, 1, 0.06), RADIUS, 12, 6, Color(1, 1, 1, 0.12), 1) + ) + b.add_theme_stylebox_override( + "hover", box(Color(1, 1, 1, 0.14), RADIUS, 12, 6, Color(1, 1, 1, 0.2), 1) + ) + b.add_theme_stylebox_override("pressed", box(Color(1, 1, 1, 0.92), RADIUS, 12, 6)) + b.add_theme_stylebox_override("hover_pressed", box(Color(1, 1, 1, 0.92), RADIUS, 12, 6)) + b.add_theme_stylebox_override( + "focus", box(Color(0, 0, 0, 0), RADIUS, 12, 6, Color(1, 1, 1, 0.85), 2) + ) own(b) return b @@ -194,7 +248,7 @@ static func pill_button(t: String, size_px: int = 15) -> Button: static func key_cap(k: String, size_px: int = 13) -> PanelContainer: var p := PanelContainer.new() p.mouse_filter = Control.MOUSE_FILTER_IGNORE - var sb := box(Color(1, 1, 1, 0.92), 4, 6, 1) + var sb := box(Color(1, 1, 1, 0.92), RADIUS, 6, 1) sb.content_margin_left = 6 if k.length() == 1 else 7 sb.content_margin_right = sb.content_margin_left p.add_theme_stylebox_override("panel", sb) diff --git a/scripts/fp/ui/hud_toasts.gd b/scripts/fp/ui/hud_toasts.gd index ffdb7846..4853a32e 100644 --- a/scripts/fp/ui/hud_toasts.gd +++ b/scripts/fp/ui/hud_toasts.gd @@ -20,8 +20,11 @@ const BANNER_BLINK := 0.8 ## The slot starts at y 76; the compass and its distance labels end at y 106. const TOP := 34.0 const KIND_STYLE := { - "info": ["dot", S.TEXT], "success": ["check", S.GOOD], "alert": ["alert", S.AMBER], - "system": ["info", S.TEXT_DIM], "chat": ["dot", S.TEXT_DIM], + "info": ["dot", S.TEXT], + "success": ["check", S.GOOD], + "alert": ["alert", S.AMBER], + "system": ["info", S.TEXT_DIM], + "chat": ["dot", S.TEXT_DIM], } ## Each: {"panel": PanelContainer, "label": Label, "text": String, "count": int, "age": float, "life": float, "y": float} @@ -40,12 +43,7 @@ func setup() -> void: _banner = PanelContainer.new() _banner.name = "Banner" _banner.mouse_filter = Control.MOUSE_FILTER_IGNORE - var sb := S.box(Color(0.32, 0.05, 0.06, 0.62), 8, 14, 7, Color(1.0, 0.35, 0.35, 0.55), 1) - sb.border_width_left = 4 - sb.border_width_top = 0 - sb.border_width_right = 0 - sb.border_width_bottom = 0 - sb.border_color = S.RED + var sb := S.stripe(S.box(Color(0.3, 0.04, 0.05, 0.72), S.RADIUS, 14, 7), S.RED, 3) _banner.add_theme_stylebox_override("panel", sb) var h := HBoxContainer.new() h.mouse_filter = Control.MOUSE_FILTER_IGNORE @@ -54,7 +52,7 @@ func setup() -> void: var ic := HudIcon.new("alert", S.RED, 18.0) ic.size_flags_vertical = Control.SIZE_SHRINK_CENTER h.add_child(ic) - _banner_label = S.label("", 17, S.WHITE, S.bold(), 2) + _banner_label = S.caps_label("", 15, S.WHITE, 2) _banner_label.name = "BannerText" _banner_label.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS h.add_child(_banner_label) @@ -75,7 +73,10 @@ func show_toast(text: String, kind: String = "info") -> void: last["life"] = LIFE var ll: Label = last["label"] ll.text = "%s ×%d" % [t, int(last["count"])] - ll.custom_minimum_size.x = minf(ceilf(S.font().get_string_size(ll.text, HORIZONTAL_ALIGNMENT_LEFT, -1, 16).x) + 2.0, MAX_TEXT_W) + ll.custom_minimum_size.x = minf( + ceilf(S.font().get_string_size(ll.text, HORIZONTAL_ALIGNMENT_LEFT, -1, 16).x) + 2.0, + MAX_TEXT_W + ) (last["panel"] as Control).reset_size() return while _items.size() >= MAX_TOASTS: @@ -85,7 +86,8 @@ func show_toast(text: String, kind: String = "info") -> void: var p := PanelContainer.new() p.name = "Toast" p.mouse_filter = Control.MOUSE_FILTER_IGNORE - p.add_theme_stylebox_override("panel", S.box(Color(0.04, 0.05, 0.08, 0.5), 8, 14, 6)) + var sb := S.box(Color(0.04, 0.05, 0.08, 0.58), S.RADIUS, 12, 6) + p.add_theme_stylebox_override("panel", S.stripe(sb, Color(style[1], 0.9), 2)) var h := HBoxContainer.new() h.mouse_filter = Control.MOUSE_FILTER_IGNORE h.add_theme_constant_override("separation", 9) @@ -98,18 +100,31 @@ func show_toast(text: String, kind: String = "info") -> void: l.autowrap_mode = TextServer.AUTOWRAP_OFF # An ellipsis label has no minimum width of its own: size it to the text, capped. l.text_overrun_behavior = TextServer.OVERRUN_TRIM_ELLIPSIS - l.custom_minimum_size.x = minf(ceilf(S.font().get_string_size(t, HORIZONTAL_ALIGNMENT_LEFT, -1, 16).x) + 2.0, MAX_TEXT_W) + l.custom_minimum_size.x = minf( + ceilf(S.font().get_string_size(t, HORIZONTAL_ALIGNMENT_LEFT, -1, 16).x) + 2.0, MAX_TEXT_W + ) h.add_child(l) add_child(p) p.reset_size() p.modulate.a = 0.0 - _items.append({"panel": p, "label": l, "text": t, "count": 1, "age": 0.0, "life": LIFE, "y": _stack_end() - SLIDE_PX}) + _items.append( + { + "panel": p, + "label": l, + "text": t, + "count": 1, + "age": 0.0, + "life": LIFE, + "y": _stack_end() - SLIDE_PX + } + ) ## Shows the red alert banner (replacing any current one); it blinks twice, then holds. func show_banner(text: String) -> void: _banner_label.text = D.debrand(text) - _banner_label.custom_minimum_size.x = minf(ceilf(S.bold().get_string_size(_banner_label.text, HORIZONTAL_ALIGNMENT_LEFT, -1, 17).x) + 2.0, MAX_TEXT_W) + var w := ceilf(S.caps_width(_banner_label.text, 15)) + 2.0 + _banner_label.custom_minimum_size.x = minf(w, MAX_TEXT_W) _banner.reset_size() _banner.visible = true _banner_t = BANNER_LIFE @@ -147,7 +162,11 @@ func _process(delta: float) -> void: _banner.visible = false else: var a := clampf(_banner_age / FADE, 0.0, 1.0) * clampf(_banner_t / FADE, 0.0, 1.0) - if _banner_age < BANNER_LIFE and _banner_age > FADE and _banner_age < FADE + BANNER_BLINK: + if ( + _banner_age < BANNER_LIFE + and _banner_age > FADE + and _banner_age < FADE + BANNER_BLINK + ): a *= 1.0 - 0.7 * absf(sin((_banner_age - FADE) / BANNER_BLINK * TAU)) _banner.modulate.a = a _banner.position = Vector2(roundf((size.x - _banner.size.x) * 0.5), y) diff --git a/scripts/fp/ui/map_screen.gd b/scripts/fp/ui/map_screen.gd index 38643074..cc38f612 100644 --- a/scripts/fp/ui/map_screen.gd +++ b/scripts/fp/ui/map_screen.gd @@ -648,7 +648,8 @@ func _draw_hover(o: Control, view: Vector2) -> void: at.y = _hover_pos.y - 16.0 - sz.y at = at.clamp(Vector2(12, 12), view - sz - Vector2(12, 12)).round() if _card_box == null: - _card_box = S.box(Color(0.045, 0.055, 0.085, 0.9), 8, 12, 8, Color(1, 1, 1, 0.1), 1) + var line := Color(1, 1, 1, 0.1) + _card_box = S.box(Color(0.045, 0.055, 0.085, 0.9), S.RADIUS_PANEL, 12, 8, line, 1) _card_box.shadow_color = Color(0, 0, 0, 0.35) _card_box.shadow_size = 8 o.draw_style_box(_card_box, Rect2(at, sz)) diff --git a/scripts/fp/ui/pause_menu.gd b/scripts/fp/ui/pause_menu.gd index a22c5bd8..d2003533 100644 --- a/scripts/fp/ui/pause_menu.gd +++ b/scripts/fp/ui/pause_menu.gd @@ -285,7 +285,7 @@ func _build_left() -> void: func _build_legend() -> void: - var p := S.panel(Color(0.055, 0.07, 0.11, 0.55), 12, 22, 18) + var p := S.panel(Color(0.055, 0.07, 0.11, 0.55), S.RADIUS_PANEL, 22, 18) p.name = "Legend" _place_right(p, 560.0) add_child(p) @@ -331,7 +331,7 @@ func _build_legend() -> void: func _build_settings() -> void: - var p := S.panel(S.PANEL_BG, 12, 24, 20) + var p := S.panel(S.PANEL_BG, S.RADIUS_PANEL, 24, 20) p.name = "Settings" p.mouse_filter = Control.MOUSE_FILTER_STOP _place_right(p, PANEL_W) @@ -564,7 +564,8 @@ func _style_slider(s: HSlider) -> void: s.add_theme_stylebox_override("grabber_area_highlight", fill_hi) s.add_theme_icon_override("grabber", _grabber) s.add_theme_icon_override("grabber_highlight", _grabber) - s.add_theme_stylebox_override("focus", S.box(Color(0, 0, 0, 0), 6, 0, 0, Color(1, 1, 1, 0.5), 1)) + var focus := S.box(Color(0, 0, 0, 0), S.RADIUS, 0, 0, Color(1, 1, 1, 0.5), 1) + s.add_theme_stylebox_override("focus", focus) static func _same(a: Variant, b: Variant) -> bool: diff --git a/scripts/fp/ui/place_card.gd b/scripts/fp/ui/place_card.gd index 853ce6ed..1e7dc0bb 100644 --- a/scripts/fp/ui/place_card.gd +++ b/scripts/fp/ui/place_card.gd @@ -36,7 +36,7 @@ func setup(p_ctx: FpContext, p_hud: Node) -> void: set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) mouse_filter = Control.MOUSE_FILTER_IGNORE visible = false - _panel = S.panel(S.PANEL_BG, 12, 22, 18) + _panel = S.panel(S.PANEL_BG, S.RADIUS_PANEL, 22, 18) _panel.name = "Card" _panel.mouse_filter = Control.MOUSE_FILTER_STOP _panel.anchor_left = 1.0 @@ -276,7 +276,7 @@ func _section(head: String, names: PackedStringArray) -> void: func _pill(icon: String, t: String, col: Color) -> PanelContainer: var p := PanelContainer.new() p.mouse_filter = Control.MOUSE_FILTER_IGNORE - p.add_theme_stylebox_override("panel", S.box(Color(col.r, col.g, col.b, 0.12), 6, 8, 3)) + p.add_theme_stylebox_override("panel", S.box(Color(col.r, col.g, col.b, 0.12), S.RADIUS, 8, 3)) var h := HBoxContainer.new() h.mouse_filter = Control.MOUSE_FILTER_IGNORE h.add_theme_constant_override("separation", 5)