# DVX Core Library (libdvx.lib)

The core GUI infrastructure for DVX, built as a DXE3 module. Provides
VESA video setup, 2D drawing primitives, dirty-rectangle compositing,
a window manager with Motif-style chrome, and the widget infrastructure
(layout engine, event dispatch, class registration). Individual widget
type implementations live in `../widgets/` as separate `.wgt` DXE
modules that register themselves at runtime via `wgtRegisterClass()`.

Core knows nothing about individual widget types. There is no
WidgetTypeE enum, no widget union, and no per-widget structs in
dvxWidget.h. All widget-specific behavior is dispatched through the
WidgetClassT dispatch table.


## 5-Layer Architecture

| Layer | Header | Source | Description |
|-------|--------|--------|-------------|
| 1. Video | `dvxVideo.h` | `dvxVideo.c` | VESA VBE init, LFB mapping, backbuffer, pixel format, `packColor()` |
| 2. Draw | `dvxDraw.h` | `dvxDraw.c` | Rect fills, bevels, text, bitmap cursors, focus rects, lines |
| 3. Compositor | `dvxComp.h` | `dvxComp.c` | Dirty rect tracking, merge, clip, LFB flush |
| 4. Window Manager | `dvxWm.h` | `dvxWm.c` | Window stack, chrome, drag/resize, menus, scrollbars, hit test |
| 5. Application | `dvxApp.h` | `dvxApp.c` | Event loop, input polling, color schemes, wallpaper, public API |

Additional modules built into libdvx.lib:

| Header | Source | Description |
|--------|--------|-------------|
| `dvxDialog.h` | `dvxDialog.c` | Modal message box and file open/save dialogs |
| `dvxPrefs.h` | `dvxPrefs.c` | INI-based preferences (read/write with typed accessors) |
| `dvxResource.h` | `dvxResource.c` | Resource system -- icons, text, and binary data appended to DXE files |
| `dvxMem.h` | (header only) | Per-app memory tracking API declarations |
| `dvxWidget.h` | `widgetClass.c`, `widgetCore.c`, `widgetEvent.c`, `widgetLayout.c`, `widgetOps.c`, `widgetScrollbar.c` | Widget infrastructure |
| `dvxWidgetPlugin.h` | (header only) | Plugin API for widget DXE modules |
| -- | `dvxImage.c` | Image loading via stb_image (BMP, PNG, JPEG, GIF) |
| -- | `dvxImageWrite.c` | PNG export via stb_image_write |


## Source Files

| File | Description |
|------|-------------|
| `dvxVideo.c` | VESA mode negotiation, LFB mapping via DPMI, backbuffer alloc, `packColor()` |
| `dvxDraw.c` | `rectFill()`, `rectCopy()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, cursor rendering |
| `dvxComp.c` | `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` |
| `dvxWm.c` | Window create/destroy, Z-order, chrome drawing, drag/resize, menu bar, scrollbars, minimize/maximize |
| `dvxApp.c` | `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, screenshots |
| `dvxDialog.c` | `dvxMessageBox()`, `dvxFileDialog()` -- modal dialogs with own event loops |
| `dvxPrefs.c` | `prefsLoad()`, `prefsSave()`, typed get/set for string/int/bool |
| `dvxResource.c` | `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` -- resource system |
| `dvxImage.c` | `dvxLoadImage()`, `dvxLoadImageFromMemory()` -- stb_image loader, converts to native pixel format |
| `dvxImageWrite.c` | `dvxSaveImage()` -- PNG writer for screenshots |
| `widgetClass.c` | `wgtRegisterClass()`, `wgtRegisterApi()`, `wgtGetApi()`, class table |
| `widgetCore.c` | Widget allocation, tree ops, focus management, clipboard, hit testing, cursor blink |
| `widgetEvent.c` | `widgetOnMouse()`, `widgetOnKey()`, `widgetOnPaint()`, `widgetOnResize()`, scrollbar management |
| `widgetLayout.c` | Two-pass flexbox layout: bottom-up `calcMinSize`, top-down space allocation with weights |
| `widgetOps.c` | `wgtPaint()`, `wgtLayout()`, `wgtInitWindow()`, text get/set, invalidation |
| `widgetScrollbar.c` | Scrollbar drawing (H/V), thumb calculation, hit testing, drag update |


## Public Headers

| Header | Purpose |
|--------|---------|
| `dvxTypes.h` | All shared types: DisplayT, RectT, BlitOpsT, BevelStyleT, BitmapFontT, ColorSchemeT, WindowT, MenuT, ScrollbarT, CursorT, PopupStateT |
| `dvxVideo.h` | `videoInit()`, `videoShutdown()`, `packColor()`, `setClipRect()`, `resetClipRect()` |
| `dvxDraw.h` | All drawing functions: `rectFill()`, `drawBevel()`, `drawText()`, `drawTextN()`, `drawTermRow()`, etc. |
| `dvxComp.h` | Dirty list operations: `dirtyListAdd()`, `dirtyListMerge()`, `flushRect()`, `rectIntersect()` |
| `dvxWm.h` | Window management: `wmCreateWindow()`, `wmDestroyWindow()`, `wmRaiseWindow()`, menus, scrollbars, chrome |
| `dvxApp.h` | Application API: `dvxInit()`, `dvxRun()`, `dvxUpdate()`, `dvxCreateWindow()`, color schemes, wallpaper, image I/O |
| `dvxDialog.h` | Modal dialogs: `dvxMessageBox()`, `dvxFileDialog()` |
| `dvxPrefs.h` | INI preferences: `prefsLoad()`, `prefsSave()`, typed accessors |
| `dvxResource.h` | Resource system: `dvxResOpen()`, `dvxResRead()`, `dvxResFind()`, `dvxResClose()` |
| `dvxMem.h` | Per-app memory tracking: `dvxMalloc()`, `dvxFree()`, `dvxMemGetAppUsage()`, etc. |
| `dvxWidget.h` | Widget system public API: WidgetT, WidgetClassT, size tags, layout, API registry, `wclsFoo()` dispatch helpers |
| `dvxWidgetPlugin.h` | Plugin API for widget DXE authors: tree ops, focus, scrollbar helpers, shared state |
| `dvxFont.h` | Embedded 8x14 and 8x16 bitmap font data (CP437) |
| `dvxCursor.h` | Mouse cursor AND/XOR mask data (arrow, resize H/V/diag, busy) |
| `dvxPalette.h` | Default 256-color VGA palette for 8-bit mode |


## Platform Layer

| File | Description |
|------|-------------|
| `platform/dvxPlatform.h` | Platform abstraction API (video, input, spans, DXE, crash recovery, memory tracking) |
| `platform/dvxPlatformDos.c` | DJGPP/DPMI implementation (VESA VBE, INT 33h mouse, INT 16h keyboard, asm spans) |

The platform layer is compiled into dvx.exe (the loader), not into
libdvx.lib. Platform functions are exported to all DXE modules via
`platformRegisterDxeExports()`.


## Third-Party Libraries

| File | Description |
|------|-------------|
| `thirdparty/stb_image.h` | Image loading (implementation compiled into dvxImage.c) |
| `thirdparty/stb_image_write.h` | PNG writing (implementation compiled into dvxImageWrite.c) |
| `thirdparty/stb_ds.h` | Dynamic arrays/hash maps (implementation in loader, exported to all DXEs) |


## Dynamic Limits

All major data structures grow dynamically via realloc. There are no
fixed-size limits for:

- **Windows** -- `WindowStackT.windows` is a dynamic array
- **Menus** -- `MenuBarT.menus` and `MenuT.items` are dynamic arrays
- **Accelerator entries** -- `AccelTableT.entries` is a dynamic array
- **Dirty rectangles** -- `DirtyListT.rects` is a dynamic array
- **Submenu depth** -- `PopupStateT.parentStack` is a dynamic array

The only fixed-size buffers remaining are per-element string fields
(`MAX_TITLE_LEN = 128`, `MAX_MENU_LABEL = 32`, `MAX_WIDGET_NAME = 32`)
and the system menu (`SYS_MENU_MAX_ITEMS = 10`).


## Resource System

Resources are appended to DXE3 files (.app, .wgt, .lib) after the
normal DXE content. The DXE loader never reads past the DXE header,
so appended data is invisible to dlopen.

File layout:

    [DXE3 content]
    [resource data entries]      -- sequential, variable length
    [resource directory]         -- fixed-size entries (48 bytes each)
    [footer]                     -- magic + directory offset + count (16 bytes)

### Resource Types

| Define | Value | Description |
|--------|-------|-------------|
| `DVX_RES_ICON` | 1 | Image data (BMP icon: 16x16, 32x32, etc.) |
| `DVX_RES_TEXT` | 2 | Null-terminated string (author, copyright, etc.) |
| `DVX_RES_BINARY` | 3 | Arbitrary binary data (app-specific) |

### Resource API

| Function | Description |
|----------|-------------|
| `dvxResOpen(path)` | Open a resource handle by reading the footer and directory. Returns NULL if no resources. |
| `dvxResRead(h, name, outSize)` | Find a resource by name and read its data into a malloc'd buffer. Caller frees. |
| `dvxResFind(h, name)` | Find a resource by name and return its directory entry pointer. |
| `dvxResClose(h)` | Close the handle and free associated memory. |

### Key Types

| Type | Description |
|------|-------------|
| `DvxResDirEntryT` | Directory entry: name[32], type, offset, size, reserved (48 bytes) |
| `DvxResFooterT` | Footer: magic (`0x52585644` = "DVXR"), dirOffset, entryCount, reserved (16 bytes) |
| `DvxResHandleT` | Runtime handle: path, entries array, entry count |


## Memory Tracking (dvxMem.h)

Per-app memory tracking wraps malloc/free/calloc/realloc/strdup with a
small header per allocation that records the owning app ID and size.
DXE code does not need to include dvxMem.h -- the DXE export table maps
the standard allocator names to these wrappers transparently.

| Function | Description |
|----------|-------------|
| `dvxMalloc(size)` | Tracked malloc |
| `dvxCalloc(nmemb, size)` | Tracked calloc |
| `dvxRealloc(ptr, size)` | Tracked realloc |
| `dvxFree(ptr)` | Tracked free (falls through to real free on non-tracked pointers) |
| `dvxStrdup(s)` | Tracked strdup |
| `dvxMemSnapshotLoad(appId)` | Record baseline memory for leak detection |
| `dvxMemGetAppUsage(appId)` | Query current memory usage for an app (bytes) |
| `dvxMemResetApp(appId)` | Free all allocations charged to an app |

The global `dvxMemAppIdPtr` pointer is set by the shell to
`&ctx->currentAppId` so the allocator knows which app to charge.


## WidgetT Structure

The WidgetT struct is generic -- no widget-specific fields or union:

```c
typedef struct WidgetT {
    int32_t                    type;       // assigned by wgtRegisterClass()
    const struct WidgetClassT *wclass;     // dispatch table pointer
    char                       name[MAX_WIDGET_NAME];

    // Tree linkage
    struct WidgetT  *parent, *firstChild, *lastChild, *nextSibling;
    WindowT         *window;

    // Geometry (relative to window content area)
    int32_t  x, y, w, h;
    int32_t  calcMinW, calcMinH;           // computed minimum size

    // Size hints (tagged: wgtPixels/wgtChars/wgtPercent, 0 = auto)
    int32_t  minW, minH, maxW, maxH;
    int32_t  prefW, prefH;
    int32_t  weight;                       // extra-space distribution (0 = fixed)

    // Container properties
    WidgetAlignE align;
    int32_t  spacing, padding;             // tagged sizes

    // Colors (0 = use color scheme defaults)
    uint32_t fgColor, bgColor;

    // State
    bool  visible, enabled, readOnly, focused;
    char  accelKey;

    // User data and callbacks
    void        *userData;
    void        *data;         // widget-private data (allocated by widget DXE)
    const char  *tooltip;
    MenuT       *contextMenu;
    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;
```


## WidgetClassT Dispatch Table

WidgetClassT is an ABI-stable dispatch table. Method IDs are fixed
constants that never change -- adding new methods appends new IDs
without shifting existing ones. Widget DXEs compiled against an older
DVX version continue to work unmodified.

```c
#define WGT_CLASS_VERSION  1   // bump on breaking ABI change
#define WGT_METHOD_MAX    32   // room for future methods

typedef struct WidgetClassT {
    uint32_t version;
    uint32_t flags;
    void    *handlers[WGT_METHOD_MAX];
} WidgetClassT;
```

### Method ID Table

21 methods are currently defined (IDs 0--20). WGT_METHOD_MAX is 32,
leaving room for 11 future methods without a version bump.

| ID | Method ID | Signature | Purpose |
|----|-----------|-----------|---------|
| 0 | `WGT_METHOD_PAINT` | `void (w, d, ops, font, colors)` | Render the widget |
| 1 | `WGT_METHOD_PAINT_OVERLAY` | `void (w, d, ops, font, colors)` | Render overlay (dropdown popup) |
| 2 | `WGT_METHOD_CALC_MIN_SIZE` | `void (w, font)` | Compute minimum size (bottom-up pass) |
| 3 | `WGT_METHOD_LAYOUT` | `void (w, font)` | Position children (top-down pass) |
| 4 | `WGT_METHOD_GET_LAYOUT_METRICS` | `void (w, font, pad, gap, extraTop, borderW)` | Return padding/gap for box layout |
| 5 | `WGT_METHOD_ON_MOUSE` | `void (w, root, vx, vy)` | Handle mouse click |
| 6 | `WGT_METHOD_ON_KEY` | `void (w, key, mod)` | Handle keyboard input |
| 7 | `WGT_METHOD_ON_ACCEL_ACTIVATE` | `void (w, root)` | Handle accelerator key match |
| 8 | `WGT_METHOD_DESTROY` | `void (w)` | Free widget-private data |
| 9 | `WGT_METHOD_ON_CHILD_CHANGED` | `void (parent, child)` | Notification when a child changes |
| 10 | `WGT_METHOD_GET_TEXT` | `const char *(w)` | Return widget text |
| 11 | `WGT_METHOD_SET_TEXT` | `void (w, text)` | Set widget text |
| 12 | `WGT_METHOD_CLEAR_SELECTION` | `bool (w)` | Clear text/item selection |
| 13 | `WGT_METHOD_CLOSE_POPUP` | `void (w)` | Close dropdown popup |
| 14 | `WGT_METHOD_GET_POPUP_RECT` | `void (w, font, contentH, popX, popY, popW, popH)` | Compute popup rectangle |
| 15 | `WGT_METHOD_ON_DRAG_UPDATE` | `void (w, root, x, y)` | Mouse move during drag |
| 16 | `WGT_METHOD_ON_DRAG_END` | `void (w, root, x, y)` | Mouse release after drag |
| 17 | `WGT_METHOD_GET_CURSOR_SHAPE` | `int32_t (w, vx, vy)` | Return cursor ID for this position |
| 18 | `WGT_METHOD_POLL` | `void (w, win)` | Periodic polling (AnsiTerm comms) |
| 19 | `WGT_METHOD_QUICK_REPAINT` | `int32_t (w, outY, outH)` | Fast incremental repaint |
| 20 | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` | `void (parent, child)` | Scroll to make a child visible |

### Typed Dispatch Helpers

Each `wclsFoo()` inline function extracts a handler by stable method
ID, casts it to the correct function pointer type, and calls it with
a NULL check. This gives callers type-safe dispatch with the same
codegen as a direct struct field call.

| Helper | Wraps Method ID |
|--------|-----------------|
| `wclsHas(w, methodId)` | Check if a method is implemented (non-NULL) |
| `wclsPaint(w, d, ops, font, colors)` | `WGT_METHOD_PAINT` |
| `wclsPaintOverlay(w, d, ops, font, colors)` | `WGT_METHOD_PAINT_OVERLAY` |
| `wclsCalcMinSize(w, font)` | `WGT_METHOD_CALC_MIN_SIZE` |
| `wclsLayout(w, font)` | `WGT_METHOD_LAYOUT` |
| `wclsGetLayoutMetrics(w, font, pad, gap, extraTop, borderW)` | `WGT_METHOD_GET_LAYOUT_METRICS` |
| `wclsOnMouse(w, root, vx, vy)` | `WGT_METHOD_ON_MOUSE` |
| `wclsOnKey(w, key, mod)` | `WGT_METHOD_ON_KEY` |
| `wclsOnAccelActivate(w, root)` | `WGT_METHOD_ON_ACCEL_ACTIVATE` |
| `wclsDestroy(w)` | `WGT_METHOD_DESTROY` |
| `wclsOnChildChanged(parent, child)` | `WGT_METHOD_ON_CHILD_CHANGED` |
| `wclsGetText(w)` | `WGT_METHOD_GET_TEXT` |
| `wclsSetText(w, text)` | `WGT_METHOD_SET_TEXT` |
| `wclsClearSelection(w)` | `WGT_METHOD_CLEAR_SELECTION` |
| `wclsClosePopup(w)` | `WGT_METHOD_CLOSE_POPUP` |
| `wclsGetPopupRect(w, font, contentH, popX, popY, popW, popH)` | `WGT_METHOD_GET_POPUP_RECT` |
| `wclsOnDragUpdate(w, root, x, y)` | `WGT_METHOD_ON_DRAG_UPDATE` |
| `wclsOnDragEnd(w, root, x, y)` | `WGT_METHOD_ON_DRAG_END` |
| `wclsGetCursorShape(w, vx, vy)` | `WGT_METHOD_GET_CURSOR_SHAPE` |
| `wclsPoll(w, win)` | `WGT_METHOD_POLL` |
| `wclsQuickRepaint(w, outY, outH)` | `WGT_METHOD_QUICK_REPAINT` |
| `wclsScrollChildIntoView(parent, child)` | `WGT_METHOD_SCROLL_CHILD_INTO_VIEW` |

### WidgetClassT Flags

| Flag | Value | Description |
|------|-------|-------------|
| `WCLASS_FOCUSABLE` | 0x0001 | Can receive keyboard focus |
| `WCLASS_BOX_CONTAINER` | 0x0002 | Uses VBox/HBox layout algorithm |
| `WCLASS_HORIZ_CONTAINER` | 0x0004 | Lays out children horizontally |
| `WCLASS_PAINTS_CHILDREN` | 0x0008 | Widget handles child rendering |
| `WCLASS_NO_HIT_RECURSE` | 0x0010 | Hit testing stops here |
| `WCLASS_FOCUS_FORWARD` | 0x0020 | Accel hit forwards focus to next focusable |
| `WCLASS_HAS_POPUP` | 0x0040 | Has dropdown popup overlay |
| `WCLASS_SCROLLABLE` | 0x0080 | Accepts mouse wheel events |
| `WCLASS_SCROLL_CONTAINER` | 0x0100 | Scroll container (ScrollPane) |
| `WCLASS_NEEDS_POLL` | 0x0200 | Needs periodic polling |
| `WCLASS_SWALLOWS_TAB` | 0x0400 | Tab key goes to widget, not focus nav |
| `WCLASS_RELAYOUT_ON_SCROLL` | 0x0800 | Full relayout on scrollbar drag |
| `WCLASS_PRESS_RELEASE` | 0x1000 | Click = press+release (buttons) |
| `WCLASS_ACCEL_WHEN_HIDDEN` | 0x2000 | Accel matching works when invisible |


## Widget Registration

Each widget DXE exports `wgtRegister()`, called by the loader after
`dlopen`. The WidgetClassT uses the `handlers[]` array indexed by
method IDs:

```c
static int32_t sButtonType;

static const WidgetClassT sButtonClass = {
    .version  = WGT_CLASS_VERSION,
    .flags    = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
    .handlers = {
        [WGT_METHOD_PAINT]            = buttonPaint,
        [WGT_METHOD_CALC_MIN_SIZE]    = buttonCalcMinSize,
        [WGT_METHOD_ON_MOUSE]         = buttonOnMouse,
        [WGT_METHOD_ON_KEY]           = buttonOnKey,
        [WGT_METHOD_DESTROY]          = buttonDestroy,
        [WGT_METHOD_GET_TEXT]         = buttonGetText,
        [WGT_METHOD_SET_TEXT]         = buttonSetText,
        [WGT_METHOD_ON_ACCEL_ACTIVATE] = buttonAccelActivate,
    }
};

static const ButtonApiT sApi = { .create = buttonCreate };

void wgtRegister(void) {
    sButtonType = wgtRegisterClass(&sButtonClass);
    wgtRegisterApi("button", &sApi);
}
```


## Per-Widget API Registry

The monolithic WidgetApiT is gone. Each widget registers a small API
struct under a name via `wgtRegisterApi()`. Callers retrieve it via
`wgtGetApi()` and cast to the widget-specific type. Per-widget headers
(e.g. `widgetButton.h`) provide typed accessors and convenience macros:

```c
// widgetButton.h
typedef struct {
    WidgetT *(*create)(WidgetT *parent, const char *text);
} ButtonApiT;

static inline const ButtonApiT *dvxButtonApi(void) {
    static const ButtonApiT *sApi;
    if (!sApi) { sApi = (const ButtonApiT *)wgtGetApi("button"); }
    return sApi;
}

#define wgtButton(parent, text) dvxButtonApi()->create(parent, text)
```


## Tagged Size Values

Size hints encode both unit type and numeric value in a single int32_t:

| Macro | Encoding | Example |
|-------|----------|---------|
| `wgtPixels(v)` | Bits 31:30 = 00 | `w->minW = wgtPixels(200);` |
| `wgtChars(v)` | Bits 31:30 = 01 | `w->minW = wgtChars(40);` |
| `wgtPercent(v)` | Bits 31:30 = 10 | `w->minW = wgtPercent(50);` |
| `0` | -- | Auto (use computed minimum) |


## Layout Algorithm

Two-pass flexbox-like layout:

1. **Bottom-up** (`calcMinSize`): Each widget computes its minimum size.
   Containers sum children along the main axis, max across the cross axis.
2. **Top-down** (`layout`): Available space is allocated to children.
   Extra space beyond minimum is distributed proportionally to weights.
   `weight=0` means fixed size, `weight=100` is the default flexible weight.

### Cross-Axis Centering

When a child widget has a `maxW` (in a VBox) or `maxH` (in an HBox)
that constrains it smaller than the available cross-axis space, the
layout engine automatically centers the child on the cross axis. This
means setting `maxW` or `maxH` on a child inside a container will both
cap its size and center it within the remaining space.


## Image Loading

Two image loading functions are available:

| Function | Description |
|----------|-------------|
| `dvxLoadImage(ctx, path, outW, outH, outPitch)` | Load from a file path |
| `dvxLoadImageFromMemory(ctx, data, dataLen, outW, outH, outPitch)` | Load from a memory buffer (e.g. resource data) |

Both convert to the display's native pixel format. Caller frees the
returned buffer with `dvxFreeImage()`. Supported formats: BMP, PNG,
JPEG, GIF (via stb_image).


## Exported Symbols

libdvx.lib exports symbols matching these prefixes:

```
dvx*, wgt*, wm*, prefs*, rect*, draw*, pack*, text*, setClip*,
resetClip*, stbi*, stbi_write*, dirtyList*, widget*,
accelParse*, clipboard*, multiClick*,
sCursor*, sDbl*, sDebug*, sClosed*, sFocused*, sKey*,
sOpen*, sDrag*, sClosed*, sKey*
```


## Build

```
make            # builds bin/libs/libdvx.lib + bin/libs/libdvx.dep
make clean      # removes objects and library
```

Depends on: `libtasks.lib` (via libdvx.dep).
