967 lines
40 KiB
C
967 lines
40 KiB
C
// dvxWidget.h — Widget system for DVX GUI
|
|
//
|
|
// A retained-mode widget toolkit layered on top of the DVX window manager.
|
|
// Widgets form a tree (parent-child via firstChild/lastChild/nextSibling
|
|
// pointers) rooted at a per-window VBox container. Layout is automatic:
|
|
// the engine measures minimum sizes bottom-up, then allocates space top-down
|
|
// using a flexbox-like algorithm with weights for extra-space distribution.
|
|
//
|
|
// Design decisions and rationale:
|
|
//
|
|
// - Single WidgetT struct with a tagged union: avoids the overhead of a
|
|
// class hierarchy and virtual dispatch (no C++ vtable indirection).
|
|
// The wclass pointer provides vtable-style dispatch only for the few
|
|
// operations that genuinely differ per widget type (paint, layout, events).
|
|
// Type-specific data lives in the `as` union to keep the struct compact.
|
|
//
|
|
// - Tagged size values (wgtPixels/wgtChars/wgtPercent): encoding the unit
|
|
// in the high bits of a single int32_t avoids extra struct fields for
|
|
// unit type and lets size hints be passed as plain integers. The 30-bit
|
|
// value range (up to ~1 billion) is more than sufficient for pixel counts.
|
|
//
|
|
// - Tree linkage uses firstChild/lastChild/nextSibling (no prevSibling):
|
|
// this halves the pointer overhead per widget and insertion/removal is
|
|
// still O(n) in the worst case, which is acceptable given typical tree
|
|
// depths of 5-10 nodes.
|
|
//
|
|
// - Large widget data (AnsiTermDataT, ListViewDataT) is separately
|
|
// allocated and stored as a pointer in the union rather than inlined,
|
|
// because these structures are hundreds of bytes and would bloat every
|
|
// WidgetT even for simple labels and buttons.
|
|
#ifndef DVX_WIDGET_H
|
|
#define DVX_WIDGET_H
|
|
|
|
#include "dvxTypes.h"
|
|
|
|
#include <time.h>
|
|
|
|
// Forward declarations
|
|
struct AppContextT;
|
|
struct WidgetClassT;
|
|
|
|
// ============================================================
|
|
// Size specifications
|
|
// ============================================================
|
|
//
|
|
// Tagged size values encode both a unit type and a numeric value in a
|
|
// single int32_t. The top 2 bits select the unit (pixels, character widths,
|
|
// or percentage of parent), and the low 30 bits hold the numeric value.
|
|
// A raw 0 means "auto" (use the widget's natural/minimum size).
|
|
//
|
|
// This encoding avoids a separate enum field for the unit type, keeping
|
|
// size hints as simple scalar assignments: w->minW = wgtChars(40);
|
|
// The wgtResolveSize() function in the layout engine decodes these tagged
|
|
// values back into pixel counts using the font metrics and parent dimensions.
|
|
|
|
#define WGT_SIZE_TYPE_MASK 0xC0000000
|
|
#define WGT_SIZE_VAL_MASK 0x3FFFFFFF
|
|
#define WGT_SIZE_PIXELS 0x00000000
|
|
#define WGT_SIZE_CHARS 0x40000000
|
|
#define WGT_SIZE_PERCENT 0x80000000
|
|
|
|
#define wgtPixels(v) ((int32_t)(WGT_SIZE_PIXELS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
|
#define wgtChars(v) ((int32_t)(WGT_SIZE_CHARS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
|
#define wgtPercent(v) ((int32_t)(WGT_SIZE_PERCENT | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
|
|
|
// ============================================================
|
|
// Widget type enum
|
|
// ============================================================
|
|
//
|
|
// Used as the index into widgetClassTable[] (in widgetInternal.h) to
|
|
// look up the vtable for each widget type. Adding a new widget type
|
|
// requires adding an enum value here, a corresponding union member in
|
|
// WidgetT, and a WidgetClassT entry in widgetClassTable[].
|
|
|
|
typedef enum {
|
|
WidgetVBoxE,
|
|
WidgetHBoxE,
|
|
WidgetLabelE,
|
|
WidgetButtonE,
|
|
WidgetCheckboxE,
|
|
WidgetRadioGroupE,
|
|
WidgetRadioE,
|
|
WidgetTextInputE,
|
|
WidgetTextAreaE,
|
|
WidgetListBoxE,
|
|
WidgetSpacerE,
|
|
WidgetSeparatorE,
|
|
WidgetFrameE,
|
|
WidgetDropdownE,
|
|
WidgetComboBoxE,
|
|
WidgetProgressBarE,
|
|
WidgetSliderE,
|
|
WidgetTabControlE,
|
|
WidgetTabPageE,
|
|
WidgetStatusBarE,
|
|
WidgetToolbarE,
|
|
WidgetTreeViewE,
|
|
WidgetTreeItemE,
|
|
WidgetImageE,
|
|
WidgetImageButtonE,
|
|
WidgetCanvasE,
|
|
WidgetAnsiTermE,
|
|
WidgetListViewE,
|
|
WidgetSpinnerE,
|
|
WidgetScrollPaneE,
|
|
WidgetSplitterE
|
|
} WidgetTypeE;
|
|
|
|
// ============================================================
|
|
// ListView types
|
|
// ============================================================
|
|
|
|
#define LISTVIEW_MAX_COLS 16
|
|
|
|
typedef enum {
|
|
ListViewAlignLeftE,
|
|
ListViewAlignCenterE,
|
|
ListViewAlignRightE
|
|
} ListViewAlignE;
|
|
|
|
typedef enum {
|
|
ListViewSortNoneE,
|
|
ListViewSortAscE,
|
|
ListViewSortDescE
|
|
} ListViewSortE;
|
|
|
|
typedef struct {
|
|
const char *title;
|
|
int32_t width; // tagged size (wgtPixels/wgtChars/wgtPercent, 0 = auto)
|
|
ListViewAlignE align;
|
|
} ListViewColT;
|
|
|
|
// ============================================================
|
|
// Alignment enum
|
|
// ============================================================
|
|
//
|
|
// Controls main-axis alignment of children within a container.
|
|
// HBox: AlignStartE=left, AlignCenterE=center, AlignEndE=right
|
|
// VBox: AlignStartE=top, AlignCenterE=center, AlignEndE=bottom
|
|
|
|
typedef enum {
|
|
AlignStartE,
|
|
AlignCenterE,
|
|
AlignEndE
|
|
} WidgetAlignE;
|
|
|
|
// ============================================================
|
|
// Frame style enum
|
|
// ============================================================
|
|
|
|
typedef enum {
|
|
FrameInE, // beveled inward (sunken) — default
|
|
FrameOutE, // beveled outward (raised)
|
|
FrameFlatE // solid color line
|
|
} FrameStyleE;
|
|
|
|
// ============================================================
|
|
// Text input mode enum
|
|
// ============================================================
|
|
|
|
typedef enum {
|
|
InputNormalE, // default free-form text
|
|
InputPasswordE, // displays bullets, no copy
|
|
InputMaskedE // format mask (e.g. "(###) ###-####")
|
|
} InputModeE;
|
|
|
|
// ============================================================
|
|
// Large widget data (separately allocated to reduce WidgetT size)
|
|
// ============================================================
|
|
//
|
|
// AnsiTermDataT and ListViewDataT are heap-allocated and stored as
|
|
// pointers in the WidgetT union. Inlining them would add hundreds of
|
|
// bytes to every WidgetT instance (even labels and buttons), wasting
|
|
// memory on the 30+ simple widgets that don't need terminal or listview
|
|
// state. The pointer indirection adds one dereference but saves
|
|
// significant memory across a typical widget tree.
|
|
|
|
// AnsiTermDataT — full VT100/ANSI terminal emulator state.
|
|
// Implements a subset of DEC VT100 escape sequences sufficient for BBS
|
|
// and DOS ANSI art rendering: cursor movement, color attributes (16-color
|
|
// with bold-as-bright), scrolling regions, and blink. The parser is a
|
|
// simple state machine (normal -> ESC -> CSI) with parameter accumulation.
|
|
typedef struct {
|
|
uint8_t *cells; // character cells: (ch, attr) pairs, cols*rows*2 bytes
|
|
int32_t cols; // columns (default 80)
|
|
int32_t rows; // rows (default 25)
|
|
int32_t cursorRow; // 0-based cursor row
|
|
int32_t cursorCol; // 0-based cursor column
|
|
bool cursorVisible;
|
|
bool wrapMode; // auto-wrap at right margin
|
|
bool bold; // SGR bold flag (brightens foreground)
|
|
bool originMode; // cursor positioning relative to scroll region
|
|
bool csiPrivate; // '?' prefix in CSI sequence
|
|
uint8_t curAttr; // current text attribute (fg | bg<<4)
|
|
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
|
|
int32_t params[8]; // CSI parameter accumulator
|
|
int32_t paramCount; // number of CSI params collected
|
|
int32_t savedRow; // saved cursor position (SCP)
|
|
int32_t savedCol;
|
|
// Scrolling region (0-based, inclusive)
|
|
int32_t scrollTop; // top row of scroll region
|
|
int32_t scrollBot; // bottom row of scroll region
|
|
// Scrollback — circular buffer so old lines age out naturally without
|
|
// memmove. Each line is cols*2 bytes (same ch/attr format as cells).
|
|
// scrollPos tracks the view offset: when equal to scrollbackCount,
|
|
// the user sees the live screen; when less, they're viewing history.
|
|
uint8_t *scrollback; // circular buffer of scrollback lines
|
|
int32_t scrollbackMax; // max lines in scrollback buffer
|
|
int32_t scrollbackCount; // current number of lines stored
|
|
int32_t scrollbackHead; // write position (circular index)
|
|
int32_t scrollPos; // view position (scrollbackCount = live)
|
|
// Blink support
|
|
bool blinkVisible; // current blink phase (true = text visible)
|
|
clock_t blinkTime; // timestamp of last blink toggle
|
|
// Cursor blink
|
|
bool cursorOn; // current cursor blink phase
|
|
clock_t cursorTime; // timestamp of last cursor toggle
|
|
// Dirty tracking — a 32-bit bitmask where each bit corresponds to one
|
|
// terminal row. Only dirty rows are repainted, which is critical because
|
|
// the ANSI terminal can receive data every frame (at 9600+ baud) and
|
|
// re-rendering all 25 rows of 80 columns each frame would dominate the
|
|
// CPU budget. Limits terminal height to 32 rows, which is fine for the
|
|
// 25-row default target.
|
|
uint32_t dirtyRows; // bitmask of rows needing repaint
|
|
int32_t lastCursorRow; // cursor row at last repaint
|
|
int32_t lastCursorCol; // cursor col at last repaint
|
|
// Pre-packed 16-color palette avoids calling packColor() (which involves
|
|
// shift/mask arithmetic) 80*25 = 2000 times per full repaint.
|
|
uint32_t packedPalette[16];
|
|
bool paletteValid;
|
|
// Selection (line indices in scrollback+screen space)
|
|
int32_t selStartLine;
|
|
int32_t selStartCol;
|
|
int32_t selEndLine;
|
|
int32_t selEndCol;
|
|
bool selecting;
|
|
// Communications interface — abstracted so the terminal can connect to
|
|
// different backends (serial port, secLink channel, local pipe) without
|
|
// knowing the transport details. When all are NULL, the terminal is in
|
|
// offline/disconnected mode (useful for viewing .ANS files).
|
|
void *commCtx;
|
|
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
|
|
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
|
|
} AnsiTermDataT;
|
|
|
|
// ListViewDataT — multi-column list with sortable headers and optional
|
|
// multi-select. cellData is a flat array of strings indexed as
|
|
// cellData[row * colCount + col]. The sortIndex is an indirection array
|
|
// that maps displayed row numbers to data row numbers, allowing sort
|
|
// without rearranging the actual data. resolvedColW[] caches pixel widths
|
|
// after resolving tagged column sizes, avoiding re-resolution on every paint.
|
|
typedef struct {
|
|
const ListViewColT *cols;
|
|
int32_t colCount;
|
|
const char **cellData;
|
|
int32_t rowCount;
|
|
int32_t selectedIdx;
|
|
int32_t scrollPos;
|
|
int32_t scrollPosH;
|
|
int32_t sortCol;
|
|
ListViewSortE sortDir;
|
|
int32_t resolvedColW[LISTVIEW_MAX_COLS];
|
|
int32_t totalColW;
|
|
int32_t *sortIndex;
|
|
bool multiSelect;
|
|
int32_t anchorIdx;
|
|
uint8_t *selBits;
|
|
bool reorderable;
|
|
int32_t dragIdx;
|
|
int32_t dropIdx;
|
|
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
|
|
} ListViewDataT;
|
|
|
|
// ============================================================
|
|
// Widget structure
|
|
// ============================================================
|
|
|
|
#define MAX_WIDGET_NAME 32
|
|
|
|
typedef struct WidgetT {
|
|
WidgetTypeE type;
|
|
// wclass points to the vtable for this widget type. Looked up once at
|
|
// creation from widgetClassTable[type]. This avoids a switch on type
|
|
// in every paint/layout/event dispatch — the cost is one pointer per
|
|
// widget, which is negligible.
|
|
const struct WidgetClassT *wclass;
|
|
char name[MAX_WIDGET_NAME];
|
|
// djb2 hash of the name string, computed at wgtSetName() time.
|
|
// wgtFind() compares hashes before strcmp, making name lookups fast
|
|
// for the common no-match case in a tree with many unnamed widgets.
|
|
uint32_t nameHash; // djb2 hash of name, 0 if unnamed
|
|
|
|
// Tree linkage
|
|
struct WidgetT *parent;
|
|
struct WidgetT *firstChild;
|
|
struct WidgetT *lastChild;
|
|
struct WidgetT *nextSibling;
|
|
WindowT *window;
|
|
|
|
// Computed geometry (relative to window content area)
|
|
int32_t x;
|
|
int32_t y;
|
|
int32_t w;
|
|
int32_t h;
|
|
|
|
// Computed minimum size — set bottom-up by calcMinSize during layout.
|
|
// These represent the smallest possible size for this widget (including
|
|
// its children if it's a container). The layout engine uses these as
|
|
// the starting point for space allocation.
|
|
int32_t calcMinW;
|
|
int32_t calcMinH;
|
|
|
|
// Size hints (tagged: wgtPixels/wgtChars/wgtPercent, 0 = auto).
|
|
// These are set by the application and influence the layout engine:
|
|
// minW/minH override calcMinW/H if larger, maxW/maxH clamp the final
|
|
// size, and prefW/prefH request a specific size (layout may override).
|
|
int32_t minW;
|
|
int32_t minH;
|
|
int32_t maxW; // 0 = no limit
|
|
int32_t maxH;
|
|
int32_t prefW; // preferred size, 0 = auto
|
|
int32_t prefH;
|
|
// weight controls how extra space beyond minimum is distributed among
|
|
// siblings in a VBox/HBox. weight=0 means fixed size (no stretching),
|
|
// weight=100 is the default for flexible widgets. A widget with
|
|
// weight=200 gets twice as much extra space as one with weight=100.
|
|
int32_t weight; // extra-space distribution (0 = fixed, 100 = normal)
|
|
|
|
// Container properties
|
|
WidgetAlignE align; // main-axis alignment for children
|
|
int32_t spacing; // tagged size for spacing between children (0 = default)
|
|
int32_t padding; // tagged size for internal padding (0 = default)
|
|
|
|
// Colors (0 = use color scheme defaults)
|
|
uint32_t fgColor;
|
|
uint32_t bgColor;
|
|
|
|
// State
|
|
bool visible;
|
|
bool enabled;
|
|
bool readOnly;
|
|
bool focused;
|
|
char accelKey; // lowercase accelerator character, 0 if none
|
|
|
|
// User data and callbacks
|
|
void *userData;
|
|
const char *tooltip; // tooltip text (NULL = none, caller owns string)
|
|
MenuT *contextMenu; // right-click context menu (NULL = none, caller owns)
|
|
void (*onClick)(struct WidgetT *w);
|
|
void (*onChange)(struct WidgetT *w);
|
|
void (*onDblClick)(struct WidgetT *w);
|
|
|
|
// Type-specific data — tagged union keyed by the `type` field.
|
|
// Only the member corresponding to `type` is valid. This is the C
|
|
// equivalent of a discriminated union / variant type. Using a union
|
|
// instead of separate structs per widget type keeps all widget data
|
|
// in a single allocation, which simplifies memory management and
|
|
// avoids pointer chasing during layout/paint traversal.
|
|
union {
|
|
struct {
|
|
const char *text;
|
|
} label;
|
|
|
|
struct {
|
|
const char *text;
|
|
bool pressed;
|
|
} button;
|
|
|
|
struct {
|
|
const char *text;
|
|
bool checked;
|
|
} checkbox;
|
|
|
|
struct {
|
|
int32_t selectedIdx;
|
|
} radioGroup;
|
|
|
|
struct {
|
|
const char *text;
|
|
int32_t index;
|
|
} radio;
|
|
|
|
// Text input has its own edit buffer (not a pointer to external
|
|
// storage) so the widget fully owns its text lifecycle. The undo
|
|
// buffer holds a single-level snapshot taken before each edit
|
|
// operation — Ctrl+Z restores to the snapshot. This is simpler
|
|
// than a full undo stack but sufficient for single-line fields.
|
|
struct {
|
|
char *buf;
|
|
int32_t bufSize;
|
|
int32_t len;
|
|
int32_t cursorPos;
|
|
int32_t scrollOff;
|
|
int32_t selStart; // selection anchor (-1 = none)
|
|
int32_t selEnd; // selection end (-1 = none)
|
|
char *undoBuf;
|
|
int32_t undoLen;
|
|
int32_t undoCursor;
|
|
InputModeE inputMode;
|
|
const char *mask; // format mask for InputMaskedE
|
|
} textInput;
|
|
|
|
// Multi-line text editor. desiredCol implements the "sticky column"
|
|
// behavior where pressing Up/Down tries to return to the column
|
|
// the cursor was at before traversing shorter lines (standard
|
|
// text editor UX). cachedLines/cachedMaxLL cache values that
|
|
// require full-buffer scans, invalidated to -1 on any text change.
|
|
struct {
|
|
char *buf;
|
|
int32_t bufSize;
|
|
int32_t len;
|
|
int32_t cursorRow;
|
|
int32_t cursorCol;
|
|
int32_t scrollRow;
|
|
int32_t scrollCol;
|
|
int32_t desiredCol; // sticky column for up/down movement
|
|
int32_t selAnchor; // selection anchor byte offset (-1 = none)
|
|
int32_t selCursor; // selection cursor byte offset (-1 = none)
|
|
char *undoBuf;
|
|
int32_t undoLen;
|
|
int32_t undoCursor; // byte offset at time of snapshot
|
|
int32_t cachedLines; // cached line count (-1 = dirty)
|
|
int32_t cachedMaxLL; // cached max line length (-1 = dirty)
|
|
} textArea;
|
|
|
|
struct {
|
|
const char **items;
|
|
int32_t itemCount;
|
|
int32_t selectedIdx; // cursor position (always valid); also sole selection in single-select
|
|
int32_t scrollPos;
|
|
int32_t maxItemLen; // cached max strlen of items
|
|
bool multiSelect;
|
|
int32_t anchorIdx; // anchor for shift+click range selection
|
|
uint8_t *selBits; // per-item selection flags (multi-select only)
|
|
bool reorderable; // allow drag-reorder of items
|
|
int32_t dragIdx; // item being dragged (-1 = none)
|
|
int32_t dropIdx; // insertion point (-1 = none)
|
|
} listBox;
|
|
|
|
struct {
|
|
bool vertical;
|
|
} separator;
|
|
|
|
struct {
|
|
const char *title;
|
|
FrameStyleE style; // FrameInE (default), FrameOutE, FrameFlatE
|
|
uint32_t color; // border color for FrameFlatE (0 = use windowShadow)
|
|
} frame;
|
|
|
|
struct {
|
|
const char **items;
|
|
int32_t itemCount;
|
|
int32_t selectedIdx;
|
|
bool open;
|
|
int32_t hoverIdx;
|
|
int32_t scrollPos;
|
|
int32_t maxItemLen; // cached max strlen of items
|
|
} dropdown;
|
|
|
|
struct {
|
|
char *buf;
|
|
int32_t bufSize;
|
|
int32_t len;
|
|
int32_t cursorPos;
|
|
int32_t scrollOff;
|
|
int32_t selStart; // selection anchor (-1 = none)
|
|
int32_t selEnd; // selection end (-1 = none)
|
|
char *undoBuf;
|
|
int32_t undoLen;
|
|
int32_t undoCursor;
|
|
const char **items;
|
|
int32_t itemCount;
|
|
int32_t selectedIdx;
|
|
bool open;
|
|
int32_t hoverIdx;
|
|
int32_t listScrollPos;
|
|
int32_t maxItemLen; // cached max strlen of items
|
|
} comboBox;
|
|
|
|
struct {
|
|
int32_t value;
|
|
int32_t maxValue;
|
|
bool vertical;
|
|
} progressBar;
|
|
|
|
struct {
|
|
int32_t value;
|
|
int32_t minValue;
|
|
int32_t maxValue;
|
|
bool vertical;
|
|
} slider;
|
|
|
|
struct {
|
|
int32_t activeTab;
|
|
int32_t scrollOffset; // horizontal scroll of tab headers
|
|
} tabControl;
|
|
|
|
struct {
|
|
const char *title;
|
|
} tabPage;
|
|
|
|
// TreeView uses widget children (WidgetTreeItemE) as its items,
|
|
// unlike ListBox which uses a string array. This allows nested
|
|
// hierarchies with expand/collapse state per item. The tree is
|
|
// rendered by flattening visible items during paint, with TREE_INDENT
|
|
// pixels of indentation per nesting level.
|
|
struct {
|
|
int32_t scrollPos;
|
|
int32_t scrollPosH;
|
|
struct WidgetT *selectedItem;
|
|
struct WidgetT *anchorItem; // anchor for shift+click range selection
|
|
bool multiSelect;
|
|
bool reorderable; // allow drag-reorder of items
|
|
struct WidgetT *dragItem; // item being dragged (NULL = none)
|
|
struct WidgetT *dropTarget; // insertion target (NULL = none)
|
|
bool dropAfter; // true = insert after target, false = before
|
|
} treeView;
|
|
|
|
struct {
|
|
const char *text;
|
|
bool expanded;
|
|
bool selected; // per-item flag for multi-select
|
|
} treeItem;
|
|
|
|
struct {
|
|
uint8_t *data; // pixel buffer in display format
|
|
int32_t imgW;
|
|
int32_t imgH;
|
|
int32_t imgPitch;
|
|
bool pressed;
|
|
} image;
|
|
|
|
struct {
|
|
uint8_t *data; // pixel buffer in display format
|
|
int32_t imgW;
|
|
int32_t imgH;
|
|
int32_t imgPitch;
|
|
bool pressed;
|
|
} imageButton;
|
|
|
|
struct {
|
|
uint8_t *data; // pixel buffer in display format
|
|
int32_t canvasW;
|
|
int32_t canvasH;
|
|
int32_t canvasPitch;
|
|
int32_t canvasBpp; // cached bytes per pixel (avoids pitch/w division)
|
|
uint32_t penColor;
|
|
int32_t penSize;
|
|
int32_t lastX;
|
|
int32_t lastY;
|
|
void (*onMouse)(struct WidgetT *w, int32_t cx, int32_t cy, bool drag);
|
|
} canvas;
|
|
|
|
AnsiTermDataT *ansiTerm;
|
|
|
|
ListViewDataT *listView;
|
|
|
|
struct {
|
|
int32_t value;
|
|
int32_t minValue;
|
|
int32_t maxValue;
|
|
int32_t step;
|
|
char buf[16]; // formatted value text
|
|
int32_t len;
|
|
int32_t cursorPos;
|
|
int32_t scrollOff;
|
|
int32_t selStart; // selection anchor (-1 = none)
|
|
int32_t selEnd; // selection end (-1 = none)
|
|
char undoBuf[16]; // undo snapshot
|
|
int32_t undoLen;
|
|
int32_t undoCursor;
|
|
bool editing; // true when user is typing
|
|
} spinner;
|
|
|
|
struct {
|
|
int32_t scrollPosV;
|
|
int32_t scrollPosH;
|
|
} scrollPane;
|
|
|
|
struct {
|
|
int32_t dividerPos; // pixels from left/top edge
|
|
bool vertical; // true = vertical divider (left|right panes)
|
|
} splitter;
|
|
} as;
|
|
} WidgetT;
|
|
|
|
// ============================================================
|
|
// Window integration
|
|
// ============================================================
|
|
|
|
// Initialize the widget system for a window. Creates a root VBox container
|
|
// that fills the window's content area, and installs callback handlers
|
|
// (onPaint, onMouse, onKey, onResize) that dispatch events to the widget
|
|
// tree. After this call, the window is fully managed by the widget system
|
|
// — the application builds its UI by adding child widgets to the returned
|
|
// root container. The window's userData is set to the AppContextT pointer.
|
|
WidgetT *wgtInitWindow(struct AppContextT *ctx, WindowT *win);
|
|
|
|
// ============================================================
|
|
// Container creation
|
|
// ============================================================
|
|
//
|
|
// VBox and HBox are the primary layout containers, analogous to CSS
|
|
// flexbox with column/row direction. Children are laid out sequentially
|
|
// along the main axis (vertical for VBox, horizontal for HBox) with
|
|
// spacing between them. Extra space is distributed according to each
|
|
// child's weight. Frame is a titled groupbox container with a bevel border.
|
|
|
|
WidgetT *wgtVBox(WidgetT *parent);
|
|
WidgetT *wgtHBox(WidgetT *parent);
|
|
WidgetT *wgtFrame(WidgetT *parent, const char *title);
|
|
|
|
// ============================================================
|
|
// Basic widgets
|
|
// ============================================================
|
|
|
|
WidgetT *wgtLabel(WidgetT *parent, const char *text);
|
|
WidgetT *wgtButton(WidgetT *parent, const char *text);
|
|
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
|
|
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
|
|
WidgetT *wgtPasswordInput(WidgetT *parent, int32_t maxLen);
|
|
WidgetT *wgtMaskedInput(WidgetT *parent, const char *mask);
|
|
|
|
// ============================================================
|
|
// Radio buttons
|
|
// ============================================================
|
|
//
|
|
// Radio buttons must be children of a RadioGroup container. The group
|
|
// tracks which child is selected (by index). Clicking a radio button
|
|
// automatically deselects the previously selected sibling. This parent-
|
|
// tracking approach avoids explicit "radio group ID" parameters.
|
|
|
|
WidgetT *wgtRadioGroup(WidgetT *parent);
|
|
WidgetT *wgtRadio(WidgetT *parent, const char *text);
|
|
|
|
// ============================================================
|
|
// Spacing and visual dividers
|
|
// ============================================================
|
|
//
|
|
// Spacer is an invisible flexible widget (weight=100) that absorbs
|
|
// extra space — useful for pushing subsequent siblings to the end of
|
|
// a container (like CSS flex: 1 auto). Separators are thin beveled
|
|
// lines for visual grouping.
|
|
|
|
WidgetT *wgtSpacer(WidgetT *parent);
|
|
WidgetT *wgtHSeparator(WidgetT *parent);
|
|
WidgetT *wgtVSeparator(WidgetT *parent);
|
|
|
|
// ============================================================
|
|
// Complex widgets
|
|
// ============================================================
|
|
|
|
WidgetT *wgtListBox(WidgetT *parent);
|
|
WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen);
|
|
|
|
// ============================================================
|
|
// Dropdown and ComboBox
|
|
// ============================================================
|
|
|
|
WidgetT *wgtDropdown(WidgetT *parent);
|
|
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count);
|
|
int32_t wgtDropdownGetSelected(const WidgetT *w);
|
|
void wgtDropdownSetSelected(WidgetT *w, int32_t idx);
|
|
|
|
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen);
|
|
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count);
|
|
int32_t wgtComboBoxGetSelected(const WidgetT *w);
|
|
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx);
|
|
|
|
// ============================================================
|
|
// ProgressBar
|
|
// ============================================================
|
|
|
|
WidgetT *wgtProgressBar(WidgetT *parent);
|
|
WidgetT *wgtProgressBarV(WidgetT *parent);
|
|
void wgtProgressBarSetValue(WidgetT *w, int32_t value);
|
|
int32_t wgtProgressBarGetValue(const WidgetT *w);
|
|
|
|
// ============================================================
|
|
// Slider (TrackBar)
|
|
// ============================================================
|
|
|
|
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal);
|
|
void wgtSliderSetValue(WidgetT *w, int32_t value);
|
|
int32_t wgtSliderGetValue(const WidgetT *w);
|
|
|
|
// ============================================================
|
|
// Spinner (numeric input)
|
|
// ============================================================
|
|
|
|
WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step);
|
|
void wgtSpinnerSetValue(WidgetT *w, int32_t value);
|
|
int32_t wgtSpinnerGetValue(const WidgetT *w);
|
|
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal);
|
|
void wgtSpinnerSetStep(WidgetT *w, int32_t step);
|
|
|
|
// ============================================================
|
|
// TabControl
|
|
// ============================================================
|
|
|
|
WidgetT *wgtTabControl(WidgetT *parent);
|
|
WidgetT *wgtTabPage(WidgetT *parent, const char *title);
|
|
void wgtTabControlSetActive(WidgetT *w, int32_t idx);
|
|
int32_t wgtTabControlGetActive(const WidgetT *w);
|
|
|
|
// ============================================================
|
|
// StatusBar and Toolbar
|
|
// ============================================================
|
|
|
|
WidgetT *wgtStatusBar(WidgetT *parent);
|
|
WidgetT *wgtToolbar(WidgetT *parent);
|
|
|
|
// ============================================================
|
|
// TreeView
|
|
// ============================================================
|
|
|
|
WidgetT *wgtTreeView(WidgetT *parent);
|
|
WidgetT *wgtTreeViewGetSelected(const WidgetT *w);
|
|
void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item);
|
|
void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi);
|
|
void wgtTreeViewSetReorderable(WidgetT *w, bool reorderable);
|
|
WidgetT *wgtTreeItem(WidgetT *parent, const char *text);
|
|
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded);
|
|
bool wgtTreeItemIsExpanded(const WidgetT *w);
|
|
bool wgtTreeItemIsSelected(const WidgetT *w);
|
|
void wgtTreeItemSetSelected(WidgetT *w, bool selected);
|
|
|
|
// ============================================================
|
|
// ListView (multi-column list)
|
|
// ============================================================
|
|
|
|
WidgetT *wgtListView(WidgetT *parent);
|
|
void wgtListViewSetColumns(WidgetT *w, const ListViewColT *cols, int32_t count);
|
|
void wgtListViewSetData(WidgetT *w, const char **cellData, int32_t rowCount);
|
|
int32_t wgtListViewGetSelected(const WidgetT *w);
|
|
void wgtListViewSetSelected(WidgetT *w, int32_t idx);
|
|
void wgtListViewSetSort(WidgetT *w, int32_t col, ListViewSortE dir);
|
|
void wgtListViewSetHeaderClickCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir));
|
|
void wgtListViewSetMultiSelect(WidgetT *w, bool multi);
|
|
bool wgtListViewIsItemSelected(const WidgetT *w, int32_t idx);
|
|
void wgtListViewSetItemSelected(WidgetT *w, int32_t idx, bool selected);
|
|
void wgtListViewSelectAll(WidgetT *w);
|
|
void wgtListViewClearSelection(WidgetT *w);
|
|
void wgtListViewSetReorderable(WidgetT *w, bool reorderable);
|
|
|
|
// ============================================================
|
|
// ScrollPane
|
|
// ============================================================
|
|
|
|
WidgetT *wgtScrollPane(WidgetT *parent);
|
|
|
|
// ============================================================
|
|
// Splitter (draggable divider between two child regions)
|
|
// ============================================================
|
|
|
|
// Create a splitter. If vertical is true, children are arranged left|right;
|
|
// if false, top|bottom. Add exactly two children.
|
|
WidgetT *wgtSplitter(WidgetT *parent, bool vertical);
|
|
void wgtSplitterSetPos(WidgetT *w, int32_t pos);
|
|
int32_t wgtSplitterGetPos(const WidgetT *w);
|
|
|
|
// ============================================================
|
|
// ImageButton
|
|
// ============================================================
|
|
|
|
// Create an image button from raw pixel data (display format).
|
|
// Takes ownership of the data buffer (freed on destroy).
|
|
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
|
|
|
|
// Load an image button from a file (BMP, PNG, JPEG, GIF).
|
|
// Returns NULL on load failure; falls through gracefully.
|
|
WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path);
|
|
|
|
// Replace the image data. Takes ownership of the new buffer.
|
|
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
|
|
|
|
// ============================================================
|
|
// Image
|
|
// ============================================================
|
|
|
|
// Create an image widget from raw pixel data (display format).
|
|
// Takes ownership of the data buffer (freed on destroy).
|
|
WidgetT *wgtImage(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
|
|
|
|
// Load an image widget from a file (BMP, PNG, JPEG, GIF).
|
|
// Returns NULL on load failure.
|
|
WidgetT *wgtImageFromFile(WidgetT *parent, const char *path);
|
|
|
|
// Replace the image data. Takes ownership of the new buffer.
|
|
void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
|
|
|
|
// ============================================================
|
|
// Canvas
|
|
// ============================================================
|
|
|
|
// Create a drawable canvas widget with the given dimensions.
|
|
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h);
|
|
|
|
// Clear the canvas to the specified color.
|
|
void wgtCanvasClear(WidgetT *w, uint32_t color);
|
|
|
|
// Set the pen color (in display pixel format).
|
|
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color);
|
|
|
|
// Set the pen size in pixels (diameter).
|
|
void wgtCanvasSetPenSize(WidgetT *w, int32_t size);
|
|
|
|
// Set the mouse callback. Called on click (drag=false) and drag (drag=true)
|
|
// with canvas-relative coordinates. If NULL (default), mouse events are ignored.
|
|
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
|
|
|
|
// Save the canvas to a PNG file. Returns 0 on success, -1 on failure.
|
|
int32_t wgtCanvasSave(WidgetT *w, const char *path);
|
|
|
|
// Load a PNG file onto the canvas. Returns 0 on success, -1 on failure.
|
|
int32_t wgtCanvasLoad(WidgetT *w, const char *path);
|
|
|
|
// Programmatic drawing (coordinates are in canvas space)
|
|
void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
|
|
void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
|
void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
|
void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
|
|
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color);
|
|
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y);
|
|
|
|
// ============================================================
|
|
// ANSI Terminal
|
|
// ============================================================
|
|
//
|
|
// A VT100/ANSI terminal emulator widget. Supports the subset of escape
|
|
// sequences needed for DOS BBS programs and ANSI art: cursor positioning,
|
|
// SGR color/attribute codes, scrolling regions, and text blink. Pairs
|
|
// with the secLink communications layer for remote serial connections
|
|
// or can be used standalone for viewing .ANS files.
|
|
|
|
// Create an ANSI terminal widget (0 for cols/rows = 80x25 default)
|
|
WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows);
|
|
|
|
// Write raw data through the ANSI escape sequence parser. Used for
|
|
// loading .ANS files or feeding data from a source that isn't using
|
|
// the comm interface.
|
|
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len);
|
|
|
|
// Clear the terminal screen and reset cursor to home
|
|
void wgtAnsiTermClear(WidgetT *w);
|
|
|
|
// Set the communications interface (NULL function pointers = disconnected)
|
|
void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t));
|
|
|
|
// Set the scrollback buffer size in lines (default 500). Clears existing scrollback.
|
|
void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines);
|
|
|
|
// Poll the comm interface for incoming data and process it. Returns bytes processed.
|
|
int32_t wgtAnsiTermPoll(WidgetT *w);
|
|
|
|
// Fast-path repaint for the terminal widget. Instead of going through the
|
|
// full widget paint pipeline (which would repaint the entire widget), this
|
|
// renders only the dirty rows (tracked via the dirtyRows bitmask) directly
|
|
// into the window's content buffer. This is essential for responsive terminal
|
|
// output — incoming serial data can dirty a few rows per frame, and
|
|
// repainting only those rows keeps the cost proportional to the actual
|
|
// change rather than the full 80x25 grid. Returns the number of rows
|
|
// repainted; outY/outH report the affected region for dirty-rect tracking.
|
|
int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH);
|
|
|
|
// ============================================================
|
|
// Operations
|
|
// ============================================================
|
|
|
|
// Walk from any widget up the tree to the root, then retrieve the
|
|
// AppContextT stored in the window's userData. This lets any widget
|
|
// access the full application context without passing it through every
|
|
// function call.
|
|
struct AppContextT *wgtGetContext(const WidgetT *w);
|
|
|
|
// Update text cursor blink state. Call once per frame from dvxUpdate.
|
|
// Toggles the cursor visibility at 250ms intervals, matching the ANSI
|
|
// terminal cursor rate.
|
|
void wgtUpdateCursorBlink(void);
|
|
|
|
// Mark a widget as needing both re-layout (measure + position) and
|
|
// repaint. Propagates upward to ancestors since a child's size change
|
|
// can affect parent layout. Use this after structural changes (adding/
|
|
// removing children, changing text that affects size).
|
|
void wgtInvalidate(WidgetT *w);
|
|
|
|
// Mark a widget as needing repaint only, without re-layout. Use this
|
|
// for visual-only changes that don't affect geometry (e.g. checkbox
|
|
// toggle, selection highlight change, cursor blink).
|
|
void wgtInvalidatePaint(WidgetT *w);
|
|
|
|
// Set/get widget text (label, button, textInput, etc.)
|
|
void wgtSetText(WidgetT *w, const char *text);
|
|
const char *wgtGetText(const WidgetT *w);
|
|
|
|
// Enable/disable a widget
|
|
void wgtSetEnabled(WidgetT *w, bool enabled);
|
|
|
|
// Set read-only mode (allows scrolling/selection but blocks editing)
|
|
void wgtSetReadOnly(WidgetT *w, bool readOnly);
|
|
|
|
// Show/hide a widget
|
|
void wgtSetVisible(WidgetT *w, bool visible);
|
|
|
|
// Set a widget's name (for lookup via wgtFind)
|
|
void wgtSetName(WidgetT *w, const char *name);
|
|
|
|
// Find a widget by name (searches the subtree rooted at root)
|
|
WidgetT *wgtFind(WidgetT *root, const char *name);
|
|
|
|
// Destroy a widget and all its children (removes from parent)
|
|
void wgtDestroy(WidgetT *w);
|
|
|
|
// ============================================================
|
|
// List box operations
|
|
// ============================================================
|
|
|
|
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count);
|
|
int32_t wgtListBoxGetSelected(const WidgetT *w);
|
|
void wgtListBoxSetSelected(WidgetT *w, int32_t idx);
|
|
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi);
|
|
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx);
|
|
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected);
|
|
void wgtListBoxSelectAll(WidgetT *w);
|
|
void wgtListBoxClearSelection(WidgetT *w);
|
|
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable);
|
|
|
|
// ============================================================
|
|
// Tooltip
|
|
// ============================================================
|
|
|
|
// Set tooltip text for a widget (NULL to remove).
|
|
// Caller owns the string — it must outlive the widget.
|
|
void wgtSetTooltip(WidgetT *w, const char *text);
|
|
|
|
// ============================================================
|
|
// Debug
|
|
// ============================================================
|
|
|
|
// Draw borders around layout containers in ugly colors
|
|
void wgtSetDebugLayout(struct AppContextT *ctx, bool enabled);
|
|
|
|
// ============================================================
|
|
// Layout (called internally; available for manual trigger)
|
|
// ============================================================
|
|
|
|
// Decode a tagged size value (WGT_SIZE_PIXELS/CHARS/PERCENT) into a
|
|
// concrete pixel count. For CHARS, multiplies by charWidth; for PERCENT,
|
|
// computes the fraction of parentSize. Returns 0 for a raw 0 input
|
|
// (meaning "auto").
|
|
int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth);
|
|
|
|
// Execute the full two-pass layout algorithm on the widget tree:
|
|
// Pass 1 (bottom-up): calcMinSize on every widget to compute minimum sizes.
|
|
// Pass 2 (top-down): allocate space within availW/availH, distributing
|
|
// extra space according to weights and respecting min/max constraints.
|
|
// Normally called automatically by the paint handler; exposed here for
|
|
// cases where layout must be forced before the next paint (e.g. dvxFitWindow).
|
|
void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT *font);
|
|
|
|
// Paint the entire widget tree by depth-first traversal. Each widget's
|
|
// clip rect is set to its bounds before calling its paint function.
|
|
// Overlays (dropdown popups, tooltips) are painted in a second pass
|
|
// after the main tree so they render on top of everything.
|
|
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
|
|
|
#endif // DVX_WIDGET_H
|