extends Node # Forgejo #228: the HUD is proportionally three times too big on a PC. # # The layout is authored in CANVAS units against a portrait phone. With # stretch "canvas_items"/"expand" and a 1920x1080 base, a 720x1280 phone # scales by 0.375 and so gets a canvas 1920x3413 units tall - while ANY # 16:9-or-wider screen lands on 1920x1080 units whatever its pixel count. # The canvas is three times shorter, so a 148-unit button goes from 4.3% of # the screen's height to 13.7% of it. Nothing collides; everything is just # enormous, and the thumb sticks in particular. # # Each HUD element's offsets are measured FROM ITS OWN ANCHOR, so scaling # them scales the element's size and its distance from the edge it hangs off # together. That is the whole trick: one multiplier per control, no layout # rules to re-derive, and anchoring keeps working untouched. # The canvas the HUD was drawn for. Not a magic number: 1280 / (720 / 1920). const AUTHORED_CANVAS_HEIGHT: float = 3413.0 # Below this the controls stop being worth pressing. A landscape phone gets # the same short canvas as a desktop, and there the sticks are still thumb # targets - so the shrink is bounded rather than purely proportional. const MIN_SCALE: float = 0.45 # Authored offsets and sizes, captured before anything is touched. # Re-reading them after a resize would measure a previous scaling and let the # HUD ratchet away to nothing - the same trap NeonPanel documents. var _authored: Dictionary = {} func _ready() -> void: var layer: Node = get_parent() for child in layer.get_children(): if child == self or not (child is Control): continue var c: Control = child # Full-bleed things - the modal menus, the breach overlay - hang off # every edge at once and have nothing to shrink. if is_equal_approx(c.anchor_left, 0.0) and is_equal_approx(c.anchor_right, 1.0) \ and is_equal_approx(c.anchor_top, 0.0) and is_equal_approx(c.anchor_bottom, 1.0): continue _authored[c] = [c.offset_left, c.offset_top, c.offset_right - c.offset_left, c.offset_bottom - c.offset_top] get_viewport().size_changed.connect(_apply) _apply() func hud_scale() -> float: var height: float = get_viewport().get_visible_rect().size.y if height <= 0.0: return 1.0 # Only ever shrinks. A canvas TALLER than the phone's is not a reason to # inflate the HUD over the game. return clampf(height / AUTHORED_CANVAS_HEIGHT, MIN_SCALE, 1.0) func _apply() -> void: var k: float = hud_scale() for control in _authored: if not is_instance_valid(control): continue var a: Array = _authored[control] var c: Control = control # Position moves toward the edge the element hangs off; the RECT keeps # its authored size and the node is scaled instead. # # Scaling the rect alone was wrong: half the HUD draws its own # contents at fixed canvas sizes - ResourceReadout lays its rows out # in code - so a shrunken rect kept full-size content inside it, and # the build button slid up underneath the resource list. Scaling the # node takes the drawn content with it. c.pivot_offset = Vector2.ZERO c.offset_left = float(a[0]) * k c.offset_top = float(a[1]) * k c.offset_right = c.offset_left + float(a[2]) c.offset_bottom = c.offset_top + float(a[3]) c.scale = Vector2(k, k)