Files
settled-reach/client/ui/checklist_overlay.gd
T
2026-04-05 11:09:10 +02:00

156 lines
4.2 KiB
GDScript

extends Control
## #503: Auto-checklist HUD overlay — shows condition progress in Gauntlet mode.
## Renders below the GauntletHUD timer. Each condition shows a check/dash + description.
## Only visible in gauntlet_mode. Latched conditions stay checked.
##
## Spec ref: D-030 (testability), #503, Sprint 10 Completion Proof.
const ChecklistEvaluator = preload("res://scripts/checklist/checklist_evaluator.gd")
const BG_COLOR := Color(0.05, 0.05, 0.08, 0.45)
const MET_COLOR := Color("#6bc9a6") # Friendly green — condition met
const UNMET_COLOR := Color("#8890a0") # Dim grey — condition pending
const HEADER_COLOR := Color("#c8d0e0") # Insert text color — header/summary
const COMPLETE_COLOR := Color("#e8c547") # Amber — all conditions met
const FONT_SIZE := 11
const LINE_HEIGHT := 16
const PADDING := Vector2(8, 6)
const MAX_DESC_CHARS := 52 # Truncate long descriptions
var _evaluator = null # ChecklistEvaluator instance
var _last_room_id: Variant = null
var _cached_font: Font = null # Cached to avoid per-frame theme lookup
func _ready() -> void:
mouse_filter = Control.MOUSE_FILTER_IGNORE
visible = false
_evaluator = ChecklistEvaluator.new()
_cached_font = get_theme_default_font()
func update_from_state() -> void:
if not GameState.gauntlet_mode:
if visible:
visible = false
return
if not visible:
visible = true
var room_id: Variant = GameState.room_id
if room_id == null:
if _evaluator.is_loaded():
_evaluator.reset()
_last_room_id = null
queue_redraw()
return
# Load checklist on room change
if room_id != _last_room_id:
_evaluator.load_room(str(room_id))
_last_room_id = room_id
# Evaluate conditions against current snapshot
_evaluator.evaluate()
queue_redraw()
func _draw() -> void:
if _evaluator == null or not _evaluator.is_loaded():
return
var font: Font = _cached_font if _cached_font else get_theme_default_font()
var results: Array = _evaluator.get_results()
if results.is_empty():
return
var met_count: int = _evaluator.get_met_count()
var total_count: int = _evaluator.get_total_count()
var all_complete: bool = _evaluator.is_complete()
# Header line: "CHECK: 5/8"
var header_text := "CHECK: %d/%d" % [met_count, total_count]
var header_color: Color = COMPLETE_COLOR if all_complete else HEADER_COLOR
# Calculate box height: header + one line per condition + padding
var line_count: int = 1 + results.size()
var box_height: float = PADDING.y * 2 + line_count * LINE_HEIGHT
# Calculate box width from longest line
var max_width: float = (
font.get_string_size(header_text, HORIZONTAL_ALIGNMENT_LEFT, -1, FONT_SIZE).x
)
for r in results:
var desc: String = r.get("description", r.get("id", ""))
if desc.length() > MAX_DESC_CHARS:
desc = desc.substr(0, MAX_DESC_CHARS - 1) + "..."
var prefix: String = "[x] " if r.get("met", false) else "[ ] "
var line_width: float = (
font.get_string_size(prefix + desc, HORIZONTAL_ALIGNMENT_LEFT, -1, FONT_SIZE).x
)
if line_width > max_width:
max_width = line_width
var box_width: float = max_width + PADDING.x * 2
# Background
draw_rect(Rect2(Vector2.ZERO, Vector2(box_width, box_height)), BG_COLOR)
# Header
var y: float = PADDING.y + FONT_SIZE
draw_string(
font,
Vector2(PADDING.x, y),
header_text,
HORIZONTAL_ALIGNMENT_LEFT,
-1,
FONT_SIZE,
header_color
)
# Condition lines
for r in results:
y += LINE_HEIGHT
var is_met: bool = r.get("met", false)
var prefix: String = "[x] " if is_met else "[ ] "
var desc: String = r.get("description", r.get("id", ""))
if desc.length() > MAX_DESC_CHARS:
desc = desc.substr(0, MAX_DESC_CHARS - 1) + "..."
var color: Color = MET_COLOR if is_met else UNMET_COLOR
draw_string(
font,
Vector2(PADDING.x, y),
prefix + desc,
HORIZONTAL_ALIGNMENT_LEFT,
-1,
FONT_SIZE,
color
)
# -- Public API ---------------------------------------------------------------
func get_evaluator():
return _evaluator
func get_met_count() -> int:
if _evaluator == null:
return 0
return _evaluator.get_met_count()
func get_total_count() -> int:
if _evaluator == null:
return 0
return _evaluator.get_total_count()
func is_complete() -> bool:
if _evaluator == null:
return false
return _evaluator.is_complete()