AgtComposites — menus + modal dialogs

AgtComposites — multi-widget compositions

The composite module hosts widgets whose internal structure is a TREE of other widgets, not a single rendering element. Examples shipped here:

  • <agt/agt-message-box.hpp>AgtMessageBox modal confirmation (Dialog + Label + 1-2 Buttons). Its card is an AgtMovableFrame, so the box is draggable by its title bar.

  • <agt/agt-prompt-dialog.hpp>AgtPromptDialog modal input (Dialog + Label + EditField + 2 Buttons); also a draggable card.

  • <agt/agt-theme-dialog.hpp>AgtThemeDialog modal theme picker (Dialog + Label + AgtListBox of the built-in theme names + OK/Cancel). Live preview: selecting a row applies the theme to AgtPalette::current() and restyle_tree()s both the target window and the dialog; OK keeps the pick, Cancel/Escape reverts. Persistence is the caller’s job (AgtSettings), like AgtPromptDialog returns its text.

  • <agt/agt-progress-dialog.hpp>AgtProgressDialog a “working…” dialog (Dialog + Label + AgtProgressBar or AgtSpinner + optional Cancel). Determinate (set_progress) or indeterminate (spinner, self-animated via start() after show(), or stepped with advance()); modal (run) or modeless (show).

  • <agt/agt-menu-bar.hpp>AgtMenuBar (HBox of top-level MenuItem titles; owns the popups).

  • <agt/agt-menu.hpp>AgtMenu (Frame with MenuItem rows as DIRECT children, laid out eagerly — see note below). Supports cascading submenus (add_submenu → an owned child popup): opening one transfers mouse-capture + keyboard focus to the child and hands it back up the parent chain on dismiss, so an arbitrary-depth menu tree drives off the single window capture slot. Casts a soft drop shadow so it reads as floating over the content it covers.

  • <agt/agt-scroll-frame.hpp>AgtScrollFrame clipped scrolling viewport (: AgtFrame). Owns a content() child positioned at (inner_offset - scroll) plus a vertical (+ optional horizontal) AgtScrollBar; overrides wants_child_clip so the render walk + hit-test confine children to the viewport (scrolled-out content is neither painted nor clickable). The first consumer of the coordinate-model primitives — it scrolls via the render-walk translate, not bespoke math.

  • <agt/agt-combo-box.hpp>AgtComboBox non-editable selection combo (: AgtFrame). A collapsed field + a dropdown that REUSES AgtListBox, shown with the AgtMenu reparent / capture / dismiss model: on open the list reparents to the window (paints on top) and the combo grabs the capture; arrow keys browse, a click / Enter commits (emits the combo’s AGT_SEL_COMMAND), an outside click or Escape dismisses.

  • <agt/agt-tab-view.hpp>AgtTabView notebook tab container (: AgtFrame): a strip of tab buttons + a stack of page widgets, only the active one shown via AgtWidget::set_visible (no reparenting — pages stay children, so ownership is the plain cascade). add_tab(label) returns the page to populate; a tab button’s command (message_id == tab index) swaps the active page.

  • <agt/agt-tool-bar.hpp>AgtToolBar command-button strip (: AgtHBox): add_button(label, target, id) (flat button) / add_separator() (vertical divider); HBox does the left-to-right layout.

  • <agt/agt-spin-box.hpp>AgtSpinBox integer field (: AgtFrame) with owned + / - stepper buttons that adjust the value by step, clamped to [min, max]; emits SEL_COMMAND on a real change.

  • <agt/agt-form.hpp> + <agt/agt-form-browser.hpp> — the declarative settings-forms framework (an HII-style “BIOS setup” builder for AGT’s own apps). AgtFormField is a POD descriptor (SECTION / BOOL / INT / ENUM / TEXT / PASSWORD / ACTION, with a typed-pointer or get/set binding, per-type constraints, defaults, and optional visible_if / enabled_if predicates — the C++ replacement for HII suppressif / grayoutif, no expression VM); a consumer declares static const AgtFormField fields[] = {…}. AgtForm wraps the array + a fixed-capacity working copy and the commit model (save / discard / load_defaults; bindings change only on Save). AgtFormBrowser (: AgtFrame) maps each field to its editor widget (checkbox / spin box / combo / edit / password / button), lays them out as labeled rows inside an owned AgtScrollFrame, drives the working copy, and carries the Restore-Defaults / Discard / Save button row. The logic/render split mirrors EDK2’s SetupBrowserDxe / DisplayEngineDxe.

  • <agt/agt-expander.hpp>AgtExpander collapsible section (: AgtFrame): a clickable header (disclosure triangle + title) toggles an owned content() via set_visible AND shrinks the expander to header height (re-flows in an AgtVBox).

  • <agt/agt-tree-view.hpp>AgtTreeView virtualized collapsible tree (: AgtFrame) over an owned AxlNTree model (per-node payload at node->data): flattens the visible nodes each change and paints only the viewport window (O(page)); indented rows + disclosure triangles; click / keyboard select + expand/collapse; reuses AgtScrollBar.

  • <agt/agt-file-dialog.hpp>AgtFileDialog modal filesystem browser (: AgtDialog): a draggable card with a path label, “Up” button, an AgtListBox of entries read via axl-fs ([name] for dirs), and Open / Cancel. Open navigates into a dir or picks a file (path() after RESULT_OK). The list / Open / Up commands use ids the dialog’s own map catches so they don’t hit AgtDialog’s dismiss range; Cancel (id 0) falls through to dismiss(0).

  • <agt/agt-main-window.hpp>AgtMainWindow (: AgtWindow) the docking-chrome window: reserves edge bands for a menu bar (top), tool bar (top), and status bar (bottom), with the remaining region as the content area — the FOX FXMainWindow shape (axedit is built on it)

  • <agt/agt-search-entry.hpp>AgtSearchEntry (: AgtEditField) an edit field with a magnifier glyph + a clear (✕) gutter button; the find-bar input idiom

  • <agt/agt-password-field.hpp>AgtPasswordField (: AgtEditField) a masked field (starts in AGT_ECHO_PASSWORD, secret kept off the clipboard) with an optional reveal (eye) gutter button that toggles the masking; the credentials-form input idiom

  • <agt/agt-table-base.hpp>AgtTableBase (: AgtFrame) the shared substrate for the AgtTable* family: the column schema + per-cell-text row model, the virtualized viewport geometry (paints only the visible window, O(page)), the owned AgtScrollBar, the command-target plumbing, and the draw skeleton (frame + header strip

    • per-column clipped text). Three protected hooks (paint_selection_bg_ / cell_text_color_ / paint_header_extra_) carry the per-subclass variation. Not instantiated directly

  • <agt/agt-table-view.hpp>AgtTableView (: AgtTableBase) the read-only DATA grid: whole-row selection + sortable columns (text / numeric, click-to-sort with a caret). The tabular counterpart to AgtTreeView

  • <agt/agt-table-edit.hpp>AgtTableEdit (: AgtTableBase) the EDITABLE cell grid: a single ACTIVE CELL (row,col) with arrow / Tab navigation and in-cell editing via a single overlaid AgtEditField (F2 / Enter / double-click / type-to-edit; Enter commits + moves down, Tab commits + moves right, Escape cancels, focus-out commits); per-column editable gate. Emits AGT_SEL_COMMAND on a cell commit

  • <agt/agt-tooltip.hpp>AgtTooltip (: AgtFrame) the hover-hint bubble: a small bordered caption on its own per-pixel-alpha child surface with a soft drop shadow; the shared instance is driven by AgtWindow’s hover-delay timer (consumers set per-widget tip text, not the widget directly)

Both dialog composites inherit AgtDialog’s frosted modal veil — the parent frame is snapshotted, blurred, and dimmed behind the card (see the render module), and Tab / Shift+Tab cycle focus over the card’s buttons + fields.

Distinction from src/widgets/: an atomic widget renders multiple visual elements inside ONE draw() (e.g. AgtCheckBox’s indicator + label both come from the checkbox’s own draw method, not from sub-widgets). A composite OWNS sub-widget AgtObject nodes that participate in the parent-child tree, get hit-tested, and dispatch their own events.

Conventions

Composites in this module follow five rules — these are the shape every new composite should adopt:

1. Construct children eagerly in the ctor

The composite’s constructor instantiates every internal sub- widget via new, parented to the composite (or to an internal container that’s parented to the composite). After the constructor returns, the widget tree is complete; consumers don’t add more children.

AgtMessageBox::AgtMessageBox(const char *title, const char *msg)
    : AgtDialog()
{
    auto *vbox = new AgtVBox(this, ...);
    new AgtLabel(vbox, ..., title);
    new AgtLabel(vbox, ..., msg);
    auto *hbox = new AgtHBox(vbox, ...);
    ok_button_ = new AgtButton(hbox, ..., "OK", &trampoline_, ...);
    set_default_button(ok_button_);
}

2. Children are heap-allocated

The composite ctor allocates sub-widgets via new, parented to the composite tree. When the composite is destroyed, the AgtObject parent-cascade dtor frees the children. Composites MUST NOT stack-allocate sub-widgets — a stack- allocated child would be double-freed when the cascade runs and again when the stack frame unwinds.

3. Don’t expose sub-widgets via public API

Consumers manipulate composite state through the composite’s own setters; they don’t reach into the internal tree. Example: messagebox.set_message("...") updates the internal label; messagebox.message_label() is NOT exposed. This keeps the composition an implementation detail — the composite’s internal structure can change without breaking consumers.

4. Route SEL_COMMAND to the composite itself (self-target)

When the composite owns interactive sub-widgets (buttons), the buttons target the composite — or a base class of it — directly, and that class hosts the AGT_SEL_COMMAND handler in its own message map. The composite already has an AGT_DECLARE_MAP (it’s an AgtObject), so no separate forwarder object is needed.

Two shipped examples:

  • AgtDialog maps the full command-id range to on_command_dismiss, which calls dismiss(message_id()). AgtMessageBox / AgtPromptDialog wire their OK / Cancel buttons to target this with target_id == the result code — so a click routes up the metaclass chain to the inherited AgtDialog handler and dismisses, with zero per-composite glue:

    // In AgtDialog's map (inherited by every dialog composite):
    AGT_MAP_COMMAND_RANGE(0, 65535, &AgtDialog::on_command_dismiss)
    
    // In the composite ctor — button id IS the dismiss code:
    ok_button_ = new AgtButton(button_row, ..., "OK", this, RESULT_OK);
    
  • AgtMenuBar maps its menu-index range to on_title_command; each title AgtMenuItem targets the bar with target_id == menu index.

When you genuinely need a separate forwarder — the button’s id space must differ from the composite’s handler ids, or a button must NOT trigger the composite’s default command behavior (e.g. an “Apply” button on a dialog that otherwise dismisses on every command) — give that button a different target: either a small file-local AgtObject subclass parented to the composite via add_child (so the dtor cascade frees it), or another already-existing object. The earlier dialog trampolines were removed once the self-target pattern subsumed them; reach for a forwarder only when self-target can’t express the wiring.

5. Inherit from existing widget bases, NOT from a composite base class

Composites that are top-level windows (AgtMessageBox, AgtPromptDialog) inherit AgtDialog. Composites that are in-window children (AgtMenuBar, AgtMenu) inherit AgtFrame (or AgtHBox / AgtVBox for layout-shaped composites). There is no AgtComposite base type — composition is a SOURCE-TREE convention and a documentation grouping, not a type-system distinction. See the discussion in the AGT-Design document.

Testing patterns

Composite tests assert on EXTERNAL state + sub-widget interaction outcomes, not on the internal tree structure:

  • “After messagebox.run() and a synthetic OK click, the result is RESULT_OK” — observable contract.

  • NOT: “messagebox owns exactly one VBox containing exactly two labels” — internal structure that could change.

Tests use the Scenario DSL where the input flow is the interesting axis (click sequences, key presses through dispatch_input). For state-machine isolation tests, direct handler invocation matches the existing widgets/-side pattern.

API Reference

AgtMenuBar

class AgtMenuBar : public AgtHBox

Public Functions

AgtMenuBar(AgtWidget *parent, int x, int y, int w, int h) noexcept
~AgtMenuBar() noexcept override

Free owned popups (see the ownership note above).

AgtMenuBar(const AgtMenuBar&) = delete
AgtMenuBar(AgtMenuBar&&) = delete
AgtMenuBar &operator=(const AgtMenuBar&) = delete
AgtMenuBar &operator=(AgtMenuBar&&) = delete
AgtMenu *add_menu(const char *label) noexcept

Add a top-level title and return its (empty) popup for the caller to populate via AgtMenu::add_item. Returns NULL if the bar is full or allocation failed.

inline int menu_count() const noexcept

Number of top-level titles.

AgtMenuItem *title_at(int index) const noexcept

Title row / popup by index, or NULL if out of range.

AgtMenu *menu_at(int index) const noexcept
inline int active_menu() const noexcept

Index of the currently-open menu, or -1 if none.

inline AxlSurface *session_surface() const noexcept

The bar’s session surface (C7 P4) while a menu session is active, else NULL. On activation the bar lifts to this surface (rendering its titles); the open menu parents its surface under it, so ONE seat pointer-grab on the bar surface confines the whole {bar → menu → submenu} subtree. A top-level AgtMenu reads this to parent under.

inline int highlighted_title() const noexcept

Index of the keyboard-highlighted top-level title (Phase 2 cursor), or -1 when menu mode is inactive. Stays set to the open menu’s index while a popup is up (Phase 3) so the title reads as active. Exposed for the nav state machine + tests.

inline bool menu_active() const noexcept

True while the menu bar is in keyboard menu mode — either a title is highlighted (Phase 2) or a popup is open (Phase 3).

void activate() noexcept

Enter keyboard menu mode: highlight the first title and take keyboard focus so subsequent arrows route here (Phase 2). This is the action F10 performs; exposed so a consumer can wire F10 (or any “open menu” accelerator) from a window-level key handler, since AGT routes keys only to the focused widget.

void deactivate() noexcept

Leave keyboard menu mode entirely: dismiss any open popup, clear the title highlight, release keyboard focus (Phase 1).

void open_menu(int index) noexcept

Open menu index (dismissing any currently-open popup), positioning its popup flush under the title. No-op for an out-of-range index. Exposed for the keyboard navigation state machine (2.7f-B) + tests.

void close_active() noexcept

Dismiss the open popup (if any) and clear active_menu_.

bool activate_mnemonic(char letter) noexcept

Open the menu whose title mnemonic matches letter — the Alt+<letter> access path (case-insensitive). Mirrors a title click (open_menu). A window-level key handler calls this on an Alt+letter press. Returns true if a title matched (menu opened), false if none did (so the caller can let the key fall through).

void menu_back_to_bar() noexcept

Popup Escape: close the popup and return to Phase 2 with the same title highlighted + the bar re-focused.

void menu_nav_adjacent(int dir) noexcept

Popup Left/Right: close the current popup and open the adjacent title’s popup (dir is -1 / +1, wrapping), staying in Phase 3.

void on_menu_dismissed(AgtMenu *p) noexcept

Called by an owned AgtMenu when it dismisses itself (e.g. click-away). Clears active_menu_ if p is the active popup so the bar’s state tracks the popup’s.

long on_title_command(AgtObject *sender, AgtEvent *ev)

Command handler — title clicks land here (each title targets the bar with target_id == index).

long on_key_press(AgtObject *sender, AgtEvent *ev)

Phase-2 keyboard handler — F10 (toggle menu mode), Left / Right (move title highlight), Down / Enter (open the highlighted title’s popup), Escape (leave menu mode). Only fires while the bar holds keyboard focus; once a popup is open it owns focus and handles its own keys.

bool handle_event(AgtEvent &ev) override

Title router for the menu session (C7 P4). While a session is active the bar’s session surface delivers TITLE-area pointer events here (the open menu’s rows go to the menu’s own surface); this hover-switches / click-toggles among the bar titles. A press OUTSIDE the {bar + menu} subtree is the bar surface’s grab dismiss → deactivate. Outside a session it defers to the base AgtHBox (keys / closed-title clicks).

Public Static Functions

static inline AgtMenuBarBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

Public Static Attributes

static constexpr int MAX_MENUS = 16

Maximum top-level titles. Fixed inline arrays — a real menu bar has a handful of titles; past this add_menu returns NULL.

static constexpr uint16_t ID_LAST = AgtHBox::ID_LAST

FOX-style message-ID chain (see AgtHBox::ID_LAST).

AgtMenu

class AgtMenu : public AgtFrame

Public Functions

AgtMenu(AgtMenuBar *bar, AgtWindow *window) noexcept

Construct a detached popup owned by bar. window is the root window it attaches to when shown (the menu bar resolves + refreshes it at open time, so passing the known root here is an optimization, not a requirement — NULL is fine).

~AgtMenu() noexcept override

Tear down the open surface (if any) and delete owned submenu popups. A menu is never a window child, so a delete-while-shown path explicitly notifies the window to drop any focused/hovered back-pointer (UAF guard).

AgtMenu(const AgtMenu&) = delete
AgtMenu(AgtMenu&&) = delete
AgtMenu &operator=(const AgtMenu&) = delete
AgtMenu &operator=(AgtMenu&&) = delete
AgtMenuItem *add_item(const char *label, const char *shortcut = nullptr, AgtObject *target = nullptr, uint16_t target_id = 0) noexcept

Append a menu row. target / target_id are forwarded to the row’s AgtMenuItem — a selection emits AGT_SEL_COMMAND to target exactly like a button click. Returns the new row, or NULL if the popup is full. Re-lays-out the popup so geometry stays valid before show.

AgtMenu *add_submenu(const char *label, const char *shortcut = nullptr) noexcept

Append a submenu row: a row that, instead of emitting a command, opens a nested child popup cascading to its right. Returns the (empty) child AgtMenu for the caller to populate via add_item / add_submenu; the child is OWNED by this popup. The row renders a right-pointing arrow indicator. Returns NULL if the popup is full or allocation failed.

AgtMenuItem *add_check_item(const char *label, AgtObject *target = nullptr, uint16_t target_id = 0, bool checked = false) noexcept

Append a checkable command row — a checkbox showing a checkmark while checked. Otherwise identical to add_item. Once any markable row is added the popup reserves a left glyph gutter for EVERY row, so labels stay column-aligned. Returns the row, or NULL if full.

AgtMenuItem *add_radio_item(const char *label, uint16_t group, AgtObject *target = nullptr, uint16_t target_id = 0) noexcept

Append a radio row in group (must be non-zero). Rows sharing a group are mutually exclusive: activating one — by click or Enter — checks it and clears the rest (and set_radio_selected does the same programmatically). Returns the row, or NULL if full.

void set_radio_selected(uint16_t group, uint16_t target_id) noexcept

Check the row in radio group whose target_id matches target_id and clear every other row in that group. The consumer calls this to reflect state it owns (e.g. the current encoding) — at startup and whenever that state changes by any path. No-op if no row matches.

inline AgtMenu *active_submenu() const noexcept

Currently-open child submenu, or NULL. Exposed for tests + the nav state machine.

inline bool is_submenu() const noexcept

True when this popup is itself a submenu (has a parent popup), false for a top-level popup owned by a menu bar. Controls Left/Right key semantics: a submenu’s Left closes back to its parent, a top-level popup’s Left switches menu- bar menus.

void on_submenu_dismissed(AgtMenu *child) noexcept

Called by an owned child submenu when it dismisses itself, so this popup drops its active_submenu_ tracking.

inline int item_count() const noexcept

Number of item rows added.

AgtMenuItem *item_at(int index) const noexcept

Item row by index, or NULL if out of range.

void show_at(int screen_x, int screen_y) noexcept

Open the popup’s child surface at output position (screen_x, screen_y) — nested under the bar’s session surface (top-level) or the parent menu’s surface (submenu) — take AGT-side keyboard focus, and highlight the first row. No-op if already shown or there is no window to attach to.

void dismiss() noexcept

Tear down: close any child submenu, clear highlights, close this popup’s surface, and hand keyboard focus back up the chain. Safe to call when not shown (no-op). Idempotent.

inline bool shown() const noexcept
inline AxlSurface *surface() const noexcept

This menu’s own child AxlSurface (C7 P4), or NULL when closed / headless. A submenu parents its surface under this one (so one grab on the bar surface confines the whole chain).

inline void set_window(AgtWindow *w) noexcept

Set the root window this popup attaches to when shown. Called by the owning AgtMenuBar at open time so the popup always targets the live root even if the tree was assembled after the popup was created.

inline AgtWindow *window() const noexcept

The window this popup targets. Intentionally shadows AgtWidget::window(): the popup caches the window pointer so it’s valid even before the popup is attached to the tree (the menu bar sets it at open time), whereas the inherited walk would return NULL for a popup that is never a window child.

inline int highlight_index() const noexcept

Index of the currently keyboard-highlighted row, or -1 for none. Exposed for the menu navigation state machine + tests.

void set_highlight_index(int index) noexcept

Move the keyboard highlight to index (clamped/ignored if out of range; pass -1 to clear). Repaints the affected rows. Used by show_at (highlight first) and the 2.7f-B arrow-key handler.

bool handle_event(AgtEvent &ev) override

Route seat-delivered positional events (content-local) to the row under the pointer with hover bookkeeping; a leaf release selects + tears down the chain. Everything else (keys, focus) defers to the base map dispatch.

void draw(AgtDrawContext &ctx) override

Paint a soft drop shadow under the popup (depth cue for the floating menu), then delegate to AgtFrame::draw for the panel background + border. Items paint on top via the render-walk.

int dirty_margin() const noexcept override

Over-draw margin covering the drop shadow (incremental present).

long on_key_press(AgtObject *sender, AgtEvent *ev)

Keyboard handler (the popup holds focus while shown) — see the “Keyboard model” note above: Up / Down move the row highlight (wrapping); Enter selects a leaf or opens a submenu row; Escape / Left close one level; Right opens a submenu or (top-level only) switches menu-bar menus; F10 exits menu mode.

Public Static Attributes

static constexpr int MAX_ITEMS = 32

Maximum item rows per popup. Fixed inline array — pre-boot menus are short (a dozen commands is already a lot); past this, add_item returns NULL.

static constexpr uint16_t ID_LAST = AgtFrame::ID_LAST

FOX-style message-ID chain (see AgtFrame::ID_LAST).

AgtMessageBox

class AgtMessageBox : public AgtDialog

Public Types

enum Buttons

Which buttons appear at the bottom of the dialog.

Values:

enumerator OK

Single “OK” button.

enumerator OK_CANCEL

“OK” (default) + “Cancel”.

enumerator YES_NO

“Yes” (default) + “No”.

enumerator SAVE_DISCARD_CANCEL

Three-way “save changes?” prompt: “Cancel” (Escape) / “Don’t Save” / “Save” (default, left → right).

Public Functions

AgtMessageBox(const char *title, const char *message, Buttons buttons = OK) noexcept

Construct a modal MessageBox. Title appears at the top; message in the body; buttons across the bottom row. All strings are held by pointer — caller storage must outlive the dialog (typically string literals).

Public Static Functions

static int info(AgtApp *app, const char *title, const char *message) noexcept

Show an informational MessageBox (single OK button). Stack-allocates internally, runs the modal, returns the result code (always RESULT_OK on dismissal — Enter or click on OK both produce it). Reads as the typical “info dialog” call site: AgtMessageBox::info(&app, “Done”, “Operation complete.”);

static int question(AgtApp *app, const char *title, const char *message) noexcept

Show a yes/no question MessageBox. Default button is Yes; Cancel button (also Escape) is No. Returns RESULT_YES or RESULT_NO.

static int save_changes(AgtApp *app, const char *title, const char *message) noexcept

Show a three-way “save changes?” prompt: “Save” (default / Enter), “Don’t Save”, and “Cancel” (also Escape). Returns RESULT_SAVE, RESULT_DISCARD, or RESULT_CANCEL — the standard editor close-with-unsaved-edits gesture.

Public Static Attributes

static constexpr int RESULT_OK = 1
static constexpr int RESULT_CANCEL = 0
static constexpr int RESULT_YES = 1
static constexpr int RESULT_NO = 0
static constexpr int RESULT_SAVE = 1

affirmative (default/Enter)

static constexpr int RESULT_DISCARD = 2

“Don’t Save”

static constexpr uint16_t ID_LAST = AgtDialog::ID_LAST

FOX-style message-ID chain (see AgtDialog::ID_LAST).

AgtPromptDialog

class AgtPromptDialog : public AgtDialog

Public Functions

AgtPromptDialog(const char *title, const char *prompt, const char *default_text = "") noexcept

Construct. default_text pre-populates the edit field and is selected so the user can type to replace. Typical use: AgtPromptDialog dlg(“Name”, “Enter device name:”); if (dlg.run(&app) == AgtPromptDialog::RESULT_OK) { use(dlg.text()); // valid while dlg is in scope } No static convenience helper because returning the entered string past the dialog’s lifetime would require an output buffer or heap copy at every call site — direct construction reads cleaner.

const char *text() const noexcept

Current text in the input field. Valid throughout the dialog’s lifetime; reads the latest committed buffer on dismissal.

Public Static Attributes

static constexpr int RESULT_OK = 1
static constexpr int RESULT_CANCEL = 0
static constexpr uint16_t ID_LAST = AgtDialog::ID_LAST

AgtScrollFrame

class AgtScrollFrame : public AgtFrame

Public Types

Message-ID chain. ID_VSCROLL / ID_HSCROLL are the ids the owned bars target the frame with (internal); consumer ids start at ID_LAST.

Values:

enumerator ID_VSCROLL
enumerator ID_HSCROLL
enumerator ID_LAST

Public Functions

AgtScrollFrame(AgtWidget *parent, int x, int y, int w, int h, bool horizontal = false) noexcept

Construct a scroll frame. horizontal adds a bottom horizontal scrollbar (default vertical-only).

inline AgtWidget *content() const noexcept

The scrollable content container. Parent your widgets to this (their coordinates are content-relative). Owned by the frame (cascade-freed).

void set_content_size(int w, int h) noexcept

Set the logical content extent. Sizes the content child, updates the scroll ranges, and re-clamps the current offset. No command.

inline int content_width() const noexcept
inline int content_height() const noexcept
inline int scroll_x() const noexcept
inline int scroll_y() const noexcept
void scroll_to(int x, int y) noexcept

Scroll so content offset (x, y) sits at the viewport top-left. Clamps to [0, max]; repositions the content child; silently syncs the bars. No SEL_COMMAND.

int viewport_width() const noexcept

Visible content area (inner rect minus the scrollbar strips).

int viewport_height() const noexcept
bool wants_child_clip(AxlGfxClip &out) const noexcept override

Confine children to the viewport (the frame’s inner rect, local coords) — render + hit-test both honor it.

inline uint32_t focus_policy() const noexcept override

The viewport itself is not a Tab target — the focusable widgets live inside content().

void draw(AgtDrawContext &ctx) override
long on_mouse_wheel(AgtObject *sender, AgtEvent *ev)
long on_vscroll(AgtObject *sender, AgtEvent *ev)

SEL_COMMAND from the owned vertical bar (id == ID_VSCROLL).

long on_hscroll(AgtObject *sender, AgtEvent *ev)

SEL_COMMAND from the owned horizontal bar (id == ID_HSCROLL).

Public Static Functions

static inline AgtScrollFrameBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

AgtComboBox

class AgtComboBox : public AgtFrame

Public Types

Message-ID chain. ID_LIST is the id the owned list targets the combo with (internal); consumer ids start at ID_LAST.

Values:

enumerator ID_LIST
enumerator ID_LAST

Public Functions

AgtComboBox(AgtWidget *parent, int x, int y, int w, int h, AgtObject *target = nullptr, uint16_t target_id = 0) noexcept
~AgtComboBox() noexcept override
int add_item(const char *text) noexcept

Append an option (text copied). Returns the new index or -1.

void clear() noexcept
int item_count() const noexcept
const char *item_text(int index) const noexcept
int current() const noexcept

Selected option index, or -1 if none.

void set_current(int index) noexcept

Set the selection programmatically (clamped; -1 clears). Updates the field. No SEL_COMMAND.

const char *current_text() const noexcept

Text of the current selection, or “” when nothing is selected.

inline bool is_open() const noexcept
void set_target(AgtObject *target, uint16_t target_id) noexcept
inline AgtObject *target() const noexcept
inline uint16_t target_id() const noexcept
inline uint32_t focus_policy() const noexcept override
void draw(AgtDrawContext &ctx) override
long on_left_button_press(AgtObject *sender, AgtEvent *ev)
long on_key_press(AgtObject *sender, AgtEvent *ev)
long on_list_command(AgtObject *sender, AgtEvent *ev)

SEL_COMMAND from the owned list (id == ID_LIST) — a browse update.

void request_deferred_commit(int index) noexcept

Commit index, but DEFERRED to the next loop tick. The dropdown’s seat-activation path calls this: commit_ tears down the dropdown surface the seat is mid-dispatch through (and the emit may delete this combo), so the work must run after the dispatch unwinds. Scheduled via axl_defer; cancelled on close / destroy. Falls back to a synchronous commit only when no loop is reachable. Public for direct testing.

Public Static Functions

static inline AgtComboBoxBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

AgtTabView

class AgtTabView : public AgtFrame

Public Functions

AgtTabView(AgtWidget *parent, int x, int y, int w, int h, BorderStyle border = AGT_FRAME_LINE, int border_width = 1) noexcept
~AgtTabView() noexcept override

Free the owned tab labels. The tab buttons + page containers are children, freed by the AgtObject cascade.

AgtWidget *add_tab(const char *label)

Add a tab titled label; returns its (initially empty) page container for the caller to populate, or NULL if MAX_TABS is exceeded. The label is copied. The first tab added is active.

inline int tab_count() const noexcept
inline int active() const noexcept
void set_active(int index) noexcept

Make tab index active (clamped to a valid tab); shows its page, hides the rest. No-op when there are no tabs. When the active tab actually changes and a change target is registered (set_change_target), emits AGT_SEL_COMMAND to it so the consumer can react (e.g. move keyboard focus into the new page) — the view itself does NOT move focus.

bool remove_tab(int index) noexcept

Remove tab index: deletes its page (cascading the hosted content) and tab button, frees the label, and compacts the remaining tabs so their ids stay equal to their index. Refuses to remove the LAST tab (a tab view always shows a page; the caller decides last-tab policy) and any out-of-range index — returns false in those cases, true on a successful removal. Adjusts active() to stay in range; does NOT emit the change signal (the caller drives any post-remove refresh / focus, since index semantics shift under compaction).

inline void set_change_target(AgtObject *target, uint16_t id) noexcept

Register the object notified when the active tab changes (via click, Ctrl+Tab, arrows, or a programmatic set_active that moves it). The target receives AGT_SEL_COMMAND with message_id == id; it reads active() for the new index. NULL target (the default) disables the signal. Mirrors AgtButton::set_target.

void set_tab_label(int index, const char *label) noexcept

Replace tab index’s title with a copy of label (NULL = empty). No-op for an out-of-range index. The view owns the copy; the tab button points at it. For a consumer that renames a tab as its content changes (a file’s basename, a dirty * marker).

AgtWidget *page(int index) const noexcept

The page container for tab index, or NULL if out of range.

inline int tab_strip_height() const noexcept
void set_tab_strip_height(int h) noexcept
inline int tab_width() const noexcept
void set_tab_width(int w) noexcept
void draw(AgtDrawContext &ctx) override
long on_tab(AgtObject *sender, AgtEvent *ev)

Tab-button command handler — message_id is the tab index.

long on_key_press(AgtObject *sender, AgtEvent *ev)

Ctrl+Tab / Ctrl+Shift+Tab cycle tabs (wrap); Left/Right switch when a tab button has focus (so page-content arrows are never hijacked). The view itself isn’t a tab stop — these reach it by bubbling up from a focused tab button or page-content widget.

Public Static Functions

static inline AgtTabViewBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

Public Static Attributes

static constexpr uint16_t ID_LAST = AgtFrame::ID_LAST

FOX-style message-ID chain (see AgtFrame::ID_LAST). Tab buttons target the view with message_id in [0, MAX_TABS), matched by an AGT_MAP_COMMAND_RANGE — so the view reserves that low-id range.

static constexpr int MAX_TABS = 12

Upper bound on tabs (fixed-capacity, no heap array — mirrors AgtMenuBar::MAX_MENUS).

static constexpr int DEFAULT_TAB_WIDTH = 110

Default per-tab button width (overridable via set_tab_width).

AgtToolBar

class AgtToolBar : public AgtHBox

Public Functions

AgtToolBar(AgtWidget *parent, int x, int y, int w, int h, int spacing = DEFAULT_SPACING, BorderStyle border = AGT_FRAME_NONE, int border_width = 0) noexcept
AgtButton *add_button(const char *label, AgtObject *target, uint16_t id, int width = DEFAULT_BUTTON_WIDTH)

Append a flat command button titled label that emits SEL_COMMAND to target with id on click. Sized width by the bar’s inner height; returns it (for further styling).

AgtBitmapButton *add_icon_button(AgtStyle::StockIcon icon, const char *tooltip, AgtObject *target, uint16_t id)

Append a flat, square icon button (icon-only AgtBitmapButton) sized to the bar height, emitting SEL_COMMAND to target with id on click, with a hover tooltip. Two icon sources:

  • a built-in themeable stock icon (AgtStyle::StockIcon), or

  • a consumer bitmap (bytes / len, decoded like set_icon). Returns the button (for further styling).

AgtBitmapButton *add_icon_button(const uint8_t *bytes, size_t len, const char *tooltip, AgtObject *target, uint16_t id)
AgtSeparator *add_separator()

Append a vertical separator (a group divider). Returns it.

void draw(AgtDrawContext &ctx) override

Base fill (header tone) + a 1px bottom divider before the content.

Public Static Functions

static inline AgtToolBarBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

Public Static Attributes

static constexpr uint16_t ID_LAST = AgtHBox::ID_LAST
static constexpr int DEFAULT_BUTTON_WIDTH = 96

Fallbacks used when the bar’s own height isn’t usable yet.

static constexpr int DEFAULT_BUTTON_HEIGHT = 34
static constexpr int DEFAULT_SPACING = 6
static constexpr int SEPARATOR_WIDTH = 8

AgtSpinBox

class AgtSpinBox : public AgtFrame

Public Types

Message-ID chain. ID_UP / ID_DOWN are the ids the owned stepper buttons target the box with (internal); consumer ids start at ID_LAST.

Values:

enumerator ID_UP
enumerator ID_DOWN
enumerator ID_LAST

Public Functions

AgtSpinBox(AgtWidget *parent, int x, int y, int w, int h, int value, int min_value, int max_value, int step = 1, AgtObject *target = nullptr, uint16_t target_id = 0, BorderStyle border = AGT_FRAME_SUNKEN, int border_width = 1) noexcept
inline int value() const noexcept
inline int min_value() const noexcept
inline int max_value() const noexcept
inline int step() const noexcept
void set_value(int v) noexcept

Set the value, clamped to [min, max]. No SEL_COMMAND — only a stepper press emits.

void set_range(int lo, int hi) noexcept

Set the inclusive range (swapped if lo > hi); re-clamps value.

void set_step(int s) noexcept
void set_target(AgtObject *target, uint16_t target_id) noexcept
void draw(AgtDrawContext &ctx) override
long on_step(AgtObject *sender, AgtEvent *ev)

Stepper command handler — ID_UP / ID_DOWN.

long on_key_press(AgtObject *sender, AgtEvent *ev)

Up/Right +step, Down/Left -step, PageUp/Down ±step, Home/End to min/max. Emits SEL_COMMAND on change like a stepper press. Reaches the box by bubbling from a focused +/- stepper button (the box itself is not a separate tab stop).

Public Static Functions

static inline AgtSpinBoxBuilder build(AgtWidget *parent) noexcept

Fluent builder: AgtSpinBox::build(p).bounds(...).range(0,9).value(3) .target(&t,id).create(). See agt-builder.hpp.

AgtExpander

class AgtExpander : public AgtFrame

Public Functions

AgtExpander(AgtWidget *parent, int x, int y, int w, int h, const char *title, bool expanded = true, BorderStyle border = AGT_FRAME_LINE, int border_width = 1) noexcept
~AgtExpander() noexcept override
inline AgtWidget *content() const noexcept

The content container — parent your section widgets here.

inline bool expanded() const noexcept
void set_expanded(bool expanded) noexcept

Expand / collapse: toggles content() visibility and the expander’s own height (header-only when collapsed).

inline int header_height() const noexcept
void set_title(const char *title)

The title text (copied).

inline const char *title() const noexcept
inline uint32_t focus_policy() const noexcept override

The header is keyboard-focusable so Space / Enter can toggle it.

void draw(AgtDrawContext &ctx) override
long on_press(AgtObject *sender, AgtEvent *ev)

Header-click handler (wired via the message map).

long on_key_press(AgtObject *sender, AgtEvent *ev)

Space / Enter toggle expand/collapse (keyboard parity with a click).

Public Static Functions

static inline AgtExpanderBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

Public Static Attributes

static constexpr uint16_t ID_LAST = AgtFrame::ID_LAST

AgtTreeView

class AgtTreeView : public AgtFrame

Public Types

ID_SCROLLBAR is the id the owned scrollbar targets the view with; consumer command ids start at ID_LAST.

Values:

enumerator ID_SCROLLBAR
enumerator ID_LAST

Public Functions

AgtTreeView(AgtWidget *parent, int x, int y, int w, int h, AgtObject *target = nullptr, uint16_t target_id = 0) noexcept
~AgtTreeView() noexcept override
AxlNTree *add_item(AxlNTree *parent, const char *label) noexcept

Add a child item under parent (NULL = a top-level item); the label is copied. Returns the node handle (owned by the view), or NULL on allocation failure.

void clear() noexcept

Remove every item (frees labels + nodes); resets selection/scroll.

const char *item_label(const AxlNTree *item) const noexcept

The item’s label, or NULL. Borrowed — the view owns it.

int visible_count() const noexcept

Number of currently-visible (flattened) rows.

bool is_expanded(const AxlNTree *item) const noexcept
void set_expanded(AxlNTree *item, bool expanded) noexcept
inline AxlNTree *current() const noexcept
void set_current(AxlNTree *item) noexcept

Select item (NULL clears); scrolls it into view. No command.

inline int top() const noexcept
int visible_rows() const noexcept
inline int row_height() const noexcept
void set_row_height(int px) noexcept
inline AxlGfxPixel text_color() const noexcept
void set_text_color(AxlGfxPixel c) noexcept
void set_selection_colors(AxlGfxPixel bg, AxlGfxPixel text) noexcept
void set_target(AgtObject *target, uint16_t target_id) noexcept
inline AgtObject *target() const noexcept
inline uint16_t target_id() const noexcept
inline uint32_t focus_policy() const noexcept override
void draw(AgtDrawContext &ctx) override
long on_left_button_press(AgtObject *sender, AgtEvent *ev)
long on_key_press(AgtObject *sender, AgtEvent *ev)
long on_mouse_wheel(AgtObject *sender, AgtEvent *ev)
long on_focus_in(AgtObject *sender, AgtEvent *ev)
long on_focus_out(AgtObject *sender, AgtEvent *ev)
long on_scroll(AgtObject *sender, AgtEvent *ev)

Public Static Functions

static inline AgtTreeViewBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

AgtFileDialog

class AgtFileDialog : public AgtDialog

Public Functions

AgtFileDialog(const char *title, const char *initial_dir) noexcept

initial_dir is the directory to open at (e.g. "fs0:\\").

~AgtFileDialog() noexcept override
inline const char *directory() const noexcept

The directory currently shown.

inline const char *path() const noexcept

The picked file path after RESULT_OK, else empty.

void set_directory(const char *dir) noexcept

Show dir (copied) and read its entries (best-effort: an unreadable directory just shows an empty list).

void navigate_up() noexcept

Navigate to the parent directory (clamped at the volume root).

int entry_count() const noexcept
long on_list(AgtObject *sender, AgtEvent *ev)
long on_open(AgtObject *sender, AgtEvent *ev)
long on_up(AgtObject *sender, AgtEvent *ev)

Public Static Attributes

static constexpr int RESULT_OK = 1
static constexpr int RESULT_CANCEL = 0
static constexpr uint16_t ID_LAST = AgtDialog::ID_LAST

AgtMainWindow

class AgtMainWindow : public AgtWindow

Public Functions

AgtMainWindow() noexcept = default
explicit AgtMainWindow(AgtApp &app) noexcept

Convenience: construct + bind the app’s input seat (see AgtWindow(AgtApp&)).

inline void set_menu_bar(AgtMenuBar *bar) noexcept

Register a chrome / content widget for a dock slot. Each must be a child of this window (so the render walk reaches it); set_* only records the pointer for layout. Passing NULL clears the slot.

inline void set_tool_bar(AgtToolBar *bar) noexcept
inline void set_status_bar(AgtStatusBar *bar) noexcept
inline void set_content(AgtWidget *content) noexcept
inline AgtMenuBar *menu_bar() const noexcept
inline AgtToolBar *tool_bar() const noexcept
inline AgtStatusBar *status_bar() const noexcept
inline AgtWidget *content() const noexcept
void draw(AgtDrawContext &ctx) override

Lay out the dock slots (top→down for menu/tool bars, bottom for the status bar, content fills the rest), then clear the background. Runs before the render walk descends, exactly like a box’s draw().

void on_widget_destroyed(AgtWidget *w) noexcept override

Clear any slot pointer matching a destroyed widget (UAF guard), then defer to the base window cleanup.

AgtSearchEntry

class AgtSearchEntry : public AgtEditField

Public Functions

AgtSearchEntry(AgtWidget *parent, int x, int y, int w, int h, const char *text = "", AgtObject *target = nullptr, uint16_t target_id = 0) noexcept
inline bool show_clear() const noexcept

Whether the clear (×) button appears when the field has text. Default true. Turning it off mid-text hides the button.

void set_show_clear(bool on) noexcept
inline AxlGfxPixel icon_color() const noexcept

Tint of the magnifier + clear glyphs. Defaults to the muted disabled-text grey so the chrome stays subordinate to content.

void set_icon_color(AxlGfxPixel c) noexcept
inline bool clear_visible() const noexcept

True when the clear button is currently visible — i.e. show_clear() is on and the field holds at least one byte.

void draw(AgtDrawContext &ctx) override

Paint the field (AgtEditField) then the magnifier and, when visible, the clear glyph on top of the gutters.

long on_left_button_press(AgtObject *sender, AgtEvent *ev)

Intercept a press on a visible clear button; otherwise defer to AgtEditField for cursor positioning + focus.

Public Static Functions

static inline AgtSearchEntryBuilder build(AgtWidget *parent) noexcept

Fluent builder: AgtSearchEntry::build(p).size(200,28).create(). See agt-builder.hpp.

Public Static Attributes

static constexpr int ICON_SIZE = 12

Side length (px) of the magnifier / clear glyph boxes.

static constexpr uint16_t ID_LAST = AgtEditField::ID_LAST

FOX-style message-ID chain (see AgtEditField::ID_LAST).

AgtPasswordField

class AgtPasswordField : public AgtEditField

Public Functions

AgtPasswordField(AgtWidget *parent, int x, int y, int w, int h, const char *text = "", AgtObject *target = nullptr, uint16_t target_id = 0) noexcept
inline bool show_reveal() const noexcept

Whether the reveal (eye) button is shown. Default true; turning it off hides the eye AND re-masks the field (a hidden eye must not leave a secret revealed).

void set_show_reveal(bool on) noexcept
inline bool revealed() const noexcept

True when the secret is currently shown (echo mode == NORMAL).

void set_revealed(bool on) noexcept

Show / hide the secret (flip the base echo mode). A no-op toward an already-matching state.

inline void toggle_reveal() noexcept

Flip the reveal state — the eye-button action.

inline bool eye_visible() const noexcept

True when the eye button is currently on screen (i.e. show_reveal()).

inline AxlGfxPixel icon_color() const noexcept

Tint of the eye glyph. Defaults to the muted disabled-text grey so the chrome stays subordinate to content (matches AgtSearchEntry).

void set_icon_color(AxlGfxPixel c) noexcept
void draw(AgtDrawContext &ctx) override

Paint the field (AgtEditField) then the eye glyph in the right gutter.

long on_left_button_press(AgtObject *sender, AgtEvent *ev)

Intercept a press on a visible eye button (toggle reveal); otherwise defer to AgtEditField for cursor positioning + focus.

void restyle() noexcept override

Re-snapshot the eye tint on a theme swap.

Public Static Functions

static inline AgtPasswordFieldBuilder build(AgtWidget *parent) noexcept

Fluent builder: AgtPasswordField::build(p).size(220,28).create().

Public Static Attributes

static constexpr int ICON_SIZE = 12

Side length (px) of the eye glyph box.

static constexpr uint16_t ID_LAST = AgtEditField::ID_LAST

FOX-style message-ID chain (see AgtEditField::ID_LAST).

AgtProgressDialog

class AgtProgressDialog : public AgtDialog

Public Types

enum Mode

Indicator style, fixed at construction.

Values:

enumerator DETERMINATE

an AgtProgressBar driven by set_progress()

enumerator INDETERMINATE

an AgtSpinner (unknown duration)

Public Functions

AgtProgressDialog(const char *title, const char *message, Mode mode = DETERMINATE, bool cancelable = true) noexcept

Construct. title is the draggable card bar; message the caption above the indicator. cancelable adds a Cancel button (also Escape) that dismisses with RESULT_CANCEL. Strings are held by pointer — caller storage must outlive the dialog.

inline Mode mode() const noexcept
inline bool cancelable() const noexcept
void set_progress(int value) noexcept

Set the current value (clamped to the range) and repaint.

int progress() const noexcept
void set_range(int min, int max) noexcept

Replace the inclusive range and repaint.

int min() const noexcept
int max() const noexcept
void start() noexcept

Start the spinner self-animating. Call after show() (a shown dialog reaches the app loop via window()->loop(), so the spinner’s timer arms on it). No-op for a determinate dialog. For a MODAL run() (which blocks before you could call this), drive the spinner with advance() from your work instead.

void stop() noexcept

Stop the spinner’s self-animation. Safe any time. The Cancel route stops it automatically; if you close() an indeterminate dialog programmatically AND keep it alive, call this first so the spinner timer doesn’t free-run (harmless no-op redraws) until destruction.

void advance() noexcept

Step the spinner one frame + repaint — the manual drive for blocking work that doesn’t return to the loop. No-op for a determinate dialog.

void set_message(const char *message) noexcept

Replace the caption text and repaint.

inline bool was_canceled() const noexcept

True only after the user dismissed the dialog via Cancel / Escape — distinct from a programmatic close(code) and false before any dismissal (unlike a bare result() == RESULT_CANCEL, which a never-dismissed dialog also satisfies).

long on_cancel(AgtObject *sender, AgtEvent *ev)

Cancel route (Cancel button click / Escape): record the cancellation, stop the spinner, then dismiss with RESULT_CANCEL. Public so a test can invoke it directly. Shadows the base range on_command_dismiss for RESULT_CANCEL only (the dialog’s sole command).

~AgtProgressDialog() noexcept override

Stop the spinner before teardown (belt-and-suspenders; the child spinner’s own dtor also stops it).

Public Static Attributes

static constexpr int RESULT_CANCEL = 0

Cancel maps to the negative dismiss code (Escape → Cancel for free).

static constexpr uint16_t ID_LAST = AgtDialog::ID_LAST

FOX-style message-ID chain (see AgtDialog::ID_LAST).

AgtForm

struct AgtFormField

A single form field. POD aggregate — no user-declared constructors, no virtuals; the static factory helpers below just return aggregate-initialized values, and the with_* / visible_if / enabled_if chain helpers return a modified COPY (so they compose in an array initializer: AgtFormField::boolean("Advanced", &adv).visible_if(show_adv)).

Per-type field usage:

  • SECTIONlabel (+ optional help).

  • BOOLbool_ptr (or get_fn/set_fn); default_int (0/1).

  • INTint_ptr (or get_fn/set_fn); min/max/step; default_int.

  • ENUMint_ptr (selected index) (or get_fn/set_fn); choices/choice_count; default_int.

  • TEXTtext_ptr + text_cap (buffer); default_text.

  • PASSWORDtext_ptr + text_cap; default_text.

  • ACTIONaction_fn (+ user).

Public Functions

inline AgtFormField with_help(const char *h) const noexcept

Attach a hover-help string (row tooltip).

inline AgtFormField with_user(void *u) const noexcept

Attach a user pointer (passed to predicates / get / set).

inline AgtFormField with_getset(AgtFormGetFn g, AgtFormSetFn s, void *u = nullptr) const noexcept

Attach a computed get/set binding (overrides the typed pointer). Only int-like fields (BOOL/INT/ENUM) support a computed binding — TEXT/ PASSWORD are always buffer-bound. u updates user ONLY when non-null, so a prior .with_user(p) is preserved if you omit it.

inline AgtFormField visible_if(AgtFormPredicate p) const noexcept

Attach a suppressif-style visibility predicate.

inline AgtFormField enabled_if(AgtFormPredicate p) const noexcept

Attach a grayoutif-style enable predicate.

inline bool is_int_like() const noexcept

True for the value-bearing field kinds (BOOL/INT/ENUM use the int slot).

inline bool is_text_like() const noexcept

True for the text-bearing field kinds.

Public Members

AgtFieldType type = AGT_FIELD_SECTION
const char *label = ""

borrowed; row caption / button text

const char *help = nullptr

borrowed; optional row tooltip

bool *bool_ptr = nullptr

BOOL direct binding.

int *int_ptr = nullptr

INT / ENUM direct binding.

char *text_ptr = nullptr

TEXT / PASSWORD buffer.

int text_cap = 0

capacity of text_ptr (incl. NUL)

AgtFormGetFn get_fn = nullptr

computed int-like read (overrides *_ptr)

AgtFormSetFn set_fn = nullptr

computed int-like write (overrides *_ptr)

void *user = nullptr

passed to get/set/action/predicates

int min = 0

INT lower bound (inclusive)

int max = 0

INT upper bound (inclusive)

int step = 1

INT spinner step.

const char *const *choices = nullptr

ENUM option labels (borrowed)

int choice_count = 0

ENUM option count.

AgtFormActionFn action_fn = nullptr
AgtFormPredicate visible_pred = nullptr
AgtFormPredicate enabled_pred = nullptr
int default_int = 0

BOOL/INT/ENUM default working value.

const char *default_text = ""

TEXT/PASSWORD default working value.

Public Static Functions

static inline AgtFormField section(const char *label) noexcept

A group header / divider row.

static inline AgtFormField boolean(const char *label, bool *binding, bool deflt = false) noexcept

An on/off field bound to binding. deflt seeds Load-Defaults.

static inline AgtFormField integer(const char *label, int *binding, int min, int max, int step = 1, int deflt = 0) noexcept

An integer field bound to binding, clamped to [min, max], stepped by step.

static inline AgtFormField choice(const char *label, int *binding, const char *const *choices, int count, int deflt = 0) noexcept

A one-of-N field; binding holds the selected index into choices.

static inline AgtFormField text(const char *label, char *buf, int cap, const char *deflt = "") noexcept

A free-text field over the caller’s buf (cap bytes incl. NUL).

static inline AgtFormField password(const char *label, char *buf, int cap, const char *deflt = "") noexcept

A masked-text field over the caller’s buf.

static inline AgtFormField action(const char *label, AgtFormActionFn fn, void *user = nullptr) noexcept

A button that fires fn(user) when pressed.

class AgtForm

The form model: a borrowed field array + a fixed-capacity working copy and the Save / Discard / Load-Defaults commit model. The working copy is read from the bindings at construction (load()); editors mutate it; save() commits it back to the bindings.

Public Functions

AgtForm(const AgtFormField *fields, int count) noexcept

Construct over fields (count count, clamped to MAX_FIELDS). The array is BORROWED (caller keeps it alive for the form’s lifetime — it is typically static const). Immediately load()s the working copy from the bindings.

inline int count() const noexcept
inline const AgtFormField &field(int i) const noexcept

The descriptor for field i. i MUST be in [0, count()) — the browser indexes by a valid field number; out-of-range is UB.

bool field_visible(int i) const noexcept

True if field i has no visible_if or its predicate returns true.

bool field_enabled(int i) const noexcept

True if field i has no enabled_if or its predicate returns true.

int work_int(int i) const noexcept
inline bool work_bool(int i) const noexcept
const char *work_text(int i) const noexcept
void set_work_int(int i, int v) noexcept

Set the int slot, clamped to the field’s constraints (INT range, ENUM index range, BOOL 0/1).

inline void set_work_bool(int i, bool v) noexcept
void set_work_text(int i, const char *v) noexcept

Set the text slot (bounded copy into the working buffer).

int read_binding_int(int i) const noexcept
void write_binding_int(int i, int v) const noexcept
const char *read_binding_text(int i) const noexcept
void write_binding_text(int i, const char *v) const noexcept
void load() noexcept

Working ← bindings (called by the ctor; the Discard path). Copies the binding value RAW — an out-of-range binding is preserved, NOT clamped (so a no-op save() writes it back unchanged); a value is normalized only when the user actually edits it via set_work_int.

void save() noexcept

Bindings ← working — the commit. SECTION / ACTION fields are skipped. Ends with a load() resync so the working copy reflects what the bindings now hold (a get/set binding may transform the value, a text buffer may truncate it), leaving is_dirty() clean right after Save.

inline void discard() noexcept

Working ← bindings — revert in-progress edits.

void load_defaults() noexcept

Working ← schema defaults (default_int / default_text).

bool is_dirty() const noexcept

True if any field’s working copy differs from its live binding.

void invoke_action(int i) const noexcept

Invoke field i’s ACTION callback (no-op if not an ACTION field).

Public Static Attributes

static constexpr int MAX_FIELDS = 64

Maximum fields in one form (one settings screen / page). Fixed-cap so the working copy needs no allocation. Excess fields are dropped (the count is clamped) — count() reports the honored number.

static constexpr int TEXT_CAP = 264

Working-copy text capacity per field, in bytes incl. NUL. Comfortably over AgtEditField::MAX_TEXT_LEN (256) so a full-length edit-field value round-trips without loss; the consumer’s own text_cap bounds the final commit.

AgtFormBrowser

class AgtFormBrowser : public AgtFrame

Public Types

Message-ID chain. ID_FIELD_BASE + i is the SEL_COMMAND id field i’s editor targets the browser with; Save/Discard/Defaults (Phase D) take the three slots before it. Consumer ids start at ID_LAST.

Values:

enumerator ID_SAVE
enumerator ID_DISCARD
enumerator ID_DEFAULTS
enumerator ID_FIELD_BASE
enumerator ID_LAST

Public Functions

AgtFormBrowser(AgtWidget *parent, int x, int y, int w, int h, const AgtFormField *fields, int count) noexcept

Construct a browser over fields (count count). The array is BORROWED by the owned AgtForm (keep it alive for the browser’s lifetime — typically static const). Builds one editor per field, seeded from the working copy, and lays the rows out.

inline AgtForm &form() noexcept

The owned form model (working copy + commit). Read it for Save state (is_dirty()), or drive save()/discard()/load_defaults() directly.

inline const AgtForm &form() const noexcept
inline int field_count() const noexcept

Number of fields (== form().count()).

AgtWidget *editor_widget(int i) const noexcept

The editor widget for field i (NULL for a SECTION row). Exposed for focus management + testing; the concrete type follows the field type (BOOL→AgtCheckBox, INT→AgtSpinBox, …).

void refresh() noexcept

Re-seed editors from the working copy (after a programmatic form().discard() / load_defaults()), then re-evaluate every field’s visible_if / enabled_if and re-lay-out — rows hide / show / grey to match. Call after any change to the working copy made outside the editors.

inline int content_height() const noexcept

Total laid-out height of the rows (content extent) — what the owned scroll frame sizes its content child to.

void commit() noexcept

Commit the working copy to the live bindings (Save), re-seed the editors from the resynced working copy, update the button enablement, and fire the set_on_save notification (if any).

void discard() noexcept

Revert the working copy to the live bindings (Discard) and re-seed.

void load_defaults() noexcept

Reset the working copy to the schema defaults (Restore Defaults) and re-seed. Does NOT commit — the bindings change only on a later Save.

inline void set_on_save(AgtObject *target, uint16_t target_id) noexcept

Optionally notify target (with AGT_SEL_COMMAND, id target_id) right after a successful commit() — the “settings applied” hook.

void draw(AgtDrawContext &ctx) override
long on_field_command(AgtObject *sender, AgtEvent *ev)

SEL_COMMAND from a field editor (id in [ID_FIELD_BASE, …)): sync that editor’s value into the working copy (or fire an ACTION), then refresh. Public for direct testing.

long on_save(AgtObject *sender, AgtEvent *ev)

Save / Discard / Restore-Defaults button handlers (ID_SAVE / ID_DISCARD / ID_DEFAULTS). Public for direct testing.

long on_discard(AgtObject *sender, AgtEvent *ev)
long on_defaults(AgtObject *sender, AgtEvent *ev)

Public Static Functions

static inline AgtFormBrowserBuilder build(AgtWidget *parent) noexcept

Fluent builder: AgtFormBrowser::build(p).bounds(...).fields(arr, n) .on_save(t, id).create(). See agt-builder.hpp.

AgtTableBase

class AgtTableBase : public AgtFrame

Subclassed by AgtTableEdit, AgtTableView

Public Types

ID_SCROLLBAR is the id the owned scrollbar targets the table with; subclass ids continue from ID_LAST.

Values:

enumerator ID_SCROLLBAR
enumerator ID_LAST

Public Functions

~AgtTableBase() noexcept override
int add_column(const char *header, int width) noexcept

Append a column with header (copied) and pixel width. Returns the column index, or -1 when full / after rows exist (define the schema first).

inline int column_count() const noexcept
const char *column_header(int col) const noexcept
int column_width(int col) const noexcept
void set_column_width(int col, int width) noexcept
virtual int add_row() noexcept

Append an empty row. Returns the row index, or -1 (no columns yet / allocation failure). Virtual so a subclass can react (e.g. seed the active cell).

int row_count() const noexcept
void set_cell(int row, int col, const char *text) noexcept

Set the (row, col) cell text (copied; NULL clears). No-op out of range.

const char *cell(int row, int col) const noexcept

The cell text, or NULL. Borrowed — the table owns it.

virtual void clear_rows() noexcept

Remove every row (frees cell text); keeps the column schema. Virtual so a subclass can also reset its selection / sort state.

inline int top() const noexcept
int visible_rows() const noexcept

Fully-visible data rows in the viewport (below the header).

inline int row_height() const noexcept
void set_row_height(int px) noexcept
inline int header_height() const noexcept
void set_header_height(int px) noexcept
inline AxlGfxPixel text_color() const noexcept
void set_text_color(AxlGfxPixel c) noexcept
void set_header_colors(AxlGfxPixel bg, AxlGfxPixel text) noexcept
void set_target(AgtObject *target, uint16_t target_id) noexcept
inline AgtObject *target() const noexcept
inline uint16_t target_id() const noexcept
inline uint32_t focus_policy() const noexcept override
void draw(AgtDrawContext &ctx) override
long on_mouse_wheel(AgtObject *sender, AgtEvent *ev)
long on_focus_in(AgtObject *sender, AgtEvent *ev)
long on_focus_out(AgtObject *sender, AgtEvent *ev)
long on_scroll(AgtObject *sender, AgtEvent *ev)

Public Static Attributes

static constexpr int MAX_COLS = 16

Maximum columns. Fixed inline per-row cell array — diagnostic tables are a handful of columns wide; past this, add_column returns -1.

AgtTableView

class AgtTableView : public AgtTableBase

Public Types

enum SortMode

How a column’s cells compare when sorted.

Values:

enumerator AGT_SORT_TEXT

case-insensitive string compare (default)

enumerator AGT_SORT_NUMERIC

compare the leading numeric value

Public Functions

AgtTableView(AgtWidget *parent, int x, int y, int w, int h, AgtObject *target = nullptr, uint16_t target_id = 0) noexcept
~AgtTableView() noexcept override
SortMode column_sort_mode(int col) const noexcept

How column col compares when sorted (default AGT_SORT_TEXT).

void set_column_sort_mode(int col, SortMode mode) noexcept
inline bool sortable() const noexcept

Enable/disable clickable, sorting column headers (default off). When off, header clicks do nothing (the v0.1 behavior).

void set_sortable(bool on) noexcept
void sort_by(int col, bool ascending) noexcept

Sort the rows by column col, ascending or descending. The selected row follows the data (selection is preserved by identity, not index). Works regardless of sortable() (that flag gates only the header-click affordance). No-op for an out-of-range column.

inline int sorted_column() const noexcept

The column the rows are currently sorted by, or -1 if never sorted.

inline bool sort_ascending() const noexcept

The current sort direction (meaningful only when sorted_column() >= 0).

void clear_rows() noexcept override
inline int current_row() const noexcept

Selected row index, or -1 for none.

void set_current_row(int row) noexcept

Select row (-1 clears); scrolls it into view. No command.

void set_selection_colors(AxlGfxPixel bg, AxlGfxPixel text) noexcept
long on_left_button_press(AgtObject *sender, AgtEvent *ev)
long on_key_press(AgtObject *sender, AgtEvent *ev)

Public Static Functions

static inline AgtTableViewBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

AgtTableEdit

class AgtTableEdit : public AgtTableBase

Public Types

ID_EDITOR is the id the overlaid edit field targets the table with on Enter; consumer command ids start at ID_LAST.

Values:

enumerator ID_EDITOR
enumerator ID_LAST

Public Functions

AgtTableEdit(AgtWidget *parent, int x, int y, int w, int h, AgtObject *target = nullptr, uint16_t target_id = 0) noexcept
~AgtTableEdit() noexcept override
int add_column(const char *header, int width, bool editable = true) noexcept

Append a column with header (copied), pixel width, and an editable flag (default true) gating in-cell editing for the whole column. Returns the column index, or -1 (full / after rows exist). Shadows AgtTableBase::add_column to carry the per-column flag.

bool column_editable(int col) const noexcept
void set_column_editable(int col, bool editable) noexcept
int add_row() noexcept override
void clear_rows() noexcept override
inline int current_row() const noexcept

Active cell row / column, or -1 for none.

inline int current_col() const noexcept
void set_current_cell(int row, int col) noexcept

Make (row, col) the active cell; clamps to the grid (or clears to -1,-1 if the grid is empty), scrolls the row into view. No command.

void set_active_colors(AxlGfxPixel bg, AxlGfxPixel text) noexcept
inline bool editing() const noexcept

True while the cell editor is open.

void begin_edit() noexcept

Open the editor over the active cell — loads the cell text and selects all (F2 / Enter / double-click semantics). No-op when there is no active cell or its column is read-only (column_editable(col) == false).

void commit_edit() noexcept

Write the editor text into the active cell and close the editor; emits AGT_SEL_COMMAND. Does NOT move the active cell. No-op when not editing.

void cancel_edit() noexcept

Close the editor WITHOUT writing (discard the edit). No-op when not editing.

bool editor_cursor_blink() const noexcept

Whether the in-cell editor’s cursor blinks (default true). Turn off for a steady cursor — accessibility, or a deterministic screenshot.

void set_editor_cursor_blink(bool on) noexcept
long on_left_button_press(AgtObject *sender, AgtEvent *ev)
long on_key_press(AgtObject *sender, AgtEvent *ev)
long on_mouse_wheel(AgtObject *sender, AgtEvent *ev)
long on_editor_commit(AgtObject *sender, AgtEvent *ev)

Public Static Functions

static inline AgtTableEditBuilder build(AgtWidget *parent) noexcept

Fluent builder: see agt-builder.hpp.

AgtTooltip

class AgtTooltip : public AgtFrame

Public Functions

AgtTooltip(AgtWidget *parent, const char *text = "") noexcept

Construct a tooltip parented to parent (normally the window). Sized to text immediately; reposition with move_to. Starts at (0, 0) — the owner hides + places it.

inline const char *text() const noexcept
void set_text(const char *text) noexcept

Replace the (borrowed) text and re-fit the widget to it.

inline AxlGfxPixel text_color() const noexcept
void set_text_color(AxlGfxPixel c) noexcept
inline float text_size() const noexcept
void set_text_size(float px) noexcept
void move_to(int x, int y) noexcept

Move the top-left to (x, y) in parent-local coordinates, clamped so the tooltip stays fully inside the parent (if the parent is a widget). Width / height are unchanged.

void draw(AgtDrawContext &ctx) override

Paint the drop shadow, then the frame (bg + border), then the tip text inside the inner rect.

int dirty_margin() const noexcept override

Over-draw margin covering the drop shadow (so a move / hide damages the shadowed footprint for incremental present).

Public Static Attributes

static constexpr uint16_t ID_LAST = AgtFrame::ID_LAST

FOX-style message-ID chain (see AgtFrame::ID_LAST).