Skip to content
Acron Manual
Features Download Blog Docs
Python C++
Get Acron
Contents Menu Expand Light mode Dark mode Auto light/dark, in light mode Auto light/dark, in dark mode Skip to content
Acron Manual
Acron Manual
/
Language
Python C++
1.11
  • Introduction to Acron
    • Installing Acron
    • Acron and Panda3D
    • Features
    • General Preparation
    • Running your Program
    • Byte of Python
      • Variables and Types
      • Collections
      • Control Flow
      • Functions
      • Classes and Objects
      • Modules and Imports
      • What’s Next
    • An Acron Hello World Tutorial
      • Starting Acron
      • Loading the Grassy Scenery
      • Controlling the Camera
      • Loading and Animating the Acron Model
      • Using Intervals to move the Acron
      • Tutorial End
  • Asset Pipeline
    • Supported Model Formats
    • Egg Files
      • Converting to Egg
      • Converting Egg to Bam
      • Parsing and Generating Egg Files
      • Egg Syntax
    • glTF Files
    • Converting from Blender
    • Converting from Autodesk Maya
    • Converting from 3ds Max
    • Previewing Models in pview
    • Pipeline Tips
  • Debugging
    • Log Messages
    • Inspection Tools
      • The Scene Graph Browser
      • Enhanced Mouse Navigation
      • Utility Functions
    • The Python Debugger
    • Running Acron under the C++ Debugger
  • Performance Optimization
    • Basic Performance Diagnostics
    • Measuring Performance with PStats
      • Basic Profiling
      • Graph Types
      • Thread Profiling
      • GPU Timing
      • User-Defined Collectors
      • Customizing the UI
      • PStats Internals
    • Common Performance Issues
      • Too Many Meshes
      • Too Many State Changes
      • Too Many Text Updates
      • Too Many Shader Instructions
      • Excessive Fill
      • Memory Full
      • Python Calculation
      • Failure to Garbage Collect
      • Collision System Misuse
      • Motherboard Integrated Video
      • Too Many Polygons
      • Miscellaneous
    • The Rigid Body Combiner
  • Distributing Acron Applications
    • Building Python Games
    • List of Build Options
    • Packaging Binaries
    • Setuptools Examples
    • Building C++ Games for the Desktop
    • App Icons and Store Art
    • Continuous Integration for Games
    • Third-party Software
    • Troubleshooting
    • Building for Android
    • Building for the Wii
    • The Web
  • Glossary
  • Programming with Acron
    • The acron Game Framework
      • The Game Loop
      • Physics and Characters
      • Input: Actions, Gamepads and Touch
      • Menus, HUDs and Text
      • Audio: Sound Banks, Acoustic Zones and Baked Sound
      • Animation Blending and Root Motion
      • Materials and PBR
      • Gaussian Splats
      • Procedural Content
      • 2D Games
      • Porting Games from Godot
      • Saving Files and Testing Games
    • The Scene Graph
      • Model Files
      • Common State Changes
      • Manipulating a Piece of a Model
      • Searching the Scene Graph
      • Instancing
      • Level of Detail
    • Animated Models
      • Loading Actors and Animations
      • Actor Animations
      • Multi-Part Actors
      • Attaching an Object to a Joint
      • Controlling a Joint Procedurally
    • The Configuration File
      • Configuring Acron
      • List of All Config Variables
      • Accessing Config Vars in a Program
    • Render Attributes
      • List of All Attributes
      • Lighting
      • Materials
      • Depth Test and Depth Write
      • Fog
      • Alpha Testing
      • Color Write Masks
      • Antialiasing
      • Clip Planes
      • Tinting and Recoloring
      • Backface Culling and Frontface Culling
      • Occlusion Culling
        • Polygon Occluder Culling
        • Portal Culling
      • Light Ramps
      • Auxiliary Bitplane Control
      • Stencil Attribute
    • Texturing
      • Simple Texturing
      • Choosing a Texture Size
      • Texture Wrap Modes
      • Texture Filter Types
      • Simple Texture Replacement
      • Multitexture Introduction
      • Texture Modes
      • Texture Order
      • Texture Combine Modes
      • Texture Transforms
      • Multiple Texture Coordinate Sets
      • Automatic Texture Coordinates
      • Projected Textures
      • Simple Environment Mapping
      • 3-D Textures
      • Cube Maps
      • Environment Mapping with Cube Maps
      • Automatic Texture Animation
      • Playing MPG and AVI files
      • Multiview Textures
      • Transparency and Blending
      • Texture Management
      • Texture Compression
      • Creating New Textures from Scratch
    • Shaders
      • Shader Basics
      • List of GLSL Shader Inputs
      • List of Possible Cg Shader Inputs
      • Shaders and Coordinate Spaces
      • Known Shader Bugs and Limitations
      • The Shader Generator
      • Compute Shaders
    • Camera Control
      • The Default Camera Driver
      • Lenses and Field of View
      • Orthographic Lenses
    • Sound
      • Loading and Playing Sounds and Music
      • Manipulating Sounds
      • Audio Managers
      • DSP Effects
      • 3D Audio
      • Multi-Channel
    • Intervals
      • Lerp Intervals
      • Function Intervals
      • Actor Intervals
      • Sound Intervals
      • Sequences and Parallels
      • Motion Paths
      • Particle Intervals
      • Projectile Intervals
    • Tasks and Event Handling
      • Tasks
      • Task Chains
      • Event Handlers
      • Coroutines
      • Main Loop
      • Threading
    • GUI
      • Rendering Text
      • Rendering Images
      • DirectGUI
        • DirectButton
        • DirectCheckButton
        • DirectRadioButton
        • DirectDialog
        • DirectEntry
        • DirectFrame
        • DirectLabel
        • DirectOptionMenu
        • DirectScrolledList
        • DirectWaitBar
        • DirectSlider
        • DirectScrollBar
        • DirectScrolledFrame
      • Text Fonts
      • Embedded Text Properties
      • RmlUi
        • Getting Started with RmlUi
        • Documents and Elements
        • Events
        • Data Binding
        • Input
        • RmlUi from C++
        • Off-screen Regions and High-DPI Displays
        • Layout and Rendering Notes
    • Render Effects
      • Billboard Effects
      • Compass Effects
      • Gaussian Splat Sorter
    • Finite State Machines
      • FSM Introduction
      • Simple FSM Usage
      • FSM with input
      • Advanced FSM Tidbits
    • Terrain
      • The Heightfield Tesselator
      • Geometrical MipMapping
    • Advanced operations with internal structures
      • How Acron Stores Vertices and Geometry
        • GeomVertexData
        • GeomVertexFormat
        • GeomPrimitive
        • Geom
        • GeomNode
        • BoundingVolume
      • Procedurally Generating 3D Models
        • Defining your own GeomVertexFormat
        • Pre-defined vertex formats
        • Creating and filling a GeomVertexData
        • Creating the GeomPrimitive objects
        • Putting your new geometry in the scene graph
      • Other Vertex and Model Manipulation
        • Reading existing geometry data
        • Modifying existing geometry data
        • Using memoryviews
        • MeshDrawer
        • More about GeomVertexReader, GeomVertexWriter, and GeomVertexRewriter
        • Writing 3D Models out to Disk
      • Matrix Representation
    • Render-to-Texture and Image Postprocessing
      • Common Image Filters
      • Generalized Image Filters
      • Dynamic Cube Maps
      • Low-Level Render to Texture
    • Acron Rendering Process
      • Multithreaded Render Pipeline
      • Introducing Graphics Classes
      • The Graphics Pipe
      • Renderers
      • Creating Windows and Buffers
      • Display Regions
      • Creating New MouseWatchers for Display Regions
      • Clearing Display Regions
      • The 2D Display Region
      • Stereo Display Regions
      • Multi-Pass Rendering
      • How to Control Render Order
      • Table of features supported per graphic renderer
    • Particle Effects
      • Using the Particle Panel
      • Loading Particle Systems
      • Particle Effect Basic Parameters
      • Particle Factories
      • Particle Emitters
      • Particle Renderers
    • Collision Detection
      • Collision Solids
      • Collision Handlers
      • Collision Entries
      • Collision Traversers
      • Collision Bitmasks
      • Rapidly-Moving Objects
      • Pusher Example
      • Event Example
      • Bitmask Example
      • Clicking on 3D Objects
    • Object Management
      • Subclassing
      • Reference Counting
      • Removing Custom Class Instances
    • Hardware Support
      • Keyboard Support
      • Mouse Support
      • Joystick Support
    • Physics
      • Acron Physics Engine
        • Enabling physics on a node
        • Applying physics to a node
        • Types of forces
        • Notes and caveats
      • Using Bullet with Acron
        • Bullet Hello World
        • Bullet Debug Renderer
        • Bullet Collision Shapes
        • Bullet Collision Filtering
        • Bullet Continuous Collision Detection
        • Bullet Queries
        • Bullet Ghosts
        • Bullet Character Controller
        • Bullet Constraints
        • Bullet Vehicles
        • Bullet Softbodies
        • Bullet Softbody Rope
        • Bullet Softbody Patch
        • Bullet Softbody Triangles
        • Bullet Softbody Tetrahedron
        • Bullet Softbody Config
        • Bullet Config Options
        • Bullet FAQ
        • Bullet Samples
      • Using ODE with Acron
        • Worlds, Bodies and Masses
        • Simulating the Physics World
        • Attaching Bodies using Joints
        • Collision Detection with ODE
      • Ragdoll Solver
    • Networking
      • Datagram Protocol
        • Client-Server Connection
        • Transmitting Data
      • Downloading a File
      • Distributed Networking
        • Servers
        • Server Repositories
        • AI Repositories
        • Clients
        • Client Repositories
        • Distributed Class Definition
        • Client-Side Created Objects
        • AI-side Created Objects
        • Uber DOGs
        • Owner View
        • Time Manager
        • Related Object Manager
        • Distributed Objects
        • Distributed Node
        • Distributed Smooth Node
      • ENet Networking
        • ENet Hello World
        • Messages, Channels and Reliability
        • The Service Loop
    • Advanced Loading Techniques
      • Acron Filename Syntax
      • Asynchronous Loading
      • The Virtual File System
      • Multifiles
      • Loading Resources from Memory
      • Patching
    • Pathfinding using AcronAI
      • Getting Started
      • Steering Behaviors
        • Seek
        • Flee
        • Pursue
        • Evade
        • Wander
        • Flock
        • Obstacle Avoidance
        • Path Follow
      • Pathfinding
        • Mesh Generation
        • Static Obstacles
        • Dynamic Obstacles
        • Uneven Terrain
      • Source Codes
    • Design Patterns in Acron
      • Acron Engine Key Facts
      • Command
      • Flyweight
      • Observer
      • Prototype
      • Singleton
      • State
      • Component
      • Event Queue
      • Service Locator
      • Type Object
      • Subclass Sandbox
      • Bytecode
      • Game Loop
      • Update Method
      • Double Buffer
      • Data Locality
      • Dirty Flag
      • Object Pool
      • Spatial Partition
      • Decision Guide
  • More Acron Resources
    • Frequently Asked Questions
    • Sample Programs in the Distribution
      • Sample Programs: Asteroids
      • Sample Programs: Ball in Maze
      • Sample Programs: Boxing Robots
      • Sample Programs: Bump Mapping
      • Sample Programs: Carousel
      • Sample Programs: Cartoon Shader
      • Sample Programs: Chessboard
      • Sample Programs: Disco Lights
      • Sample Programs: Distortion
      • Sample Programs: Fireflies
      • Sample Programs: Fractal Plants
      • Sample Programs: Gamepad
      • Sample Programs: Glow Filter
      • Sample Programs: Infinite Tunnel
      • Sample Programs: Looking and Gripping
      • Sample Programs: Media Player
      • Sample Programs: Motion Trails
      • Sample Programs: Mouse Modes
      • Sample Programs: Music Box
      • Sample Programs: Particles
      • Sample Programs: Procedural Cube
      • Sample Programs: Teapot on TV
      • Sample Programs: Roaming Ralph
      • Sample Programs: Shadows
      • Sample Programs: Solar System
    • Cheat Sheets
    • User Contributed Tutorials and Examples
acronengine.com Features Download Blog
Back to top

Menus, HUDs and Text¶

This page describes part of the acron Python framework that comes with the acron wheel. It has no C++ counterpart; a C++ game uses RmlUi directly, as described in RmlUi from C++.

A game’s interface usually has two halves. The half made of words – menus, settings screens, panels, lists – is an RmlUi document, described in RmlUi. The half that is drawn – a minimap, a compass tape, a dial – is pixels on a card in aspect2d. The acron framework has a module for each half and one that keeps them lined up:

acron.window

the configuration a window needs before ShowBase opens it

acron.ui

an RmlUi layer for one window, and a keyboard-driven menu on it

acron.hud

the drawn half: a pixel frame on aspect2d, a raster you paint, a map view, compass mapping

acron.layout

one set of panel numbers that both halves read

acron.strings

a string table with locales, for every word the interface shows

The modules supply the mechanism; the documents, style sheets, colours and button ids are the game’s.

Before the window opens¶

Some settings only take effect if they are loaded before ShowBase creates the window. acron.window.apply_prc() loads them in one call:

from acron import window
from direct.showbase.ShowBase import ShowBase

window.apply_prc("My Game")     # before ShowBase
base = ShowBase()
window.fit_window(base)         # after: real pixels on a HiDPI display

apply_prc(title, *, stencil=True, msaa=False, dpi_aware=True, npot=True, absolute_img_src=False, name="acron-window") sets:

  • the window title;

  • a stencil buffer (framebuffer-stencil true), which RmlUi needs to clip its elements;

  • no multisampling, unless msaa=True;

  • textures-power-2 none, so textures keep their size;

  • dpi-aware true, so a HiDPI (Retina) window is backed by device pixels rather than a stretched low-resolution buffer;

  • with absolute_img_src=True, the filesystem root on the model path, so an RmlUi <img src="/absolute/path.png"> is found.

A game that writes the rest of its configuration itself still needs the stencil line: acron.ui.apply_prc() loads just that.

On a dpi-aware window the requested win-size is in device pixels, so on a 2x display the window would open at half its intended size. fit_window() re-requests it at its size times the display zoom; it does nothing for an offscreen buffer or a 1x display.

acron.window.blocking_frame(base, *hidden) renders one frame immediately, from inside a long call such as a bake, with the given NodePaths hidden for that frame. It is how a loading bar advanced from a progress callback gets on screen; hide render while the scene is half-built.

An RmlUi layer¶

acron.ui.RmlUI creates an RmlRegion on a window, its context, and (unless interactive=False) an RmlInputHandler under the mouse watcher. Documents are authored in a fixed pixel space, authored_height pixels tall (1080 by default), and the context’s dp ratio maps that onto the window; it is retargeted when the window is resized.

from acron.ui import RmlUI

ui = RmlUI(base, name="menu")
ui.load_fonts("fonts/Inter-Regular.ttf")

# A data model must exist before a document that names it is loaded.
ui.make_model("menu", start=lambda *args: ui.defer(start_game),
              quit=ui.defer_exit)
doc = ui.load_document("ui/menu.rml")
doc.show()

The keyword arguments are name="ui", authored_height=1080.0, watch_resize=True, scale=1.0 and interactive=True:

  • scale multiplies the dp ratio – a HUD-size setting. set_scale() changes it while the game runs. A game that scales its HUD but not its menus gives each its own RmlUI.

  • interactive=False creates no input handler, for a context whose documents take no input, such as a HUD.

  • watch_resize=False is for a region that does not cover the whole window (a letterboxed view): call set_window_height() with the region’s pixel height yourself.

  • Context names are global to the process. A name that was used before gets a number appended (menu2, menu3); name is the name the context got.

The other methods:

load_fonts(*paths)

load font faces into this context (fonts are per context)

make_model(name, **callbacks)

create a data model with its event callbacks bound

load_document(path), unload(document)

load a document (hidden until show()), unload one

on_click(element, callback), on_hover(element, callback)

call callback() when an element is clicked or the pointer enters it; for elements created at runtime, which a data model cannot bind

set_input_enabled(on)

attach or detach the input handler; two interactive contexts on one window both receive every click, so the one underneath detaches

set_region_sort(sort)

the draw order among the window’s display regions

defer(fn), defer_exit()

run fn next frame, or exit the app next frame

set_cursor_visible(visible)

show or hide the system cursor

destroy()

unload every document, remove the input handler and the region

Do not change the scene graph or raise SystemExit inside an RmlUi event callback. Changing the documents themselves (visibility, properties, text) is safe there; anything else goes through defer, and quitting through defer_exit.

acron.ui.event_string_arg() and acron.ui.event_number_arg() pick the string or numeric argument out of a data-model event callback’s *args. acron.ui.ChangedWrites applies a value only when it differs from the last one applied under the same key – RmlUi lays the document out again on every property set, so a HUD that writes the same text every frame should go through it:

from acron.ui import ChangedWrites

writes = ChangedWrites()
ammo = doc.get_element_by_id("ammo")

def update_hud(task):
    writes.set("ammo", str(player.ammo), ammo.set_inner_rml)
    return task.cont

acron.ui.escape_rml() escapes text that comes from data before it goes into markup.

A menu with screens¶

acron.ui.StateMenu is one menu document with several screens. Each state names its buttons, in navigation order; a button is an element with the id btn_<name>. show() displays one state’s buttons and hides the rest, and while the menu is open the arrow keys move the selection (the selected button gets the class selected) and Enter activates it. The mouse keeps working through RmlUi.

from acron.ui import StateMenu

class PauseMenu(StateMenu):
    def on_activate(self, bid):
        if bid == "resume":
            self.hide()
        elif bid == "options":
            self.show("options")
        elif bid == "quit":
            self.ui.defer_exit()

menu = PauseMenu(ui, doc, {
    "main": ["resume", "options", "quit"],
    "options": ["audio", "video", "back"],
}, pause=game.set_paused)
menu.show("main")

on_activate() must be overridden; on_show() is called with the state on every show(), for titles and labels. pause is called with True when the menu shows and False when it hides. nav_events (default ("arrow_up", "arrow_down")) and activate_event (default "enter") name the events it listens to.

The drawn half¶

acron.hud draws what RCSS cannot: instruments that change every frame, rotate, or are made of lines. Everything works in authored pixels, y growing down, the coordinates the design was drawn in.

A pixel frame¶

acron.hud.PixelFrame(base, *, authored_height=1080.0, scale=1.0) is a pixel space on aspect2d, anchored to window corners, so a panel pinned to the bottom right keeps its margin when the window changes shape. The anchors are tl, tr, bl, br, tc, bc, lc, rc and c (the centre).

from acron.hud import PixelFrame

hud = PixelFrame(base)
score = hud.label("0", 40, 60, "tr", size=32, align="right")
score.node().set_text("1200")

place(np, x, y, corner) puts a node at a pixel position, card(width, height, x, y, corner) makes a card with its top-left corner there, and label(text, x, y, corner, size=12.0, align="left", color=..., font=None) makes a text node whose y is the text’s baseline. set_scale resizes the whole frame (the HUD-size setting); hide, show and destroy act on every anchor.

Painting¶

acron.hud.Painter is the drawing interface an instrument is written against: clear, fill_rect, stroke_rect, line, polyline, lines (many segments of one colour in a single call), polygon, disc, arc, text and blit. Colours are (r, g, b) or (r, g, b, a) from 0 to 1; angles are in radians, measured from +x towards +y, which is down.

Two classes implement it:

  • acron.hud.Raster(width, height) draws into an RGBA pixel buffer with light anti-aliasing and shows it on a card. attach(frame, x, y, corner) mounts it on a PixelFrame; nothing reaches the GPU until upload(), which does nothing when nothing was drawn. Text goes through a LabelPool of text nodes in the raster’s coordinates: call begin_text() before a frame’s text and end_text() after it. pixels() returns the image as floats, for a test. Raster needs numpy, which the web runtime does not have; the rest of the module works without it.

  • acron.hud.RecordPainter writes the calls down instead (ops, and filtered views such as strokes, texts and discs), so the layout of an instrument can be tested with no window and no GPU.

import math
from acron.hud import Raster, relative_bearing, tape_offset

tape = Raster(400, 40)
tape.attach(hud, -200, 20, "tc")

def draw_compass(heading):
    tape.clear((0, 0, 0, 0.4))
    tape.begin_text()
    for bearing in range(0, 360, 15):
        x = tape_offset(bearing, heading, span=90, width=400)
        if x is None:
            continue
        tall = bearing % 45 == 0
        tape.line(200 + x, 0, 200 + x, 14 if tall else 7, (1, 1, 1))
        if tall:
            tape.text(str(bearing), 200 + x, 34, size=12, align="center")
    tape.end_text()
    tape.upload()

~acron.hud.relative_bearing(bearing, heading) is bearing - heading wrapped into the range -180 to 180 degrees; ~acron.hud.tape_offset(bearing, heading, span, width) is where a bearing falls on a tape showing span degrees across width pixels, measured from the centre, or None when it is off the tape.

A map view¶

acron.hud.MapView(parent, width, height) shows a baked top-down map texture on a card and windows it with a texture transform, so panning, zooming and turning the map cost nothing per frame. set_map(tex, cx, cz, half) binds the texture and the square of world it covers (centre (cx, cz), half units to each edge, north at the top). focus(x, z, span, heading=0.0) shows span units of world centred on (x, z); passing the player’s heading makes it a heading-up radar. world_to_local(x, z) gives the card pixel of a world position, for markers drawn on a Raster over the map.

The map frame is a Y-up ground plane: world (x, z), north towards -z. A Z-up game, whose ground plane is x/y with north towards +y, passes z = -y. node= shows the map on a node of your own (a radar disc) instead of a generated card; its UVs must span 0 to 1.

Shaped chips¶

RCSS cannot clip an element to a shape, and RmlUi loads images from files. acron.hud.hex_chip_png(cache_dir, name, rgba, px=64) writes a hexagonal chip of a colour (rgba in 0-255) to a PNG in cache_dir the first time it is asked for, and returns its absolute path for an <img src>. The colour is part of the file name, so a changed palette never shows old pixels.

Lining the halves up¶

acron.layout.Box(corner, x, y, w, h) is one panel in authored pixels, y down, measured from a PixelFrame anchor (a bottom-anchored panel has a negative y). Both halves read the same box: rml() gives the absolute-position properties for an RmlUi element and pixel_xy() the position for a card. One authored pixel is one dp in an RmlUI with the same authored_height, so a HUD-size setting scales both together.

from acron.layout import Box

RADAR = Box("br", 24, -224, 200, 200)

panel = doc.get_element_by_id("radar-frame")
for prop, value in RADAR.rml().items():
    panel.set_property(prop, value)

radar = Raster(200, 200)
radar.attach(hud, *RADAR.pixel_xy(), RADAR.corner)

Strings and locales¶

acron.strings.StringTable(reference, *, markup=(), locale_dir=None) holds every string the interface shows, by key. The reference table is in the language the game was written in; a locale file, <lang>.json in locale_dir, is merged over it, and a key the locale lacks falls back to the reference.

from acron.strings import StringTable

strings = StringTable({
    "menu.start": "Start",
    "hud.ammo": "Ammo: {count}",
    "help.keys": "Press <b>Space</b> to jump",
}, markup={"help.keys"}, locale_dir="locales")

strings.load_locale("de")
strings.tr("hud.ammo", count=12)
strings.fill(doc, {"start": "menu.start", "help": "help.keys"})
  • tr(key, **fields) is the string in the active table with its fields filled in.

  • rml(key, **fields) is the same, escaped for RmlUi unless the key is in markup.

  • fill(doc, {element_id: key}) writes strings into a document’s elements; an element the document does not have is skipped.

  • load_locale(lang) merges a locale over the reference (None or an unknown language goes back to the reference) and returns the locale’s whole dictionary; reset() goes back to the reference; locale is the language loaded; available() lists the languages in locale_dir.

The module-level functions work on the files directly: ~acron.strings.read_locale(path) reads one locale file, keeping only its string entries (None if it is missing or is not a JSON object), ~acron.strings.available(directory) lists the languages a directory holds, and ~acron.strings.check(reference, locale) returns the keys a locale is missing and the keys it has that the reference no longer does – what a translator needs to be told.

Next
Audio: Sound Banks, Acoustic Zones and Baked Sound
Previous
Input: Actions, Gamepads and Touch
Copyright © 2026 Astracron. Panda3D manual 2019 Carnegie Mellon University
acronengine.com Forum Report a docs issue
On this page
  • Menus, HUDs and Text
    • Before the window opens
    • An RmlUi layer
      • A menu with screens
    • The drawn half
      • A pixel frame
      • Painting
      • A map view
      • Shaped chips
    • Lining the halves up
    • Strings and locales