DVX_GUI/README.md
2026-03-09 22:50:06 -05:00

621 lines
19 KiB
Markdown

# DV/X GUI
A DESQview/X-style 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.
## Building
Requires the DJGPP cross-compiler (`i586-pc-msdosdjgpp-gcc`).
```
make # builds 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 |
| `dvxIcon.c` | stb_image implementation unit (BMP/PNG/JPEG/GIF) |
| `thirdparty/stb_image.h` | Third-party single-header image loader |
## 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.");
strncpy(lbl->name, "status", MAX_WIDGET_NAME);
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
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).
```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, TGA, or PNG 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.
### 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.
### 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
MenuBarT *wmAddMenuBar(WindowT *win);
```
Add a menu bar to a window. Call `wmUpdateContentRect()` and
`wmReallocContentBuf()` after adding the menu bar (the menu bar reduces
the content area).
```c
MenuT *wmAddMenu(MenuBarT *bar, const char *label);
```
Add a top-level menu to the bar. Returns a `MenuT *` to populate with items.
```c
void wmAddMenuItem(MenuT *menu, const char *label, int32_t id);
void wmAddMenuSeparator(MenuT *menu);
```
Add items to a menu. `id` is an application-defined command passed to
`onMenu`. Up to 16 items per menu, 8 menus per bar.
---
## Scrollbars
```c
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);
```
Add a scrollbar to a window. `pageSize` controls the thumb size relative to
the range. Call `wmUpdateContentRect()` and `wmReallocContentBuf()` afterward.
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.
---
## 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 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.
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) |
The title text is vertically centered on the top border line.
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 |
### Widgets
```c
WidgetT *wgtLabel(WidgetT *parent, const char *text);
```
Static text label. Sized to fit its text.
```c
WidgetT *wgtButton(WidgetT *parent, const char *text);
```
Beveled push button. Set `onClick` to handle clicks.
```c
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
```
Toggle checkbox. Read/write `w->as.checkbox.checked`. Set `onChange` to
be notified.
```c
WidgetT *wgtRadioGroup(WidgetT *parent);
WidgetT *wgtRadio(WidgetT *parent, const char *text);
```
Radio buttons. Create a group, then add options as children. The group
tracks the selected index in `w->as.radioGroup.selectedIdx`. Set
`onChange` on the group to be notified. Radio indices are auto-assigned.
```c
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
```
Single-line text input field. Supports typing, cursor movement (arrows,
Home, End), backspace, and delete. `maxLen` is the buffer capacity.
Default weight is 100 (stretches to fill). Set `onChange` to be notified
on edits.
```c
WidgetT *wgtListBox(WidgetT *parent);
```
List box (basic -- set items with `wgtListBoxSetItems()`).
```c
WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen);
```
Multi-line text area (basic).
### Spacing and dividers
```c
WidgetT *wgtSpacer(WidgetT *parent);
```
Invisible spacer. Default weight is 100 -- pushes siblings apart.
```c
WidgetT *wgtHSeparator(WidgetT *parent); // horizontal line
WidgetT *wgtVSeparator(WidgetT *parent); // vertical line
```
Visual divider lines (shadow + highlight, 2px).
### 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, and text inputs.
```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
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
WidgetT *wgtFind(WidgetT *root, const char *name);
```
Search the subtree for a widget by name. Set `w->name` (up to 31 chars)
to make a widget findable.
```c
void wgtDestroy(WidgetT *w);
```
Remove a widget and all its children from the tree and free memory.
### List box operations
```c
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count);
int32_t wgtListBoxGetSelected(const WidgetT *w);
void wgtListBoxSetSelected(WidgetT *w, int32_t idx);
```
---
## 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 snapshot of the window
content is used. Double-click an icon to restore.
---
## 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.