DVX_GUI/dvx/README.md
2026-03-20 20:00:05 -05:00

39 KiB

DVX GUI Library (libdvx.a)

The core GUI compositor library for DVX. Provides VESA video setup, 2D drawing primitives, dirty-rectangle compositing, a full window manager with Motif-style chrome, and a 32-type widget toolkit with automatic layout. Applications include dvxApp.h (which pulls in all lower layers) and optionally dvxWidget.h for the widget system.

Architecture

The library is organized in five layers, each a .h/.c pair. Higher layers depend on lower ones but never the reverse.

Layer 5  dvxApp      Event loop, mouse/keyboard input, public API
Layer 4  dvxWm       Window stack, chrome, drag, resize, focus, menus
Layer 3  dvxComp     Dirty rectangle list, merge, LFB flush
Layer 2  dvxDraw     Spans, rects, bevels, text, bitmaps (asm inner loops)
Layer 1  dvxVideo    VESA init, LFB mapping, backbuffer, pixel format
         dvxWidget   Widget/layout system (optional, standalone)

File Structure

Core Layers

File Purpose
dvxVideo.h/.c Layer 1: VESA VBE mode negotiation, LFB mapping, system RAM backbuffer, pixel format discovery, color packing
dvxDraw.h/.c Layer 2: Rectangle fills, bitmap blits, text rendering, bevels, lines, cursor/icon rendering
dvxComp.h/.c Layer 3: Dirty rectangle tracking, merge, backbuffer-to-LFB flush
dvxWm.h/.c Layer 4: Window lifecycle, Z-order stack, chrome drawing, hit testing, drag/resize/scroll
dvxApp.h/.c Layer 5: AppContextT, event loop, window creation, color scheme, wallpaper, screenshots

Supporting Files

File Purpose
dvxTypes.h Shared type definitions used by all layers (PixelFormatT, DisplayT, WindowT, ColorSchemeT, etc.)
dvxWidget.h Widget system public API (32 widget types, layout, events)
dvxDialog.h/.c Modal dialogs (message box, file open/save)
dvxPrefs.h/.c INI-based preferences system (read/write with typed accessors)
dvxFont.h Embedded 8x16 VGA bitmap font glyph data (CP437, 256 glyphs)
dvxCursor.h Mouse cursor bitmask data (5 shapes: arrow, resize H/V/NWSE/NESW)
dvxPalette.h Default VGA palette for 8-bit mode
dvxIcon.c stb_image implementation unit (BMP/PNG/JPEG/GIF loading)
dvxImageWrite.c stb_image_write implementation unit (PNG export)

Platform Abstraction

File Purpose
platform/dvxPlatform.h OS/CPU-neutral interface: video, input, span ops, filesystem
platform/dvxPlatformDos.c DOS/DJGPP: VESA, DPMI, INT 16h/33h, rep stosl/movsd asm spans

To port DVX to a new platform, implement a new dvxPlatformXxx.c against platform/dvxPlatform.h and swap it in the Makefile. No other files need modification.

Widget System

File Purpose
widgets/widgetInternal.h Shared internal header: vtable type, constants, cross-widget prototypes
widgets/widgetClass.c Widget class table: one WidgetClassT vtable entry per type
widgets/widgetCore.c Allocation, tree ops, hit testing, flag-based type queries
widgets/widgetLayout.c Two-pass layout engine (measure + arrange)
widgets/widgetEvent.c Mouse, keyboard, scroll, resize, and paint event dispatch
widgets/widgetOps.c Paint dispatcher, public operations (wgtFind, wgtDestroy, etc.)
widgets/widget*.c One file per widget type (button, checkbox, slider, etc.)

Each widget type is self-contained in its own .c file. Dispatch for paint, layout, mouse, keyboard, getText/setText, and destroy is driven by a per-type vtable (WidgetClassT) rather than switch statements. Adding a new widget type requires only a new .c file and an entry in the class table.

Third Party

File Purpose
thirdparty/stb_image.h Single-header image loader (BMP, PNG, JPEG, GIF)
thirdparty/stb_image_write.h Single-header image writer (PNG)

Building

Requires the DJGPP cross-compiler (i586-pc-msdosdjgpp-gcc).

make            # builds ../lib/libdvx.a
make clean      # removes obj/ and lib/

Set DJGPP_PREFIX in the Makefile if your toolchain is installed somewhere other than ~/djgpp/djgpp.


Quick Start

Minimal window (raw drawing)

#include "dvxApp.h"

static void onPaint(WindowT *win, RectT *dirty) {
    AppContextT *ctx = (AppContextT *)win->userData;
    const BlitOpsT *ops = dvxGetBlitOps(ctx);
    const DisplayT *d   = dvxGetDisplay(ctx);
    uint32_t blue = packColor(d, 0, 0, 200);

    for (int32_t y = 0; y < win->contentH; y++) {
        ops->spanFill(win->contentBuf + y * win->contentPitch,
                      blue, win->contentW);
    }
}

int main(void) {
    AppContextT ctx;
    if (dvxInit(&ctx, 1024, 768, 16) != 0) {
        return 1;
    }

    WindowT *win = dvxCreateWindow(&ctx, "Hello", 100, 100, 300, 200, true);
    win->userData = &ctx;
    win->onPaint  = onPaint;

    RectT r = {0, 0, win->contentW, win->contentH};
    win->onPaint(win, &r);

    dvxRun(&ctx);
    dvxShutdown(&ctx);
    return 0;
}

Widget-based window

#include "dvxApp.h"
#include "dvxWidget.h"

static void onButtonClick(WidgetT *w) {
    WidgetT *root = w;
    while (root->parent) root = root->parent;

    WidgetT *lbl = wgtFind(root, "status");
    if (lbl) {
        wgtSetText(lbl, "Clicked!");
        wgtInvalidate(lbl);
    }
}

int main(void) {
    AppContextT ctx;
    dvxInit(&ctx, 1024, 768, 16);

    WindowT *win = dvxCreateWindow(&ctx, "Widgets", 100, 100, 260, 200, true);
    win->userData = &ctx;

    WidgetT *root = wgtInitWindow(&ctx, win);

    WidgetT *lbl = wgtLabel(root, "Ready.");
    wgtSetName(lbl, "status");

    wgtHSeparator(root);

    WidgetT *row = wgtHBox(root);
    wgtLabel(row, "Name:");
    wgtTextInput(row, 64);

    WidgetT *btn = wgtButton(root, "Go");
    btn->onClick = onButtonClick;

    wgtInvalidate(root);

    dvxRun(&ctx);
    dvxShutdown(&ctx);
    return 0;
}

Application API (dvxApp.h)

Lifecycle

int32_t dvxInit(AppContextT *ctx, int32_t requestedW,
                int32_t requestedH, int32_t preferredBpp);

Initialize VESA video, input, fonts, color scheme, and cursors. Finds a mode matching the requested resolution and bit depth. Returns 0 on success, -1 on failure.

void dvxRun(AppContextT *ctx);

Enter the main event loop. Handles mouse, keyboard, window management, compositing, and LFB flush. Returns when dvxQuit() is called.

bool dvxUpdate(AppContextT *ctx);

Process one iteration of the event loop. Returns true if still running, false when exit requested. Use instead of dvxRun() when embedding the GUI in an existing main loop.

void dvxShutdown(AppContextT *ctx);

Restore text mode, release LFB mapping, free the backbuffer.

void dvxQuit(AppContextT *ctx);

Request exit from the main loop.

Video Mode

int32_t dvxChangeVideoMode(AppContextT *ctx, int32_t requestedW,
                           int32_t requestedH, int32_t preferredBpp);

Switch to a new video mode live. Reallocates the backbuffer, all window content buffers, repacks colors, rescales wallpaper, and repositions windows. Returns 0 on success, -1 on failure (old mode restored).

const VideoModeInfoT *dvxGetVideoModes(const AppContextT *ctx,
                                       int32_t *count);

Return the list of available VESA modes (enumerated at init).

Windows

WindowT *dvxCreateWindow(AppContextT *ctx, const char *title,
                         int32_t x, int32_t y, int32_t w, int32_t h,
                         bool resizable);
WindowT *dvxCreateWindowCentered(AppContextT *ctx, const char *title,
                                 int32_t w, int32_t h, bool resizable);
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);

After creation, set win->userData and install callbacks:

Callback Signature When Called
onPaint void (WindowT *win, RectT *dirtyArea) Content needs redrawing
onKey void (WindowT *win, int32_t key, int32_t mod) Key press (focused window)
onMouse void (WindowT *win, int32_t x, int32_t y, int32_t buttons) Mouse event in content area
onResize void (WindowT *win, int32_t newW, int32_t newH) Window resized by user
onClose void (WindowT *win) Close button double-clicked
onMenu void (WindowT *win, int32_t menuId) Menu item selected or accelerator triggered
onScroll void (WindowT *win, ScrollbarOrientE orient, int32_t value) Scrollbar moved

Mouse/key coordinates are relative to the content area. buttons is a bitmask: MOUSE_LEFT (1), MOUSE_RIGHT (2), MOUSE_MIDDLE (4).

Example:

static void onClose(WindowT *win) {
    AppContextT *ctx = (AppContextT *)win->userData;
    dvxDestroyWindow(ctx, win);
}

WindowT *win = dvxCreateWindow(&ctx, "My Window", 50, 50, 400, 300, true);
win->userData = &ctx;
win->onClose  = onClose;
win->onPaint  = myPaintHandler;

Window Properties

Set directly on WindowT after creation:

Field Type Default Description
maxW int32_t -1 Maximum width when maximized (-1 = screen width)
maxH int32_t -1 Maximum height when maximized (-1 = screen height)
modal bool false When true, only this window receives input
userData void * NULL Application data pointer

Window Operations

void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title);
int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path);
void dvxMinimizeWindow(AppContextT *ctx, WindowT *win);
void dvxMaximizeWindow(AppContextT *ctx, WindowT *win);
void dvxFitWindow(AppContextT *ctx, WindowT *win);

dvxFitWindow resizes the window to exactly fit its widget tree's computed minimum size (plus chrome). Useful for dialog boxes.

Window Arrangement

dvxCascadeWindows(&ctx);      // staggered diagonal cascade
dvxTileWindows(&ctx);         // NxM grid fill
dvxTileWindowsH(&ctx);        // side-by-side, equal width
dvxTileWindowsV(&ctx);        // stacked, equal height

Invalidation

void dvxInvalidateRect(AppContextT *ctx, WindowT *win,
                       int32_t x, int32_t y, int32_t w, int32_t h);
void dvxInvalidateWindow(AppContextT *ctx, WindowT *win);

Mark a region (or the entire content area) as needing repaint. The compositor flushes dirty rectangles to the LFB on the next frame.

Content Buffer

Each window has a persistent content backbuffer in display pixel format:

Field Description
contentBuf Pixel data in native display format
contentPitch Bytes per scanline
contentW Width in pixels
contentH Height in pixels

Paint callbacks write directly into contentBuf. The compositor copies visible portions to the screen backbuffer, then flushes dirty rects to the LFB.

Clipboard

dvxClipboardCopy("Hello", 5);

int32_t len;
const char *text = dvxClipboardGet(&len);

Process-wide static buffer. Adequate for copy/paste within DVX on single-tasking DOS.

Screenshots

dvxScreenshot(&ctx, "screen.png");            // entire screen
dvxWindowScreenshot(&ctx, win, "window.png"); // single window content

Converts from native pixel format to RGB for the PNG encoder.

Image Loading

uint8_t *dvxLoadImage(const AppContextT *ctx, const char *path,
                      int32_t *outW, int32_t *outH, int32_t *outPitch);
void dvxFreeImage(uint8_t *data);
int32_t dvxSaveImage(const AppContextT *ctx, const uint8_t *data,
                     int32_t w, int32_t h, int32_t pitch, const char *path);

Load BMP/PNG/JPEG/GIF files and convert to the display's native pixel format. Save native-format pixel data to PNG.

Accessors

DisplayT         *dvxGetDisplay(AppContextT *ctx);
const BlitOpsT   *dvxGetBlitOps(const AppContextT *ctx);
const BitmapFontT *dvxGetFont(const AppContextT *ctx);
const ColorSchemeT *dvxGetColors(const AppContextT *ctx);

Mouse Configuration

void dvxSetMouseConfig(AppContextT *ctx, int32_t wheelDir,
                       int32_t dblClickMs, int32_t accelThreshold);

wheelDir: 1 = normal, -1 = reversed. dblClickMs: double-click speed in milliseconds. accelThreshold: double-speed threshold in mickeys/sec (0 = unchanged).


Color System

DVX uses a 20-color scheme that controls the entire UI appearance. All colors are pre-packed into native pixel format at init time for zero-cost per-pixel rendering.

ColorSchemeT Fields

Field Usage
desktop Desktop background fill
windowFace Window frame fill
windowHighlight Bevel light edge (top/left)
windowShadow Bevel dark edge (bottom/right)
activeTitleBg / activeTitleFg Focused window title bar
inactiveTitleBg / inactiveTitleFg Unfocused window title bar
contentBg / contentFg Window content area defaults
menuBg / menuFg Menu bar and popup menus
menuHighlightBg / menuHighlightFg Highlighted menu item
buttonFace Push button interior
scrollbarBg / scrollbarFg / scrollbarTrough Scrollbar elements
cursorFg / cursorBg Mouse cursor colors

ColorIdE Enum

Each color has an integer ID for programmatic access:

ColorDesktopE, ColorWindowFaceE, ColorWindowHighlightE,
ColorWindowShadowE, ColorActiveTitleBgE, ColorActiveTitleFgE,
ColorInactiveTitleBgE, ColorInactiveTitleFgE, ColorContentBgE,
ColorContentFgE, ColorMenuBgE, ColorMenuFgE, ColorMenuHighlightBgE,
ColorMenuHighlightFgE, ColorButtonFaceE, ColorScrollbarBgE,
ColorScrollbarFgE, ColorScrollbarTroughE, ColorCursorFgE,
ColorCursorBgE, ColorCountE

Color API

void dvxSetColor(AppContextT *ctx, ColorIdE id, uint8_t r, uint8_t g, uint8_t b);
void dvxGetColor(const AppContextT *ctx, ColorIdE id, uint8_t *r, uint8_t *g, uint8_t *b);
void dvxApplyColorScheme(AppContextT *ctx);
void dvxResetColorScheme(AppContextT *ctx);

Theme Files

Theme files are INI-format with a [colors] section containing RGB values for each of the 20 color IDs:

bool dvxLoadTheme(AppContextT *ctx, const char *filename);
bool dvxSaveTheme(const AppContextT *ctx, const char *filename);
const char *dvxColorName(ColorIdE id);   // INI key name
const char *dvxColorLabel(ColorIdE id);  // human-readable label

Bundled themes: cde.thm, geos.thm, win31.thm.


Wallpaper System

bool dvxSetWallpaper(AppContextT *ctx, const char *path);
void dvxSetWallpaperMode(AppContextT *ctx, WallpaperModeE mode);

Three display modes:

Mode Enum Description
Stretch WallpaperStretchE Bilinear scaling to fill screen; ordered dithering for 16bpp
Tile WallpaperTileE Repeating pattern from top-left
Center WallpaperCenterE Centered with desktop color fill around edges

The wallpaper is pre-rendered to screen dimensions in native pixel format. Pass NULL to dvxSetWallpaper to clear. The wallpaper path is stored so the image can be reloaded after a video mode change.

Supported formats: BMP, PNG, JPEG, GIF (via stb_image).


Preferences System (dvxPrefs.h)

INI-based configuration with typed read/write accessors and caller-supplied defaults.

bool prefsLoad(const char *filename);
bool prefsSave(void);
bool prefsSaveAs(const char *filename);
void prefsFree(void);

const char *prefsGetString(const char *section, const char *key, const char *defaultVal);
int32_t     prefsGetInt(const char *section, const char *key, int32_t defaultVal);
bool        prefsGetBool(const char *section, const char *key, bool defaultVal);

void prefsSetString(const char *section, const char *key, const char *value);
void prefsSetInt(const char *section, const char *key, int32_t value);
void prefsSetBool(const char *section, const char *key, bool value);
void prefsRemove(const char *section, const char *key);

Only one file may be loaded at a time. If the file is missing or a key is absent, getters return the caller-supplied default silently. Boolean values recognize true/yes/1 and false/no/0.


Menu System

Menu Bars

MenuBarT *wmAddMenuBar(WindowT *win);
MenuT    *wmAddMenu(MenuBarT *bar, const char *label);
void      wmAddMenuItem(MenuT *menu, const char *label, int32_t id);
void      wmAddMenuCheckItem(MenuT *menu, const char *label, int32_t id, bool checked);
void      wmAddMenuRadioItem(MenuT *menu, const char *label, int32_t id, bool checked);
void      wmAddMenuSeparator(MenuT *menu);
MenuT    *wmAddSubMenu(MenuT *parentMenu, const char *label);

The & prefix marks accelerator keys -- "&File" means Alt+F opens the menu. Up to 8 menus per bar, 16 items per menu, submenus nested up to 4 levels deep.

After adding menus, call wmUpdateContentRect(win) and wmReallocContentBuf(win, &ctx.display) to adjust the content area.

Example:

MenuBarT *bar = wmAddMenuBar(win);

MenuT *fileMenu = wmAddMenu(bar, "&File");
wmAddMenuItem(fileMenu, "&New", CMD_NEW);
wmAddMenuItem(fileMenu, "&Open...", CMD_OPEN);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "E&xit", CMD_EXIT);

MenuT *viewMenu = wmAddMenu(bar, "&View");
wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_TOOLBAR, true);
wmAddMenuRadioItem(viewMenu, "&Small", CMD_SMALL, false);
wmAddMenuRadioItem(viewMenu, "&Large", CMD_LARGE, true);

MenuT *zoomMenu = wmAddSubMenu(viewMenu, "&Zoom");
wmAddMenuItem(zoomMenu, "Zoom &In", CMD_ZOOM_IN);
wmAddMenuItem(zoomMenu, "Zoom &Out", CMD_ZOOM_OUT);

wmUpdateContentRect(win);
wmReallocContentBuf(win, &ctx.display);

Context Menus

Right-click menus attached to windows or widgets:

MenuT *ctxMenu = wmCreateMenu();
wmAddMenuItem(ctxMenu, "Cu&t", CMD_CUT);
wmAddMenuItem(ctxMenu, "&Copy", CMD_COPY);
wmAddMenuItem(ctxMenu, "&Paste", CMD_PASTE);
win->contextMenu = ctxMenu;

// Widget-level context menu
WidgetT *lb = wgtListBox(root);
lb->contextMenu = wmCreateMenu();
wmAddMenuItem(lb->contextMenu, "&Delete", CMD_DELETE);

Context menus route through the window's onMenu callback. The caller owns the MenuT allocation (free with wmFreeMenu()).

Accelerator Tables

Keyboard shortcuts routed through onMenu:

AccelTableT *accel = dvxCreateAccelTable();
dvxAddAccel(accel, 'N', ACCEL_CTRL, CMD_NEW);
dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_OPEN);
dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_SAVE);
dvxAddAccel(accel, KEY_F1, 0, CMD_HELP);
win->accelTable = accel;

Modifier constants: ACCEL_SHIFT (0x03), ACCEL_CTRL (0x04), ACCEL_ALT (0x08).

Key constants for extended keys: KEY_F1--KEY_F12, KEY_INSERT, KEY_DELETE, KEY_HOME, KEY_END, KEY_PGUP, KEY_PGDN.

Up to 32 entries per table. Free with dvxFreeAccelTable().


Scrollbars

ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t pageSize);
ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t pageSize);

After adding scrollbars, call wmUpdateContentRect(win) and wmReallocContentBuf(win, &ctx.display). The onScroll callback fires when the user drags the thumb or clicks arrow buttons / trough.

Widget-managed windows handle their own scrollbars automatically -- do not add them manually to widget windows.


Dialogs (dvxDialog.h)

Message Box

int32_t result = dvxMessageBox(&ctx, "Confirm",
    "Are you sure?", MB_YESNO | MB_ICONQUESTION);

if (result == ID_YES) {
    dvxQuit(&ctx);
}

Button flags: MB_OK, MB_OKCANCEL, MB_YESNO, MB_YESNOCANCEL, MB_RETRYCANCEL.

Icon flags: MB_ICONINFO, MB_ICONWARNING, MB_ICONERROR, MB_ICONQUESTION.

Return values: ID_OK, ID_CANCEL, ID_YES, ID_NO, ID_RETRY.

File Dialog

static const FileFilterT filters[] = {
    {"All Files (*.*)",      "*.*"},
    {"Text Files (*.txt)",   "*.txt"},
    {"Bitmap Files (*.bmp)", "*.bmp"}
};

char path[260];
if (dvxFileDialog(&ctx, "Open File", FD_OPEN, NULL,
                  filters, 3, path, sizeof(path))) {
    // path contains the selected filename
}

Flags: FD_OPEN, FD_SAVE. Pass NULL for initialDir to use the current directory. Filename validation is platform-specific (8.3 on DOS).


Video and Drawing (dvxVideo.h, dvxDraw.h)

Lower-level APIs. Application code typically only needs packColor.

Color Packing

uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b);

Pack RGB into the display's native pixel format. For 8-bit mode, returns the nearest palette index.

Clip Rectangle

void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h);
void resetClipRect(DisplayT *d);

Rectangle Operations

void rectFill(DisplayT *d, const BlitOpsT *ops,
              int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
void rectCopy(DisplayT *d, const BlitOpsT *ops,
              int32_t dstX, int32_t dstY, const uint8_t *srcBuf,
              int32_t srcPitch, int32_t srcX, int32_t srcY,
              int32_t w, int32_t h);

Bevel Drawing

void drawBevel(DisplayT *d, const BlitOpsT *ops,
               int32_t x, int32_t y, int32_t w, int32_t h,
               const BevelStyleT *style);

Convenience macros for common bevel styles:

  • BEVEL_RAISED(cs, bw) -- standard raised (buttons, window frames)
  • BEVEL_SUNKEN(cs, face, bw) -- sunken (text fields, list boxes)
  • BEVEL_TROUGH(cs) -- scrollbar trough (1px sunken)
  • BEVEL_SB_BUTTON(cs) -- scrollbar arrow button (1px raised)

Text Rendering

void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font,
              int32_t x, int32_t y, const char *text,
              uint32_t fg, uint32_t bg, bool opaque);
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font,
               int32_t x, int32_t y, const char *text, int32_t count,
               uint32_t fg, uint32_t bg, bool opaque);
int32_t textWidth(const BitmapFontT *font, const char *text);

drawTextN is the fast path for bulk rendering -- computes clip bounds once and fills the background in a single pass.

void drawTextAccel(DisplayT *d, const BlitOpsT *ops,
                   const BitmapFontT *font, int32_t x, int32_t y,
                   const char *text, uint32_t fg, uint32_t bg, bool opaque);
int32_t textWidthAccel(const BitmapFontT *font, const char *text);
char accelParse(const char *text);

Text with & accelerator markers. The character after & is underlined. && produces a literal &.

Terminal Row Rendering

void drawTermRow(DisplayT *d, const BlitOpsT *ops,
                 const BitmapFontT *font, int32_t x, int32_t y,
                 int32_t cols, const uint8_t *lineData,
                 const uint32_t *palette, bool blinkVisible,
                 int32_t cursorCol);

Renders an entire 80-column terminal row in one call. lineData points to (char, attr) byte pairs. Much faster than per-character rendering.

Lines and Focus

void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, uint32_t color);
void drawVLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t h, uint32_t color);
void drawFocusRect(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);

drawFocusRect draws a dotted rectangle (alternating pixels) for keyboard focus indicators.


Compositor (dvxComp.h)

void dirtyListInit(DirtyListT *dl);
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h);
void dirtyListMerge(DirtyListT *dl);
void dirtyListClear(DirtyListT *dl);
void flushRect(DisplayT *d, const RectT *r);
bool rectIntersect(const RectT *a, const RectT *b, RectT *result);
bool rectIsEmpty(const RectT *r);

The compositing pipeline each frame:

  1. Layers above call dirtyListAdd() for changed regions
  2. dirtyListMerge() consolidates overlapping/adjacent rects
  3. For each merged dirty rect, redraw desktop then each window (back-to-front, painter's algorithm)
  4. flushRect() copies each dirty rect from backBuf to the LFB

Up to 128 dirty rects per frame (MAX_DIRTY_RECTS).


Window Manager (dvxWm.h)

Window Stack

Windows are stored in an array of pointers ordered front-to-back: index count-1 is the topmost window. The compositor iterates back-to-front for painting and front-to-back for hit testing.

void wmInit(WindowStackT *stack);
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d, const char *title,
                        int32_t x, int32_t y, int32_t w, int32_t h, bool resizable);
void wmDestroyWindow(WindowStackT *stack, WindowT *win);
void wmRaiseWindow(WindowStackT *stack, DirtyListT *dl, int32_t idx);
void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx);

Hit Testing

int32_t wmHitTest(const WindowStackT *stack, int32_t mx, int32_t my, int32_t *hitPart);

Returns the stack index and hit region: HIT_CONTENT, HIT_TITLE, HIT_CLOSE, HIT_RESIZE, HIT_MENU, HIT_VSCROLL, HIT_HSCROLL, HIT_MINIMIZE, HIT_MAXIMIZE, HIT_NONE.

Window Operations

void wmMaximize(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
void wmMinimize(WindowStackT *stack, DirtyListT *dl, WindowT *win);
void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win);

System Menu

The system menu (control menu) appears on single-click of the close gadget. Commands: Restore, Move, Size, Minimize, Maximize, Close, Screenshot, Window Screenshot. Matches Windows 3.x behavior.

Keyboard Window Management

Key Action
Alt+Tab Cycle windows forward
Shift+Alt+Tab Cycle windows backward
Alt+F4 Close focused window
Alt+Space Open system menu
F10 Activate menu bar

Widget System (dvxWidget.h)

A retained-mode widget toolkit layered on top of the window manager. Widgets form a tree rooted at a per-window VBox container. Layout is automatic via a flexbox-like algorithm with weighted space distribution.

Widget Catalog (32 types)

Containers

Widget Constructor Description
VBox wgtVBox(parent) Vertical stack layout
HBox wgtHBox(parent) Horizontal stack layout
Frame wgtFrame(parent, title) Titled groupbox with bevel border
TabControl wgtTabControl(parent) Tabbed page container with scrollable headers
TabPage wgtTabPage(tabs, title) Single page within a TabControl
ScrollPane wgtScrollPane(parent) Scrollable container with automatic scrollbars
Splitter wgtSplitter(parent, vertical) Draggable divider between two child regions
Toolbar wgtToolbar(parent) Horizontal raised container for toolbar buttons
StatusBar wgtStatusBar(parent) Horizontal sunken container for status text

Basic Widgets

Widget Constructor Description
Label wgtLabel(parent, text) Static text display with optional alignment
Button wgtButton(parent, text) Beveled push button with press animation
Checkbox wgtCheckbox(parent, text) Toggle checkbox with checkmark glyph
RadioGroup wgtRadioGroup(parent) Container for mutually exclusive radio buttons
Radio wgtRadio(group, text) Individual radio button with diamond indicator

Text Input Widgets

Widget Constructor Description
TextInput wgtTextInput(parent, maxLen) Single-line text field with selection, undo, clipboard
PasswordInput wgtPasswordInput(parent, maxLen) Bullet-masked text field (no copy/cut)
MaskedInput wgtMaskedInput(parent, mask) Formatted input (e.g. "(###) ###-####")
TextArea wgtTextArea(parent, maxLen) Multi-line text editor with scrollbars

Selection Widgets

Widget Constructor Description
ListBox wgtListBox(parent) Scrollable item list with optional multi-select and drag reorder
Dropdown wgtDropdown(parent) Non-editable selection with popup list
ComboBox wgtComboBox(parent, maxLen) Editable text field with dropdown suggestions
TreeView wgtTreeView(parent) Hierarchical tree with expand/collapse, multi-select, drag reorder
TreeItem wgtTreeItem(parent, text) Item node within a TreeView
ListView wgtListView(parent) Multi-column list with sortable/resizable headers, multi-select

Value Widgets

Widget Constructor Description
Slider wgtSlider(parent, min, max) Draggable track bar (horizontal or vertical)
Spinner wgtSpinner(parent, min, max, step) Numeric up/down input with increment buttons
ProgressBar wgtProgressBar(parent) Percentage display bar (horizontal or vertical)

Visual Widgets

Widget Constructor Description
Image wgtImage(parent, data, w, h, pitch) Static image display (BMP/PNG/JPEG/GIF)
ImageButton wgtImageButton(parent, data, w, h, pitch) Clickable image button with press animation
Canvas wgtCanvas(parent, w, h) Drawable bitmap surface with programmatic drawing API
Spacer wgtSpacer(parent) Invisible flexible space (weight=100)
Separator wgtHSeparator(parent) / wgtVSeparator(parent) Beveled divider line

Special Widgets

Widget Constructor Description
AnsiTerm wgtAnsiTerm(parent, cols, rows) VT100/ANSI terminal emulator with 16-color CGA palette, scrollback, blink
Timer wgtTimer(parent, intervalMs, repeat) Invisible callback timer (one-shot or repeating)

Widget Event Model

All widget types share a universal set of event callbacks set directly on the WidgetT struct:

Callback Signature Description
onClick void (WidgetT *w) Mouse click completed on the widget
onDblClick void (WidgetT *w) Double-click on the widget
onChange void (WidgetT *w) Value or state changed (checkbox toggle, text edit, slider move, timer fire)
onFocus void (WidgetT *w) Widget received keyboard focus
onBlur void (WidgetT *w) Widget lost keyboard focus

Type-specific handlers (button press animation, listbox selection highlight) run first, then these universal callbacks fire.

Additional per-widget fields: userData (void pointer), tooltip (hover text), contextMenu (right-click menu).

Initialization

WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win);

Attach the widget system to a window. Returns the root VBox container. Installs onPaint, onMouse, onKey, and onResize handlers. Build the UI by adding child widgets to the returned root.

Call wgtInvalidate(root) after the tree is fully built to trigger the initial layout and paint.

Layout System

The layout engine runs in two passes:

  1. Measure (bottom-up): compute minimum sizes for every widget
  2. Arrange (top-down): distribute available space according to weights, respecting min/max constraints

Size Specifications

Size fields (minW, minH, maxW, maxH, prefW, prefH, spacing, padding) accept tagged values:

wgtPixels(120)     // 120 pixels
wgtChars(15)       // 15 character widths (15 * 8 = 120 pixels)
wgtPercent(50)     // 50% of parent dimension

A raw 0 means auto (use the widget's natural/computed size).

Weight

The weight field controls extra-space distribution among siblings:

  • weight = 0 -- fixed size, does not stretch (default for most widgets)
  • weight = 100 -- normal stretch (default for spacers, text inputs)
  • Relative ratios work: weights of 100, 200, 100 give a 1:2:1 split

When all children have weight 0, the container's align property (AlignStartE, AlignCenterE, AlignEndE) positions children within the extra space.

Container Properties

Field Type Default Description
align WidgetAlignE AlignStartE Main-axis alignment
spacing int32_t 0 (4px) Tagged gap between children
padding int32_t 0 (4px) Tagged internal padding

Widget Operations

void        wgtSetText(WidgetT *w, const char *text);
const char *wgtGetText(const WidgetT *w);
void        wgtSetEnabled(WidgetT *w, bool enabled);
void        wgtSetReadOnly(WidgetT *w, bool readOnly);
void        wgtSetVisible(WidgetT *w, bool visible);
void        wgtSetFocused(WidgetT *w);
WidgetT    *wgtGetFocused(void);
void        wgtSetName(WidgetT *w, const char *name);
WidgetT    *wgtFind(WidgetT *root, const char *name);
void        wgtDestroy(WidgetT *w);
void        wgtInvalidate(WidgetT *w);
void        wgtInvalidatePaint(WidgetT *w);
void        wgtSetTooltip(WidgetT *w, const char *text);
void        wgtSetDebugLayout(AppContextT *ctx, bool enabled);
AppContextT *wgtGetContext(const WidgetT *w);
  • wgtInvalidate triggers full relayout + repaint (use after structural changes)
  • wgtInvalidatePaint triggers repaint only (use for visual-only changes like checkbox toggle)
  • wgtFind searches by name using djb2 hash for fast rejection
  • wgtGetContext walks up the tree to retrieve the AppContextT

Timer Widget

WidgetT *wgtTimer(WidgetT *parent, int32_t intervalMs, bool repeat);
void     wgtTimerStart(WidgetT *w);
void     wgtTimerStop(WidgetT *w);
void     wgtTimerSetInterval(WidgetT *w, int32_t intervalMs);
bool     wgtTimerIsRunning(const WidgetT *w);

Invisible widget that fires onChange at the specified interval. Does not participate in layout. Set repeat = false for one-shot timers that auto-stop after the first fire. wgtUpdateTimers() is called automatically by dvxUpdate() each frame.

Layout and Paint Internals

Available for manual use when embedding in a custom event loop:

int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth);
void    wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT *font);
void    wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops,
                 const BitmapFontT *font, const ColorSchemeT *colors);

Font System

DVX uses a fixed-width 8x16 bitmap font covering the full IBM Code Page 437 character set (256 glyphs). The font data is compiled in as a static array -- no external font files.

Property Value
Width 8 pixels (fixed)
Height 16 pixels (8x14 also available)
Encoding CP437 (full 256 glyphs)
Format 1 bit per pixel, MSB = leftmost, 16 bytes per glyph
Box drawing Characters 176--223 (scrollbar arrows, window gadgets)

Character positions are pure multiplication (x = col * 8), and each glyph scanline is exactly one byte, enabling simple per-scanline rendering without bit shifting across byte boundaries.

Cursor System

Five software-rendered cursor shapes, stored as 16x16 AND/XOR bitmask pairs (the standard IBM VGA hardware cursor format):

ID Constant Shape
0 CURSOR_ARROW Standard arrow pointer
1 CURSOR_RESIZE_H Horizontal resize (left-right)
2 CURSOR_RESIZE_V Vertical resize (up-down)
3 CURSOR_RESIZE_DIAG_NWSE NW-SE diagonal resize
4 CURSOR_RESIZE_DIAG_NESW NE-SW diagonal resize

Cursors are painted in software into the backbuffer because VESA VBE does not provide a hardware sprite. The affected region is flushed to the LFB each frame.

Window Chrome

Windows use a Motif/GEOS-style frame:

  • 4px beveled outer border with perpendicular groove breaks
  • 20px title bar (dark background when focused)
  • Close button on the left edge (single-click opens system menu, double-click closes)
  • Minimize button on the right edge (always present)
  • Maximize button to the left of minimize (resizable windows only)
  • Optional menu bar below the title bar (20px)
  • Optional scrollbars along the right and bottom edges (16px)

Minimized windows appear as 64x64 beveled icons along the bottom of the screen. If a window icon is set via dvxSetWindowIcon(), that image is shown; otherwise a scaled thumbnail of the window's content buffer is used. Thumbnails refresh automatically one per frame (staggered). Double-click an icon to restore.

Platform Abstraction (platform/dvxPlatform.h)

All OS-specific code is behind a single interface. To port DVX, implement these functions in a new dvxPlatformXxx.c:

Function Purpose
platformInit() One-time setup (signal handlers, etc.)
platformYield() Cooperative multitasking yield
platformVideoInit() Set up video mode, LFB, backbuffer
platformVideoShutdown() Restore text mode, free resources
platformVideoEnumModes() Enumerate available video modes
platformVideoSetPalette() Set 8-bit palette entries
platformVideoFreeBuffers() Free backbuffer without restoring text mode
platformFlushRect() Copy rectangle from backbuffer to LFB
platformSpanFill8/16/32() Optimized pixel fill (rep stosl on DOS)
platformSpanCopy8/16/32() Optimized pixel copy (rep movsd on DOS)
platformMouseInit() Initialize mouse driver
platformMousePoll() Read mouse position and buttons
platformMouseWheelInit() Detect and activate mouse wheel
platformMouseWheelPoll() Read wheel delta
platformMouseWarp() Move cursor to absolute position
platformMouseSetAccel() Set double-speed threshold
platformKeyboardGetModifiers() Read shift/ctrl/alt state
platformKeyboardRead() Non-blocking key read
platformAltScanToChar() Map Alt+scancode to ASCII
platformValidateFilename() Check filename for OS constraints
platformGetSystemInfo() Gather hardware info string
platformGetMemoryInfo() Query total/free RAM
platformMkdirRecursive() Create directory tree
platformChdir() Change directory (with drive support on DOS)
platformPathDirEnd() Find last path separator
platformLineEnding() Native line ending string
platformStripLineEndings() Remove CR from CR+LF pairs

Key Constants (dvxTypes.h)

Extended Key Codes

Non-ASCII keys encoded as (scancode | 0x100):

KEY_F1 through KEY_F12
KEY_INSERT, KEY_DELETE
KEY_HOME, KEY_END
KEY_PGUP, KEY_PGDN

Chrome Constants

CHROME_BORDER_WIDTH    4      Border thickness
CHROME_TITLE_HEIGHT    20     Title bar height
CHROME_MENU_HEIGHT     20     Menu bar height
CHROME_CLOSE_BTN_SIZE  16     Close gadget size
SCROLLBAR_WIDTH        16     Scrollbar track width
MAX_WINDOWS            64     Maximum simultaneous windows
MAX_DIRTY_RECTS        128    Dirty rects per frame

Mouse Constants

MOUSE_LEFT             1      Left button bitmask
MOUSE_RIGHT            2      Right button bitmask
MOUSE_MIDDLE           4      Middle button bitmask
MOUSE_WHEEL_STEP       3      Lines per wheel notch

Hardware Requirements

  • 486 or later CPU
  • VESA VBE 2.0+ compatible video card with linear framebuffer
  • PS/2 mouse (or compatible driver)
  • DPMI host (CWSDPMI, Windows DOS box, DOSBox, 86Box)

Tested on 86Box with PCI video cards.