Files
aetherbound-guild/runtime/ui/shared/page_chrome.gd
T

223 lines
8.8 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 PLATE_SECONDARY = preload("res://assets/ui_m57_chrome/command_plate_secondary.png")
const PLATE_PRIMARY = preload("res://assets/ui_m57_chrome/command_plate_primary.png")
const PLATE_DISABLED = preload("res://assets/ui_m57_chrome/command_plate_disabled.png")
const PANEL = preload("res://assets/ui_m57_chrome/panel_parchment.png")
const PLATE_UTILITY = preload("res://assets/ui_m57_chrome/command_plate_utility.png")
const DOCK_RAIL = preload("res://assets/ui_m57_chrome/dock_rail.png")
const BADGE_WARNING = preload("res://assets/ui_m57_chrome/badge_warning.png")
const BADGE_NOTICE = preload("res://assets/ui_m57_chrome/badge_notice.png")
const BADGE_INFO = preload("res://assets/ui_m57_chrome/badge_info.png")
const PLATE_EDGE_X := 0.062
const PLATE_EDGE_Y := 0.235
const PANEL_EDGE_X := 0.055
const PANEL_EDGE_Y := 0.15
const DOCK_EDGE_X := 0.022
const DOCK_EDGE_Y := 0.2
const UTILITY_EDGE_X := 0.115
const UTILITY_EDGE_Y := 0.15
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()
"utility_plate":
_nine_patch(PLATE_UTILITY, Rect2(0.0, 0.0, size.x, size.y), UTILITY_EDGE_X, UTILITY_EDGE_Y)
"card":
_draw_card()
"warning_icon":
_draw_warning_icon()
"pip":
_draw_pip()
"progress":
_draw_progress()
"selection_ring":
_draw_selection_ring()
_:
_draw_card()
func _draw_header_band() -> void:
var rect := Rect2(0.0, 0.0, size.x, size.y)
_nine_patch(PANEL, rect, PANEL_EDGE_X, PANEL_EDGE_Y)
var middle := rect.position.y + rect.size.y * 0.5
var half := rect.size.y * 0.30
var dividers: Array = params.get("dividers", [])
for value in dividers:
var x := rect.position.x + rect.size.x * float(value)
draw_line(Vector2(x, middle - half), Vector2(x, middle + half), Color(GOLD, 0.7), 1.5, true)
func _draw_dock_band() -> void:
_nine_patch(DOCK_RAIL, Rect2(0.0, 0.0, size.x, size.y), DOCK_EDGE_X, DOCK_EDGE_Y)
func _draw_command_plate() -> void:
var state := String(params.get("state", "secondary"))
var texture: Texture2D = PLATE_PRIMARY if state == "primary" else PLATE_DISABLED if state == "disabled" else PLATE_SECONDARY
_nine_patch(texture, Rect2(0.0, 0.0, size.x, size.y), PLATE_EDGE_X, PLATE_EDGE_Y)
func _nine_patch(texture: Texture2D, rect: Rect2, edge_x: float, edge_y: float, modulate := Color(1, 1, 1, 1)) -> void:
var source := texture.get_size()
var sx := [0.0, round(edge_x * source.x), source.x - round(edge_x * source.x), source.x]
var sy := [0.0, round(edge_y * source.y), source.y - round(edge_y * source.y), source.y]
var dx := [rect.position.x, rect.position.x + edge_x * rect.size.x, rect.end.x - edge_x * rect.size.x, rect.end.x]
var dy := [rect.position.y, rect.position.y + edge_y * rect.size.y, rect.end.y - edge_y * rect.size.y, rect.end.y]
for row in 3:
for column in 3:
var from := Rect2(sx[column], sy[row], sx[column + 1] - sx[column], sy[row + 1] - sy[row])
var to := Rect2(dx[column], dy[row], dx[column + 1] - dx[column], dy[row + 1] - dy[row])
if from.size.x < 1.0 or from.size.y < 1.0 or to.size.x < 1.0 or to.size.y < 1.0:
continue
draw_texture_rect_region(texture, to, from, modulate)
func _draw_card() -> void:
_nine_patch(PANEL, Rect2(0.0, 0.0, size.x, size.y), PANEL_EDGE_X, PANEL_EDGE_Y)
func _draw_warning_icon() -> void:
var severity := String(params.get("severity", "warning"))
var texture: Texture2D = BADGE_WARNING if severity == "warning" else BADGE_NOTICE if severity == "notice" else BADGE_INFO
var side := minf(size.x, size.y)
var rect := Rect2((size.x - side) * 0.5, (size.y - side) * 0.5, side, side)
draw_texture_rect(texture, rect, false)
var ink := Color("#f7efdb")
var center := rect.get_center()
if severity == "info":
draw_rect(Rect2(center.x - side * 0.055, center.y - side * 0.22, side * 0.11, side * 0.30), ink)
draw_circle(Vector2(center.x, center.y + side * 0.20), side * 0.062, ink)
else:
draw_rect(Rect2(center.x - side * 0.055, center.y - side * 0.24, side * 0.11, side * 0.32), ink)
draw_circle(Vector2(center.x, center.y + side * 0.22), side * 0.062, ink)
func set_progress(next_value: float) -> void:
params["value"] = clampf(next_value, 0.0, 1.0)
queue_redraw()
func _draw_progress() -> void:
# Shared carved track + value fill. Pages supply the value as engine data;
# no page draws its own progress bar or ships its own fill stylebox.
var value := clampf(float(params.get("value", 0.0)), 0.0, 1.0)
var track := Rect2(0.0, size.y * 0.16, size.x, size.y * 0.68)
var radius := maxf(3.0, track.size.y * 0.45)
_round(Rect2(track.position + Vector2(1.0, 1.5), track.size), radius, Color(INK, 0.22))
_round(track, radius, PAPER_SHADE, Color(INK_SOFT, 0.75), 1.0)
if value > 0.0:
var fill_width := maxf(radius * 1.8, track.size.x * value - 3.0)
var fill := Rect2(track.position + Vector2(1.5, 1.5), Vector2(fill_width, track.size.y - 3.0))
_round(fill, maxf(2.0, radius * 0.7), Color("#5dc9e9"), Color(GOLD, 0.55), 1.0)
func _draw_selection_ring() -> void:
# Selection highlight for portrait/member slots: a translucent gold field with
# a bright authored ring. Geometry stays with the page; the material is shared.
var active := bool(params.get("active", true))
var radius := float(params.get("radius", 10.0))
var rect := Rect2(1.5, 1.5, size.x - 3.0, size.y - 3.0)
_round(rect, radius, Color(GOLD_LIGHT, 0.16 if active else 0.06), Color(GOLD_LIGHT, 0.92 if active else 0.32), 2.0)
func _draw_pip() -> void:
var shape := String(params.get("shape", "circle"))
var filled := bool(params.get("filled", false))
var active := bool(params.get("active", 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.85) if active 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)