DVX_GUI/core/dvxWidget.h

414 lines
18 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:
//
// - Generic WidgetT with void *data: core knows nothing about individual
// widget types. Each widget DXE allocates and manages its own private
// data struct via w->data. The wclass vtable provides polymorphic
// dispatch for paint, layout, events, and all type-specific operations.
// No widget-specific enums, structs, or union members in this header.
//
// - Dynamic type IDs: wgtRegisterClass() returns a runtime ID at load
// time. No compile-time enum. Adding a new widget requires zero core
// changes -- just drop a .wgt DXE in the widgets directory.
//
// - 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.
#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 IDs are assigned dynamically by wgtRegisterClass() at
// runtime. There is no compile-time enum. Each widget DXE stores
// its assigned ID(s) in file-scope globals.
// ============================================================
// ListView column types (used by ListView API in widgetListView.h)
// ============================================================
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;
// ============================================================
// Widget-specific enums and types used by the public API
// ============================================================
//
// These types are referenced by application code through the wgtApi
// function table. Widget-private types (data structs) are defined
// in their respective widget .c files.
// ============================================================
// Widget class vtable
// ============================================================
//
// Each widget type has a WidgetClassT that defines its behavior.
// Built-in classes are static const; dynamically registered classes
// (from DXE plugins) are stored in the mutable region of
// widgetClassTable[].
//
// Flags encode static properties checked by the framework:
// FOCUSABLE -- can receive keyboard focus (Tab navigation)
// BOX_CONTAINER -- uses the generic VBox/HBox layout algorithm
// HORIZ_CONTAINER -- lays out children horizontally (vs. vertical)
// PAINTS_CHILDREN -- widget handles child rendering itself
// NO_HIT_RECURSE -- hit testing stops here, no child recursion
// Flags encode static properties checked by the framework.
// Widgets set these in their WidgetClassT definition.
#define WCLASS_FOCUSABLE 0x00000001
#define WCLASS_BOX_CONTAINER 0x00000002
#define WCLASS_HORIZ_CONTAINER 0x00000004
#define WCLASS_PAINTS_CHILDREN 0x00000008
#define WCLASS_NO_HIT_RECURSE 0x00000010
#define WCLASS_FOCUS_FORWARD 0x00000020 // accel hit forwards focus to next focusable
#define WCLASS_HAS_POPUP 0x00000040 // has dropdown popup overlay
#define WCLASS_SCROLLABLE 0x00000080 // accepts mouse wheel events
#define WCLASS_SCROLL_CONTAINER 0x00000100 // scroll container (ScrollPane)
#define WCLASS_NEEDS_POLL 0x00000200 // needs periodic polling (AnsiTerm comms)
#define WCLASS_SWALLOWS_TAB 0x00000400 // Tab key goes to widget, not focus nav
#define WCLASS_RELAYOUT_ON_SCROLL 0x00000800 // full relayout on scrollbar drag
#define WCLASS_PRESS_RELEASE 0x00001000 // click = press+release (Button, ImageButton)
#define WCLASS_ACCEL_WHEN_HIDDEN 0x00002000 // accel matching works even when invisible
typedef struct WidgetClassT {
uint32_t flags;
// Rendering
void (*paint)(struct WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void (*paintOverlay)(struct WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
// Layout
void (*calcMinSize)(struct WidgetT *w, const BitmapFontT *font);
void (*layout)(struct WidgetT *w, const BitmapFontT *font);
void (*getLayoutMetrics)(const struct WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW);
// Input
void (*onMouse)(struct WidgetT *w, struct WidgetT *root, int32_t vx, int32_t vy);
void (*onKey)(struct WidgetT *w, int32_t key, int32_t mod);
void (*onAccelActivate)(struct WidgetT *w, struct WidgetT *root);
// Lifecycle
void (*destroy)(struct WidgetT *w);
void (*onChildChanged)(struct WidgetT *parent, struct WidgetT *child);
// Text
const char *(*getText)(const struct WidgetT *w);
void (*setText)(struct WidgetT *w, const char *text);
// Selection / drag-select
bool (*clearSelection)(struct WidgetT *w);
void (*dragSelect)(struct WidgetT *w, struct WidgetT *root, int32_t vx, int32_t vy);
// Popup (dropdown/combobox)
void (*openPopup)(struct WidgetT *w);
void (*closePopup)(struct WidgetT *w);
int32_t (*getPopupItemCount)(const struct WidgetT *w);
void (*getPopupRect)(const struct WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
// Button press state
void (*setPressed)(struct WidgetT *w, bool pressed);
// Drag reorder (listbox/listview/treeview)
void (*reorderDrop)(struct WidgetT *w);
void (*reorderUpdate)(struct WidgetT *w, struct WidgetT *root, int32_t x, int32_t y);
// Cursor shape (returns cursor ID, 0 = default)
int32_t (*getCursorShape)(const struct WidgetT *w, int32_t vx, int32_t vy);
// Polling (AnsiTerm comms)
void (*poll)(struct WidgetT *w, WindowT *win);
// Fast incremental repaint (returns dirty row count, 0 = none)
int32_t (*quickRepaint)(struct WidgetT *w, int32_t *outY, int32_t *outH);
// Text field width (for scroll calculations; 0 = use widget width)
int32_t (*getTextFieldWidth)(const struct WidgetT *w);
// Scroll a child widget into the visible area (ScrollPane, etc.)
void (*scrollChildIntoView)(struct WidgetT *parent, const struct WidgetT *child);
// Scrollbar drag update (orient: 0=vert, 1=horiz)
void (*scrollDragUpdate)(struct WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
} WidgetClassT;
// ============================================================
// Widget structure
// ============================================================
#define MAX_WIDGET_NAME 32
typedef struct WidgetT {
int32_t type; // assigned by wgtRegisterClass()
// 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];
// 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. These fire for ALL widget types from the
// central event dispatcher, not from individual widget handlers.
// Type-specific handlers (e.g. button press animation, listbox
// selection) run first, then these universal callbacks fire.
void *userData;
void *data; // widget-private data (allocated by widget DXE)
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 (*onDblClick)(struct WidgetT *w);
void (*onChange)(struct WidgetT *w);
void (*onFocus)(struct WidgetT *w);
void (*onBlur)(struct WidgetT *w);
} 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);
// ============================================================
// 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);
// 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);
// Set/get keyboard focus
void wgtSetFocused(WidgetT *w);
WidgetT *wgtGetFocused(void);
// 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);
// ============================================================
// 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);
// ============================================================
// Dynamic widget registration
// ============================================================
// Register a new widget class at runtime. Returns the assigned type ID
// Appends wclass to widgetClassTable and returns the assigned type ID.
// The WidgetClassT must remain valid for the lifetime of the process
// (typically a static const in the registering DXE).
int32_t wgtRegisterClass(const WidgetClassT *wclass);
// ============================================================
// 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);
// ============================================================
// Widget API registry
// ============================================================
//
// Each widget DXE registers a small API struct under a name
// during wgtRegister(). Callers retrieve it via wgtGetApi()
// and cast to the widget-specific API type. Per-widget headers
// (e.g. widgetButton.h) provide typed accessors and convenience
// macros so callers don't need manual casts.
//
// This replaces the monolithic WidgetApiT -- adding a new widget
// requires zero changes to dvxWidget.h.
void wgtRegisterApi(const char *name, const void *api);
const void *wgtGetApi(const char *name);
#endif // DVX_WIDGET_H