UI Primitives
Screen control, field editors, lightbar menus, forms, and scrollable viewers
This is where your BBS stops looking like a stream of text and starts looking like software. Cursor-addressed screens. Color-blocked headers. Bounded input fields that scroll when the text gets long. Lightbar menus you navigate with arrow keys. Multi-field forms with Tab navigation and validation. Scrollable text viewers for help screens and log windows.
All of this lives in maxui.mh and works alongside max.mh:
#include <max.mh>
#include <maxui.mh>
Everything here works with ANSI and AVATAR terminals automatically. You don’t need to emit raw escape sequences — the display layer handles translation based on the caller’s terminal type. You paint screens, the system worries about the protocol.
On This Page
- Quick Reference
- Low-Level Screen Primitives
- Field Editors
- Lightbar Menus
- Forms
- Scrolling Regions & Text Viewers
- Constants Reference
- Complete Examples
Quick Reference
Screen Primitives
| Function | What It Does |
|---|---|
ui_goto() |
Move cursor to a screen position |
ui_set_attr() |
Set foreground + background color attribute |
ui_fill_rect() |
Fill a rectangle with a character and color |
ui_write_padded() |
Write a padded string at a position |
ui_read_key() |
Read a keypress (handles arrow/function keys) |
Field Editors
| Function | What It Does |
|---|---|
ui_edit_field() |
Positioned, styled text input field |
ui_prompt_field() |
Prompt string + inline edit field |
Lightbar & Selection
| Function | What It Does |
|---|---|
ui_lightbar() |
Vertical lightbar menu |
ui_lightbar_pos() |
Positioned / multi-column lightbar |
ui_select_prompt() |
Inline Yes/No style selection |
Forms
| Function | What It Does |
|---|---|
ui_form_run() |
Multi-field form with navigation and validation |
Scrolling & Viewing
| Function | What It Does |
|---|---|
ui_scroll_region_*() |
Append-only scrollable text panel |
ui_text_viewer_*() |
Read-only paging text viewer |
Low-Level Screen Primitives
These are the building blocks. Most scripts won’t use them directly — the higher-level widgets (fields, lightbars, forms) call them internally. But they’re available when you need pixel-perfect control.
ui_goto(row, col)
Move the cursor to an absolute screen position.
void ui_goto(int: row, int: col);
Coordinates are 1-based — ui_goto(1, 1) is the top-left corner.
ui_goto(10, 5);
print("Hello!"); // prints at row 10, column 5
ui_set_attr(attr)
Set the current display attribute (foreground + background color).
void ui_set_attr(int: attr);
The attribute byte is foreground + (background * 16). Use the UI_*
constants from maxui.mh for readability:
ui_set_attr(UI_YELLOWONBLUE);
print("Highlighted text");
ui_fill_rect(row, col, width, height, ch, attr)
Fill a rectangular screen area with a character and attribute.
void ui_fill_rect(int: row, int: col, int: width, int: height,
char: ch, int: attr);
// Draw a cyan header bar across the top row
ui_fill_rect(1, 1, 80, 1, ' ', UI_CYAN);
ui_write_padded(row, col, width, s, attr)
Write a string padded to a fixed width with a given attribute. Useful for labels, headers, and status bars.
void ui_write_padded(int: row, int: col, int: width, string: s, int: attr);
ui_write_padded(1, 1, 80, "[ Main Menu ]", UI_CYAN);
ui_read_key()
Read a single keypress. Returns an integer key code consistent with the C
layer (keys.h). Handles arrow keys, function keys, and escape sequences
transparently.
int: k;
k := ui_read_key();
All the higher-level widgets use this internally for their input loops.
Field Editors
Field editors give you bounded, styled text input — the kind you’d use for login forms, registration screens, or any prompt where you want control over position, width, and appearance.
ui_edit_field
Low-level field editor with full control over positioning and style.
string ui_edit_field(int: row, int: col, int: width, int: max_len,
string: buf, ref struct ui_edit_field_style: style);
| Parameter | Purpose |
|---|---|
row, col |
Screen position (1-based) |
width |
Visible width on screen |
max_len |
Maximum input length (can exceed width — field scrolls) |
buf |
Initial value (pre-fill) |
style |
Style struct (see below) |
Returns the edited string. The result code is stored in regs_2[0].
Style struct:
struct ui_edit_field_style
{
int: normal_attr; // Attribute when field is unfocused
int: focus_attr; // Attribute when field is active
char: fill_ch; // Background fill character (' ', '_', etc.)
int: flags; // UI_EDIT_FLAG_* constants
string: format_mask; // Format mask for structured input
};
Initialize with ui_edit_field_style_default(es) before customizing.
Editing keys: Left/Right/Home/End move within the field. Backspace deletes left, Delete removes under cursor. Enter accepts, Escape cancels, Tab/Down advance to next field, Shift-Tab/Up go to previous.
Example:
string: username;
struct ui_edit_field_style: es;
ui_edit_field_style_default(es);
es.normal_attr := UI_GRAY;
es.focus_attr := UI_YELLOWONBLUE;
es.fill_ch := ' ';
es.flags := 0;
ui_goto(5, 1);
print("Username: ");
username := ui_edit_field(5, 11, 20, 30, "", es);
ui_prompt_field
Combines a prompt string and an edit field in one call. The prompt is displayed at the current cursor position, then the field follows.
string ui_prompt_field(string: prompt, int: width, int: max_len,
string: buf,
ref struct ui_prompt_field_style: style);
Style struct:
struct ui_prompt_field_style
{
int: prompt_attr; // Color for the prompt text
int: field_attr; // Color for the input field
char: fill_ch; // Background fill character
int: flags; // UI_EDIT_FLAG_* constants
int: start_mode; // Where to position (see prompt start modes)
string: format_mask; // Format mask
};
Example:
string: email;
struct ui_prompt_field_style: ps;
ui_prompt_field_style_default(ps);
ps.prompt_attr := UI_YELLOW;
ps.field_attr := UI_YELLOWONBLUE;
ps.fill_ch := ' ';
ps.start_mode := UI_PROMPT_START_HERE;
email := ui_prompt_field("Email address: ", 40, 60, "", ps);
Lightbar Menus
Lightbar menus let your caller select from a list using arrow keys. The selected item is highlighted with a distinct attribute — the classic BBS lightbar look.
ui_lightbar — Vertical List
int ui_lightbar(ref array [1..] of string: items, int: count,
int: x, int: y, int: width,
ref struct ui_lightbar_style: style);
Returns the 1-based selected index on accept, or -1 on cancel (Escape).
Style struct:
struct ui_lightbar_style
{
int: justify; // Text justification
int: wrap; // Wrap around at ends
int: enable_hotkeys; // Allow single-key selection
int: show_brackets; // Bracket style (see constants)
int: normal_attr; // Unselected item color
int: selected_attr; // Highlighted item color
int: hotkey_attr; // Hotkey letter color (unselected)
int: hotkey_highlight_attr;// Hotkey letter color (selected)
int: margin; // Extra padding columns
int: out_hotkey; // Output: selected hotkey character
};
Hotkey coloring: The first uppercase letter in each item string becomes
the hotkey. When unselected, it renders with hotkey_attr. When selected,
it uses hotkey_highlight_attr. Set either to 0 to use the row’s
normal/selected attribute instead.
Example:
string: items[3];
struct ui_lightbar_style: ls;
int: choice;
items[1] := "Read Messages";
items[2] := "File Areas";
items[3] := "Goodbye";
ui_lightbar_style_default(ls);
ls.normal_attr := UI_GRAY;
ls.selected_attr := UI_YELLOWONBLUE;
ls.wrap := 1;
choice := ui_lightbar(items, 3, 5, 10, 20, ls);
ui_lightbar_pos — Positioned/Multi-Column
For menus where items aren’t in a simple vertical list — each item has its own screen position and width.
int ui_lightbar_pos(ref array [1..] of struct ui_lightbar_item: items,
int: count,
ref struct ui_lightbar_style: style);
Item struct:
struct ui_lightbar_item
{
string: text;
int: x;
int: y;
int: width;
int: justify;
};
Arrow keys navigate using a nearest-neighbor heuristic in 2D space — pressing right jumps to the closest item to the right, pressing down finds the closest below. This works naturally for grid layouts, L-shaped menus, and scattered button arrangements.
ui_select_prompt — Inline Selection
For Yes/No style prompts that appear inline with text:
int ui_select_prompt(string: prompt,
ref array [1..] of string: options, int: count,
ref struct ui_select_prompt_style: style);
Style struct:
struct ui_select_prompt_style
{
int: prompt_attr; // Prompt text color
int: normal_attr; // Unselected option color
int: selected_attr; // Highlighted option color
int: hotkey_attr; // Hotkey color in options
int: show_brackets; // Bracket style
int: margin; // Padding on each side when highlighted
string: separator; // String between options (e.g., " or ")
int: default_index; // 1-based pre-selected option (0 = first)
int: out_hotkey; // Output: selected hotkey character
};
The cursor is hidden during selection and restored on exit.
Forms
ui_form_run manages a complete multi-field form with Tab/Shift-Tab
navigation, field validation, hotkeys, and save/cancel handling.
int ui_form_run(ref array [1..] of struct ui_form_field: fields,
int: count, ref struct ui_form_style: style);
Returns 1 if saved (Ctrl+S), 0 if cancelled (Escape), -1 on error.
Field struct:
struct ui_form_field
{
string: name; // Field identifier
string: label; // Display label
int: x; // Field X position
int: y; // Field Y position
int: width; // Visible width
int: max_len; // Max input length
int: field_type; // UI_FIELD_TEXT or UI_FIELD_FORMAT
char: hotkey; // Direct-jump hotkey
int: required; // 1 = must be non-empty to save
int: label_attr; // Label color (0 = use form default)
int: normal_attr; // Field color unfocused (0 = form default)
int: focus_attr; // Field color focused (0 = form default)
string: format_mask; // Mask for formatted fields
string: value; // Current/output value
};
Style struct:
struct ui_form_style
{
int: label_attr; // Default label color
int: normal_attr; // Default field color
int: focus_attr; // Default focus color
int: save_mode; // Save behavior
int: wrap; // Wrap navigation at edges
string: required_msg; // Message shown for empty required fields
int: required_x; // Position for required message
int: required_y;
int: required_attr; // Color for required message
};
Navigation: Arrow keys use 2D nearest-neighbor (like ui_lightbar_pos).
Tab/Shift-Tab move sequentially. Enter edits the selected field. Ctrl+S
saves. Escape cancels.
Cursor behavior: While navigating between fields, the cursor is hidden. While actively editing a field (after pressing Enter), the cursor is shown so the user sees the insert position.
Required fields: Fields with required=1 must be non-empty to save.
For text fields, whitespace-only input counts as empty. On save, values are
updated in-place in the fields array.
Scrolling Regions & Text Viewers
These are the heavy-hitters for content display — scrollable text panels that handle their own navigation internally.
ScrollingRegion
An append-only scrollable text panel. Lines are added over time and the region auto-scrolls to follow new content (like a log viewer or chat window).
// Lifecycle
ui_scroll_region_style_default(ref style);
ui_scroll_region_create(key, x, y, w, h, max_lines, style);
ui_scroll_region_destroy(key);
// Content
ui_scroll_region_append(key, text, flags);
// Display
ui_scroll_region_render(key);
// Input
ui_scroll_region_handle_key(key, keycode); // returns non-zero if consumed
The key is a string identifier (like "sr") — you can have multiple
regions active simultaneously.
TextBufferViewer
A read-only text viewer with paging. Set the full text once, then let the user scroll through it.
// Lifecycle
ui_text_viewer_style_default(ref style);
ui_text_viewer_create(key, x, y, w, h, style);
ui_text_viewer_destroy(key);
// Content
ui_text_viewer_set_text(key, text);
// Display
ui_text_viewer_render(key);
// Input (two patterns)
ui_text_viewer_handle_key(key, keycode); // returns non-zero if consumed
ui_text_viewer_read_key(key); // returns 0 if consumed, key if not
Input Loop Patterns
Pattern A — Focus routing (multiple widgets):
Use when you have multiple interactive regions on one screen (e.g., a scrolling feed + a viewer + a command bar):
int: focus; // 0=region, 1=viewer
int: k;
focus := 0;
while (1)
{
k := ui_read_key();
if (k = 27) // ESC
return;
if (k = 't' or k = 'T')
focus := 1 - focus; // toggle focus
else if (focus = 0)
{
if (ui_scroll_region_handle_key("sr", k))
ui_scroll_region_render("sr");
}
else
{
if (ui_text_viewer_handle_key("tv", k))
ui_text_viewer_render("tv");
}
}
Pattern B — Viewer-driven (single widget):
Use when the viewer is the primary UI. Scroll keys are consumed internally; everything else passes through as a command:
int: k;
while (1)
{
k := ui_text_viewer_read_key("tv");
if (k = 0)
; // viewer consumed it
else if (k = 27)
return;
else
{
// command keys (R, [, ], etc.)
}
}
Constants Reference
Color Attributes
Integer attribute values for ui_set_attr and style structs:
// Foreground colors (on black background)
#define UI_BLACK 0x00
#define UI_BLUE 0x01
#define UI_GREEN 0x02
#define UI_CYAN 0x03
#define UI_RED 0x04
#define UI_MAGENTA 0x05
#define UI_BROWN 0x06
#define UI_GRAY 0x07
#define UI_DKGRAY 0x08
#define UI_LBLUE 0x09
#define UI_LGREEN 0x0a
#define UI_LCYAN 0x0b
#define UI_LRED 0x0c
#define UI_LMAGENTA 0x0d
#define UI_YELLOW 0x0e
#define UI_WHITE 0x0f
// Common foreground+background combinations
#define UI_YELLOWONBLUE 0x1e
#define UI_BLACKONGREEN 0x20
#define UI_REDONGREEN 0x2c
#define UI_WHITEONGREEN 0x2f
Formula: foreground + (background * 16). For example, white on blue =
0x0f + (0x01 * 16) = 0x1f.
Edit Result Codes
Returned in regs_2[0] after field editing:
| Constant | Value | Meaning |
|---|---|---|
UI_EDIT_ERROR |
0 | Error occurred |
UI_EDIT_CANCEL |
1 | User pressed Escape |
UI_EDIT_ACCEPT |
2 | User pressed Enter |
UI_EDIT_PREVIOUS |
3 | User pressed Up/Shift-Tab |
UI_EDIT_NEXT |
4 | User pressed Down/Tab |
UI_EDIT_NOROOM |
5 | Not enough screen space |
Edit Flags
| Constant | Value | Purpose |
|---|---|---|
UI_EDIT_FLAG_ALLOW_CANCEL |
0x0020 |
Allow Escape to cancel |
UI_EDIT_FLAG_FIELD_MODE |
0x0002 |
Field mode (vs line mode) |
UI_EDIT_FLAG_MASK |
0x0004 |
Mask input with fill_ch (for passwords) |
Bracket Styles
For lightbar and select prompt show_brackets:
| Constant | Value | Renders as |
|---|---|---|
UI_BRACKET_NONE |
0 | Item |
UI_BRACKET_SQUARE |
1 | [Item] |
UI_BRACKET_ROUNDED |
2 | (Item) |
UI_BRACKET_CURLY |
3 | {Item} |
Prompt Start Modes
For ui_prompt_field:
| Constant | Value | Behavior |
|---|---|---|
UI_PROMPT_START_HERE |
0 | Start at current cursor position |
UI_PROMPT_START_CR |
1 | Start at beginning of line |
UI_PROMPT_START_CLBOL |
2 | Clear to BOL, then start |
Complete Examples
Simple Login Form
#include <max.mh>
#include <maxui.mh>
void main()
{
string: username;
string: password;
struct ui_edit_field_style: es;
print(AVATAR_CLS);
// Draw header
ui_fill_rect(1, 1, 80, 1, ' ', UI_CYAN);
ui_write_padded(1, 1, 80, "[ System Login ]", UI_CYAN);
// Username field
ui_goto(5, 1);
ui_set_attr(UI_YELLOW);
print("Username: ");
ui_edit_field_style_default(es);
es.normal_attr := UI_GRAY;
es.focus_attr := UI_YELLOWONBLUE;
es.fill_ch := ' ';
username := ui_edit_field(5, 11, 20, 30, "", es);
// Password field (masked)
ui_goto(7, 1);
ui_set_attr(UI_YELLOW);
print("Password: ");
ui_edit_field_style_default(es);
es.normal_attr := UI_GRAY;
es.focus_attr := UI_YELLOWONBLUE;
es.fill_ch := '*';
es.flags := UI_EDIT_FLAG_MASK;
password := ui_edit_field(7, 11, 20, 20, "", es);
// Result
ui_goto(10, 1);
if (strlen(username) > 0 && strlen(password) > 0)
print("Login successful!\n");
else
print("Login cancelled.\n");
}
Registration Form with Prompt Fields
#include <max.mh>
#include <maxui.mh>
void main()
{
string: name;
string: email;
string: city;
struct ui_prompt_field_style: ps;
print(AVATAR_CLS);
ui_set_attr(UI_LGREEN);
print("User Registration\n");
print("=================\n\n");
ui_prompt_field_style_default(ps);
ps.prompt_attr := UI_YELLOW;
ps.field_attr := UI_YELLOWONBLUE;
ps.fill_ch := ' ';
ps.start_mode := UI_PROMPT_START_HERE;
name := ui_prompt_field("Full Name: ", 30, 50, "", ps);
email := ui_prompt_field("Email: ", 40, 60, "", ps);
ps.fill_ch := '_';
city := ui_prompt_field("City: ", 25, 30, "", ps);
print("\n\nThank you, " + name + "!\n");
}
See Also
- Standard Intrinsics — the core MEX functions for I/O, user data, and file access
- Display Codes — pipe color codes and MCI formatting operators
- MEX Getting Started — introduction to MEX scripting and the compiler