1579 lines
46 KiB
Markdown
1579 lines
46 KiB
Markdown
# DVX GUI
|
|
|
|
A DOS Visual eXecutive windowed GUI compositor for DOS, targeting DJGPP/DPMI with
|
|
VESA VBE 2.0+ linear framebuffer.
|
|
|
|
Motif-style beveled chrome, dirty-rectangle compositing, draggable and
|
|
resizable windows, dropdown menus, scrollbars, and a declarative widget/layout
|
|
system with buttons, checkboxes, radios, text inputs, dropdowns, combo boxes,
|
|
sliders, spinners, progress bars, tab controls, tree views, list views,
|
|
scroll panes, splitters, toolbars, status bars, images, image buttons,
|
|
drawable canvases, password inputs, masked/formatted inputs, and an ANSI BBS
|
|
terminal emulator.
|
|
|
|
## Building
|
|
|
|
Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`).
|
|
|
|
The GUI is built as a static library (`lib/libdvx.a`). The demo links
|
|
against it.
|
|
|
|
```
|
|
cd dvxdemo
|
|
make # builds lib/libdvx.a then bin/demo.exe
|
|
make clean # removes obj/ and bin/
|
|
```
|
|
|
|
Set `DJGPP_PREFIX` in the Makefile if your toolchain is installed somewhere
|
|
other than `~/djgpp/djgpp`.
|
|
|
|
## Architecture
|
|
|
|
The library is organized in five layers. Each layer is a `.h`/`.c` pair.
|
|
Application code only needs to include `dvxApp.h` (which pulls in the rest)
|
|
and optionally `dvxWidget.h`.
|
|
|
|
```
|
|
Layer 1 dvxVideo VESA init, LFB mapping, backbuffer, pixel format
|
|
Layer 2 dvxDraw Spans, rects, bevels, text, bitmaps (asm inner loops)
|
|
Layer 3 dvxComp Dirty rectangle list, merge, LFB flush
|
|
Layer 4 dvxWm Window stack, chrome, drag, resize, focus, menus, scrollbars
|
|
Layer 5 dvxApp Event loop, mouse/keyboard input, public API
|
|
dvxWidget Widget/layout system (optional, standalone)
|
|
```
|
|
|
|
Supporting files:
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `dvxTypes.h` | Shared type definitions used by all layers |
|
|
| `dvxFont.h` | Built-in 8x14 bitmap font glyph data |
|
|
| `dvxCursor.h` | Mouse cursor bitmask data (5 shapes) |
|
|
| `dvxPalette.h` | Default VGA palette for 8-bit mode |
|
|
| `dvxDialog.h` | Modal dialogs (message box, file dialog) |
|
|
| `dvxIcon.c` | stb_image implementation unit (BMP/PNG/JPEG/GIF) |
|
|
| `dvxImageWrite.c` | stb_image_write implementation unit (PNG export) |
|
|
| `thirdparty/stb_image.h` | Third-party single-header image loader |
|
|
| `thirdparty/stb_image_write.h` | Third-party single-header image writer |
|
|
|
|
The platform abstraction lives in `platform/`:
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `platform/dvxPlatform.h` | OS/CPU-neutral interface: video, input, span ops, filename validation |
|
|
| `platform/dvxPlatformDos.c` | DOS/DJGPP implementation: VESA, DPMI, INT 16h/33h, rep stosl/movsl |
|
|
|
|
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.
|
|
|
|
The widget system lives in `widgets/`:
|
|
|
|
| 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 widget 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 in
|
|
the core files. Adding a new widget type requires only a new `.c` file
|
|
and an entry in the class table.
|
|
|
|
---
|
|
|
|
## Quick start
|
|
|
|
```c
|
|
#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;
|
|
}
|
|
```
|
|
|
|
## Quick start (widgets)
|
|
|
|
```c
|
|
#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); // initial layout + paint
|
|
|
|
dvxRun(&ctx);
|
|
dvxShutdown(&ctx);
|
|
|
|
return 0;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Application API (`dvxApp.h`)
|
|
|
|
### Lifecycle
|
|
|
|
```c
|
|
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 (8, 15, 16, or 32). Returns
|
|
0 on success, -1 on failure.
|
|
|
|
```c
|
|
void dvxRun(AppContextT *ctx);
|
|
```
|
|
Enter the main event loop. Handles mouse movement, button clicks, keyboard
|
|
input, window management, dirty-rectangle compositing, and LFB flush.
|
|
Returns when `dvxQuit()` is called or ESC is pressed.
|
|
|
|
```c
|
|
bool dvxUpdate(AppContextT *ctx);
|
|
```
|
|
Process one iteration of the event loop: poll input, dispatch events,
|
|
composite dirty regions, and flush. Returns `true` if the GUI is still
|
|
running, `false` when exit has been requested. Use this instead of
|
|
`dvxRun()` when embedding the GUI inside an existing main loop.
|
|
|
|
```c
|
|
void dvxShutdown(AppContextT *ctx);
|
|
```
|
|
Restore text mode, release LFB mapping, and free the backbuffer.
|
|
|
|
```c
|
|
void dvxQuit(AppContextT *ctx);
|
|
```
|
|
Request exit from the main loop. `dvxRun()` returns on the next iteration.
|
|
|
|
### Windows
|
|
|
|
```c
|
|
WindowT *dvxCreateWindow(AppContextT *ctx, const char *title,
|
|
int32_t x, int32_t y, int32_t w, int32_t h,
|
|
bool resizable);
|
|
```
|
|
Create a window at screen position (`x`, `y`) with outer dimensions `w` x `h`.
|
|
If `resizable` is true, the window gets resize handles and a maximize button.
|
|
The window is raised to the top and given focus. Returns NULL on failure.
|
|
|
|
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 |
|
|
| `onClose` | `void (WindowT *win)` | Close button double-clicked |
|
|
| `onMenu` | `void (WindowT *win, int32_t menuId)` | Menu item selected |
|
|
| `onScroll` | `void (WindowT *win, ScrollbarOrientE orient, int32_t value)` | Scrollbar moved |
|
|
|
|
Mouse/key coordinates are relative to the content area. `buttons` is a
|
|
bitmask (bit 0 = left, bit 1 = right, bit 2 = middle).
|
|
|
|
Example:
|
|
|
|
```c
|
|
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;
|
|
win->onMenu = myMenuHandler;
|
|
```
|
|
|
|
```c
|
|
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
|
|
```
|
|
Remove a window from the stack and free its resources.
|
|
|
|
```c
|
|
void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title);
|
|
```
|
|
Change the title bar text.
|
|
|
|
```c
|
|
int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path);
|
|
```
|
|
Load a BMP, PNG, JPEG, or GIF image and assign it as the window's minimized icon.
|
|
The image is converted to the display pixel format and scaled to 64x64.
|
|
Returns 0 on success.
|
|
|
|
```c
|
|
void dvxMinimizeWindow(AppContextT *ctx, WindowT *win);
|
|
void dvxMaximizeWindow(AppContextT *ctx, WindowT *win);
|
|
void dvxFitWindow(AppContextT *ctx, WindowT *win);
|
|
```
|
|
`dvxFitWindow` resizes the window to fit its widget tree's natural size.
|
|
|
|
### Window arrangement
|
|
|
|
```c
|
|
dvxCascadeWindows(&ctx); // staggered cascade
|
|
dvxTileWindows(&ctx); // grid tile
|
|
dvxTileWindowsH(&ctx); // side-by-side
|
|
dvxTileWindowsV(&ctx); // top-to-bottom
|
|
```
|
|
|
|
### Invalidation
|
|
|
|
```c
|
|
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.
|
|
|
|
### Clipboard
|
|
|
|
```c
|
|
dvxClipboardCopy("Hello", 5);
|
|
|
|
int32_t len;
|
|
const char *text = dvxClipboardGet(&len);
|
|
```
|
|
|
|
### Screenshots
|
|
|
|
```c
|
|
dvxScreenshot(&ctx, "screen.png"); // entire screen
|
|
dvxWindowScreenshot(&ctx, win, "window.png"); // single window content
|
|
```
|
|
|
|
### Accessors
|
|
|
|
```c
|
|
DisplayT *dvxGetDisplay(AppContextT *ctx);
|
|
const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx);
|
|
const BitmapFontT *dvxGetFont(const AppContextT *ctx);
|
|
const ColorSchemeT *dvxGetColors(const AppContextT *ctx);
|
|
```
|
|
|
|
### Window properties
|
|
|
|
Set these directly on the `WindowT` struct 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) |
|
|
| `userData` | `void *` | NULL | Application data pointer, passed through to callbacks |
|
|
|
|
### Content buffer
|
|
|
|
Each window has a persistent content backbuffer:
|
|
|
|
| Field | Description |
|
|
|-------|-------------|
|
|
| `contentBuf` | Pixel data in 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.
|
|
|
|
---
|
|
|
|
## Menu bars
|
|
|
|
```c
|
|
WindowT *win = dvxCreateWindow(&ctx, "Menus", 50, 50, 400, 300, true);
|
|
win->onMenu = onMenuCb;
|
|
|
|
MenuBarT *bar = wmAddMenuBar(win);
|
|
|
|
MenuT *fileMenu = wmAddMenu(bar, "&File");
|
|
wmAddMenuItem(fileMenu, "&New", CMD_FILE_NEW);
|
|
wmAddMenuItem(fileMenu, "&Open...", CMD_FILE_OPEN);
|
|
wmAddMenuItem(fileMenu, "&Save", CMD_FILE_SAVE);
|
|
wmAddMenuSeparator(fileMenu);
|
|
wmAddMenuItem(fileMenu, "E&xit", CMD_FILE_EXIT);
|
|
|
|
MenuT *viewMenu = wmAddMenu(bar, "&View");
|
|
wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_VIEW_TOOLBAR, true);
|
|
wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUSBAR, true);
|
|
wmAddMenuSeparator(viewMenu);
|
|
wmAddMenuRadioItem(viewMenu, "S&mall", CMD_VIEW_SIZE_SMALL, false);
|
|
wmAddMenuRadioItem(viewMenu, "Me&dium", CMD_VIEW_SIZE_MED, true);
|
|
wmAddMenuRadioItem(viewMenu, "&Large", CMD_VIEW_SIZE_LARGE, false);
|
|
|
|
// Cascading submenu
|
|
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);
|
|
```
|
|
|
|
The `&` prefix marks accelerator keys -- Alt+F opens "&File".
|
|
Up to 8 menus per bar, 16 items per menu, submenus nested up to 4 deep.
|
|
|
|
Menu callback:
|
|
|
|
```c
|
|
static void onMenuCb(WindowT *win, int32_t menuId) {
|
|
AppContextT *ctx = (AppContextT *)win->userData;
|
|
|
|
switch (menuId) {
|
|
case CMD_FILE_EXIT:
|
|
dvxQuit(ctx);
|
|
break;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Context menus
|
|
|
|
Right-click menus can be attached to any window or widget:
|
|
|
|
```c
|
|
// Window-level context menu
|
|
MenuT *ctxMenu = wmCreateMenu();
|
|
wmAddMenuItem(ctxMenu, "Cu&t", CMD_CUT);
|
|
wmAddMenuItem(ctxMenu, "&Copy", CMD_COPY);
|
|
wmAddMenuItem(ctxMenu, "&Paste", CMD_PASTE);
|
|
wmAddMenuSeparator(ctxMenu);
|
|
wmAddMenuItem(ctxMenu, "&Properties...", CMD_PROPS);
|
|
win->contextMenu = ctxMenu;
|
|
|
|
// Widget-level context menu
|
|
WidgetT *lb = wgtListBox(root);
|
|
MenuT *lbCtx = wmCreateMenu();
|
|
wmAddMenuItem(lbCtx, "&Delete", CMD_DELETE);
|
|
wmAddMenuItem(lbCtx, "Select &All", CMD_SELALL);
|
|
lb->contextMenu = lbCtx;
|
|
```
|
|
|
|
Context menus route through the window's `onMenu` callback. The caller
|
|
owns the `MenuT` (not freed by the widget system).
|
|
|
|
---
|
|
|
|
## Accelerator tables
|
|
|
|
Global hotkeys routed through the window's `onMenu` callback:
|
|
|
|
```c
|
|
AccelTableT *accel = dvxCreateAccelTable();
|
|
dvxAddAccel(accel, 'N', ACCEL_CTRL, CMD_FILE_NEW);
|
|
dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_FILE_OPEN);
|
|
dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_FILE_SAVE);
|
|
dvxAddAccel(accel, 'Q', ACCEL_CTRL, CMD_FILE_EXIT);
|
|
dvxAddAccel(accel, KEY_F1, 0, CMD_HELP_ABOUT);
|
|
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. The table is freed with `dvxFreeAccelTable()`.
|
|
|
|
---
|
|
|
|
## Scrollbars
|
|
|
|
```c
|
|
wmAddVScrollbar(win, 0, 100, 25); // vertical, range 0-100, page size 25
|
|
wmAddHScrollbar(win, 0, 100, 25); // horizontal
|
|
wmUpdateContentRect(win);
|
|
wmReallocContentBuf(win, &ctx.display);
|
|
```
|
|
|
|
The `onScroll` callback fires when the user drags the thumb or clicks the
|
|
arrow buttons / trough.
|
|
|
|
Widget windows manage their own scrollbars automatically -- do not add them
|
|
manually.
|
|
|
|
---
|
|
|
|
## Dialogs (`dvxDialog.h`)
|
|
|
|
### Message box
|
|
|
|
```c
|
|
int32_t result = dvxMessageBox(&ctx, "Confirm",
|
|
"Are you sure you want to exit?",
|
|
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
|
|
|
|
```c
|
|
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
|
|
}
|
|
|
|
if (dvxFileDialog(&ctx, "Save As", FD_SAVE, NULL, filters, 3, path, sizeof(path))) {
|
|
// path contains the chosen filename
|
|
}
|
|
```
|
|
|
|
Flags: `FD_OPEN`, `FD_SAVE`. Pass `NULL` for `initialDir` to use the
|
|
current directory. Filename validation is platform-specific (DOS 8.3 on
|
|
the DOS platform).
|
|
|
|
---
|
|
|
|
## Video and drawing (`dvxVideo.h`, `dvxDraw.h`)
|
|
|
|
These are lower-level APIs. Application code typically only needs `packColor`.
|
|
|
|
```c
|
|
uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b);
|
|
```
|
|
Pack an RGB triple into the display's pixel format.
|
|
|
|
```c
|
|
void rectFill(DisplayT *d, const BlitOpsT *ops,
|
|
int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
|
|
```
|
|
Fill a rectangle with a solid color (clipped to the display clip rect).
|
|
|
|
```c
|
|
void drawBevel(DisplayT *d, const BlitOpsT *ops,
|
|
int32_t x, int32_t y, int32_t w, int32_t h,
|
|
const BevelStyleT *style);
|
|
```
|
|
Draw a beveled frame. `BevelStyleT` specifies highlight, shadow, face colors
|
|
and border width.
|
|
|
|
```c
|
|
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);
|
|
int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font,
|
|
int32_t x, int32_t y, char ch,
|
|
uint32_t fg, uint32_t bg, bool opaque);
|
|
int32_t textWidth(const BitmapFontT *font, const char *text);
|
|
```
|
|
Draw text using the built-in 8-pixel-wide bitmap font. `opaque` controls
|
|
whether background pixels are drawn. `drawChar` returns the advance width.
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Bulk-render exactly `count` characters. Much faster than calling `drawChar`
|
|
per character because clip bounds are computed once and background is filled
|
|
in bulk.
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Bulk-render a row of terminal character cells. `lineData` points to
|
|
`(char, attr)` byte pairs (2 bytes per cell). `palette` is a 16-entry
|
|
packed-color table. `blinkVisible` controls blink attribute visibility.
|
|
`cursorCol` is the column to draw inverted (-1 for no cursor). This is
|
|
much faster than calling `drawChar` per cell because clip bounds are
|
|
computed once for the whole row and there is no per-character function
|
|
call overhead.
|
|
|
|
```c
|
|
char accelParse(const char *text);
|
|
```
|
|
Parse an accelerator key from text with `&` markers (e.g. `"E&xit"` returns
|
|
`'x'`). Returns the lowercase accelerator character, or 0 if none found.
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Draw/measure text with `&` accelerator processing. The character after `&`
|
|
is underlined. `&&` produces a literal `&`. `textWidthAccel` returns the
|
|
width in pixels, excluding `&` markers.
|
|
|
|
```c
|
|
void drawFocusRect(DisplayT *d, const BlitOpsT *ops,
|
|
int32_t x, int32_t y, int32_t w, int32_t h,
|
|
uint32_t color);
|
|
```
|
|
Draw a dotted rectangle outline (every other pixel). Used to indicate
|
|
keyboard focus on widgets.
|
|
|
|
```c
|
|
void drawMaskedBitmap(DisplayT *d, const BlitOpsT *ops,
|
|
int32_t x, int32_t y, int32_t w, int32_t h,
|
|
const uint16_t *andMask, const uint16_t *xorData,
|
|
uint32_t fgColor, uint32_t bgColor);
|
|
```
|
|
Draw a 1-bit bitmap with AND/XOR masks (used for mouse cursors). Each row
|
|
is a `uint16_t`. Pixels where the AND mask is 0 are drawn using the XOR
|
|
data to select `fgColor` or `bgColor`.
|
|
|
|
```c
|
|
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);
|
|
```
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Blit from a source buffer to the backbuffer with clipping.
|
|
|
|
```c
|
|
void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h);
|
|
void resetClipRect(DisplayT *d);
|
|
```
|
|
|
|
---
|
|
|
|
## Widget system (`dvxWidget.h`)
|
|
|
|
An optional declarative layout system inspired by Amiga MUI and PC GEOS.
|
|
Widgets are arranged in a tree of VBox/HBox containers. A two-pass layout
|
|
engine (measure minimum sizes, then distribute space) handles geometry
|
|
automatically. Scrollbars appear when the window is too small for the
|
|
content.
|
|
|
|
### Initialization
|
|
|
|
```c
|
|
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win);
|
|
```
|
|
Attach the widget system to a window. Returns the root container (a VBox
|
|
filling the content area). Installs `onPaint`, `onMouse`, `onKey`, and
|
|
`onResize` handlers on the window. Build the widget tree by passing the
|
|
returned root (or its children) as `parent` to the widget creation
|
|
functions below.
|
|
|
|
Call `wgtInvalidate(root)` after the tree is fully built to trigger the
|
|
initial layout and paint.
|
|
|
|
### Containers
|
|
|
|
```c
|
|
WidgetT *wgtVBox(WidgetT *parent); // vertical stack
|
|
WidgetT *wgtHBox(WidgetT *parent); // horizontal stack
|
|
WidgetT *wgtFrame(WidgetT *parent, const char *title); // titled border
|
|
```
|
|
Containers hold child widgets and control layout direction. `wgtFrame`
|
|
draws a styled border with a title label and lays out children vertically.
|
|
|
|
Example:
|
|
|
|
```c
|
|
WidgetT *root = wgtInitWindow(&ctx, win);
|
|
|
|
WidgetT *frame = wgtFrame(root, "User Input");
|
|
WidgetT *row1 = wgtHBox(frame);
|
|
wgtLabel(row1, "Name:");
|
|
wgtTextInput(row1, 64);
|
|
|
|
WidgetT *row2 = wgtHBox(frame);
|
|
wgtLabel(row2, "Email:");
|
|
wgtTextInput(row2, 128);
|
|
|
|
WidgetT *btnRow = wgtHBox(root);
|
|
btnRow->align = AlignEndE;
|
|
wgtButton(btnRow, "OK");
|
|
wgtButton(btnRow, "Cancel");
|
|
|
|
wgtInvalidate(root);
|
|
```
|
|
|
|
Frame styles (set `w->as.frame.style` after creation):
|
|
|
|
| Style | Description |
|
|
|-------|-------------|
|
|
| `FrameInE` | Beveled inward / sunken (default) |
|
|
| `FrameOutE` | Beveled outward / raised |
|
|
| `FrameFlatE` | Solid color line; set `w->as.frame.color` (0 = windowShadow) |
|
|
|
|
Container properties (set directly on the returned `WidgetT *`):
|
|
|
|
| Field | Type | Default | Description |
|
|
|-------|------|---------|-------------|
|
|
| `align` | `WidgetAlignE` | `AlignStartE` | Main-axis alignment when no children have weight |
|
|
| `spacing` | `int32_t` | 0 (4px) | Tagged size: gap between children |
|
|
| `padding` | `int32_t` | 0 (4px) | Tagged size: internal padding |
|
|
|
|
Alignment values:
|
|
|
|
| Value | HBox meaning | VBox meaning |
|
|
|-------|-------------|-------------|
|
|
| `AlignStartE` | left | top |
|
|
| `AlignCenterE` | center | center |
|
|
| `AlignEndE` | right | bottom |
|
|
|
|
### Label
|
|
|
|
```c
|
|
WidgetT *wgtLabel(WidgetT *parent, const char *text);
|
|
```
|
|
Static text label. Sized to fit its text. Supports `&` accelerator markers.
|
|
|
|
```c
|
|
WidgetT *lbl = wgtLabel(root, "&Status:");
|
|
wgtSetText(lbl, "Connected"); // change text later
|
|
```
|
|
|
|
### Button
|
|
|
|
```c
|
|
WidgetT *wgtButton(WidgetT *parent, const char *text);
|
|
```
|
|
Beveled push button. Visually depresses on mouse-down, tracks the cursor
|
|
while held, fires `onClick` on release if still over the button.
|
|
|
|
```c
|
|
static void onOkClick(WidgetT *w) {
|
|
// handle button press
|
|
}
|
|
|
|
WidgetT *btn = wgtButton(root, "&OK");
|
|
btn->onClick = onOkClick;
|
|
```
|
|
|
|
### Checkbox
|
|
|
|
```c
|
|
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
|
|
```
|
|
Toggle checkbox. Read/write `w->as.checkbox.checked`.
|
|
|
|
```c
|
|
WidgetT *chk = wgtCheckbox(root, "Enable feature &A");
|
|
chk->as.checkbox.checked = true; // pre-check
|
|
chk->onChange = onCheckChanged; // notified on toggle
|
|
```
|
|
|
|
### Radio buttons
|
|
|
|
```c
|
|
WidgetT *wgtRadioGroup(WidgetT *parent);
|
|
WidgetT *wgtRadio(WidgetT *parent, const char *text);
|
|
```
|
|
Radio buttons with diamond-shaped indicators. Create a group, then add
|
|
options as children. The group tracks the selected index.
|
|
|
|
```c
|
|
WidgetT *rg = wgtRadioGroup(root);
|
|
wgtRadio(rg, "Option &1");
|
|
wgtRadio(rg, "Option &2");
|
|
wgtRadio(rg, "Option &3");
|
|
rg->onChange = onRadioChanged;
|
|
|
|
// Read selection:
|
|
int32_t idx = rg->as.radioGroup.selectedIdx;
|
|
```
|
|
|
|
### TextInput
|
|
|
|
```c
|
|
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
|
|
```
|
|
Single-line text input field. `maxLen` is the buffer capacity. Default
|
|
weight is 100 (stretches to fill).
|
|
|
|
```c
|
|
WidgetT *row = wgtHBox(root);
|
|
wgtLabel(row, "&Name:");
|
|
WidgetT *input = wgtTextInput(row, 64);
|
|
wgtSetText(input, "Default text");
|
|
input->onChange = onTextChanged;
|
|
|
|
// Read text:
|
|
const char *text = wgtGetText(input);
|
|
```
|
|
|
|
Editing features: cursor movement (arrows, Home, End), insert/overwrite,
|
|
selection (Shift+arrows, Shift+Home/End, Ctrl+A, double-click word,
|
|
triple-click all), clipboard (Ctrl+C copy, Ctrl+V paste, Ctrl+X cut),
|
|
single-level undo (Ctrl+Z), and horizontal scrolling when text exceeds
|
|
the visible width.
|
|
|
|
### PasswordInput
|
|
|
|
```c
|
|
WidgetT *wgtPasswordInput(WidgetT *parent, int32_t maxLen);
|
|
```
|
|
Identical to `wgtTextInput` except characters are displayed as bullets.
|
|
Copy and cut to clipboard are disabled; paste is allowed.
|
|
|
|
```c
|
|
WidgetT *pw = wgtPasswordInput(root, 32);
|
|
```
|
|
|
|
### MaskedInput
|
|
|
|
```c
|
|
WidgetT *wgtMaskedInput(WidgetT *parent, const char *mask);
|
|
```
|
|
Formatted input field. Mask characters: `#` = digit, `A` = letter,
|
|
`*` = any printable. All other characters are fixed literals.
|
|
|
|
```c
|
|
wgtMaskedInput(root, "(###) ###-####"); // US phone
|
|
wgtMaskedInput(root, "##/##/####"); // date
|
|
wgtMaskedInput(root, "###-##-####"); // SSN
|
|
```
|
|
|
|
The mask string must remain valid for the widget's lifetime.
|
|
|
|
### TextArea
|
|
|
|
```c
|
|
WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen);
|
|
```
|
|
Multi-line text editor with vertical and horizontal scrollbars.
|
|
|
|
```c
|
|
WidgetT *ta = wgtTextArea(root, 4096);
|
|
ta->weight = 100;
|
|
wgtSetText(ta, "Line 1\nLine 2\nLine 3");
|
|
```
|
|
|
|
Supports all TextInput editing features plus multi-line selection,
|
|
Enter for newlines, and vertical/horizontal auto-scroll.
|
|
|
|
### ListBox
|
|
|
|
```c
|
|
WidgetT *wgtListBox(WidgetT *parent);
|
|
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count);
|
|
int32_t wgtListBoxGetSelected(const WidgetT *w);
|
|
void wgtListBoxSetSelected(WidgetT *w, int32_t idx);
|
|
```
|
|
|
|
```c
|
|
static const char *items[] = {"Alpha", "Beta", "Gamma", "Delta"};
|
|
|
|
WidgetT *lb = wgtListBox(root);
|
|
wgtListBoxSetItems(lb, items, 4);
|
|
wgtListBoxSetSelected(lb, 0);
|
|
lb->weight = 100;
|
|
lb->onClick = onItemSelected;
|
|
```
|
|
|
|
Multi-select mode:
|
|
|
|
```c
|
|
wgtListBoxSetMultiSelect(lb, true);
|
|
wgtListBoxSetItemSelected(lb, 0, true);
|
|
wgtListBoxSetItemSelected(lb, 2, true);
|
|
bool sel = wgtListBoxIsItemSelected(lb, 1);
|
|
wgtListBoxSelectAll(lb);
|
|
wgtListBoxClearSelection(lb);
|
|
```
|
|
|
|
Reorderable (drag-and-drop items):
|
|
|
|
```c
|
|
wgtListBoxSetReorderable(lb, true);
|
|
```
|
|
|
|
### Dropdown
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Non-editable selection widget with a popup list.
|
|
|
|
```c
|
|
static const char *colors[] = {"Red", "Green", "Blue", "Yellow"};
|
|
|
|
WidgetT *dd = wgtDropdown(root);
|
|
wgtDropdownSetItems(dd, colors, 4);
|
|
wgtDropdownSetSelected(dd, 0);
|
|
dd->onChange = onColorChanged;
|
|
```
|
|
|
|
The items array must remain valid for the widget's lifetime.
|
|
|
|
### ComboBox
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Editable text field with a dropdown list.
|
|
|
|
```c
|
|
static const char *sizes[] = {"Small", "Medium", "Large", "Extra Large"};
|
|
|
|
WidgetT *cb = wgtComboBox(root, 32);
|
|
wgtComboBoxSetItems(cb, sizes, 4);
|
|
wgtComboBoxSetSelected(cb, 1);
|
|
cb->onChange = onSizeChanged;
|
|
|
|
// Read typed/selected text:
|
|
const char *text = wgtGetText(cb);
|
|
```
|
|
|
|
### ProgressBar
|
|
|
|
```c
|
|
WidgetT *wgtProgressBar(WidgetT *parent); // horizontal
|
|
WidgetT *wgtProgressBarV(WidgetT *parent); // vertical
|
|
void wgtProgressBarSetValue(WidgetT *w, int32_t value);
|
|
int32_t wgtProgressBarGetValue(const WidgetT *w);
|
|
```
|
|
|
|
```c
|
|
WidgetT *pb = wgtProgressBar(root);
|
|
pb->weight = 100;
|
|
wgtProgressBarSetValue(pb, 65);
|
|
|
|
WidgetT *pbV = wgtProgressBarV(root);
|
|
wgtProgressBarSetValue(pbV, 75);
|
|
```
|
|
|
|
Value is 0--100 (percentage).
|
|
|
|
### Slider
|
|
|
|
```c
|
|
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal);
|
|
void wgtSliderSetValue(WidgetT *w, int32_t value);
|
|
int32_t wgtSliderGetValue(const WidgetT *w);
|
|
```
|
|
|
|
```c
|
|
WidgetT *slider = wgtSlider(root, 0, 100);
|
|
wgtSliderSetValue(slider, 50);
|
|
slider->onChange = onVolumeChanged;
|
|
|
|
// Vertical slider:
|
|
WidgetT *vSlider = wgtSlider(root, 0, 255);
|
|
vSlider->as.slider.vertical = true;
|
|
```
|
|
|
|
### Spinner
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Numeric up/down input with increment/decrement buttons.
|
|
|
|
```c
|
|
WidgetT *spin = wgtSpinner(root, 0, 999, 1);
|
|
wgtSpinnerSetValue(spin, 42);
|
|
spin->weight = 50;
|
|
spin->onChange = onQuantityChanged;
|
|
```
|
|
|
|
Up/Down arrows increment/decrement by `step`; Page Up/Down by `step * 10`.
|
|
Enter commits the typed value; Escape reverts.
|
|
|
|
### TabControl
|
|
|
|
```c
|
|
WidgetT *wgtTabControl(WidgetT *parent);
|
|
WidgetT *wgtTabPage(WidgetT *parent, const char *title);
|
|
void wgtTabControlSetActive(WidgetT *w, int32_t idx);
|
|
int32_t wgtTabControlGetActive(const WidgetT *w);
|
|
```
|
|
|
|
```c
|
|
WidgetT *tabs = wgtTabControl(root);
|
|
|
|
WidgetT *page1 = wgtTabPage(tabs, "&General");
|
|
wgtLabel(page1, "General settings");
|
|
wgtCheckbox(page1, "&Enable logging");
|
|
|
|
WidgetT *page2 = wgtTabPage(tabs, "&Advanced");
|
|
wgtLabel(page2, "Advanced settings");
|
|
WidgetT *slider = wgtSlider(page2, 0, 100);
|
|
wgtSliderSetValue(slider, 75);
|
|
|
|
WidgetT *page3 = wgtTabPage(tabs, "A&bout");
|
|
wgtLabel(page3, "Version 1.0");
|
|
```
|
|
|
|
Each page is a VBox. Only the active page is visible and receives layout.
|
|
|
|
### StatusBar
|
|
|
|
```c
|
|
WidgetT *wgtStatusBar(WidgetT *parent);
|
|
```
|
|
Horizontal container with sunken panel background. Place at the bottom of
|
|
the root VBox.
|
|
|
|
```c
|
|
WidgetT *sb = wgtStatusBar(root);
|
|
WidgetT *msg = wgtLabel(sb, "Ready");
|
|
msg->weight = 100; // stretch to fill
|
|
wgtLabel(sb, "Line 1, Col 1");
|
|
```
|
|
|
|
### Toolbar
|
|
|
|
```c
|
|
WidgetT *wgtToolbar(WidgetT *parent);
|
|
```
|
|
Horizontal container with raised background. Place at the top of the root
|
|
VBox.
|
|
|
|
```c
|
|
WidgetT *tb = wgtToolbar(root);
|
|
WidgetT *btnNew = wgtImageButton(tb, newPixels, 16, 16, 16 * bpp);
|
|
WidgetT *btnOpen = wgtImageButton(tb, openPixels, 16, 16, 16 * bpp);
|
|
WidgetT *btnSave = wgtImageButton(tb, savePixels, 16, 16, 16 * bpp);
|
|
wgtVSeparator(tb);
|
|
wgtButton(tb, "&Help");
|
|
```
|
|
|
|
### TreeView
|
|
|
|
```c
|
|
WidgetT *wgtTreeView(WidgetT *parent);
|
|
WidgetT *wgtTreeItem(WidgetT *parent, const char *text);
|
|
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded);
|
|
bool wgtTreeItemIsExpanded(const WidgetT *w);
|
|
WidgetT *wgtTreeViewGetSelected(const WidgetT *w);
|
|
void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item);
|
|
```
|
|
|
|
```c
|
|
WidgetT *tree = wgtTreeView(root);
|
|
|
|
WidgetT *docs = wgtTreeItem(tree, "Documents");
|
|
wgtTreeItemSetExpanded(docs, true);
|
|
wgtTreeItem(docs, "README.md");
|
|
wgtTreeItem(docs, "DESIGN.md");
|
|
|
|
WidgetT *src = wgtTreeItem(docs, "src");
|
|
wgtTreeItemSetExpanded(src, true);
|
|
wgtTreeItem(src, "main.c");
|
|
wgtTreeItem(src, "utils.c");
|
|
|
|
WidgetT *images = wgtTreeItem(tree, "Images");
|
|
wgtTreeItem(images, "logo.png");
|
|
wgtTreeItem(images, "icon.bmp");
|
|
```
|
|
|
|
Multi-select and reorderable:
|
|
|
|
```c
|
|
wgtTreeViewSetMultiSelect(tree, true);
|
|
wgtTreeViewSetReorderable(tree, true);
|
|
```
|
|
|
|
The tree view manages its own scrollbars automatically. Default weight is 100.
|
|
|
|
### ListView
|
|
|
|
```c
|
|
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);
|
|
```
|
|
Multi-column list with sortable headers and resizable columns.
|
|
|
|
```c
|
|
static const ListViewColT cols[] = {
|
|
{"Name", wgtChars(16), ListViewAlignLeftE},
|
|
{"Size", wgtChars(8), ListViewAlignRightE},
|
|
{"Type", wgtChars(12), ListViewAlignLeftE},
|
|
{"Modified", wgtChars(12), ListViewAlignLeftE}
|
|
};
|
|
|
|
static const char *data[] = {
|
|
"AUTOEXEC.BAT", "412", "Batch File", "03/15/1994",
|
|
"CONFIG.SYS", "256", "System File", "03/15/1994",
|
|
"COMMAND.COM", "54,645", "Application", "09/30/1993",
|
|
};
|
|
|
|
WidgetT *lv = wgtListView(root);
|
|
wgtListViewSetColumns(lv, cols, 4);
|
|
wgtListViewSetData(lv, data, 3);
|
|
wgtListViewSetSelected(lv, 0);
|
|
lv->weight = 100;
|
|
```
|
|
|
|
Multi-select and sorting:
|
|
|
|
```c
|
|
wgtListViewSetMultiSelect(lv, true);
|
|
wgtListViewSetSort(lv, 0, ListViewSortAscE);
|
|
wgtListViewSetReorderable(lv, true);
|
|
|
|
// Header click callback for custom sort logic:
|
|
wgtListViewSetHeaderClickCallback(lv, onHeaderClick);
|
|
```
|
|
|
|
Column widths support tagged sizes (`wgtPixels`, `wgtChars`, `wgtPercent`).
|
|
Cell data is a flat array of strings in row-major order. Both the column
|
|
and data arrays must remain valid for the widget's lifetime.
|
|
|
|
### ScrollPane
|
|
|
|
```c
|
|
WidgetT *wgtScrollPane(WidgetT *parent);
|
|
```
|
|
Scrollable container. Children are laid out vertically at their natural
|
|
size. Scrollbars appear automatically when content exceeds the visible area.
|
|
|
|
```c
|
|
WidgetT *sp = wgtScrollPane(root);
|
|
sp->weight = 100;
|
|
sp->padding = wgtPixels(4);
|
|
sp->spacing = wgtPixels(4);
|
|
|
|
wgtLabel(sp, "Scrollable content:");
|
|
wgtHSeparator(sp);
|
|
for (int32_t i = 0; i < 8; i++) {
|
|
wgtCheckbox(sp, items[i]);
|
|
}
|
|
wgtHSeparator(sp);
|
|
wgtButton(sp, "Scrolled Button");
|
|
```
|
|
|
|
### Splitter
|
|
|
|
```c
|
|
WidgetT *wgtSplitter(WidgetT *parent, bool vertical);
|
|
void wgtSplitterSetPos(WidgetT *w, int32_t pos);
|
|
int32_t wgtSplitterGetPos(const WidgetT *w);
|
|
```
|
|
Resizable divider between exactly two child widgets. `vertical = true`
|
|
creates a left|right split; `false` creates a top/bottom split.
|
|
|
|
```c
|
|
// Horizontal split: tree on left, list on right
|
|
WidgetT *vSplit = wgtSplitter(root, true);
|
|
vSplit->weight = 100;
|
|
wgtSplitterSetPos(vSplit, 150);
|
|
|
|
WidgetT *leftTree = wgtTreeView(vSplit);
|
|
// ... populate tree ...
|
|
|
|
WidgetT *rightList = wgtListBox(vSplit);
|
|
// ... populate list ...
|
|
```
|
|
|
|
Nested splitters for complex layouts:
|
|
|
|
```c
|
|
// Outer: top/bottom split
|
|
WidgetT *hSplit = wgtSplitter(root, false);
|
|
hSplit->weight = 100;
|
|
wgtSplitterSetPos(hSplit, 120);
|
|
|
|
// Top pane: left/right split
|
|
WidgetT *vSplit = wgtSplitter(hSplit, true);
|
|
wgtSplitterSetPos(vSplit, 120);
|
|
wgtTreeView(vSplit); // left
|
|
wgtListBox(vSplit); // right
|
|
|
|
// Bottom pane: detail view
|
|
WidgetT *detail = wgtFrame(hSplit, "Preview");
|
|
wgtLabel(detail, "Select a file above.");
|
|
```
|
|
|
|
### Image
|
|
|
|
```c
|
|
WidgetT *wgtImage(WidgetT *parent, uint8_t *data,
|
|
int32_t w, int32_t h, int32_t pitch);
|
|
WidgetT *wgtImageFromFile(WidgetT *parent, const char *path);
|
|
void wgtImageSetData(WidgetT *w, uint8_t *data,
|
|
int32_t imgW, int32_t imgH, int32_t pitch);
|
|
```
|
|
|
|
```c
|
|
// From file (BMP, PNG, JPEG, GIF):
|
|
wgtImageFromFile(root, "sample.bmp");
|
|
|
|
// From pixel buffer (display format, takes ownership):
|
|
uint8_t *pixels = loadMyImage(&w, &h, &pitch);
|
|
wgtImage(root, pixels, w, h, pitch);
|
|
```
|
|
|
|
### ImageButton
|
|
|
|
```c
|
|
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data,
|
|
int32_t w, int32_t h, int32_t pitch);
|
|
void wgtImageButtonSetData(WidgetT *w, uint8_t *data,
|
|
int32_t imgW, int32_t imgH, int32_t pitch);
|
|
```
|
|
Push button that displays an image instead of text. Behaves identically
|
|
to `wgtButton`.
|
|
|
|
```c
|
|
uint8_t *iconData = loadBmpPixels(&ctx, "new.bmp", &imgW, &imgH, &imgPitch);
|
|
WidgetT *btn = wgtImageButton(tb, iconData, imgW, imgH, imgPitch);
|
|
btn->onClick = onNewClick;
|
|
```
|
|
|
|
Takes ownership of the pixel buffer (freed on destroy).
|
|
|
|
### Canvas
|
|
|
|
```c
|
|
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h);
|
|
```
|
|
Drawable bitmap canvas with a sunken bevel border. Supports interactive
|
|
freehand drawing (mouse strokes) and programmatic drawing.
|
|
|
|
```c
|
|
const DisplayT *d = dvxGetDisplay(&ctx);
|
|
WidgetT *cv = wgtCanvas(root, 280, 100);
|
|
|
|
wgtCanvasClear(cv, packColor(d, 255, 255, 255));
|
|
|
|
wgtCanvasSetPenColor(cv, packColor(d, 200, 0, 0));
|
|
wgtCanvasDrawRect(cv, 5, 5, 50, 35);
|
|
|
|
wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 200));
|
|
wgtCanvasFillCircle(cv, 150, 50, 25);
|
|
|
|
wgtCanvasSetPenColor(cv, packColor(d, 0, 150, 0));
|
|
wgtCanvasSetPenSize(cv, 3);
|
|
wgtCanvasDrawLine(cv, 70, 5, 130, 90);
|
|
|
|
// Individual pixels:
|
|
wgtCanvasSetPixel(cv, 10, 10, packColor(d, 255, 0, 0));
|
|
uint32_t px = wgtCanvasGetPixel(cv, 10, 10);
|
|
|
|
// Save/load:
|
|
wgtCanvasSave(cv, "drawing.png");
|
|
wgtCanvasLoad(cv, "image.png");
|
|
```
|
|
|
|
Drawing primitives: `wgtCanvasDrawLine`, `wgtCanvasDrawRect` (outline),
|
|
`wgtCanvasFillRect` (solid), `wgtCanvasFillCircle`. All use the current
|
|
pen color and clip to the canvas bounds.
|
|
|
|
### ANSI Terminal
|
|
|
|
```c
|
|
WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows);
|
|
```
|
|
ANSI BBS terminal emulator. Displays a character grid (default 80x25 if
|
|
cols/rows are 0) with full ANSI escape sequence support and a 16-color
|
|
CGA palette.
|
|
|
|
```c
|
|
WidgetT *term = wgtAnsiTerm(root, 80, 25);
|
|
term->weight = 100;
|
|
wgtAnsiTermSetScrollback(term, 500);
|
|
|
|
// Feed ANSI content directly:
|
|
static const uint8_t ansiData[] =
|
|
"\x1B[2J" // clear screen
|
|
"\x1B[1;34m=== Welcome ===\x1B[0m\r\n"
|
|
"\x1B[1mBold\x1B[0m, "
|
|
"\x1B[7mReverse\x1B[0m, "
|
|
"\x1B[5mBlinking\x1B[0m\r\n"
|
|
"\x1B[31m Red \x1B[32m Green \x1B[34m Blue \x1B[0m\r\n";
|
|
|
|
wgtAnsiTermWrite(term, ansiData, sizeof(ansiData) - 1);
|
|
```
|
|
|
|
Connect to a communications interface:
|
|
|
|
```c
|
|
static int32_t myRead(void *ctx, uint8_t *buf, int32_t maxLen) {
|
|
return serialRead((SerialT *)ctx, buf, maxLen);
|
|
}
|
|
|
|
static int32_t myWrite(void *ctx, const uint8_t *buf, int32_t len) {
|
|
return serialWrite((SerialT *)ctx, buf, len);
|
|
}
|
|
|
|
wgtAnsiTermSetComm(term, &serial, myRead, myWrite);
|
|
```
|
|
|
|
ANSI escape sequences supported:
|
|
|
|
| Sequence | Description |
|
|
|----------|-------------|
|
|
| `ESC[H` / `ESC[f` | Cursor position (CUP/HVP) |
|
|
| `ESC[A/B/C/D` | Cursor up/down/forward/back |
|
|
| `ESC[J` | Erase display (0=to end, 1=to start, 2=all) |
|
|
| `ESC[K` | Erase line (0=to end, 1=to start, 2=all) |
|
|
| `ESC[m` | SGR: colors 30-37/40-47, bright 90-97/100-107, bold(1), blink(5), reverse(7), reset(0) |
|
|
| `ESC[s` / `ESC[u` | Save / restore cursor position |
|
|
| `ESC[S` / `ESC[T` | Scroll up / down |
|
|
| `ESC[L` / `ESC[M` | Insert / delete lines |
|
|
| `ESC[?7h/l` | Enable / disable auto-wrap |
|
|
| `ESC[?25h/l` | Show / hide cursor |
|
|
|
|
Additional functions:
|
|
|
|
```c
|
|
void wgtAnsiTermClear(WidgetT *w); // clear + reset cursor
|
|
int32_t wgtAnsiTermPoll(WidgetT *w); // poll comm for data
|
|
int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *y, int32_t *h); // fast dirty-row repaint
|
|
void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines);
|
|
```
|
|
|
|
### Spacing and dividers
|
|
|
|
```c
|
|
WidgetT *wgtSpacer(WidgetT *parent); // flexible space (weight=100)
|
|
WidgetT *wgtHSeparator(WidgetT *parent); // horizontal line
|
|
WidgetT *wgtVSeparator(WidgetT *parent); // vertical line
|
|
```
|
|
|
|
```c
|
|
WidgetT *row = wgtHBox(root);
|
|
wgtButton(row, "Left");
|
|
wgtSpacer(row); // pushes next button to the right
|
|
wgtButton(row, "Right");
|
|
```
|
|
|
|
### Tooltips
|
|
|
|
```c
|
|
wgtSetTooltip(widget, "Tooltip text appears on hover");
|
|
```
|
|
|
|
### Size specifications
|
|
|
|
Size fields (`minW`, `minH`, `maxW`, `maxH`, `prefW`, `prefH`, `spacing`,
|
|
`padding`) accept tagged values created with:
|
|
|
|
```c
|
|
wgtPixels(120) // 120 pixels
|
|
wgtChars(15) // 15 character widths
|
|
wgtPercent(50) // 50% of parent
|
|
```
|
|
|
|
A raw `0` means auto (use the widget's natural/computed size).
|
|
|
|
### Weight
|
|
|
|
The `weight` field controls how extra space is distributed among siblings.
|
|
When a container is larger than its children's combined minimum size, the
|
|
surplus is divided proportionally by weight.
|
|
|
|
- `weight = 0` -- fixed size, does not stretch (default for most widgets)
|
|
- `weight = 100` -- normal stretch (default for spacers and text inputs)
|
|
- Relative values work: weights of 100, 200, 100 give a 1:2:1 split
|
|
|
|
When all children have weight 0, the container's `align` property
|
|
determines where children are placed within the extra space.
|
|
|
|
### Operations
|
|
|
|
```c
|
|
void wgtSetText(WidgetT *w, const char *text);
|
|
const char *wgtGetText(const WidgetT *w);
|
|
```
|
|
Get/set text for labels, buttons, checkboxes, radios, text inputs,
|
|
password inputs, masked inputs, combo boxes, and dropdowns.
|
|
|
|
```c
|
|
void wgtSetEnabled(WidgetT *w, bool enabled);
|
|
void wgtSetVisible(WidgetT *w, bool visible);
|
|
```
|
|
Disabled widgets are drawn grayed out and ignore input. Hidden widgets
|
|
are excluded from layout.
|
|
|
|
```c
|
|
// Disable an entire group of widgets:
|
|
wgtSetEnabled(btn, false);
|
|
wgtSetEnabled(slider, false);
|
|
wgtSetEnabled(textInput, false);
|
|
```
|
|
|
|
```c
|
|
void wgtInvalidate(WidgetT *w);
|
|
```
|
|
Trigger a full relayout and repaint of the widget tree. Call after
|
|
modifying widget properties, adding/removing widgets, or changing text.
|
|
|
|
```c
|
|
void wgtInvalidatePaint(WidgetT *w);
|
|
```
|
|
Lightweight repaint without relayout. Use when only visual state changed
|
|
(slider value, cursor blink, checkbox toggle) but widget sizes are stable.
|
|
|
|
```c
|
|
WidgetT *wgtFind(WidgetT *root, const char *name);
|
|
void wgtSetName(WidgetT *w, const char *name);
|
|
```
|
|
Search the subtree for a widget by name (up to 31 chars). Uses djb2 hash
|
|
for fast rejection.
|
|
|
|
```c
|
|
wgtSetName(statusLabel, "status");
|
|
// ... later, from any callback:
|
|
WidgetT *lbl = wgtFind(root, "status");
|
|
wgtSetText(lbl, "Updated!");
|
|
wgtInvalidate(lbl);
|
|
```
|
|
|
|
```c
|
|
void wgtDestroy(WidgetT *w);
|
|
```
|
|
Remove a widget and all its children from the tree and free memory.
|
|
|
|
```c
|
|
void wgtSetDebugLayout(AppContextT *ctx, bool enabled);
|
|
```
|
|
When enabled, draws 1px neon-colored borders around all layout containers
|
|
so their bounds are visible. Each container gets a distinct color derived
|
|
from its pointer.
|
|
|
|
### Layout and paint internals
|
|
|
|
These are called automatically by `wgtInvalidate()`, but are available
|
|
for manual use when embedding the widget system in a custom event loop.
|
|
|
|
```c
|
|
int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth);
|
|
```
|
|
Convert a tagged size (from `wgtPixels`, `wgtChars`, or `wgtPercent`) to
|
|
a pixel value given the parent's size and the font's character width.
|
|
|
|
```c
|
|
void wgtLayout(WidgetT *root, int32_t availW, int32_t availH,
|
|
const BitmapFontT *font);
|
|
```
|
|
Run the two-pass layout engine on the widget tree: measure minimum sizes
|
|
bottom-up, then distribute available space top-down. Sets `x`, `y`, `w`,
|
|
`h` on every widget.
|
|
|
|
```c
|
|
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops,
|
|
const BitmapFontT *font, const ColorSchemeT *colors);
|
|
```
|
|
Paint the entire widget tree into the window's content buffer.
|
|
|
|
---
|
|
|
|
## Types reference (`dvxTypes.h`)
|
|
|
|
### WindowT
|
|
|
|
Core window structure. Key fields:
|
|
|
|
| Field | Type | Description |
|
|
|-------|------|-------------|
|
|
| `id` | `int32_t` | Unique window identifier |
|
|
| `x`, `y`, `w`, `h` | `int32_t` | Outer frame position and size |
|
|
| `contentX`, `contentY` | `int32_t` | Content area offset within frame |
|
|
| `contentW`, `contentH` | `int32_t` | Content area dimensions |
|
|
| `contentBuf` | `uint8_t *` | Pixel buffer (display format) |
|
|
| `contentPitch` | `int32_t` | Bytes per scanline in content buffer |
|
|
| `title` | `char[128]` | Title bar text |
|
|
| `visible` | `bool` | Window is shown |
|
|
| `focused` | `bool` | Window has input focus |
|
|
| `minimized` | `bool` | Window is minimized to icon |
|
|
| `maximized` | `bool` | Window is maximized |
|
|
| `resizable` | `bool` | Window has resize handles |
|
|
| `maxW`, `maxH` | `int32_t` | Max dimensions for maximize (-1 = screen) |
|
|
| `userData` | `void *` | Application data pointer |
|
|
|
|
### ColorSchemeT
|
|
|
|
All colors are pre-packed via `packColor()`:
|
|
|
|
| Field | Usage |
|
|
|-------|-------|
|
|
| `desktop` | Desktop background |
|
|
| `windowFace` | Window frame fill |
|
|
| `windowHighlight` | Bevel light edge |
|
|
| `windowShadow` | Bevel dark edge |
|
|
| `activeTitleBg/Fg` | Focused title bar |
|
|
| `inactiveTitleBg/Fg` | Unfocused title bar |
|
|
| `contentBg/Fg` | Window content area |
|
|
| `menuBg/Fg` | Menu bar and popups |
|
|
| `menuHighlightBg/Fg` | Highlighted menu item |
|
|
| `buttonFace` | Button interior |
|
|
| `scrollbarBg/Fg/Trough` | Scrollbar elements |
|
|
|
|
### BitmapFontT
|
|
|
|
Fixed-width bitmap font (8px wide, 14 or 16px tall). Glyphs are packed
|
|
1bpp, `charHeight` bytes per glyph, MSB-first.
|
|
|
|
### BevelStyleT
|
|
|
|
```c
|
|
typedef struct {
|
|
uint32_t highlight; // top/left edge color
|
|
uint32_t shadow; // bottom/right edge color
|
|
uint32_t face; // interior fill (0 = no fill)
|
|
int32_t width; // border thickness in pixels
|
|
} BevelStyleT;
|
|
```
|
|
|
|
---
|
|
|
|
## Window chrome
|
|
|
|
Windows use a Motif/GEOS-style frame:
|
|
|
|
- 4px beveled outer border with perpendicular groove breaks
|
|
- 20px title bar (dark charcoal background when focused)
|
|
- Close button on the left edge (requires double-click)
|
|
- 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 has an icon image set via `dvxSetWindowIcon()`, that
|
|
image is shown; otherwise a nearest-neighbor-scaled thumbnail of the
|
|
window's content buffer is used. Thumbnails are refreshed automatically
|
|
when the window's content changes, with updates staggered across frames
|
|
so only one icon redraws per interval. Double-click an icon to restore.
|
|
|
|
### 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 |
|
|
|
|
---
|
|
|
|
## 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 handling, etc.) |
|
|
| `platformYield()` | Cooperative multitasking yield |
|
|
| `platformVideoInit()` | Set up video mode, LFB, backbuffer |
|
|
| `platformVideoShutdown()` | Restore text mode, free resources |
|
|
| `platformVideoSetPalette()` | Set 8-bit palette entries |
|
|
| `platformFlushRect()` | Copy rectangle from backbuffer to LFB |
|
|
| `platformSpanFill8/16/32()` | Optimized pixel fill |
|
|
| `platformSpanCopy8/16/32()` | Optimized pixel copy |
|
|
| `platformMouseInit()` | Initialize mouse driver |
|
|
| `platformMousePoll()` | Read mouse position and buttons |
|
|
| `platformKeyboardGetModifiers()` | Read shift/ctrl/alt state |
|
|
| `platformKeyboardRead()` | Read next key from buffer |
|
|
| `platformAltScanToChar()` | Map Alt+scancode to ASCII |
|
|
| `platformValidateFilename()` | Check filename for OS constraints |
|
|
|
|
---
|
|
|
|
## Hardware requirements
|
|
|
|
- 486 or later CPU
|
|
- VESA VBE 2.0+ compatible video card with linear framebuffer support
|
|
- PS/2 mouse (or compatible mouse driver)
|
|
- DPMI host (CWSDPMI, Windows DOS box, DOSBox, 86Box)
|
|
|
|
Tested on 86Box with PCI video cards. DOSBox is a trusted reference
|
|
platform.
|