HUD Anchors

a game HUD pinned to screen corners that scales with the window.

▶ Run in browser

Tags: ui hud anchors layout responsive

A game-style heads-up display whose pieces are anchored to the viewport edges, not placed at absolute coordinates: a health bar hugs the top-left, the score sits in the top-right, and a hint label is centred along the bottom. Resize the window and every element stays glued to its corner while the bar and labels keep their margins, because each top-level Control uses an AnchorPreset plus symmetric margins rather than a fixed position.

What it demonstrates

  • Anchoring top-level Controls to viewport corners with set_anchor_preset(): TOP_LEFT, TOP_RIGHT, CENTER_BOTTOM, never an absolute position.

  • Margins as pixel offsets from the chosen anchor, so a corner gadget keeps a consistent gutter as the window grows or shrinks.

  • A live health bar (background Panel + foreground fill Panel) whose width tracks a value, driven each frame from on_update.

  • A score readout and a centred hint that follow the bottom/top edges on resize.

Controls: Up / Down - Heal / take damage (animate the health bar) ESC - Quit

Source

  1"""HUD Anchors: a game HUD pinned to screen corners that scales with the window.
  2
  3A game-style heads-up display whose pieces are anchored to the viewport edges,
  4not placed at absolute coordinates: a health bar hugs the top-left, the score
  5sits in the top-right, and a hint label is centred along the bottom. Resize the
  6window and every element stays glued to its corner while the bar and labels
  7keep their margins, because each top-level Control uses an AnchorPreset plus
  8symmetric margins rather than a fixed position.
  9
 10# /// simvx
 11# tags = ["ui", "hud", "anchors", "layout", "responsive"]
 12# web = { root = "HudAnchorsDemo", width = 800, height = 600, responsive = true }
 13# ///
 14
 15## What it demonstrates
 16
 17- Anchoring top-level Controls to viewport corners with `set_anchor_preset()`:
 18  `TOP_LEFT`, `TOP_RIGHT`, `CENTER_BOTTOM`, never an absolute `position`.
 19- Margins as pixel offsets from the chosen anchor, so a corner gadget keeps a
 20  consistent gutter as the window grows or shrinks.
 21- A live health bar (background Panel + foreground fill Panel) whose width
 22  tracks a value, driven each frame from `on_update`.
 23- A score readout and a centred hint that follow the bottom/top edges on resize.
 24
 25Controls:
 26  Up / Down - Heal / take damage (animate the health bar)
 27  ESC       - Quit
 28"""
 29
 30from simvx.core import (
 31    AnchorPreset,
 32    Colour,
 33    Input,
 34    InputMap,
 35    Key,
 36    Label,
 37    Node,
 38    Panel,
 39    Vec2,
 40)
 41from simvx.graphics import App
 42
 43WIDTH, HEIGHT = 800, 600
 44BAR_W, BAR_H = 220.0, 22.0  # health bar size in pixels
 45PAD = 16.0  # gutter from the anchored corner
 46
 47
 48class HudAnchorsDemo(Node):
 49    """Root node: builds a corner-anchored HUD and animates the health bar."""
 50
 51    def on_ready(self):
 52        InputMap.add_action("heal", [Key.UP])
 53        InputMap.add_action("hurt", [Key.DOWN])
 54        InputMap.add_action("quit", [Key.ESCAPE])
 55
 56        self._health = 0.75  # 0..1
 57        self._score = 0
 58
 59        # --- Health bar, top-left ---------------------------------------
 60        # The bar background is the anchored top-level Control; the fill is a
 61        # child that the on_update loop resizes to reflect current health.
 62        bar_bg = Panel(name="HealthBarBG")
 63        bar_bg.set_anchor_preset(AnchorPreset.TOP_LEFT)
 64        bar_bg.margin_left = PAD
 65        bar_bg.margin_top = PAD
 66        bar_bg.size = Vec2(BAR_W, BAR_H)
 67        bar_bg.bg_colour = Colour.hex("#202028")
 68        self.add_child(bar_bg)
 69
 70        self._bar_fill = Panel(name="HealthBarFill")
 71        self._bar_fill.set_anchor_preset(AnchorPreset.TOP_LEFT)
 72        self._bar_fill.margin_left = 2
 73        self._bar_fill.margin_top = 2
 74        self._bar_fill.size = Vec2((BAR_W - 4) * self._health, BAR_H - 4)
 75        self._bar_fill.bg_colour = Colour.hex("#43C463")
 76        bar_bg.add_child(self._bar_fill)
 77
 78        self._hp_label = Label("HP")
 79        self._hp_label.set_anchor_preset(AnchorPreset.CENTER)
 80        self._hp_label.font_size = 12.0
 81        self._hp_label.text_colour = Colour.WHITE
 82        self._hp_label.alignment = "center"
 83        bar_bg.add_child(self._hp_label)
 84
 85        # --- Score, top-right -------------------------------------------
 86        # TOP_RIGHT anchors both edges to the right side; negative right margin
 87        # pushes the box inward, so it tracks the right edge on resize.
 88        self._score_label = Label("Score: 0")
 89        self._score_label.set_anchor_preset(AnchorPreset.TOP_RIGHT)
 90        self._score_label.size = Vec2(180, 24)
 91        self._score_label.margin_left = -180 - PAD
 92        self._score_label.margin_right = PAD
 93        self._score_label.margin_top = PAD
 94        self._score_label.font_size = 16.0
 95        self._score_label.text_colour = Colour.hex("#FFD166")
 96        self._score_label.alignment = "right"
 97        self.add_child(self._score_label)
 98
 99        # --- Hint, bottom-centre ----------------------------------------
100        # CENTER_BOTTOM keeps the label horizontally centred and pinned to the
101        # bottom edge; symmetric left/right margins centre a fixed-width box.
102        hint = Label("Up / Down to change health  |  ESC to quit")
103        hint.set_anchor_preset(AnchorPreset.CENTER_BOTTOM)
104        hint.margin_left = -240
105        hint.margin_right = 240
106        hint.margin_top = -36
107        hint.margin_bottom = -12
108        hint.font_size = 13.0
109        hint.text_colour = Colour.LIGHT_GRAY
110        hint.alignment = "center"
111        self.add_child(hint)
112
113    def on_update(self, dt: float):
114        if Input.is_action_pressed("quit"):
115            self.app.quit()
116            return
117
118        # Animate health and bump the score so the HUD is visibly live.
119        if Input.is_action_pressed("heal"):
120            self._health = min(1.0, self._health + dt * 0.6)
121        if Input.is_action_pressed("hurt"):
122            self._health = max(0.0, self._health - dt * 0.6)
123
124        self._score += 1
125        self._score_label.text = f"Score: {self._score}"
126
127        # Resize the fill to match health and recolour toward red when low.
128        self._bar_fill.size = Vec2((BAR_W - 4) * self._health, BAR_H - 4)
129        if self._health < 0.3:
130            self._bar_fill.bg_colour = Colour.hex("#E63946")
131        elif self._health < 0.6:
132            self._bar_fill.bg_colour = Colour.hex("#F4A261")
133        else:
134            self._bar_fill.bg_colour = Colour.hex("#43C463")
135        self._hp_label.text = f"HP {int(self._health * 100)}"
136
137
138if __name__ == "__main__":
139    app = App(title="SimVX HUD Anchors", width=WIDTH, height=HEIGHT)
140    app.run(HudAnchorsDemo())