Files
aetherbound-guild/runtime/ui/shared/page_chrome.gd
T
wushenghua 5cf76a07b2 fix(m57-c): per-region readiness chrome and typography
Stop reusing one plaque for every region. The header is now a compartmented
band with dividers, icons and left-aligned values; the bottom is a carved dock
band with four independent command plates (icon above label, wider green
primary) instead of buttons inside a long frame; the warning stack is three
cards with severity icons; the summary is a parchment card with shape markers,
values and pip rows; and typography uses explicit tracking and embolden so
titles, values and captions read as different ranks. View contract names and
z-order are unchanged.
2026-09-13 15:25:55 +08:00

183 lines
7.0 KiB
GDScript

class_name ABGPageChrome
extends Control
## Per-region drawn chrome for first-play pages.
##
## One generic plaque was previously reused as the header, the command dock,
## the warning stack and the summary, which produced large empty frames with
## floating text. Each region now gets its own structure: a compartmented
## header band, a carved command dock with independent command plates, small
## warning cards and parchment cards. Everything scales cleanly at any size and
## text stays engine-rendered.
const PAPER := Color("#f0e0bd")
const PAPER_LIGHT := Color("#faf1d8")
const PAPER_SHADE := Color("#d9c096")
const WOOD := Color("#6f4a2e")
const WOOD_LIGHT := Color("#9a6b40")
const GOLD := Color("#c2a061")
const GOLD_LIGHT := Color("#eed9a4")
const INK := Color("#2f2417")
const INK_SOFT := Color("#6a5236")
const DOCK := Color("#1d3f4a")
const DOCK_LIGHT := Color("#2d5b67")
const DOCK_DARK := Color("#12272e")
const GREEN := Color("#57813f")
const GREEN_LIGHT := Color("#7aa85f")
const STONE := Color("#4a4f52")
const RED := Color("#a8503f")
const AMBER := Color("#b98a3c")
const TEAL := Color("#4f8a8f")
const SHADOW := Color(0.05, 0.06, 0.07, 0.34)
var variant := "card"
var params: Dictionary = {}
func configure(next_variant: String, next_params: Dictionary = {}) -> ABGPageChrome:
variant = next_variant
params = next_params
return self
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
resized.connect(queue_redraw)
queue_redraw()
func _draw() -> void:
if size.x < 2.0 or size.y < 2.0:
return
match variant:
"header_band":
_draw_header_band()
"dock_band":
_draw_dock_band()
"command_plate":
_draw_command_plate()
"card":
_draw_card()
"warning_icon":
_draw_warning_icon()
"pip":
_draw_pip()
_:
_draw_card()
func _draw_header_band() -> void:
var rect := Rect2(2.0, 3.0, size.x - 4.0, size.y - 6.0)
_round(Rect2(rect.position + Vector2(0.0, 3.0), rect.size), 7.0, SHADOW)
_round(rect, 7.0, PAPER, GOLD, 2.0)
_round(rect.grow(-3.0), 5.0, Color(PAPER_LIGHT, 0.45), Color(PAPER_SHADE, 0.85), 1.0)
var dividers: Array = params.get("dividers", [])
for value in dividers:
var x := rect.position.x + rect.size.x * float(value)
draw_line(Vector2(x, rect.position.y + 8.0), Vector2(x, rect.end.y - 8.0), Color(GOLD, 0.75), 1.5, true)
draw_line(Vector2(rect.position.x + 10.0, rect.position.y + 1.0), Vector2(rect.end.x - 10.0, rect.position.y + 1.0), Color(PAPER_LIGHT, 0.9), 1.5, true)
func _draw_dock_band() -> void:
var rect := Rect2(0.0, 0.0, size.x, size.y)
draw_rect(Rect2(0.0, 0.0, size.x, size.y * 0.55), DOCK_LIGHT)
draw_rect(Rect2(0.0, size.y * 0.55, size.x, size.y * 0.45), DOCK)
draw_rect(Rect2(0.0, 0.0, size.x, 2.5), Color(GOLD, 0.85))
draw_rect(Rect2(0.0, 2.5, size.x, 2.0), Color(DOCK_DARK, 0.6))
draw_rect(Rect2(0.0, size.y - 3.0, size.x, 3.0), Color(DOCK_DARK, 0.7))
draw_rect(Rect2(0.0, 0.0, size.x, 1.0), Color(GOLD_LIGHT, 0.9))
func _draw_command_plate() -> void:
var state := String(params.get("state", "secondary"))
var face := PAPER
var edge := GOLD
var inner := PAPER_LIGHT
var label_line := PAPER_SHADE
match state:
"primary":
face = GREEN
edge = GOLD_LIGHT
inner = GREEN_LIGHT
label_line = Color(0.12, 0.20, 0.10, 0.35)
"disabled":
face = STONE
edge = Color("#6d7074")
inner = Color("#5c6064")
label_line = Color(0.0, 0.0, 0.0, 0.28)
var rect := Rect2(2.0, 3.0, size.x - 4.0, size.y - 7.0)
_round(Rect2(rect.position + Vector2(0.0, 4.0), rect.size), 9.0, SHADOW)
_round(rect, 9.0, WOOD, Color(0.10, 0.07, 0.05, 0.55), 1.0)
var face_rect := rect.grow(-5.0)
_round(face_rect, 6.0, face, edge, 2.0)
_round(face_rect.grow(-3.0), 4.0, Color(inner, 0.35), Color(edge, 0.55), 1.0)
draw_line(Vector2(face_rect.position.x + 8.0, face_rect.position.y + 3.0), Vector2(face_rect.end.x - 8.0, face_rect.position.y + 3.0), Color(PAPER_LIGHT, 0.55) if state != "disabled" else Color(1, 1, 1, 0.12), 1.2, true)
draw_line(Vector2(face_rect.position.x + 12.0, face_rect.end.y - 4.0), Vector2(face_rect.end.x - 12.0, face_rect.end.y - 4.0), label_line, 2.0, true)
func _draw_card() -> void:
var rect := Rect2(3.0, 3.0, size.x - 6.0, size.y - 6.0)
_round(Rect2(rect.position + Vector2(0.0, 3.0), rect.size), 7.0, SHADOW)
_round(rect, 7.0, PAPER, GOLD, 2.0)
_round(rect.grow(-3.0), 5.0, Color(PAPER_LIGHT, 0.50), Color(PAPER_SHADE, 0.85), 1.0)
draw_line(Vector2(rect.position.x + 8.0, rect.position.y + 2.0), Vector2(rect.end.x - 8.0, rect.position.y + 2.0), Color(PAPER_LIGHT, 0.85), 1.2, true)
func _draw_warning_icon() -> void:
var severity := String(params.get("severity", "warning"))
var fill := RED if severity == "warning" else AMBER if severity == "notice" else TEAL
var rect := Rect2(1.0, 1.0, size.x - 2.0, size.y - 2.0)
_round(rect, 5.0, Color(0.08, 0.06, 0.05, 0.4))
_round(rect.grow(-1.0), 4.0, fill, GOLD_LIGHT, 1.5)
var center := rect.get_center()
var bar_w := maxf(2.0, rect.size.x * 0.11)
draw_rect(Rect2(center.x - bar_w * 0.5, rect.position.y + rect.size.y * 0.26, bar_w, rect.size.y * 0.36), Color(PAPER_LIGHT, 0.95))
draw_circle(Vector2(center.x, rect.position.y + rect.size.y * 0.74), bar_w * 0.62, Color(PAPER_LIGHT, 0.95))
func _draw_pip() -> void:
var shape := String(params.get("shape", "circle"))
var filled := bool(params.get("filled", false))
var rect := Rect2(1.5, 1.5, size.x - 3.0, size.y - 3.0)
var line := Color(GOLD, 0.95) if filled else Color(GOLD, 0.55)
match shape:
"shield":
var points := PackedVector2Array([
Vector2(rect.position.x + rect.size.x * 0.5, rect.position.y),
Vector2(rect.end.x, rect.position.y + rect.size.y * 0.28),
Vector2(rect.end.x - rect.size.x * 0.16, rect.end.y),
Vector2(rect.position.x + rect.size.x * 0.16, rect.end.y),
Vector2(rect.position.x, rect.position.y + rect.size.y * 0.28)
])
if filled:
draw_colored_polygon(points, Color(GOLD, 0.35))
draw_polyline(points + PackedVector2Array([points[0]]), line, 1.6, true)
"diamond":
var center := rect.get_center()
var radius := minf(rect.size.x, rect.size.y) * 0.5
var diamond := PackedVector2Array([center + Vector2(0, -radius), center + Vector2(radius, 0), center + Vector2(0, radius), center + Vector2(-radius, 0)])
if filled:
draw_colored_polygon(diamond, Color(GOLD, 0.35))
draw_polyline(diamond + PackedVector2Array([diamond[0]]), line, 1.6, true)
_:
var center := rect.get_center()
var radius := minf(rect.size.x, rect.size.y) * 0.42
if filled:
draw_circle(center, radius * 0.8, Color(GOLD, 0.35))
draw_arc(center, radius, 0.0, TAU, 22, line, 1.6, true)
if filled:
draw_circle(rect.get_center(), 1.4, Color(GOLD, 0.95))
func _round(rect: Rect2, radius: float, fill: Color, border := Color(0, 0, 0, 0), width := 0.0) -> void:
var style := StyleBoxFlat.new()
style.bg_color = fill
style.corner_radius_top_left = int(radius)
style.corner_radius_top_right = int(radius)
style.corner_radius_bottom_left = int(radius)
style.corner_radius_bottom_right = int(radius)
if width > 0.0:
style.border_color = border
style.set_border_width_all(int(width))
draw_style_box(style, rect)