# How the ArmorPaint UI Framework Is Implemented: Core Architecture and Immediate-Mode Design

> Discover ArmorPaint's custom immediate-mode UI framework. Learn about its C implementation, theme-driven scaling, ratio-based layouts, and GPU-baked assets for efficient rendering.

- Repository: [Armory 3D/armorpaint](https://github.com/armory3d/armorpaint)
- Tags: architecture
- Published: 2026-09-13

---

**ArmorPaint implements a lightweight, custom immediate-mode UI (IMGUI) framework written in C within the Iron engine, featuring theme-driven scaling, ratio-based layouts, and GPU-baked static assets.**

The ArmorPaint UI framework provides the foundation for the application’s entire interface, from the node editor to the toolbar and sidebar. Built directly on the Iron rendering back-end rather than relying on external dependencies, this self-contained system handles everything from layout calculations to text editing and input processing. Understanding how this **UI framework** structures its context, drawing primitives, and widget state reveals why ArmorPaint maintains high performance across platforms while offering a fully customizable interface.

## Core Architecture and State Management

At the heart of the ArmorPaint UI framework lies a minimalist state management approach that follows immediate-mode principles, where interface elements are redrawn every frame based on current application state.

### The UI Context Singleton

The framework maintains global UI state through a singleton context structure. In [`base/sources/iron_ui.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_ui.c), the `ui_t *current` pointer holds all transient data for the active frame, including the active window handle, input flags, theme configuration, and cursor position. Developers initialize the context with `ui_set_current()` and signal frame completion with `ui_end_frame()`, which clears per-frame flags and finalizes draw commands. This singleton pattern ensures that any part of the codebase can access UI state without passing context pointers through every function call.

### Theme-Driven Scaling and Dimensions

Visual consistency relies on the `ui_theme_t` structure, which stores base measurements for element widths, heights, offsets, font sizes, and color palettes. Helper macros like `UI_ELEMENT_W()`, `UI_ELEMENT_H()`, and `UI_SCALE()` translate these base units to the current DPI scale, automatically handling high-resolution displays. When the UI is disabled, functions such as `ui_fill()`, `ui_rect()`, and `ui_draw_shadow()` automatically apply the theme’s fade-out alpha values to indicate inactive states.

## Immediate-Mode Rendering Pipeline

Unlike retained-mode frameworks that maintain persistent widget objects, ArmorPaint’s UI framework reconstructs the interface every frame through explicit drawing commands.

### Direct Drawing Primitives

Text rendering utilizes `ui_draw_string()`, which handles dynamic glyph loading, string truncation, and optional color-coding for syntax highlighting. Lower-level drawing wraps GPU calls through functions like `draw_filled_rect` and `draw_rect`, exposed via convenient wrappers that apply theme colors automatically. This direct approach eliminates the need for a separate UI scene graph, reducing memory overhead and simplifying synchronization with application state.

### Layout System with Rows and Ratios

The layout engine uses a flexible ratio-based system defined in [`base/sources/iron_ui.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_ui.c). The `ui_row()` function accepts an array of floating-point values where positive numbers indicate proportional column widths and negative values specify absolute pixel widths. Convenience macros—including `ui_row2()` through `ui_row7()`—provide predefined layouts for common configurations. The framework supports both `UI_LAYOUT_VERTICAL` and `UI_LAYOUT_HORIZONTAL` modes; `ui_end_element_of_size()` advances either the Y-coordinate (new line) or X-coordinate (same row) based on the current mode, enabling complex nested layouts without manual coordinate calculations.

## Input Handling and Widget State

Processing user interaction requires careful state tracking across frames while maintaining the simplicity of immediate-mode design.

### Input Processing Pipeline

All mouse, touch, and keyboard events flow through `ui_end_input()` in the core UI file. This function updates hover detection, pressed/released states, and key repeat timers. The framework translates touch-hold gestures into right-click events for mobile compatibility and resets per-frame input flags to ensure state doesn’t leak between frames. This centralized input handling ensures consistent behavior across different hardware configurations.

### Widget Identification and Focus

To track focus and editing state across frames without retaining widget objects, the framework generates stable identifiers using `ui_widget_id()`. This function hashes the current window pointer, a kind identifier constant, and an optional user-provided seed to create unique IDs for each element. These IDs power the focus management system, tooltip tracking, and text editing state persistence, solving the classic immediate-mode problem of tracking which element is currently active.

## Advanced Text and Asset Features

Beyond basic widgets, the ArmorPaint UI framework includes sophisticated systems for text manipulation and rendering optimization.

### Text Editing Implementation

The framework provides a full-featured text field editor through `ui_update_text_edit()` and associated helper functions including `ui_insert_char_at()` and `ui_remove_char_at()`. This implementation handles cursor movement, text selection, clipboard operations, tab insertion, and Ctrl-based word navigation. By integrating directly into the immediate-mode loop, text fields support real-time validation and formatting without asynchronous callbacks.

### Tooltips and Pop-ups

Delayed tooltips appear through `ui_draw_tooltip()`, which renders text or image tips after detecting a sustained hover state. The system resets the tooltip timer whenever input changes, preventing stale pop-ups from lingering during rapid interface interactions. This timing-based approach fits naturally within the per-frame update cycle.

### GPU Baking for Static Assets

To optimize performance, `ui_bake_elements()` pre-renders common UI assets—such as checkboxes, radio buttons, and rounded corners—into GPU textures once per UI session. This caching strategy avoids recreating static geometry every frame while retaining the flexibility of immediate-mode API design. The baked textures persist across frames until the UI scale or theme changes, significantly reducing draw call overhead for complex interfaces.

## Modular UI Components

Higher-level interface modules build upon these core primitives to create the complete ArmorPaint interface. The `paint/sources/ui/` directory contains specialized implementations: [`ui_toolbar.c`](https://github.com/armory3d/armorpaint/blob/main/ui_toolbar.c) renders the top tool strip, [`ui_sidebar.c`](https://github.com/armory3d/armorpaint/blob/main/ui_sidebar.c) manages the left-hand layer panel, [`ui_menubar.c`](https://github.com/armory3d/armorpaint/blob/main/ui_menubar.c) handles application menus, [`ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/ui_nodes.c) implements the visual node editor, and [`ui_statusbar.c`](https://github.com/armory3d/armorpaint/blob/main/ui_statusbar.c) displays cursor information at the bottom. Each module follows the same immediate-mode pattern, calling core functions like `ui_button()` and `ui_text_input()` to compose functional panels.

```c
/* Begin a UI region for a window (x, y, width) */
ui_begin_region(ui, 0, 0, UI_ELEMENT_W() * 10);

/* Layout a row with three columns: 30% / 40% / remaining */
f32_array_t ratios = { .length = 3, .buffer = (float[]){0.3f, 0.4f, -1.0f} };
ui_row(&ratios);

/* A simple button */
if (ui_button("Paint", UI_ALIGN_LEFT, "")) {
    /* Button was pressed → toggle painting mode */
    paint_mode = !paint_mode;
}

/* Text input field */
char name[UI_TEXT_MAX] = "";
ui_text_input(&name, "Enter name", UI_ALIGN_LEFT, true, true);

/* End the region – automatically draws combo boxes and tooltips */
ui_end_frame();

```

## Summary

The ArmorPaint UI framework demonstrates how immediate-mode principles can power professional-grade applications:

- **Singleton context architecture** in [`base/sources/iron_ui.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_ui.c) manages per-frame state through `ui_set_current()` and `ui_end_frame()`
- **Theme-driven scaling** via `UI_ELEMENT_W()`, `UI_ELEMENT_H()`, and `UI_SCALE()` ensures DPI-independent rendering
- **Ratio-based layout system** using `ui_row()` and layout modes supports complex responsive designs without manual coordinate math
- **Stable widget identification** through `ui_widget_id()` hashing enables focus management in immediate-mode context
- **GPU asset baking** via `ui_bake_elements()` optimizes performance by caching static UI elements into textures
- **Modular component structure** separates concerns across files like [`ui_toolbar.c`](https://github.com/armory3d/armorpaint/blob/main/ui_toolbar.c), [`ui_sidebar.c`](https://github.com/armory3d/armorpaint/blob/main/ui_sidebar.c), and [`ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/ui_nodes.c)

## Frequently Asked Questions

### What makes ArmorPaint's UI framework different from traditional retained-mode GUI libraries?

ArmorPaint uses an immediate-mode architecture where widgets are reconstructed and drawn every frame rather than maintaining persistent object hierarchies. This approach eliminates synchronization issues between application state and UI state, reduces memory allocation overhead, and allows the entire interface to scale dynamically with DPI changes through the `ui_theme_t` scaling system. Functions like `ui_end_frame()` clear all transient state, ensuring the UI always reflects current application data.

### How does the UI framework handle high-DPI displays and different screen resolutions?

The framework implements automatic scaling through helper macros defined in [`base/sources/iron_ui.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_ui.c). The `UI_SCALE()` macro calculates the ratio between base theme dimensions and actual display DPI, while `UI_ELEMENT_W()` and `UI_ELEMENT_H()` apply this scale to all measurements. Theme structures store base units, and all drawing operations multiply these values by the current scale factor, ensuring crisp rendering on both standard and high-resolution displays without manual coordinate adjustments.

### Where is text editing functionality implemented in the ArmorPaint UI framework?

Full text editing capabilities reside in [`base/sources/iron_ui.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_ui.c) within functions like `ui_update_text_edit()`, `ui_insert_char_at()`, and `ui_remove_char_at()`. This system handles cursor navigation, selection ranges, clipboard operations, tab characters, and Ctrl-based word navigation. The immediate-mode design allows text fields to validate input in real-time, with state persisted across frames through the widget ID system rather than through retained text box objects.

### What files should developers examine to understand the complete UI implementation?

Start with [`base/sources/iron_ui.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_ui.c) for the core engine and [`base/sources/iron_ui.h`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_ui.h) for the public API. For concrete usage examples, examine [`paint/sources/ui/ui_toolbar.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_toolbar.c) for toolbar buttons, [`paint/sources/ui/ui_nodes.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_nodes.c) for the visual node editor, and [`paint/sources/ui/ui_sidebar.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_sidebar.c) for panel layouts. The header file defines all public structures including `ui_t`, `ui_theme_t`, and layout constants, while the source files demonstrate how higher-level components compose interfaces from primitive drawing functions.