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)