19 KiB
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
#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)
#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
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.
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.
void dvxShutdown(AppContextT *ctx);
Restore text mode, release LFB mapping, and free the backbuffer.
void dvxQuit(AppContextT *ctx);
Request exit from the main loop. dvxRun() returns on the next iteration.
Windows
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).
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
Remove a window from the stack and free its resources.
void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title);
Change the title bar text.
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
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
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
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).
MenuT *wmAddMenu(MenuBarT *bar, const char *label);
Add a top-level menu to the bar. Returns a MenuT * to populate with items.
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
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.
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.
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).
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.
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.
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 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.
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
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
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
WidgetT *wgtLabel(WidgetT *parent, const char *text);
Static text label. Sized to fit its text.
WidgetT *wgtButton(WidgetT *parent, const char *text);
Beveled push button. Set onClick to handle clicks.
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
Toggle checkbox. Read/write w->as.checkbox.checked. Set onChange to
be notified.
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.
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.
WidgetT *wgtListBox(WidgetT *parent);
List box (basic -- set items with wgtListBoxSetItems()).
WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen);
Multi-line text area (basic).
Spacing and dividers
WidgetT *wgtSpacer(WidgetT *parent);
Invisible spacer. Default weight is 100 -- pushes siblings apart.
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:
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
void wgtSetText(WidgetT *w, const char *text);
const char *wgtGetText(const WidgetT *w);
Get/set text for labels, buttons, checkboxes, radios, and text inputs.
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.
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.
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.
void wgtDestroy(WidgetT *w);
Remove a widget and all its children from the tree and free memory.
List box operations
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
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.