Initial commit.

This commit is contained in:
Scott Duensing 2026-03-09 20:55:12 -05:00
parent ca6c5dc5c7
commit 67900cbd7f
21 changed files with 17152 additions and 0 deletions

42
dvx/Makefile Normal file
View file

@ -0,0 +1,42 @@
# DV/X GUI Makefile for DJGPP cross-compilation
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586
LDFLAGS = -lm
OBJDIR = ../obj
BINDIR = ../bin
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxWidget.c dvxApp.c demo.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
TARGET = $(BINDIR)/demo.exe
.PHONY: all clean
all: $(TARGET)
$(TARGET): $(OBJS) | $(BINDIR)
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJS)
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
$(BINDIR):
mkdir -p $(BINDIR)
# Dependencies
$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h dvxTypes.h dvxPalette.h
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h dvxTypes.h
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h dvxTypes.h
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h thirdparty/stb_image.h
$(OBJDIR)/dvxIcon.o: dvxIcon.c thirdparty/stb_image.h
$(OBJDIR)/dvxWidget.o: dvxWidget.c dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
$(OBJDIR)/demo.o: demo.c dvxApp.h dvxWidget.h
clean:
rm -rf $(OBJDIR) $(BINDIR)

611
dvx/README.md Normal file
View file

@ -0,0 +1,611 @@
# 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/TGA/PNG) |
| `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 beveled border with a title label and lays out children vertically.
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.

397
dvx/demo.c Normal file
View file

@ -0,0 +1,397 @@
// demo.c — DV/X GUI demonstration application
#include "dvxApp.h"
#include "dvxWidget.h"
#include <stdio.h>
#include <string.h>
// ============================================================
// Menu command IDs
// ============================================================
#define CMD_FILE_NEW 100
#define CMD_FILE_OPEN 101
#define CMD_FILE_SAVE 102
#define CMD_FILE_EXIT 103
#define CMD_HELP_ABOUT 200
// ============================================================
// Prototypes
// ============================================================
static void onCloseCb(WindowT *win);
static void onMenuCb(WindowT *win, int32_t menuId);
static void onOkClick(WidgetT *w);
static void onPaintColor(WindowT *win, RectT *dirtyArea);
static void onPaintPattern(WindowT *win, RectT *dirtyArea);
static void onPaintText(WindowT *win, RectT *dirtyArea);
static void setupWindows(AppContextT *ctx);
// ============================================================
// onCloseCb
// ============================================================
static void onCloseCb(WindowT *win) {
AppContextT *ctx = (AppContextT *)win->userData;
if (ctx) {
dvxDestroyWindow(ctx, win);
}
}
// ============================================================
// onMenuCb
// ============================================================
static void onMenuCb(WindowT *win, int32_t menuId) {
AppContextT *ctx = (AppContextT *)win->userData;
switch (menuId) {
case CMD_FILE_EXIT:
if (ctx) {
dvxQuit(ctx);
}
break;
case CMD_HELP_ABOUT:
// Could create an about dialog window here
break;
}
}
// ============================================================
// onOkClick
// ============================================================
static void onOkClick(WidgetT *w) {
// Find the status label and update it
WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
WidgetT *status = wgtFind(root, "status");
if (status) {
wgtSetText(status, "Button clicked!");
wgtInvalidate(status);
}
}
// ============================================================
// onPaintColor
// ============================================================
static void onPaintColor(WindowT *win, RectT *dirtyArea) {
(void)dirtyArea;
AppContextT *ctx = (AppContextT *)win->userData;
if (!win->contentBuf || !ctx) {
return;
}
const DisplayT *d = dvxGetDisplay(ctx);
const BlitOpsT *ops = dvxGetBlitOps(ctx);
for (int32_t y = 0; y < win->contentH; y++) {
uint8_t r = (uint8_t)((y * 255) / (win->contentH > 1 ? win->contentH - 1 : 1));
uint8_t g = (uint8_t)(100);
uint8_t b = (uint8_t)(200 - (y * 150) / (win->contentH > 1 ? win->contentH - 1 : 1));
uint32_t color = packColor(d, r, g, b);
uint8_t *row = win->contentBuf + y * win->contentPitch;
ops->spanFill(row, color, win->contentW);
}
}
// ============================================================
// onPaintPattern
// ============================================================
static void onPaintPattern(WindowT *win, RectT *dirtyArea) {
(void)dirtyArea;
AppContextT *ctx = (AppContextT *)win->userData;
if (!win->contentBuf || !ctx) {
return;
}
const DisplayT *d = dvxGetDisplay(ctx);
int32_t bpp = d->format.bytesPerPixel;
int32_t sq = 16; // square size
uint32_t c1 = packColor(d, 255, 255, 255);
uint32_t c2 = packColor(d, 0, 0, 180);
for (int32_t y = 0; y < win->contentH; y++) {
for (int32_t x = 0; x < win->contentW; x++) {
uint32_t color = ((x / sq) + (y / sq)) & 1 ? c2 : c1;
uint8_t *px = win->contentBuf + y * win->contentPitch + x * bpp;
if (bpp == 1) {
*px = (uint8_t)color;
} else if (bpp == 2) {
*(uint16_t *)px = (uint16_t)color;
} else {
*(uint32_t *)px = color;
}
}
}
}
// ============================================================
// onPaintText
// ============================================================
static void onPaintText(WindowT *win, RectT *dirtyArea) {
(void)dirtyArea;
AppContextT *ctx = (AppContextT *)win->userData;
if (!win->contentBuf || !ctx) {
return;
}
const DisplayT *d = dvxGetDisplay(ctx);
const BlitOpsT *ops = dvxGetBlitOps(ctx);
const BitmapFontT *font = dvxGetFont(ctx);
int32_t bpp = d->format.bytesPerPixel;
// Fill white background
uint32_t bg = packColor(d, 255, 255, 255);
uint32_t fg = packColor(d, 0, 0, 0);
for (int32_t y = 0; y < win->contentH; y++) {
ops->spanFill(win->contentBuf + y * win->contentPitch, bg, win->contentW);
}
// Draw text lines directly into the content buffer
static const char *lines[] = {
"DV/X GUI Compositor",
"",
"A DESQview/X-style windowed GUI",
"compositor for DOS, targeting",
"DJGPP/DPMI.",
"",
"Features:",
" - VESA VBE 2.0+ LFB",
" - Dirty-rect compositing",
" - Beveled Motif-style chrome",
" - Draggable windows",
" - Resizable windows",
" - Menu bars",
" - Scrollbars",
" - Widget system",
"",
"Press ESC to exit.",
NULL
};
int32_t textY = 4;
for (int32_t i = 0; lines[i] != NULL; i++) {
const char *line = lines[i];
int32_t textX = 4;
for (int32_t j = 0; line[j] != '\0'; j++) {
int32_t idx = (uint8_t)line[j] - font->firstChar;
if (idx < 0 || idx >= font->numChars) {
textX += font->charWidth;
continue;
}
const uint8_t *glyph = font->glyphData + idx * font->charHeight;
for (int32_t row = 0; row < font->charHeight; row++) {
int32_t py = textY + row;
if (py >= win->contentH) {
break;
}
uint8_t bits = glyph[row];
for (int32_t col = 0; col < font->charWidth; col++) {
int32_t px = textX + col;
if (px >= win->contentW) {
break;
}
if (bits & (0x80 >> col)) {
uint8_t *dst = win->contentBuf + py * win->contentPitch + px * bpp;
if (bpp == 1) {
*dst = (uint8_t)fg;
} else if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)fg;
} else {
*(uint32_t *)dst = fg;
}
}
}
}
textX += font->charWidth;
}
textY += font->charHeight + 2;
}
}
// ============================================================
// setupWindows
// ============================================================
static void setupWindows(AppContextT *ctx) {
// Window 1: Text information window with menu bar
WindowT *win1 = dvxCreateWindow(ctx, "DV/X Information", 50, 40, 340, 350, true);
if (win1) {
win1->userData = ctx;
win1->onPaint = onPaintText;
win1->onClose = onCloseCb;
win1->onMenu = onMenuCb;
MenuBarT *bar = wmAddMenuBar(win1);
if (bar) {
MenuT *fileMenu = wmAddMenu(bar, "File");
if (fileMenu) {
wmAddMenuItem(fileMenu, "New", CMD_FILE_NEW);
wmAddMenuItem(fileMenu, "Open...", CMD_FILE_OPEN);
wmAddMenuItem(fileMenu, "Save", CMD_FILE_SAVE);
wmAddMenuSeparator(fileMenu);
wmAddMenuItem(fileMenu, "Exit", CMD_FILE_EXIT);
}
MenuT *helpMenu = wmAddMenu(bar, "Help");
if (helpMenu) {
wmAddMenuItem(helpMenu, "About...", CMD_HELP_ABOUT);
}
}
wmUpdateContentRect(win1);
wmReallocContentBuf(win1, &ctx->display);
// Paint initial content
RectT fullRect = {0, 0, win1->contentW, win1->contentH};
win1->onPaint(win1, &fullRect);
}
// Window 2: Color gradient
WindowT *win2 = dvxCreateWindow(ctx, "Color Gradient", 200, 100, 280, 250, true);
if (win2) {
win2->userData = ctx;
win2->onPaint = onPaintColor;
win2->onClose = onCloseCb;
RectT fullRect = {0, 0, win2->contentW, win2->contentH};
win2->onPaint(win2, &fullRect);
}
// Window 3: Checkerboard pattern with scrollbars
WindowT *win3 = dvxCreateWindow(ctx, "Pattern", 400, 150, 250, 220, true);
if (win3) {
win3->userData = ctx;
win3->onPaint = onPaintPattern;
win3->onClose = onCloseCb;
wmAddVScrollbar(win3, 0, 100, 25);
wmAddHScrollbar(win3, 0, 100, 25);
wmUpdateContentRect(win3);
wmReallocContentBuf(win3, &ctx->display);
RectT fullRect = {0, 0, win3->contentW, win3->contentH};
win3->onPaint(win3, &fullRect);
}
// Window 4: Widget demo
WindowT *win4 = dvxCreateWindow(ctx, "Widget Demo", 80, 200, 280, 320, true);
if (win4) {
win4->userData = ctx;
win4->onClose = onCloseCb;
WidgetT *root = wgtInitWindow(ctx, win4);
// Status label at top
WidgetT *status = wgtLabel(root, "Ready.");
strncpy(status->name, "status", MAX_WIDGET_NAME);
wgtHSeparator(root);
// Frame with text input
WidgetT *frame = wgtFrame(root, "User Input");
WidgetT *row1 = wgtHBox(frame);
wgtLabel(row1, "Name:");
wgtTextInput(row1, 64);
wgtHSeparator(root);
// Checkboxes
wgtCheckbox(root, "Enable feature A");
wgtCheckbox(root, "Enable feature B");
wgtHSeparator(root);
// Radio buttons
WidgetT *rg = wgtRadioGroup(root);
wgtRadio(rg, "Option 1");
wgtRadio(rg, "Option 2");
wgtRadio(rg, "Option 3");
wgtHSeparator(root);
// Button row at bottom
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE;
WidgetT *okBtn = wgtButton(btnRow, "OK");
okBtn->onClick = onOkClick;
wgtButton(btnRow, "Cancel");
// Layout and paint now that widget tree is complete
wgtInvalidate(root);
}
}
// ============================================================
// main
// ============================================================
int main(void) {
AppContextT ctx;
printf("DV/X GUI Demo\n");
printf("Initializing VESA video...\n");
if (dvxInit(&ctx, 1024, 768, 16) != 0) {
fprintf(stderr, "Failed to initialize DV/X GUI\n");
return 1;
}
setupWindows(&ctx);
dvxRun(&ctx);
dvxShutdown(&ctx);
printf("DV/X GUI Demo ended.\n");
return 0;
}

886
dvx/dvxApp.c Normal file
View file

@ -0,0 +1,886 @@
// dvx_app.c — Layer 5: Application API for DV/X GUI
#include "dvxApp.h"
#include "dvxFont.h"
#include "dvxCursor.h"
#include <string.h>
#include <time.h>
#include <dpmi.h>
#define DBLCLICK_THRESHOLD (CLOCKS_PER_SEC / 2)
// ============================================================
// Prototypes
// ============================================================
static void compositeAndFlush(AppContextT *ctx);
static void dirtyCursorArea(AppContextT *ctx, int32_t x, int32_t y);
static void dispatchEvents(AppContextT *ctx);
static void drawCursorAt(AppContextT *ctx, int32_t x, int32_t y);
static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t buttons);
static void initColorScheme(AppContextT *ctx);
static void initMouse(AppContextT *ctx);
static void pollKeyboard(AppContextT *ctx);
static void pollMouse(AppContextT *ctx);
static void updateCursorShape(AppContextT *ctx);
// ============================================================
// compositeAndFlush
// ============================================================
static void compositeAndFlush(AppContextT *ctx) {
DisplayT *d = &ctx->display;
BlitOpsT *ops = &ctx->blitOps;
DirtyListT *dl = &ctx->dirty;
WindowStackT *ws = &ctx->stack;
dirtyListMerge(dl);
for (int32_t i = 0; i < dl->count; i++) {
RectT *dr = &dl->rects[i];
// Clip dirty rect to screen bounds
if (dr->x < 0) { dr->w += dr->x; dr->x = 0; }
if (dr->y < 0) { dr->h += dr->y; dr->y = 0; }
if (dr->x + dr->w > d->width) { dr->w = d->width - dr->x; }
if (dr->y + dr->h > d->height) { dr->h = d->height - dr->y; }
if (dr->w <= 0 || dr->h <= 0) { continue; }
// Set clip rect to this dirty rect
setClipRect(d, dr->x, dr->y, dr->w, dr->h);
// 1. Draw desktop background
rectFill(d, ops, dr->x, dr->y, dr->w, dr->h, ctx->colors.desktop);
// 2. Walk window stack bottom-to-top
for (int32_t j = 0; j < ws->count; j++) {
WindowT *win = ws->windows[j];
if (!win->visible || win->minimized) {
continue;
}
// Check if window intersects this dirty rect
RectT winRect = {win->x, win->y, win->w, win->h};
RectT isect;
if (!rectIntersect(dr, &winRect, &isect)) {
continue;
}
wmDrawChrome(d, ops, &ctx->font, &ctx->colors, win, dr);
wmDrawContent(d, ops, win, dr);
if (win->vScroll || win->hScroll) {
wmDrawScrollbars(d, ops, &ctx->colors, win, dr);
}
}
// 2b. Draw minimized window icons
wmDrawMinimizedIcons(d, ops, &ctx->colors, ws, dr);
// 3. Draw popup menu if active
if (ctx->popup.active) {
// Draw popup dropdown
RectT popRect = {
ctx->popup.popupX, ctx->popup.popupY,
ctx->popup.popupW, ctx->popup.popupH
};
RectT popIsect;
if (rectIntersect(dr, &popRect, &popIsect)) {
setClipRect(d, dr->x, dr->y, dr->w, dr->h);
// Find the window and menu
for (int32_t j = 0; j < ws->count; j++) {
if (ws->windows[j]->id == ctx->popup.windowId) {
WindowT *win = ws->windows[j];
MenuBarT *bar = win->menuBar;
if (bar && ctx->popup.menuIdx < bar->menuCount) {
MenuT *menu = &bar->menus[ctx->popup.menuIdx];
// Draw popup background
BevelStyleT popBevel;
popBevel.highlight = ctx->colors.windowHighlight;
popBevel.shadow = ctx->colors.windowShadow;
popBevel.face = ctx->colors.menuBg;
popBevel.width = 2;
drawBevel(d, ops, ctx->popup.popupX, ctx->popup.popupY,
ctx->popup.popupW, ctx->popup.popupH, &popBevel);
// Draw menu items
int32_t itemY = ctx->popup.popupY + 2;
for (int32_t k = 0; k < menu->itemCount; k++) {
MenuItemT *item = &menu->items[k];
if (item->separator) {
drawHLine(d, ops, ctx->popup.popupX + 2,
itemY + ctx->font.charHeight / 2,
ctx->popup.popupW - 4,
ctx->colors.windowShadow);
itemY += ctx->font.charHeight;
continue;
}
uint32_t bg = ctx->colors.menuBg;
uint32_t fg = ctx->colors.menuFg;
if (k == ctx->popup.hoverItem) {
bg = ctx->colors.menuHighlightBg;
fg = ctx->colors.menuHighlightFg;
}
rectFill(d, ops, ctx->popup.popupX + 2, itemY,
ctx->popup.popupW - 4, ctx->font.charHeight, bg);
drawText(d, ops, &ctx->font,
ctx->popup.popupX + CHROME_TITLE_PAD + 2, itemY,
item->label, fg, bg, true);
itemY += ctx->font.charHeight;
}
}
break;
}
}
}
}
// 4. Draw cursor
drawCursorAt(ctx, ctx->mouseX, ctx->mouseY);
// 5. Flush this dirty rect to LFB
flushRect(d, ops, dr);
}
resetClipRect(d);
dirtyListClear(dl);
}
// ============================================================
// dirtyCursorArea
// ============================================================
static void dirtyCursorArea(AppContextT *ctx, int32_t x, int32_t y) {
// Dirty the union of all cursor shapes at this position to handle shape changes
// All cursors are 16x16 with hotspot at either (0,0) or (7,7), so worst case
// covers from (x-7, y-7) to (x+15, y+15) = 23x23 area
dirtyListAdd(&ctx->dirty, x - 7, y - 7, 23, 23);
}
// ============================================================
// dispatchEvents
// ============================================================
static void dispatchEvents(AppContextT *ctx) {
int32_t mx = ctx->mouseX;
int32_t my = ctx->mouseY;
int32_t buttons = ctx->mouseButtons;
int32_t prevBtn = ctx->prevMouseButtons;
// Mouse movement always dirties old and new cursor positions
if (mx != ctx->prevMouseX || my != ctx->prevMouseY) {
dirtyCursorArea(ctx, ctx->prevMouseX, ctx->prevMouseY);
dirtyCursorArea(ctx, mx, my);
}
// Update cursor shape based on what the mouse is hovering over
updateCursorShape(ctx);
// Handle active drag
if (ctx->stack.dragWindow >= 0) {
if (buttons & 1) {
wmDragMove(&ctx->stack, &ctx->dirty, mx, my);
} else {
wmDragEnd(&ctx->stack);
}
return;
}
// Handle active resize
if (ctx->stack.resizeWindow >= 0) {
if (buttons & 1) {
wmResizeMove(&ctx->stack, &ctx->dirty, &ctx->display, mx, my);
} else {
wmResizeEnd(&ctx->stack);
}
return;
}
// Handle active scrollbar thumb drag
if (ctx->stack.scrollWindow >= 0) {
if (buttons & 1) {
wmScrollbarDrag(&ctx->stack, &ctx->dirty, mx, my);
} else {
wmScrollbarEnd(&ctx->stack);
}
return;
}
// Handle popup menu interaction
if (ctx->popup.active) {
// Check if mouse is inside popup
if (mx >= ctx->popup.popupX && mx < ctx->popup.popupX + ctx->popup.popupW &&
my >= ctx->popup.popupY && my < ctx->popup.popupY + ctx->popup.popupH) {
// Find which item is hovered
int32_t relY = my - ctx->popup.popupY - 2;
int32_t itemIdx = relY / ctx->font.charHeight;
if (itemIdx != ctx->popup.hoverItem) {
ctx->popup.hoverItem = itemIdx;
dirtyListAdd(&ctx->dirty, ctx->popup.popupX, ctx->popup.popupY,
ctx->popup.popupW, ctx->popup.popupH);
}
// Click on item
if ((buttons & 1) && !(prevBtn & 1)) {
// Find the window and menu
for (int32_t j = 0; j < ctx->stack.count; j++) {
if (ctx->stack.windows[j]->id == ctx->popup.windowId) {
WindowT *win = ctx->stack.windows[j];
MenuBarT *bar = win->menuBar;
if (bar && ctx->popup.menuIdx < bar->menuCount) {
MenuT *menu = &bar->menus[ctx->popup.menuIdx];
if (itemIdx >= 0 && itemIdx < menu->itemCount) {
MenuItemT *item = &menu->items[itemIdx];
if (item->enabled && !item->separator && win->onMenu) {
win->onMenu(win, item->id);
}
}
}
break;
}
}
// Close popup
dirtyListAdd(&ctx->dirty, ctx->popup.popupX, ctx->popup.popupY,
ctx->popup.popupW, ctx->popup.popupH);
ctx->popup.active = false;
}
} else if ((buttons & 1) && !(prevBtn & 1)) {
// Click outside popup — close it
dirtyListAdd(&ctx->dirty, ctx->popup.popupX, ctx->popup.popupY,
ctx->popup.popupW, ctx->popup.popupH);
ctx->popup.active = false;
}
return;
}
// Handle button press
if ((buttons & 1) && !(prevBtn & 1)) {
handleMouseButton(ctx, mx, my, buttons);
}
// Handle button release on content — send to focused window
if (!(buttons & 1) && (prevBtn & 1)) {
if (ctx->stack.focusedIdx >= 0) {
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
if (win->onMouse) {
int32_t relX = mx - win->x - win->contentX;
int32_t relY = my - win->y - win->contentY;
win->onMouse(win, relX, relY, buttons);
}
}
}
// Mouse movement in content area — send to focused window
if ((mx != ctx->prevMouseX || my != ctx->prevMouseY) &&
ctx->stack.focusedIdx >= 0 && (buttons & 1)) {
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
if (win->onMouse) {
int32_t relX = mx - win->x - win->contentX;
int32_t relY = my - win->y - win->contentY;
win->onMouse(win, relX, relY, buttons);
}
}
}
// ============================================================
// drawCursorAt
// ============================================================
static void drawCursorAt(AppContextT *ctx, int32_t x, int32_t y) {
const CursorT *cur = &ctx->cursors[ctx->cursorId];
drawMaskedBitmap(&ctx->display, &ctx->blitOps,
x - cur->hotX, y - cur->hotY,
cur->width, cur->height,
cur->andMask, cur->xorData,
ctx->cursorFg, ctx->cursorBg);
}
// ============================================================
// dvxCreateWindow
// ============================================================
WindowT *dvxCreateWindow(AppContextT *ctx, const char *title,
int32_t x, int32_t y, int32_t w, int32_t h,
bool resizable) {
WindowT *win = wmCreateWindow(&ctx->stack, &ctx->display,
title, x, y, w, h, resizable);
if (win) {
// Raise and focus
int32_t idx = ctx->stack.count - 1;
wmSetFocus(&ctx->stack, &ctx->dirty, idx);
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
}
return win;
}
// ============================================================
// dvxDestroyWindow
// ============================================================
void dvxDestroyWindow(AppContextT *ctx, WindowT *win) {
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
wmDestroyWindow(&ctx->stack, win);
// Focus the new top window
if (ctx->stack.count > 0) {
wmSetFocus(&ctx->stack, &ctx->dirty, ctx->stack.count - 1);
}
}
// ============================================================
// dvxGetBlitOps
// ============================================================
const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx) {
return &ctx->blitOps;
}
// ============================================================
// dvxGetColors
// ============================================================
const ColorSchemeT *dvxGetColors(const AppContextT *ctx) {
return &ctx->colors;
}
// ============================================================
// dvxGetDisplay
// ============================================================
DisplayT *dvxGetDisplay(AppContextT *ctx) {
return &ctx->display;
}
// ============================================================
// dvxGetFont
// ============================================================
const BitmapFontT *dvxGetFont(const AppContextT *ctx) {
return &ctx->font;
}
// ============================================================
// dvxInit
// ============================================================
int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
memset(ctx, 0, sizeof(*ctx));
// Initialize video
if (videoInit(&ctx->display, requestedW, requestedH, preferredBpp) != 0) {
return -1;
}
// Initialize blit ops
drawInit(&ctx->blitOps, &ctx->display);
// Initialize window stack
wmInit(&ctx->stack);
// Initialize dirty list
dirtyListInit(&ctx->dirty);
// Set up font (use 8x16)
ctx->font = dvxFont8x16;
// Set up cursors
memcpy(ctx->cursors, dvxCursors, sizeof(dvxCursors));
ctx->cursorId = CURSOR_ARROW;
// Initialize colors
initColorScheme(ctx);
// Cache cursor colors
ctx->cursorFg = packColor(&ctx->display, 255, 255, 255);
ctx->cursorBg = packColor(&ctx->display, 0, 0, 0);
// Initialize mouse
initMouse(ctx);
ctx->running = true;
ctx->lastIconClickId = -1;
ctx->lastIconClickTime = 0;
ctx->lastCloseClickId = -1;
ctx->lastCloseClickTime = 0;
// Dirty the entire screen for first paint
dirtyListAdd(&ctx->dirty, 0, 0, ctx->display.width, ctx->display.height);
return 0;
}
// ============================================================
// dvxInvalidateRect
// ============================================================
void dvxInvalidateRect(AppContextT *ctx, WindowT *win,
int32_t x, int32_t y, int32_t w, int32_t h) {
// Convert from content-relative to screen coordinates
int32_t screenX = win->x + win->contentX + x;
int32_t screenY = win->y + win->contentY + y;
dirtyListAdd(&ctx->dirty, screenX, screenY, w, h);
}
// ============================================================
// dvxInvalidateWindow
// ============================================================
void dvxInvalidateWindow(AppContextT *ctx, WindowT *win) {
dirtyListAdd(&ctx->dirty, win->x, win->y, win->w, win->h);
}
// ============================================================
// dvxQuit
// ============================================================
void dvxQuit(AppContextT *ctx) {
ctx->running = false;
}
// ============================================================
// dvxRun
// ============================================================
void dvxRun(AppContextT *ctx) {
while (ctx->running) {
pollMouse(ctx);
pollKeyboard(ctx);
dispatchEvents(ctx);
if (ctx->dirty.count > 0) {
compositeAndFlush(ctx);
} else {
// Nothing to do — yield timeslice
__dpmi_yield();
}
ctx->prevMouseX = ctx->mouseX;
ctx->prevMouseY = ctx->mouseY;
ctx->prevMouseButtons = ctx->mouseButtons;
}
}
// ============================================================
// dvxShutdown
// ============================================================
void dvxShutdown(AppContextT *ctx) {
// Destroy all remaining windows
while (ctx->stack.count > 0) {
wmDestroyWindow(&ctx->stack, ctx->stack.windows[ctx->stack.count - 1]);
}
videoShutdown(&ctx->display);
}
// ============================================================
// dvxSetTitle
// ============================================================
void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title) {
wmSetTitle(win, &ctx->dirty, title);
}
// ============================================================
// dvxSetWindowIcon
// ============================================================
int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path) {
return wmSetIcon(win, path, &ctx->display);
}
// ============================================================
// handleMouseButton
// ============================================================
static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t buttons) {
// Check for click on minimized icon first
int32_t iconIdx = wmMinimizedIconHit(&ctx->stack, &ctx->display, mx, my);
if (iconIdx >= 0) {
WindowT *iconWin = ctx->stack.windows[iconIdx];
clock_t now = clock();
if (ctx->lastIconClickId == iconWin->id &&
(now - ctx->lastIconClickTime) < DBLCLICK_THRESHOLD) {
// Double-click: restore minimized window
// Dirty the entire icon strip area
dirtyListAdd(&ctx->dirty, 0,
ctx->display.height - ICON_TOTAL_SIZE - ICON_SPACING,
ctx->display.width, ICON_TOTAL_SIZE + ICON_SPACING);
wmRestoreMinimized(&ctx->stack, &ctx->dirty, iconWin);
ctx->lastIconClickId = -1;
} else {
// First click — record for double-click detection
ctx->lastIconClickTime = now;
ctx->lastIconClickId = iconWin->id;
}
return;
}
int32_t hitPart;
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
if (hitIdx < 0) {
return; // clicked on desktop
}
WindowT *win = ctx->stack.windows[hitIdx];
// Raise and focus if not already
if (hitIdx != ctx->stack.focusedIdx) {
wmRaiseWindow(&ctx->stack, &ctx->dirty, hitIdx);
// After raise, the window is now at count-1
hitIdx = ctx->stack.count - 1;
wmSetFocus(&ctx->stack, &ctx->dirty, hitIdx);
}
switch (hitPart) {
case 0: // content
if (win->onMouse) {
int32_t relX = mx - win->x - win->contentX;
int32_t relY = my - win->y - win->contentY;
win->onMouse(win, relX, relY, buttons);
}
break;
case 1: // title bar — start drag
wmDragBegin(&ctx->stack, hitIdx, mx, my);
break;
case 2: // close button (double-click to close)
{
clock_t now = clock();
if (ctx->lastCloseClickId == win->id &&
(now - ctx->lastCloseClickTime) < DBLCLICK_THRESHOLD) {
ctx->lastCloseClickId = -1;
if (win->onClose) {
win->onClose(win);
} else {
dvxDestroyWindow(ctx, win);
}
} else {
ctx->lastCloseClickTime = now;
ctx->lastCloseClickId = win->id;
}
}
break;
case 3: // resize edge
{
int32_t edge = wmResizeEdgeHit(win, mx, my);
wmResizeBegin(&ctx->stack, hitIdx, edge, mx, my);
}
break;
case 4: // menu bar
{
// Determine which menu was clicked
if (!win->menuBar) {
break;
}
int32_t relX = mx - win->x;
for (int32_t i = 0; i < win->menuBar->menuCount; i++) {
MenuT *menu = &win->menuBar->menus[i];
if (relX >= menu->barX && relX < menu->barX + menu->barW) {
// Open popup
ctx->popup.active = true;
ctx->popup.windowId = win->id;
ctx->popup.menuIdx = i;
ctx->popup.popupX = win->x + menu->barX;
ctx->popup.popupY = win->y + CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT + CHROME_MENU_HEIGHT;
ctx->popup.hoverItem = -1;
// Calculate popup size
int32_t maxW = 0;
for (int32_t k = 0; k < menu->itemCount; k++) {
int32_t itemW = textWidth(&ctx->font, menu->items[k].label);
if (itemW > maxW) {
maxW = itemW;
}
}
ctx->popup.popupW = maxW + CHROME_TITLE_PAD * 2 + 8;
ctx->popup.popupH = menu->itemCount * ctx->font.charHeight + 4;
dirtyListAdd(&ctx->dirty, ctx->popup.popupX, ctx->popup.popupY,
ctx->popup.popupW, ctx->popup.popupH);
break;
}
}
}
break;
case 5: // vertical scrollbar
wmScrollbarClick(&ctx->stack, &ctx->dirty, hitIdx, 0, mx, my);
break;
case 6: // horizontal scrollbar
wmScrollbarClick(&ctx->stack, &ctx->dirty, hitIdx, 1, mx, my);
break;
case 7: // minimize
wmMinimize(&ctx->stack, &ctx->dirty, win);
// Dirty the icon strip area so the new icon gets drawn
dirtyListAdd(&ctx->dirty, 0,
ctx->display.height - ICON_TOTAL_SIZE - ICON_SPACING,
ctx->display.width, ICON_TOTAL_SIZE + ICON_SPACING);
break;
case 8: // maximize / restore
if (win->maximized) {
wmRestore(&ctx->stack, &ctx->dirty, &ctx->display, win);
} else {
wmMaximize(&ctx->stack, &ctx->dirty, &ctx->display, win);
}
break;
}
}
// ============================================================
// initColorScheme
// ============================================================
static void initColorScheme(AppContextT *ctx) {
DisplayT *d = &ctx->display;
// GEOS Ensemble Motif-style color scheme
ctx->colors.desktop = packColor(d, 0, 128, 128); // GEOS teal desktop
ctx->colors.windowFace = packColor(d, 192, 192, 192); // standard Motif grey
ctx->colors.windowHighlight = packColor(d, 255, 255, 255);
ctx->colors.windowShadow = packColor(d, 80, 80, 80);
ctx->colors.activeTitleBg = packColor(d, 48, 48, 48); // GEOS dark charcoal
ctx->colors.activeTitleFg = packColor(d, 255, 255, 255);
ctx->colors.inactiveTitleBg = packColor(d, 160, 160, 160); // lighter grey
ctx->colors.inactiveTitleFg = packColor(d, 64, 64, 64);
ctx->colors.contentBg = packColor(d, 255, 255, 255);
ctx->colors.contentFg = packColor(d, 0, 0, 0);
ctx->colors.menuBg = packColor(d, 192, 192, 192);
ctx->colors.menuFg = packColor(d, 0, 0, 0);
ctx->colors.menuHighlightBg = packColor(d, 48, 48, 48);
ctx->colors.menuHighlightFg = packColor(d, 255, 255, 255);
ctx->colors.buttonFace = packColor(d, 192, 192, 192);
ctx->colors.scrollbarBg = packColor(d, 192, 192, 192);
ctx->colors.scrollbarFg = packColor(d, 128, 128, 128);
ctx->colors.scrollbarTrough = packColor(d, 160, 160, 160); // GEOS lighter trough
}
// ============================================================
// initMouse
// ============================================================
static void initMouse(AppContextT *ctx) {
__dpmi_regs r;
// Reset mouse driver
memset(&r, 0, sizeof(r));
r.x.ax = 0x0000;
__dpmi_int(0x33, &r);
// Set horizontal range to match screen width
memset(&r, 0, sizeof(r));
r.x.ax = 0x0007;
r.x.cx = 0;
r.x.dx = ctx->display.width - 1;
__dpmi_int(0x33, &r);
// Set vertical range to match screen height
memset(&r, 0, sizeof(r));
r.x.ax = 0x0008;
r.x.cx = 0;
r.x.dx = ctx->display.height - 1;
__dpmi_int(0x33, &r);
// Position cursor at center of screen
ctx->mouseX = ctx->display.width / 2;
ctx->mouseY = ctx->display.height / 2;
ctx->prevMouseX = ctx->mouseX;
ctx->prevMouseY = ctx->mouseY;
// Set mouse position
memset(&r, 0, sizeof(r));
r.x.ax = 0x0004;
r.x.cx = ctx->mouseX;
r.x.dx = ctx->mouseY;
__dpmi_int(0x33, &r);
}
// ============================================================
// pollKeyboard
// ============================================================
static void pollKeyboard(AppContextT *ctx) {
__dpmi_regs r;
// Check if key is available (INT 16h, function 01h)
while (1) {
memset(&r, 0, sizeof(r));
r.x.ax = 0x0100;
__dpmi_int(0x16, &r);
// Zero flag set = no key available
if (r.x.flags & 0x40) {
break;
}
// Read the key (INT 16h, function 00h)
memset(&r, 0, sizeof(r));
r.x.ax = 0x0000;
__dpmi_int(0x16, &r);
int32_t scancode = (r.x.ax >> 8) & 0xFF;
int32_t ascii = r.x.ax & 0xFF;
// ESC = quit
if (ascii == 0x1B) {
dvxQuit(ctx);
return;
}
// Send to focused window
if (ctx->stack.focusedIdx >= 0) {
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
if (win->onKey) {
win->onKey(win, ascii ? ascii : (scancode | 0x100), 0);
}
}
}
}
// ============================================================
// pollMouse
// ============================================================
static void pollMouse(AppContextT *ctx) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x33, &r);
ctx->mouseX = r.x.cx;
ctx->mouseY = r.x.dx;
ctx->mouseButtons = r.x.bx;
}
// ============================================================
// updateCursorShape
// ============================================================
static void updateCursorShape(AppContextT *ctx) {
int32_t newCursor = CURSOR_ARROW;
int32_t mx = ctx->mouseX;
int32_t my = ctx->mouseY;
// During active resize, keep the resize cursor
if (ctx->stack.resizeWindow >= 0) {
int32_t edge = ctx->stack.resizeEdge;
bool horiz = (edge & (RESIZE_LEFT | RESIZE_RIGHT)) != 0;
bool vert = (edge & (RESIZE_TOP | RESIZE_BOTTOM)) != 0;
if (horiz && vert) {
if ((edge & RESIZE_LEFT && edge & RESIZE_TOP) ||
(edge & RESIZE_RIGHT && edge & RESIZE_BOTTOM)) {
newCursor = CURSOR_RESIZE_DIAG_NWSE;
} else {
newCursor = CURSOR_RESIZE_DIAG_NESW;
}
} else if (horiz) {
newCursor = CURSOR_RESIZE_H;
} else {
newCursor = CURSOR_RESIZE_V;
}
}
// Not in an active drag/resize — check what we're hovering
else if (ctx->stack.dragWindow < 0 && ctx->stack.scrollWindow < 0) {
int32_t hitPart;
int32_t hitIdx = wmHitTest(&ctx->stack, mx, my, &hitPart);
if (hitIdx >= 0 && hitPart == 3) {
// Hovering over a resize edge
WindowT *win = ctx->stack.windows[hitIdx];
int32_t edge = wmResizeEdgeHit(win, mx, my);
bool horiz = (edge & (RESIZE_LEFT | RESIZE_RIGHT)) != 0;
bool vert = (edge & (RESIZE_TOP | RESIZE_BOTTOM)) != 0;
if (horiz && vert) {
if ((edge & RESIZE_LEFT && edge & RESIZE_TOP) ||
(edge & RESIZE_RIGHT && edge & RESIZE_BOTTOM)) {
newCursor = CURSOR_RESIZE_DIAG_NWSE;
} else {
newCursor = CURSOR_RESIZE_DIAG_NESW;
}
} else if (horiz) {
newCursor = CURSOR_RESIZE_H;
} else if (vert) {
newCursor = CURSOR_RESIZE_V;
}
}
}
// If cursor shape changed, dirty the cursor area
if (newCursor != ctx->cursorId) {
dirtyCursorArea(ctx, mx, my);
ctx->cursorId = newCursor;
}
}

87
dvx/dvxApp.h Normal file
View file

@ -0,0 +1,87 @@
// dvx_app.h — Layer 5: Application API for DV/X GUI
#ifndef DVX_APP_H
#define DVX_APP_H
#include "dvxTypes.h"
#include "dvxVideo.h"
#include "dvxDraw.h"
#include "dvxComp.h"
#include "dvxWm.h"
#include <time.h>
// ============================================================
// Application context
// ============================================================
typedef struct AppContextT {
DisplayT display;
WindowStackT stack;
DirtyListT dirty;
BlitOpsT blitOps;
BitmapFontT font;
ColorSchemeT colors;
PopupStateT popup;
CursorT cursors[5]; // indexed by CURSOR_xxx
int32_t cursorId; // active cursor shape
uint32_t cursorFg; // pre-packed cursor colors
uint32_t cursorBg;
bool running;
int32_t mouseX;
int32_t mouseY;
int32_t mouseButtons;
int32_t prevMouseX;
int32_t prevMouseY;
int32_t prevMouseButtons;
clock_t lastIconClickTime;
int32_t lastIconClickId; // window ID of last-clicked minimized icon (-1 = none)
clock_t lastCloseClickTime;
int32_t lastCloseClickId; // window ID of last-clicked close gadget (-1 = none)
} AppContextT;
// Initialize the application (VESA mode, input, etc.)
int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
// Shut down and restore text mode
void dvxShutdown(AppContextT *ctx);
// Run the main event loop (returns when ctx->running is set to false)
void dvxRun(AppContextT *ctx);
// Create a window
WindowT *dvxCreateWindow(AppContextT *ctx, const char *title,
int32_t x, int32_t y, int32_t w, int32_t h,
bool resizable);
// Destroy a window
void dvxDestroyWindow(AppContextT *ctx, WindowT *win);
// Invalidate a region of a window's content area (triggers repaint)
void dvxInvalidateRect(AppContextT *ctx, WindowT *win,
int32_t x, int32_t y, int32_t w, int32_t h);
// Invalidate entire window content
void dvxInvalidateWindow(AppContextT *ctx, WindowT *win);
// Request exit from main loop
void dvxQuit(AppContextT *ctx);
// Set window title
void dvxSetTitle(AppContextT *ctx, WindowT *win, const char *title);
// Get the default font
const BitmapFontT *dvxGetFont(const AppContextT *ctx);
// Get the color scheme
const ColorSchemeT *dvxGetColors(const AppContextT *ctx);
// Get the display
DisplayT *dvxGetDisplay(AppContextT *ctx);
// Get blit ops
const BlitOpsT *dvxGetBlitOps(const AppContextT *ctx);
// Load an icon for a window from an image file
int32_t dvxSetWindowIcon(AppContextT *ctx, WindowT *win, const char *path);
#endif // DVX_APP_H

224
dvx/dvxComp.c Normal file
View file

@ -0,0 +1,224 @@
// dvx_comp.c — Layer 3: Dirty rectangle compositor for DV/X GUI (optimized)
#include "dvxComp.h"
#include <string.h>
// ============================================================
// Prototypes
// ============================================================
static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_t gap);
static inline void rectUnion(const RectT *a, const RectT *b, RectT *result);
// ============================================================
// dirtyListAdd
// ============================================================
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h) {
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
if (__builtin_expect(dl->count >= MAX_DIRTY_RECTS, 0)) {
dirtyListMerge(dl);
if (dl->count >= MAX_DIRTY_RECTS) {
RectT merged = dl->rects[0];
for (int32_t i = 1; i < dl->count; i++) {
rectUnion(&merged, &dl->rects[i], &merged);
}
RectT newRect = {x, y, w, h};
rectUnion(&merged, &newRect, &merged);
dl->rects[0] = merged;
dl->count = 1;
return;
}
}
dl->rects[dl->count].x = x;
dl->rects[dl->count].y = y;
dl->rects[dl->count].w = w;
dl->rects[dl->count].h = h;
dl->count++;
}
// ============================================================
// dirtyListClear
// ============================================================
void dirtyListClear(DirtyListT *dl) {
dl->count = 0;
}
// ============================================================
// dirtyListInit
// ============================================================
void dirtyListInit(DirtyListT *dl) {
dl->count = 0;
}
// ============================================================
// dirtyListMerge
// ============================================================
void dirtyListMerge(DirtyListT *dl) {
if (dl->count <= 1) {
return;
}
bool merged = true;
while (merged) {
merged = false;
for (int32_t i = 0; i < dl->count; i++) {
for (int32_t j = i + 1; j < dl->count; j++) {
if (rectsOverlapOrAdjacent(&dl->rects[i], &dl->rects[j], 4)) {
rectUnion(&dl->rects[i], &dl->rects[j], &dl->rects[i]);
dl->rects[j] = dl->rects[dl->count - 1];
dl->count--;
j--;
merged = true;
}
}
}
}
}
// ============================================================
// flushRect
// ============================================================
void flushRect(DisplayT *d, const BlitOpsT *ops, const RectT *r) {
(void)ops;
int32_t bpp = d->format.bytesPerPixel;
int32_t x = r->x;
int32_t y = r->y;
int32_t w = r->w;
int32_t h = r->h;
if (__builtin_expect(x < 0, 0)) { w += x; x = 0; }
if (__builtin_expect(y < 0, 0)) { h += y; y = 0; }
if (__builtin_expect(x + w > d->width, 0)) { w = d->width - x; }
if (__builtin_expect(y + h > d->height, 0)) { h = d->height - y; }
if (__builtin_expect(w <= 0 || h <= 0, 0)) { return; }
int32_t rowBytes = w * bpp;
int32_t pitch = d->pitch;
uint8_t *src = d->backBuf + y * pitch + x * bpp;
uint8_t *dst = d->lfb + y * pitch + x * bpp;
// Full-width flush: single large copy (write-combined memory loves sequential writes)
if (rowBytes == pitch) {
// Entire scanlines — copy as one contiguous block
int32_t totalBytes = pitch * h;
int32_t dwords = totalBytes >> 2;
int32_t remainder = totalBytes & 3;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(dwords)
:
: "memory"
);
// Handle any trailing bytes (unlikely for aligned pitch)
while (remainder-- > 0) {
*dst++ = *src++;
}
} else {
// Partial scanlines — copy row by row with rep movsd
for (int32_t i = 0; i < h; i++) {
int32_t dwords = rowBytes >> 2;
int32_t remainder = rowBytes & 3;
uint8_t *s = src;
uint8_t *dd = dst;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dd), "+S"(s), "+c"(dwords)
:
: "memory"
);
// Trailing bytes (dd and s already advanced by rep movsl)
if (__builtin_expect(remainder > 0, 0)) {
while (remainder-- > 0) {
*dd++ = *s++;
}
}
src += pitch;
dst += pitch;
}
}
}
// ============================================================
// rectIntersect
// ============================================================
bool rectIntersect(const RectT *a, const RectT *b, RectT *result) {
int32_t ix1 = a->x > b->x ? a->x : b->x;
int32_t iy1 = a->y > b->y ? a->y : b->y;
int32_t ix2 = (a->x + a->w) < (b->x + b->w) ? (a->x + a->w) : (b->x + b->w);
int32_t iy2 = (a->y + a->h) < (b->y + b->h) ? (a->y + a->h) : (b->y + b->h);
if (__builtin_expect(ix1 >= ix2 || iy1 >= iy2, 0)) {
return false;
}
result->x = ix1;
result->y = iy1;
result->w = ix2 - ix1;
result->h = iy2 - iy1;
return true;
}
// ============================================================
// rectIsEmpty
// ============================================================
bool rectIsEmpty(const RectT *r) {
return (r->w <= 0 || r->h <= 0);
}
// ============================================================
// rectsOverlapOrAdjacent
// ============================================================
static inline bool rectsOverlapOrAdjacent(const RectT *a, const RectT *b, int32_t gap) {
if (a->x + a->w + gap < b->x) { return false; }
if (b->x + b->w + gap < a->x) { return false; }
if (a->y + a->h + gap < b->y) { return false; }
if (b->y + b->h + gap < a->y) { return false; }
return true;
}
// ============================================================
// rectUnion
// ============================================================
static inline void rectUnion(const RectT *a, const RectT *b, RectT *result) {
int32_t x1 = a->x < b->x ? a->x : b->x;
int32_t y1 = a->y < b->y ? a->y : b->y;
int32_t x2 = (a->x + a->w) > (b->x + b->w) ? (a->x + a->w) : (b->x + b->w);
int32_t y2 = (a->y + a->h) > (b->y + b->h) ? (a->y + a->h) : (b->y + b->h);
result->x = x1;
result->y = y1;
result->w = x2 - x1;
result->h = y2 - y1;
}

28
dvx/dvxComp.h Normal file
View file

@ -0,0 +1,28 @@
// dvx_comp.h — Layer 3: Dirty rectangle compositor for DV/X GUI
#ifndef DVX_COMP_H
#define DVX_COMP_H
#include "dvxTypes.h"
// Initialize the dirty list
void dirtyListInit(DirtyListT *dl);
// Add a rectangle to the dirty list
void dirtyListAdd(DirtyListT *dl, int32_t x, int32_t y, int32_t w, int32_t h);
// Merge overlapping/adjacent dirty rects to reduce redraw work
void dirtyListMerge(DirtyListT *dl);
// Clear the dirty list
void dirtyListClear(DirtyListT *dl);
// Flush a single rectangle from backbuffer to LFB
void flushRect(DisplayT *d, const BlitOpsT *ops, const RectT *r);
// Intersect two rectangles, returns true if intersection is non-empty
bool rectIntersect(const RectT *a, const RectT *b, RectT *result);
// Test if a rectangle is empty (zero or negative area)
bool rectIsEmpty(const RectT *r);
#endif // DVX_COMP_H

261
dvx/dvxCursor.h Normal file
View file

@ -0,0 +1,261 @@
// dvxCursor.h — Embedded mouse cursor bitmaps for DV/X GUI
#ifndef DVX_CURSOR_H
#define DVX_CURSOR_H
#include "dvxTypes.h"
// ============================================================
// Cursor shape IDs
// ============================================================
#define CURSOR_ARROW 0
#define CURSOR_RESIZE_H 1 // left/right (horizontal)
#define CURSOR_RESIZE_V 2 // up/down (vertical)
#define CURSOR_RESIZE_DIAG_NWSE 3 // NW-SE diagonal (top-left / bottom-right)
#define CURSOR_RESIZE_DIAG_NESW 4 // NE-SW diagonal (top-right / bottom-left)
#define CURSOR_COUNT 5
// ============================================================
// Standard arrow cursor, 16x16
// ============================================================
// AND mask: 0 = draw cursor pixel, 1 = transparent
// XOR data: 0 = black, 1 = white (where AND = 0)
//
// The cursor is a left-pointing arrow with black outline and white fill.
// Hot spot is at (0, 0) — top-left corner of the arrow tip.
static const uint16_t cursorArrowAnd[16] = {
0x3FFF, // 0011111111111111 row 0
0x1FFF, // 0001111111111111 row 1
0x0FFF, // 0000111111111111 row 2
0x07FF, // 0000011111111111 row 3
0x03FF, // 0000001111111111 row 4
0x01FF, // 0000000111111111 row 5
0x00FF, // 0000000011111111 row 6
0x007F, // 0000000001111111 row 7
0x003F, // 0000000000111111 row 8
0x001F, // 0000000000011111 row 9
0x01FF, // 0000000111111111 row 10
0x10FF, // 0001000011111111 row 11
0x30FF, // 0011000011111111 row 12
0xF87F, // 1111100001111111 row 13
0xF87F, // 1111100001111111 row 14
0xFC3F // 1111110000111111 row 15
};
static const uint16_t cursorArrowXor[16] = {
0x0000, // 0000000000000000 row 0
0x4000, // 0100000000000000 row 1
0x6000, // 0110000000000000 row 2
0x7000, // 0111000000000000 row 3
0x7800, // 0111100000000000 row 4
0x7C00, // 0111110000000000 row 5
0x7E00, // 0111111000000000 row 6
0x7F00, // 0111111100000000 row 7
0x7F80, // 0111111110000000 row 8
0x7C00, // 0111110000000000 row 9
0x6C00, // 0110110000000000 row 10
0x4600, // 0100011000000000 row 11
0x0600, // 0000011000000000 row 12
0x0300, // 0000001100000000 row 13
0x0300, // 0000001100000000 row 14
0x0000 // 0000000000000000 row 15
};
// ============================================================
// Horizontal resize cursor (left/right arrows), 16x16
// ============================================================
// Hot spot at center (7, 7)
//
// <-->
// ..XX..XX..
// .X..XX..X.
// X..XXXX..X
// ..XXXXXX..
// X..XXXX..X
// .X..XX..X.
// ..XX..XX..
static const uint16_t cursorResizeHAnd[16] = {
0xFFFF, // row 0 (transparent)
0xFFFF, // row 1
0xFFFF, // row 2
0xF7EF, // 1111011111101111 row 3 arrows start
0xE3C7, // 1110001111000111 row 4
0xC1A3, // 1100000110100011 row 5
0x8081, // 1000000010000001 row 6
0x0000, // 0000000000000000 row 7 center row
0x8081, // 1000000010000001 row 8
0xC1A3, // 1100000110100011 row 9
0xE3C7, // 1110001111000111 row 10
0xF7EF, // 1111011111101111 row 11
0xFFFF, // row 12
0xFFFF, // row 13
0xFFFF, // row 14
0xFFFF // row 15
};
static const uint16_t cursorResizeHXor[16] = {
0x0000, // row 0
0x0000, // row 1
0x0000, // row 2
0x0000, // row 3
0x0810, // 0000100000010000 row 4
0x1C38, // 0001110000111000 row 5
0x3E7C, // 0011111001111100 row 6
0x7FFE, // 0111111111111110 row 7
0x3E7C, // 0011111001111100 row 8
0x1C38, // 0001110000111000 row 9
0x0810, // 0000100000010000 row 10
0x0000, // row 11
0x0000, // row 12
0x0000, // row 13
0x0000, // row 14
0x0000 // row 15
};
// ============================================================
// Vertical resize cursor (up/down arrows), 16x16
// ============================================================
// Hot spot at center (7, 7)
static const uint16_t cursorResizeVAnd[16] = {
0xFFFF, // row 0 (transparent)
0xFFFF, // row 1
0xFE7F, // 1111111001111111 row 2 top arrow
0xFC3F, // 1111110000111111 row 3
0xF81F, // 1111100000011111 row 4
0xF00F, // 1111000000001111 row 5
0xFE7F, // 1111111001111111 row 6
0xFE7F, // 1111111001111111 row 7 stem
0xFE7F, // 1111111001111111 row 8 stem
0xFE7F, // 1111111001111111 row 9
0xF00F, // 1111000000001111 row 10
0xF81F, // 1111100000011111 row 11
0xFC3F, // 1111110000111111 row 12
0xFE7F, // 1111111001111111 row 13 bottom arrow
0xFFFF, // row 14
0xFFFF // row 15
};
static const uint16_t cursorResizeVXor[16] = {
0x0000, // row 0
0x0000, // row 1
0x0100, // 0000000100000000 row 2
0x0380, // 0000001110000000 row 3
0x07C0, // 0000011111000000 row 4
0x0FE0, // 0000111111100000 row 5 full width
0x0100, // 0000000100000000 row 6 stem
0x0100, // 0000000100000000 row 7
0x0100, // 0000000100000000 row 8
0x0100, // 0000000100000000 row 9 stem
0x0FE0, // 0000111111100000 row 10 full width
0x07C0, // 0000011111000000 row 11
0x0380, // 0000001110000000 row 12
0x0100, // 0000000100000000 row 13
0x0000, // row 14
0x0000 // row 15
};
// ============================================================
// Diagonal resize cursor NW-SE (top-left / bottom-right), 16x16
// ============================================================
// Hot spot at center (7, 7)
static const uint16_t cursorResizeDiagNWSEAnd[16] = {
0xFFFF, // row 0
0xFFFF, // row 1
0xE0FF, // 1110000011111111 row 2 top-left arrow
0xE0FF, // 1110000011111111 row 3
0xE4FF, // 1110010011111111 row 4
0xECFF, // 1110110011111111 row 5
0xF4FF, // 1111010011111111 row 6
0xFCFF, // 1111110011111111 row 7
0xFF3F, // 1111111100111111 row 8
0xFF2F, // 1111111100101111 row 9
0xFF37, // 1111111100110111 row 10
0xFF27, // 1111111100100111 row 11
0xFF07, // 1111111100000111 row 12
0xFF07, // 1111111100000111 row 13 bottom-right arrow
0xFFFF, // row 14
0xFFFF // row 15
};
static const uint16_t cursorResizeDiagNWSEXor[16] = {
0x0000, // row 0
0x0000, // row 1
0x1F00, // 0001111100000000 row 2
0x0E00, // 0000111000000000 row 3
0x0E00, // 0000111000000000 row 4
0x0700, // 0000011100000000 row 5
0x0B00, // 0000101100000000 row 6
0x0100, // 0000000100000000 row 7
0x0080, // 0000000010000000 row 8
0x00D0, // 0000000011010000 row 9
0x00E0, // 0000000011100000 row 10
0x0070, // 0000000001110000 row 11
0x0070, // 0000000001110000 row 12
0x00F8, // 0000000011111000 row 13
0x0000, // row 14
0x0000 // row 15
};
// ============================================================
// Diagonal resize cursor NE-SW (top-right / bottom-left), 16x16
// ============================================================
// Hot spot at center (7, 7)
static const uint16_t cursorResizeDiagNESWAnd[16] = {
0xFFFF, // row 0
0xFFFF, // row 1
0xFF07, // 1111111100000111 row 2 top-right arrow
0xFF07, // 1111111100000111 row 3
0xFF27, // 1111111100100111 row 4
0xFF37, // 1111111100110111 row 5
0xFF2F, // 1111111100101111 row 6
0xFF3F, // 1111111100111111 row 7
0xFCFF, // 1111110011111111 row 8
0xF4FF, // 1111010011111111 row 9
0xECFF, // 1110110011111111 row 10
0xE4FF, // 1110010011111111 row 11
0xE0FF, // 1110000011111111 row 12
0xE0FF, // 1110000011111111 row 13 bottom-left arrow
0xFFFF, // row 14
0xFFFF // row 15
};
static const uint16_t cursorResizeDiagNESWXor[16] = {
0x0000, // row 0
0x0000, // row 1
0x00F8, // 0000000011111000 row 2
0x0070, // 0000000001110000 row 3
0x0070, // 0000000001110000 row 4
0x00E0, // 0000000011100000 row 5
0x00D0, // 0000000011010000 row 6
0x0080, // 0000000010000000 row 7
0x0100, // 0000000100000000 row 8
0x0B00, // 0000101100000000 row 9
0x0700, // 0000011100000000 row 10
0x0E00, // 0000111000000000 row 11
0x0E00, // 0000111000000000 row 12
0x1F00, // 0001111100000000 row 13
0x0000, // row 14
0x0000 // row 15
};
// ============================================================
// Cursor table
// ============================================================
static const CursorT dvxCursors[CURSOR_COUNT] = {
{ 16, 16, 0, 0, cursorArrowAnd, cursorArrowXor }, // CURSOR_ARROW
{ 16, 16, 7, 7, cursorResizeHAnd, cursorResizeHXor }, // CURSOR_RESIZE_H
{ 16, 16, 7, 7, cursorResizeVAnd, cursorResizeVXor }, // CURSOR_RESIZE_V
{ 16, 16, 7, 7, cursorResizeDiagNWSEAnd, cursorResizeDiagNWSEXor }, // CURSOR_RESIZE_DIAG_NWSE
{ 16, 16, 7, 7, cursorResizeDiagNESWAnd, cursorResizeDiagNESWXor }, // CURSOR_RESIZE_DIAG_NESW
};
// Legacy alias for backward compatibility
static const CursorT dvxCursor = { 16, 16, 0, 0, cursorArrowAnd, cursorArrowXor };
#endif // DVX_CURSOR_H

642
dvx/dvxDraw.c Normal file
View file

@ -0,0 +1,642 @@
// dvx_draw.c — Layer 2: Drawing primitives for DV/X GUI (optimized)
#include "dvxDraw.h"
#include <string.h>
// ============================================================
// Prototypes
// ============================================================
static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h);
static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp);
static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count);
static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count);
static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count);
static void spanFill8(uint8_t *dst, uint32_t color, int32_t count);
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count);
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count);
// ============================================================
// clipRect
// ============================================================
static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *w, int32_t *h) {
int32_t cx2 = d->clipX + d->clipW;
int32_t cy2 = d->clipY + d->clipH;
int32_t rx1 = *x;
int32_t ry1 = *y;
int32_t rx2 = rx1 + *w;
int32_t ry2 = ry1 + *h;
if (__builtin_expect(rx1 < d->clipX, 0)) { rx1 = d->clipX; }
if (__builtin_expect(ry1 < d->clipY, 0)) { ry1 = d->clipY; }
if (__builtin_expect(rx2 > cx2, 0)) { rx2 = cx2; }
if (__builtin_expect(ry2 > cy2, 0)) { ry2 = cy2; }
*x = rx1;
*y = ry1;
*w = rx2 - rx1;
*h = ry2 - ry1;
}
// ============================================================
// drawBevel
// ============================================================
void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, const BevelStyleT *style) {
int32_t bw = style->width;
// Fill interior if requested
if (style->face != 0) {
rectFill(d, ops, x + bw, y + bw, w - bw * 2, h - bw * 2, style->face);
}
// Fast path for the common bevel widths (1 and 2)
// Directly emit spans instead of calling drawHLine->rectFill->clipRect per line
if (bw == 2) {
// Top 2 highlight lines
rectFill(d, ops, x, y, w, 1, style->highlight);
rectFill(d, ops, x + 1, y + 1, w - 2, 1, style->highlight);
// Left 2 highlight columns
rectFill(d, ops, x, y + 1, 1, h - 1, style->highlight);
rectFill(d, ops, x + 1, y + 2, 1, h - 3, style->highlight);
// Bottom 2 shadow lines
rectFill(d, ops, x, y + h - 1, w, 1, style->shadow);
rectFill(d, ops, x + 1, y + h - 2, w - 2, 1, style->shadow);
// Right 2 shadow columns
rectFill(d, ops, x + w - 1, y + 1, 1, h - 2, style->shadow);
rectFill(d, ops, x + w - 2, y + 2, 1, h - 4, style->shadow);
} else if (bw == 1) {
rectFill(d, ops, x, y, w, 1, style->highlight);
rectFill(d, ops, x, y + 1, 1, h - 1, style->highlight);
rectFill(d, ops, x, y + h - 1, w, 1, style->shadow);
rectFill(d, ops, x + w - 1, y + 1, 1, h - 2, style->shadow);
} else {
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + i, y + i, w - i * 2, 1, style->highlight);
}
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + i, y + i + 1, 1, h - i * 2 - 1, style->highlight);
}
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + i, y + h - 1 - i, w - i * 2, 1, style->shadow);
}
for (int32_t i = 0; i < bw; i++) {
rectFill(d, ops, x + w - 1 - i, y + i + 1, 1, h - i * 2 - 2, style->shadow);
}
}
}
// ============================================================
// drawChar
// ============================================================
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 cw = font->charWidth;
int32_t chh = font->charHeight;
// Quick reject: entirely outside clip rect
if (__builtin_expect(x + cw <= d->clipX || x >= d->clipX + d->clipW || y + chh <= d->clipY || y >= d->clipY + d->clipH, 0)) {
return cw;
}
int32_t idx = (uint8_t)ch - font->firstChar;
if (__builtin_expect(idx < 0 || idx >= font->numChars, 0)) {
if (opaque) {
rectFill(d, ops, x, y, cw, chh, bg);
}
return cw;
}
const uint8_t *glyph = font->glyphData + idx * chh;
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Calculate clipped row/col bounds once
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
int32_t rowStart = 0;
int32_t rowEnd = chh;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + chh > clipY2) { rowEnd = clipY2 - y; }
int32_t colStart = 0;
int32_t colEnd = cw;
if (x < clipX1) { colStart = clipX1 - x; }
if (x + cw > clipX2) { colEnd = clipX2 - x; }
if (opaque) {
// Opaque mode: fill entire cell with bg, then overwrite fg pixels
// This avoids a branch per pixel for the common case
for (int32_t row = rowStart; row < rowEnd; row++) {
int32_t py = y + row;
uint8_t *dst = d->backBuf + py * pitch + (x + colStart) * bpp;
int32_t span = colEnd - colStart;
// Fill row with background
ops->spanFill(dst, bg, span);
// Overwrite foreground pixels
uint8_t bits = glyph[row];
if (bits == 0) {
continue; // entirely background row — already filled
}
// Shift bits to account for colStart
dst = d->backBuf + py * pitch + x * bpp;
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & (0x80 >> col)) {
*(uint16_t *)(dst + col * 2) = fg16;
}
}
} else if (bpp == 4) {
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & (0x80 >> col)) {
*(uint32_t *)(dst + col * 4) = fg;
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & (0x80 >> col)) {
dst[col] = fg8;
}
}
}
}
} else {
// Transparent mode: only write foreground pixels
for (int32_t row = rowStart; row < rowEnd; row++) {
uint8_t bits = glyph[row];
if (bits == 0) {
continue;
}
int32_t py = y + row;
uint8_t *dst = d->backBuf + py * pitch + x * bpp;
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & (0x80 >> col)) {
*(uint16_t *)(dst + col * 2) = fg16;
}
}
} else if (bpp == 4) {
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & (0x80 >> col)) {
*(uint32_t *)(dst + col * 4) = fg;
}
}
} else {
uint8_t fg8 = (uint8_t)fg;
for (int32_t col = colStart; col < colEnd; col++) {
if (bits & (0x80 >> col)) {
dst[col] = fg8;
}
}
}
}
}
return cw;
}
// ============================================================
// drawHLine
// ============================================================
void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, uint32_t color) {
rectFill(d, ops, x, y, w, 1, color);
}
// ============================================================
// drawInit
// ============================================================
void drawInit(BlitOpsT *ops, const DisplayT *d) {
ops->bytesPerPixel = d->format.bytesPerPixel;
ops->pitch = d->pitch;
switch (d->format.bytesPerPixel) {
case 1:
ops->spanFill = spanFill8;
ops->spanCopy = spanCopy8;
break;
case 2:
ops->spanFill = spanFill16;
ops->spanCopy = spanCopy16;
break;
case 4:
ops->spanFill = spanFill32;
ops->spanCopy = spanCopy32;
break;
default:
ops->spanFill = spanFill8;
ops->spanCopy = spanCopy8;
break;
}
}
// ============================================================
// drawMaskedBitmap
// ============================================================
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) {
int32_t bpp = ops->bytesPerPixel;
int32_t pitch = d->pitch;
// Pre-clip row/col bounds
int32_t clipX1 = d->clipX;
int32_t clipX2 = d->clipX + d->clipW;
int32_t clipY1 = d->clipY;
int32_t clipY2 = d->clipY + d->clipH;
int32_t rowStart = 0;
int32_t rowEnd = h;
if (y < clipY1) { rowStart = clipY1 - y; }
if (y + h > clipY2) { rowEnd = clipY2 - y; }
int32_t colStart = 0;
int32_t colEnd = w;
if (x < clipX1) { colStart = clipX1 - x; }
if (x + w > clipX2) { colEnd = clipX2 - x; }
if (colStart >= colEnd || rowStart >= rowEnd) {
return;
}
for (int32_t row = rowStart; row < rowEnd; row++) {
uint16_t mask = andMask[row];
uint16_t data = xorData[row];
// Skip fully transparent rows
uint16_t colMask = 0;
for (int32_t col = colStart; col < colEnd; col++) {
colMask |= (0x8000 >> col);
}
if ((mask & colMask) == colMask) {
continue; // all visible columns are transparent
}
int32_t py = y + row;
uint8_t *dst = d->backBuf + py * pitch + x * bpp;
if (bpp == 2) {
uint16_t fg16 = (uint16_t)fgColor;
uint16_t bg16 = (uint16_t)bgColor;
for (int32_t col = colStart; col < colEnd; col++) {
uint16_t bit = 0x8000 >> col;
if (!(mask & bit)) {
*(uint16_t *)(dst + col * 2) = (data & bit) ? fg16 : bg16;
}
}
} else if (bpp == 4) {
for (int32_t col = colStart; col < colEnd; col++) {
uint16_t bit = 0x8000 >> col;
if (!(mask & bit)) {
*(uint32_t *)(dst + col * 4) = (data & bit) ? fgColor : bgColor;
}
}
} else {
uint8_t fg8 = (uint8_t)fgColor;
uint8_t bg8 = (uint8_t)bgColor;
for (int32_t col = colStart; col < colEnd; col++) {
uint16_t bit = 0x8000 >> col;
if (!(mask & bit)) {
dst[col] = (data & bit) ? fg8 : bg8;
}
}
}
}
}
// ============================================================
// drawText
// ============================================================
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 cw = font->charWidth;
int32_t clipX2 = d->clipX + d->clipW;
while (*text) {
// Early out if we've moved past the right clip edge
if (__builtin_expect(x >= clipX2, 0)) {
break;
}
// Skip characters entirely to the left of clip
if (__builtin_expect(x + cw <= d->clipX, 0)) {
x += cw;
text++;
continue;
}
x += drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
text++;
}
}
// ============================================================
// drawVLine
// ============================================================
void drawVLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t h, uint32_t color) {
(void)ops;
// Inline single-pixel-wide fill to avoid rectFill overhead for narrow lines
if (__builtin_expect(x < d->clipX || x >= d->clipX + d->clipW, 0)) {
return;
}
int32_t y1 = y;
int32_t y2 = y + h;
if (y1 < d->clipY) { y1 = d->clipY; }
if (y2 > d->clipY + d->clipH) { y2 = d->clipY + d->clipH; }
if (y1 >= y2) { return; }
int32_t bpp = d->format.bytesPerPixel;
uint8_t *dst = d->backBuf + y1 * d->pitch + x * bpp;
int32_t pitch = d->pitch;
if (bpp == 2) {
uint16_t c16 = (uint16_t)color;
for (int32_t i = y1; i < y2; i++) {
*(uint16_t *)dst = c16;
dst += pitch;
}
} else if (bpp == 4) {
for (int32_t i = y1; i < y2; i++) {
*(uint32_t *)dst = color;
dst += pitch;
}
} else {
uint8_t c8 = (uint8_t)color;
for (int32_t i = y1; i < y2; i++) {
*dst = c8;
dst += pitch;
}
}
}
// ============================================================
// putPixel
// ============================================================
static inline void putPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)color;
} else if (bpp == 4) {
*(uint32_t *)dst = color;
} else {
*dst = (uint8_t)color;
}
}
// ============================================================
// rectCopy
// ============================================================
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) {
int32_t bpp = ops->bytesPerPixel;
// Clip to display clip rect
int32_t origDstX = dstX;
int32_t origDstY = dstY;
clipRect(d, &dstX, &dstY, &w, &h);
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
// Adjust source position by the amount we clipped
srcX += dstX - origDstX;
srcY += dstY - origDstY;
const uint8_t *srcRow = srcBuf + srcY * srcPitch + srcX * bpp;
uint8_t *dstRow = d->backBuf + dstY * d->pitch + dstX * bpp;
int32_t rowBytes = w * bpp;
int32_t dstPitch = d->pitch;
// For full-width copies aligned to pitch, use memcpy (may optimize to rep movsd)
if (rowBytes == dstPitch && rowBytes == srcPitch) {
memcpy(dstRow, srcRow, rowBytes * h);
} else {
for (int32_t i = 0; i < h; i++) {
memcpy(dstRow, srcRow, rowBytes);
srcRow += srcPitch;
dstRow += dstPitch;
}
}
}
// ============================================================
// rectFill
// ============================================================
void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color) {
clipRect(d, &x, &y, &w, &h);
if (__builtin_expect(w <= 0 || h <= 0, 0)) {
return;
}
uint8_t *row = d->backBuf + y * d->pitch + x * d->format.bytesPerPixel;
int32_t pitch = d->pitch;
for (int32_t i = 0; i < h; i++) {
ops->spanFill(row, color, w);
row += pitch;
}
}
// ============================================================
// spanCopy8
// ============================================================
static void spanCopy8(uint8_t *dst, const uint8_t *src, int32_t count) {
// Align to 4 bytes
while (((uintptr_t)dst & 3) && count > 0) {
*dst++ = *src++;
count--;
}
if (count >= 4) {
int32_t dwordCount = count >> 2;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(dwordCount)
:
: "memory"
);
dst += dwordCount * 4;
src += dwordCount * 4;
}
int32_t rem = count & 3;
while (rem-- > 0) {
*dst++ = *src++;
}
}
// ============================================================
// spanCopy16
// ============================================================
static void spanCopy16(uint8_t *dst, const uint8_t *src, int32_t count) {
// Handle odd leading pixel for dword alignment
if (((uintptr_t)dst & 2) && count > 0) {
*(uint16_t *)dst = *(const uint16_t *)src;
dst += 2;
src += 2;
count--;
}
if (count >= 2) {
int32_t dwordCount = count >> 1;
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(dwordCount)
:
: "memory"
);
dst += dwordCount * 4;
src += dwordCount * 4;
}
if (count & 1) {
*(uint16_t *)dst = *(const uint16_t *)src;
}
}
// ============================================================
// spanCopy32
// ============================================================
static void spanCopy32(uint8_t *dst, const uint8_t *src, int32_t count) {
__asm__ __volatile__ (
"rep movsl"
: "+D"(dst), "+S"(src), "+c"(count)
:
: "memory"
);
}
// ============================================================
// spanFill8
// ============================================================
static void spanFill8(uint8_t *dst, uint32_t color, int32_t count) {
uint8_t c = (uint8_t)color;
uint32_t dword = (uint32_t)c | ((uint32_t)c << 8) | ((uint32_t)c << 16) | ((uint32_t)c << 24);
// Align to 4 bytes
while (((uintptr_t)dst & 3) && count > 0) {
*dst++ = c;
count--;
}
if (count >= 4) {
int32_t dwordCount = count >> 2;
__asm__ __volatile__ (
"rep stosl"
: "+D"(dst), "+c"(dwordCount)
: "a"(dword)
: "memory"
);
dst += dwordCount * 4;
}
int32_t rem = count & 3;
while (rem-- > 0) {
*dst++ = c;
}
}
// ============================================================
// spanFill16
// ============================================================
static void spanFill16(uint8_t *dst, uint32_t color, int32_t count) {
uint16_t c = (uint16_t)color;
// Handle odd leading pixel for dword alignment
if (((uintptr_t)dst & 2) && count > 0) {
*(uint16_t *)dst = c;
dst += 2;
count--;
}
// Fill pairs of pixels as 32-bit dwords
if (count >= 2) {
uint32_t dword = ((uint32_t)c << 16) | c;
int32_t dwordCount = count >> 1;
__asm__ __volatile__ (
"rep stosl"
: "+D"(dst), "+c"(dwordCount)
: "a"(dword)
: "memory"
);
dst += dwordCount * 4;
}
// Handle trailing odd pixel
if (count & 1) {
*(uint16_t *)dst = c;
}
}
// ============================================================
// spanFill32
// ============================================================
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count) {
__asm__ __volatile__ (
"rep stosl"
: "+D"(dst), "+c"(count)
: "a"(color)
: "memory"
);
}
// ============================================================
// textWidth
// ============================================================
int32_t textWidth(const BitmapFontT *font, const char *text) {
int32_t w = 0;
while (*text) {
w += font->charWidth;
text++;
}
return w;
}

46
dvx/dvxDraw.h Normal file
View file

@ -0,0 +1,46 @@
// dvx_draw.h — Layer 2: Drawing primitives for DV/X GUI
#ifndef DVX_DRAW_H
#define DVX_DRAW_H
#include "dvxTypes.h"
// Initialize blit operations based on pixel format
void drawInit(BlitOpsT *ops, const DisplayT *d);
// Solid color rectangle fill (clips to display clip rect)
void rectFill(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h, uint32_t color);
// Copy rectangle from source buffer to backbuffer (clips to display clip rect)
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);
// Draw a beveled frame
void drawBevel(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, int32_t h,
const BevelStyleT *style);
// Draw a single character, returns advance width
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);
// Draw a null-terminated string
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);
// Measure text width in pixels
int32_t textWidth(const BitmapFontT *font, const char *text);
// Draw a 1-bit bitmap with mask (for cursors, icons)
// andMask/xorData are arrays of uint16_t, one per row
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);
// Horizontal line
void drawHLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t w, uint32_t color);
// Vertical line
void drawVLine(DisplayT *d, const BlitOpsT *ops, int32_t x, int32_t y, int32_t h, uint32_t color);
#endif // DVX_DRAW_H

1071
dvx/dvxFont.h Normal file

File diff suppressed because it is too large Load diff

13
dvx/dvxIcon.c Normal file
View file

@ -0,0 +1,13 @@
// dvxIcon.c — stb_image implementation for DV/X GUI
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"
#define STBI_ONLY_BMP
#define STBI_ONLY_TGA
#define STBI_ONLY_PNG
#define STBI_NO_SIMD
#define STB_IMAGE_IMPLEMENTATION
#include "thirdparty/stb_image.h"
#pragma GCC diagnostic pop

135
dvx/dvxPalette.h Normal file
View file

@ -0,0 +1,135 @@
// dvx_palette.h — 8-bit mode palette definition for DV/X GUI
#ifndef DVX_PALETTE_H
#define DVX_PALETTE_H
#include <stdint.h>
// 256-entry palette for 8-bit mode:
// 0-215: 6x6x6 color cube (index = r*36 + g*6 + b)
// 216-231: 16-step grey ramp
// 232-239: UI chrome colors
// 240-255: reserved
// Chrome color indices for 8-bit mode
#define PAL_CHROME_HIGHLIGHT 232
#define PAL_CHROME_SHADOW 233
#define PAL_CHROME_ACTIVE_BG 234
#define PAL_CHROME_INACTIVE_BG 235
#define PAL_CHROME_DESKTOP 236
#define PAL_CHROME_SELECTION 237
#define PAL_CHROME_TEXT 238
#define PAL_CHROME_WHITE 239
// Generate the default 8-bit palette into a 768-byte buffer (256 * 3, RGB)
static inline void dvxGeneratePalette(uint8_t *pal)
{
int32_t idx = 0;
// Entries 0-215: 6x6x6 color cube
for (int32_t r = 0; r < 6; r++) {
for (int32_t g = 0; g < 6; g++) {
for (int32_t b = 0; b < 6; b++) {
pal[idx * 3 + 0] = (uint8_t)(r * 51);
pal[idx * 3 + 1] = (uint8_t)(g * 51);
pal[idx * 3 + 2] = (uint8_t)(b * 51);
idx++;
}
}
}
// Entries 216-231: 16-step grey ramp
for (int32_t i = 0; i < 16; i++) {
uint8_t v = (uint8_t)(i * 17);
pal[idx * 3 + 0] = v;
pal[idx * 3 + 1] = v;
pal[idx * 3 + 2] = v;
idx++;
}
// Entries 232-239: UI chrome colors
// 232: highlight (white)
pal[232 * 3 + 0] = 255;
pal[232 * 3 + 1] = 255;
pal[232 * 3 + 2] = 255;
// 233: shadow (dark grey)
pal[233 * 3 + 0] = 80;
pal[233 * 3 + 1] = 80;
pal[233 * 3 + 2] = 80;
// 234: active title bg (navy blue)
pal[234 * 3 + 0] = 0;
pal[234 * 3 + 1] = 0;
pal[234 * 3 + 2] = 128;
// 235: inactive title bg (grey)
pal[235 * 3 + 0] = 128;
pal[235 * 3 + 1] = 128;
pal[235 * 3 + 2] = 128;
// 236: desktop (steel blue)
pal[236 * 3 + 0] = 51;
pal[236 * 3 + 1] = 102;
pal[236 * 3 + 2] = 153;
// 237: selection (navy)
pal[237 * 3 + 0] = 0;
pal[237 * 3 + 1] = 0;
pal[237 * 3 + 2] = 128;
// 238: text (black)
pal[238 * 3 + 0] = 0;
pal[238 * 3 + 1] = 0;
pal[238 * 3 + 2] = 0;
// 239: bright white
pal[239 * 3 + 0] = 255;
pal[239 * 3 + 1] = 255;
pal[239 * 3 + 2] = 255;
// Entries 240-255: reserved (black)
for (int32_t i = 240; i < 256; i++) {
pal[i * 3 + 0] = 0;
pal[i * 3 + 1] = 0;
pal[i * 3 + 2] = 0;
}
}
// Find nearest palette entry for an RGB color (for 8-bit mode)
static inline uint8_t dvxNearestPalEntry(const uint8_t *pal, uint8_t r, uint8_t g, uint8_t b)
{
// Try the color cube first — fast path
int32_t ri = (r + 25) / 51;
int32_t gi = (g + 25) / 51;
int32_t bi = (b + 25) / 51;
if (ri > 5) { ri = 5; }
if (gi > 5) { gi = 5; }
if (bi > 5) { bi = 5; }
uint8_t cubeIdx = (uint8_t)(ri * 36 + gi * 6 + bi);
int32_t cubeR = ri * 51;
int32_t cubeG = gi * 51;
int32_t cubeB = bi * 51;
int32_t cubeDist = (r - cubeR) * (r - cubeR) + (g - cubeG) * (g - cubeG) + (b - cubeB) * (b - cubeB);
// Check grey ramp and chrome colors for a closer match
int32_t bestDist = cubeDist;
uint8_t bestIdx = cubeIdx;
for (int32_t i = 216; i < 240; i++) {
int32_t dr = (int32_t)r - (int32_t)pal[i * 3 + 0];
int32_t dg = (int32_t)g - (int32_t)pal[i * 3 + 1];
int32_t db = (int32_t)b - (int32_t)pal[i * 3 + 2];
int32_t dist = dr * dr + dg * dg + db * db;
if (dist < bestDist) {
bestDist = dist;
bestIdx = (uint8_t)i;
}
}
return bestIdx;
}
#endif // DVX_PALETTE_H

321
dvx/dvxTypes.h Normal file
View file

@ -0,0 +1,321 @@
// dvx_types.h — Shared type definitions for DV/X GUI
#ifndef DVX_TYPES_H
#define DVX_TYPES_H
#include <stdint.h>
#include <stdbool.h>
// ============================================================
// Pixel format descriptor
// ============================================================
typedef struct {
int32_t bitsPerPixel; // 8, 15, 16, or 32
int32_t bytesPerPixel; // 1, 2, 2, or 4
uint32_t redMask; // e.g. 0xF800 for 565
uint32_t greenMask;
uint32_t blueMask;
int32_t redShift; // bit position of red field
int32_t greenShift;
int32_t blueShift;
int32_t redBits; // number of red bits (5, 6, 8...)
int32_t greenBits;
int32_t blueBits;
} PixelFormatT;
// ============================================================
// Display context
// ============================================================
typedef struct {
int32_t width;
int32_t height;
int32_t pitch; // bytes per scanline
PixelFormatT format;
uint8_t *lfb; // mapped linear framebuffer
uint8_t *backBuf; // system RAM backbuffer
uint8_t *palette; // 768 bytes for 8-bit mode, NULL otherwise
int32_t clipX; // current clip rectangle
int32_t clipY;
int32_t clipW;
int32_t clipH;
} DisplayT;
// ============================================================
// Rectangle
// ============================================================
typedef struct {
int32_t x;
int32_t y;
int32_t w;
int32_t h;
} RectT;
// ============================================================
// Span operation function pointers
// ============================================================
typedef void (*SpanFillFnT)(uint8_t *dst, uint32_t color, int32_t count);
typedef void (*SpanCopyFnT)(uint8_t *dst, const uint8_t *src, int32_t count);
typedef struct {
SpanFillFnT spanFill;
SpanCopyFnT spanCopy;
int32_t bytesPerPixel;
int32_t pitch;
} BlitOpsT;
// ============================================================
// Bevel style
// ============================================================
typedef struct {
uint32_t highlight; // lighter color (top/left edges)
uint32_t shadow; // darker color (bottom/right edges)
uint32_t face; // fill color for interior (0 = no fill)
int32_t width; // border thickness in pixels (typically 2)
} BevelStyleT;
// ============================================================
// Bitmap font
// ============================================================
typedef struct {
int32_t charWidth; // fixed width per glyph (e.g. 8)
int32_t charHeight; // e.g. 14 or 16
int32_t firstChar; // ASCII code of first glyph (typically 0)
int32_t numChars; // number of glyphs (typically 256)
const uint8_t *glyphData; // packed 1bpp, charHeight bytes per glyph
} BitmapFontT;
// ============================================================
// Color scheme
// ============================================================
typedef struct {
uint32_t desktop;
uint32_t windowFace;
uint32_t windowHighlight;
uint32_t windowShadow;
uint32_t activeTitleBg;
uint32_t activeTitleFg;
uint32_t inactiveTitleBg;
uint32_t inactiveTitleFg;
uint32_t contentBg;
uint32_t contentFg;
uint32_t menuBg;
uint32_t menuFg;
uint32_t menuHighlightBg;
uint32_t menuHighlightFg;
uint32_t buttonFace;
uint32_t scrollbarBg;
uint32_t scrollbarFg;
uint32_t scrollbarTrough;
} ColorSchemeT;
// ============================================================
// Dirty rectangle list
// ============================================================
#define MAX_DIRTY_RECTS 128
typedef struct {
RectT rects[MAX_DIRTY_RECTS];
int32_t count;
} DirtyListT;
// ============================================================
// Window chrome constants
// ============================================================
#define CHROME_BORDER_WIDTH 4
#define CHROME_TITLE_HEIGHT 20
#define CHROME_TITLE_PAD 4
#define CHROME_INNER_BORDER 2
#define CHROME_MENU_HEIGHT 20
#define CHROME_TOTAL_TOP (CHROME_BORDER_WIDTH + CHROME_TITLE_HEIGHT + CHROME_INNER_BORDER)
#define CHROME_TOTAL_SIDE (CHROME_BORDER_WIDTH + CHROME_INNER_BORDER)
#define CHROME_TOTAL_BOTTOM (CHROME_BORDER_WIDTH + CHROME_INNER_BORDER)
#define CHROME_CLOSE_BTN_SIZE 16
// ============================================================
// Menu types
// ============================================================
#define MAX_MENU_ITEMS 16
#define MAX_MENUS 8
#define MAX_MENU_LABEL 32
typedef struct {
char label[MAX_MENU_LABEL];
int32_t id; // application-defined command ID
bool separator; // true = this is a separator line, not a clickable item
bool enabled;
bool checked;
} MenuItemT;
typedef struct {
char label[MAX_MENU_LABEL]; // menu bar label (e.g. "File")
MenuItemT items[MAX_MENU_ITEMS];
int32_t itemCount;
int32_t barX; // computed position on menu bar
int32_t barW; // computed width on menu bar
} MenuT;
typedef struct {
MenuT menus[MAX_MENUS];
int32_t menuCount;
} MenuBarT;
// ============================================================
// Scrollbar types
// ============================================================
typedef enum {
ScrollbarVerticalE,
ScrollbarHorizontalE
} ScrollbarOrientE;
#define SCROLLBAR_WIDTH 16
typedef struct {
ScrollbarOrientE orient;
int32_t min; // scroll range min
int32_t max; // scroll range max
int32_t value; // current scroll position
int32_t pageSize; // visible portion (for thumb sizing)
int32_t x; // computed position relative to window
int32_t y;
int32_t length; // total length of scrollbar track
} ScrollbarT;
// ============================================================
// Window
// ============================================================
#define MAX_WINDOWS 64
#define MAX_TITLE_LEN 128
#define RESIZE_NONE 0
#define RESIZE_LEFT 1
#define RESIZE_RIGHT 2
#define RESIZE_TOP 4
#define RESIZE_BOTTOM 8
#define MIN_WINDOW_W 80
#define MIN_WINDOW_H 60
#define ICON_SIZE 64
#define ICON_BORDER 2
#define ICON_TOTAL_SIZE (ICON_SIZE + ICON_BORDER * 2)
#define ICON_SPACING 2
typedef struct WindowT {
int32_t id;
int32_t x;
int32_t y;
int32_t w;
int32_t h;
int32_t contentX; // offset from frame to content area
int32_t contentY;
int32_t contentW;
int32_t contentH;
char title[MAX_TITLE_LEN];
bool visible;
bool focused;
bool minimized;
bool maximized;
bool resizable;
int32_t maxW; // maximum width (-1 = screen width)
int32_t maxH; // maximum height (-1 = screen height)
int32_t preMaxX; // saved position before maximize
int32_t preMaxY;
int32_t preMaxW;
int32_t preMaxH;
// Per-window content backbuffer
uint8_t *contentBuf;
int32_t contentPitch;
// Icon image (display pixel format, NULL if none)
uint8_t *iconData;
int32_t iconW;
int32_t iconH;
int32_t iconPitch;
// Menu bar (NULL if no menus)
MenuBarT *menuBar;
// Scrollbars (NULL if not present)
ScrollbarT *vScroll;
ScrollbarT *hScroll;
// Widget tree root (NULL if no widgets)
struct WidgetT *widgetRoot;
// Callbacks
void *userData;
void (*onPaint)(struct WindowT *win, RectT *dirtyArea);
void (*onKey)(struct WindowT *win, int32_t key, int32_t mod);
void (*onMouse)(struct WindowT *win, int32_t x, int32_t y, int32_t buttons);
void (*onResize)(struct WindowT *win, int32_t newW, int32_t newH);
void (*onClose)(struct WindowT *win);
void (*onMenu)(struct WindowT *win, int32_t menuId);
void (*onScroll)(struct WindowT *win, ScrollbarOrientE orient, int32_t value);
} WindowT;
// ============================================================
// Window stack
// ============================================================
typedef struct {
WindowT *windows[MAX_WINDOWS];
int32_t count;
int32_t focusedIdx;
int32_t dragWindow;
int32_t dragOffX;
int32_t dragOffY;
int32_t resizeWindow;
int32_t resizeEdge;
int32_t scrollWindow; // window being scroll-dragged (-1 = none)
int32_t scrollOrient; // 0 = vertical, 1 = horizontal
int32_t scrollDragOff; // mouse offset from thumb start
} WindowStackT;
// ============================================================
// Mouse cursor
// ============================================================
typedef struct {
int32_t width;
int32_t height;
int32_t hotX;
int32_t hotY;
const uint16_t *andMask; // 0 = draw cursor pixel, 1 = transparent
const uint16_t *xorData; // 0 = black, 1 = white (where andMask = 0)
} CursorT;
// ============================================================
// Popup state for dropdown menus
// ============================================================
typedef struct {
bool active;
int32_t windowId; // which window owns this popup
int32_t menuIdx; // which menu is open
int32_t popupX; // screen position of popup
int32_t popupY;
int32_t popupW;
int32_t popupH;
int32_t hoverItem; // which item is highlighted (-1 = none)
} PopupStateT;
// ============================================================
// Utility macros
// ============================================================
#define DVX_MIN(a, b) ((a) < (b) ? (a) : (b))
#define DVX_MAX(a, b) ((a) > (b) ? (a) : (b))
#endif // DVX_TYPES_H

445
dvx/dvxVideo.c Normal file
View file

@ -0,0 +1,445 @@
// dvx_video.c — Layer 1: VESA VBE video backend for DV/X GUI
#include "dvxVideo.h"
#include "dvxPalette.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dpmi.h>
#include <go32.h>
#include <pc.h>
#include <sys/nearptr.h>
#include <sys/farptr.h>
// ============================================================
// Prototypes
// ============================================================
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp, uint16_t *outMode, DisplayT *d);
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
static int32_t setVesaMode(uint16_t mode);
// ============================================================
// findBestMode
// ============================================================
static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t preferredBpp,
uint16_t *outMode, DisplayT *d) {
__dpmi_regs r;
uint16_t bestMode = 0;
int32_t bestScore = -1;
DisplayT bestDisplay;
memset(&bestDisplay, 0, sizeof(bestDisplay));
// Get VBE controller info
// Put "VBE2" signature at ES:DI in conventional memory
uint32_t infoSeg = __tb >> 4;
uint32_t infoOff = __tb & 0x0F;
// Write "VBE2" at transfer buffer to request VBE 2.0 info
_farpokeb(_dos_ds, __tb + 0, 'V');
_farpokeb(_dos_ds, __tb + 1, 'B');
_farpokeb(_dos_ds, __tb + 2, 'E');
_farpokeb(_dos_ds, __tb + 3, '2');
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F00;
r.x.es = infoSeg;
r.x.di = infoOff;
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
fprintf(stderr, "VBE: Function 0x4F00 failed (AX=0x%04X)\n", r.x.ax);
return -1;
}
// Verify VBE signature
char sig[5];
for (int32_t i = 0; i < 4; i++) {
sig[i] = _farpeekb(_dos_ds, __tb + i);
}
sig[4] = '\0';
if (strcmp(sig, "VESA") != 0) {
fprintf(stderr, "VBE: Bad signature '%s'\n", sig);
return -1;
}
// Check VBE version (need 2.0+)
uint16_t vbeVersion = _farpeekw(_dos_ds, __tb + 4);
if (vbeVersion < 0x0200) {
fprintf(stderr, "VBE: Version %d.%d too old (need 2.0+)\n",
vbeVersion >> 8, vbeVersion & 0xFF);
return -1;
}
// Get mode list pointer (far pointer at offset 14)
uint16_t modeListOff = _farpeekw(_dos_ds, __tb + 14);
uint16_t modeListSeg = _farpeekw(_dos_ds, __tb + 16);
uint32_t modeListAddr = ((uint32_t)modeListSeg << 4) + modeListOff;
// Walk mode list
for (int32_t i = 0; i < 256; i++) {
uint16_t mode = _farpeekw(_dos_ds, modeListAddr + i * 2);
if (mode == 0xFFFF) {
break;
}
DisplayT candidate;
int32_t score = 0;
memset(&candidate, 0, sizeof(candidate));
getModeInfo(mode, &candidate, &score, requestedW, requestedH, preferredBpp);
if (score > bestScore) {
bestScore = score;
bestMode = mode;
bestDisplay = candidate;
}
}
if (bestScore < 0) {
fprintf(stderr, "VBE: No suitable mode found for %ldx%ld\n", (long)requestedW, (long)requestedH);
return -1;
}
*outMode = bestMode;
*d = bestDisplay;
return 0;
}
// ============================================================
// getModeInfo
// ============================================================
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score,
int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
__dpmi_regs r;
*score = -1;
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F01;
r.x.cx = mode;
r.x.es = __tb >> 4;
r.x.di = __tb & 0x0F;
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
return;
}
// Read mode attributes
uint16_t attr = _farpeekw(_dos_ds, __tb + 0);
// Must have LFB support (bit 7)
if (!(attr & 0x0080)) {
return;
}
// Must be a graphics mode (bit 4)
if (!(attr & 0x0010)) {
return;
}
int32_t w = _farpeekw(_dos_ds, __tb + 18);
int32_t h = _farpeekw(_dos_ds, __tb + 20);
int32_t bpp = _farpeekb(_dos_ds, __tb + 25);
int32_t pitch = _farpeekw(_dos_ds, __tb + 16);
uint32_t physAddr = _farpeekl(_dos_ds, __tb + 40);
// Must match or exceed requested resolution
if (w < requestedW || h < requestedH) {
return;
}
// Must be a supported bpp
if (bpp != 8 && bpp != 15 && bpp != 16 && bpp != 32) {
// Some cards report 24-bit; skip those
if (bpp == 24) {
return;
}
return;
}
// Score this mode
int32_t s = 0;
if (bpp == 16) {
s = 100;
} else if (bpp == 15) {
s = 90;
} else if (bpp == 32) {
s = 85;
} else if (bpp == 8) {
s = 70;
}
// Prefer the user's preferred bpp
if (bpp == preferredBpp) {
s += 20;
}
// Exact resolution match is preferred
if (w == requestedW && h == requestedH) {
s += 10;
} else {
s -= 10;
}
*score = s;
// Fill in display info
d->width = w;
d->height = h;
d->pitch = pitch;
d->format.bitsPerPixel = bpp;
d->format.bytesPerPixel = (bpp + 7) / 8;
// Read color masks from mode info
if (bpp >= 15) {
int32_t redSize = _farpeekb(_dos_ds, __tb + 31);
int32_t redPos = _farpeekb(_dos_ds, __tb + 32);
int32_t greenSize = _farpeekb(_dos_ds, __tb + 33);
int32_t greenPos = _farpeekb(_dos_ds, __tb + 34);
int32_t blueSize = _farpeekb(_dos_ds, __tb + 35);
int32_t bluePos = _farpeekb(_dos_ds, __tb + 36);
d->format.redBits = redSize;
d->format.redShift = redPos;
d->format.redMask = ((1U << redSize) - 1) << redPos;
d->format.greenBits = greenSize;
d->format.greenShift = greenPos;
d->format.greenMask = ((1U << greenSize) - 1) << greenPos;
d->format.blueBits = blueSize;
d->format.blueShift = bluePos;
d->format.blueMask = ((1U << blueSize) - 1) << bluePos;
} else {
// 8-bit mode — masks not used, packColor returns palette index
d->format.redBits = 0;
d->format.redShift = 0;
d->format.redMask = 0;
d->format.greenBits = 0;
d->format.greenShift = 0;
d->format.greenMask = 0;
d->format.blueBits = 0;
d->format.blueShift = 0;
d->format.blueMask = 0;
}
// Store physical address in lfb field temporarily (will be remapped)
d->lfb = (uint8_t *)(uintptr_t)physAddr;
}
// ============================================================
// mapLfb
// ============================================================
static int32_t mapLfb(DisplayT *d, uint32_t physAddr) {
__dpmi_meminfo info;
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
info.address = physAddr;
info.size = fbSize;
if (__dpmi_physical_address_mapping(&info) != 0) {
fprintf(stderr, "VBE: Failed to map LFB at 0x%08lX\n", (unsigned long)physAddr);
return -1;
}
// Lock the region to prevent paging
__dpmi_meminfo lockInfo;
lockInfo.address = info.address;
lockInfo.size = fbSize;
__dpmi_lock_linear_region(&lockInfo);
// Enable near pointers for direct access
if (__djgpp_nearptr_enable() == 0) {
fprintf(stderr, "VBE: Failed to enable near pointers\n");
return -1;
}
d->lfb = (uint8_t *)(info.address + __djgpp_conventional_base);
return 0;
}
// ============================================================
// packColor
// ============================================================
uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b) {
if (d->format.bitsPerPixel == 8) {
return dvxNearestPalEntry(d->palette, r, g, b);
}
uint32_t rv = ((uint32_t)r >> (8 - d->format.redBits)) << d->format.redShift;
uint32_t gv = ((uint32_t)g >> (8 - d->format.greenBits)) << d->format.greenShift;
uint32_t bv = ((uint32_t)b >> (8 - d->format.blueBits)) << d->format.blueShift;
return rv | gv | bv;
}
// ============================================================
// resetClipRect
// ============================================================
void resetClipRect(DisplayT *d) {
d->clipX = 0;
d->clipY = 0;
d->clipW = d->width;
d->clipH = d->height;
}
// ============================================================
// setClipRect
// ============================================================
void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h) {
d->clipX = x;
d->clipY = y;
d->clipW = w;
d->clipH = h;
}
// ============================================================
// setVesaMode
// ============================================================
static int32_t setVesaMode(uint16_t mode) {
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x4F02;
r.x.bx = mode | 0x4000; // bit 14 = use LFB
__dpmi_int(0x10, &r);
if (r.x.ax != 0x004F) {
fprintf(stderr, "VBE: Failed to set mode 0x%04X (AX=0x%04X)\n", mode, r.x.ax);
return -1;
}
return 0;
}
// ============================================================
// videoInit
// ============================================================
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
uint16_t bestMode;
uint32_t physAddr;
memset(d, 0, sizeof(*d));
// Find the best VESA mode
if (findBestMode(requestedW, requestedH, preferredBpp, &bestMode, d) != 0) {
return -1;
}
// Save the physical address before we overwrite it
physAddr = (uint32_t)(uintptr_t)d->lfb;
// Set the mode
if (setVesaMode(bestMode) != 0) {
return -1;
}
// Map the LFB
if (mapLfb(d, physAddr) != 0) {
return -1;
}
// Allocate backbuffer
uint32_t fbSize = (uint32_t)d->pitch * (uint32_t)d->height;
d->backBuf = (uint8_t *)malloc(fbSize);
if (!d->backBuf) {
fprintf(stderr, "VBE: Failed to allocate %lu byte backbuffer\n", (unsigned long)fbSize);
return -1;
}
memset(d->backBuf, 0, fbSize);
// Set up palette for 8-bit mode
if (d->format.bitsPerPixel == 8) {
d->palette = (uint8_t *)malloc(768);
if (!d->palette) {
fprintf(stderr, "VBE: Failed to allocate palette\n");
free(d->backBuf);
d->backBuf = NULL;
return -1;
}
dvxGeneratePalette(d->palette);
videoSetPalette(d->palette, 0, 256);
}
// Initialize clip rect to full display
resetClipRect(d);
fprintf(stderr, "VBE: Mode 0x%04X set: %ldx%ldx%ld, pitch=%ld\n",
bestMode, (long)d->width, (long)d->height, (long)d->format.bitsPerPixel, (long)d->pitch);
return 0;
}
// ============================================================
// videoSetPalette
// ============================================================
void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count) {
// Set VGA DAC registers directly via port I/O
// Port 0x3C8 = write index, Port 0x3C9 = data (R, G, B in 6-bit values)
outportb(0x3C8, (uint8_t)firstEntry);
for (int32_t i = 0; i < count; i++) {
int32_t idx = (firstEntry + i) * 3;
outportb(0x3C9, pal[idx + 0] >> 2); // VGA DAC uses 6-bit values
outportb(0x3C9, pal[idx + 1] >> 2);
outportb(0x3C9, pal[idx + 2] >> 2);
}
}
// ============================================================
// videoShutdown
// ============================================================
void videoShutdown(DisplayT *d) {
// Restore text mode (mode 3)
__dpmi_regs r;
memset(&r, 0, sizeof(r));
r.x.ax = 0x0003;
__dpmi_int(0x10, &r);
if (d->backBuf) {
free(d->backBuf);
d->backBuf = NULL;
}
if (d->palette) {
free(d->palette);
d->palette = NULL;
}
d->lfb = NULL;
__djgpp_nearptr_disable();
}

28
dvx/dvxVideo.h Normal file
View file

@ -0,0 +1,28 @@
// dvx_video.h — Layer 1: VESA VBE video backend for DV/X GUI
#ifndef DVX_VIDEO_H
#define DVX_VIDEO_H
#include "dvxTypes.h"
// Initialize VESA video mode and map LFB
// Returns 0 on success, -1 on failure (error message printed to stderr)
int32_t videoInit(DisplayT *d, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
// Shut down video — restore text mode, unmap LFB, free backbuffer
void videoShutdown(DisplayT *d);
// Pack an RGB color into the display's pixel format
// For 15/16/32-bit: returns packed pixel value
// For 8-bit: returns nearest palette index
uint32_t packColor(const DisplayT *d, uint8_t r, uint8_t g, uint8_t b);
// Set the clip rectangle on the display
void setClipRect(DisplayT *d, int32_t x, int32_t y, int32_t w, int32_t h);
// Reset clip rectangle to full display
void resetClipRect(DisplayT *d);
// Set VGA DAC palette (8-bit mode only)
void videoSetPalette(const uint8_t *pal, int32_t firstEntry, int32_t count);
#endif // DVX_VIDEO_H

1634
dvx/dvxWidget.c Normal file

File diff suppressed because it is too large Load diff

273
dvx/dvxWidget.h Normal file
View file

@ -0,0 +1,273 @@
// dvxWidget.h — Widget system for DV/X GUI
#ifndef DVX_WIDGET_H
#define DVX_WIDGET_H
#include "dvxTypes.h"
// Forward declaration
struct AppContextT;
// ============================================================
// Size specifications
// ============================================================
//
// Tagged size values encode both a unit type and a numeric value.
// Use wgtPixels(), wgtChars(), or wgtPercent() to create them.
// A raw 0 means "auto" (use the widget's natural size).
#define WGT_SIZE_TYPE_MASK 0xC0000000
#define WGT_SIZE_VAL_MASK 0x3FFFFFFF
#define WGT_SIZE_PIXELS 0x00000000
#define WGT_SIZE_CHARS 0x40000000
#define WGT_SIZE_PERCENT 0x80000000
static inline int32_t wgtPixels(int32_t v) {
return (int32_t)(WGT_SIZE_PIXELS | ((uint32_t)v & WGT_SIZE_VAL_MASK));
}
static inline int32_t wgtChars(int32_t v) {
return (int32_t)(WGT_SIZE_CHARS | ((uint32_t)v & WGT_SIZE_VAL_MASK));
}
static inline int32_t wgtPercent(int32_t v) {
return (int32_t)(WGT_SIZE_PERCENT | ((uint32_t)v & WGT_SIZE_VAL_MASK));
}
// ============================================================
// Widget type enum
// ============================================================
typedef enum {
WidgetVBoxE,
WidgetHBoxE,
WidgetLabelE,
WidgetButtonE,
WidgetCheckboxE,
WidgetRadioGroupE,
WidgetRadioE,
WidgetTextInputE,
WidgetTextAreaE,
WidgetListBoxE,
WidgetSpacerE,
WidgetSeparatorE,
WidgetFrameE
} WidgetTypeE;
// ============================================================
// Alignment enum
// ============================================================
//
// Controls main-axis alignment of children within a container.
// HBox: AlignStartE=left, AlignCenterE=center, AlignEndE=right
// VBox: AlignStartE=top, AlignCenterE=center, AlignEndE=bottom
typedef enum {
AlignStartE,
AlignCenterE,
AlignEndE
} WidgetAlignE;
// ============================================================
// Widget structure
// ============================================================
#define MAX_WIDGET_NAME 32
typedef struct WidgetT {
WidgetTypeE type;
char name[MAX_WIDGET_NAME];
// Tree linkage
struct WidgetT *parent;
struct WidgetT *firstChild;
struct WidgetT *lastChild;
struct WidgetT *nextSibling;
WindowT *window;
// Computed geometry (relative to window content area)
int32_t x;
int32_t y;
int32_t w;
int32_t h;
// Computed minimum size (set by layout engine)
int32_t calcMinW;
int32_t calcMinH;
// Size hints (tagged: wgtPixels/wgtChars/wgtPercent, 0 = auto)
int32_t minW;
int32_t minH;
int32_t maxW; // 0 = no limit
int32_t maxH;
int32_t prefW; // preferred size, 0 = auto
int32_t prefH;
int32_t weight; // extra-space distribution (0 = fixed, 100 = normal)
// Container properties
WidgetAlignE align; // main-axis alignment for children
int32_t spacing; // tagged size for spacing between children (0 = default)
int32_t padding; // tagged size for internal padding (0 = default)
// State
bool visible;
bool enabled;
bool focused;
// User data and callbacks
void *userData;
void (*onClick)(struct WidgetT *w);
void (*onChange)(struct WidgetT *w);
// Type-specific data
union {
struct {
const char *text;
} label;
struct {
const char *text;
bool pressed;
} button;
struct {
const char *text;
bool checked;
} checkbox;
struct {
int32_t selectedIdx;
} radioGroup;
struct {
const char *text;
int32_t index;
} radio;
struct {
char *buf;
int32_t bufSize;
int32_t len;
int32_t cursorPos;
int32_t scrollOff;
} textInput;
struct {
char *buf;
int32_t bufSize;
int32_t len;
int32_t cursorRow;
int32_t cursorCol;
int32_t scrollRow;
} textArea;
struct {
const char **items;
int32_t itemCount;
int32_t selectedIdx;
int32_t scrollPos;
} listBox;
struct {
bool vertical;
} separator;
struct {
const char *title;
} frame;
} as;
} WidgetT;
// ============================================================
// Window integration
// ============================================================
// Set up a window for widgets. Returns the root container (VBox).
// Automatically installs onPaint, onMouse, onKey, and onResize handlers.
WidgetT *wgtInitWindow(struct AppContextT *ctx, WindowT *win);
// ============================================================
// Container creation
// ============================================================
WidgetT *wgtVBox(WidgetT *parent);
WidgetT *wgtHBox(WidgetT *parent);
WidgetT *wgtFrame(WidgetT *parent, const char *title);
// ============================================================
// Basic widgets
// ============================================================
WidgetT *wgtLabel(WidgetT *parent, const char *text);
WidgetT *wgtButton(WidgetT *parent, const char *text);
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
// ============================================================
// Radio buttons
// ============================================================
WidgetT *wgtRadioGroup(WidgetT *parent);
WidgetT *wgtRadio(WidgetT *parent, const char *text);
// ============================================================
// Spacing and visual dividers
// ============================================================
WidgetT *wgtSpacer(WidgetT *parent);
WidgetT *wgtHSeparator(WidgetT *parent);
WidgetT *wgtVSeparator(WidgetT *parent);
// ============================================================
// Complex widgets
// ============================================================
WidgetT *wgtListBox(WidgetT *parent);
WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen);
// ============================================================
// Operations
// ============================================================
// Mark a widget (and ancestors) for relayout and repaint
void wgtInvalidate(WidgetT *w);
// Set/get widget text (label, button, textInput, etc.)
void wgtSetText(WidgetT *w, const char *text);
const char *wgtGetText(const WidgetT *w);
// Enable/disable a widget
void wgtSetEnabled(WidgetT *w, bool enabled);
// Show/hide a widget
void wgtSetVisible(WidgetT *w, bool visible);
// Find a widget by name (searches the subtree rooted at root)
WidgetT *wgtFind(WidgetT *root, const char *name);
// Destroy a widget and all its children (removes from parent)
void wgtDestroy(WidgetT *w);
// ============================================================
// 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);
// ============================================================
// Layout (called internally; available for manual trigger)
// ============================================================
// Resolve a tagged size to pixels
int32_t wgtResolveSize(int32_t taggedSize, int32_t parentSize, int32_t charWidth);
// Run layout on the entire widget tree
void wgtLayout(WidgetT *root, int32_t availW, int32_t availH,
const BitmapFontT *font);
// Paint the entire widget tree
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops,
const BitmapFontT *font, const ColorSchemeT *colors);
#endif // DVX_WIDGET_H

1895
dvx/dvxWm.c Normal file

File diff suppressed because it is too large Load diff

125
dvx/dvxWm.h Normal file
View file

@ -0,0 +1,125 @@
// dvx_wm.h — Layer 4: Window manager for DV/X GUI
#ifndef DVX_WM_H
#define DVX_WM_H
#include "dvxTypes.h"
// Initialize window stack
void wmInit(WindowStackT *stack);
// Create a new window and add it to the stack (raised to top)
WindowT *wmCreateWindow(WindowStackT *stack, DisplayT *d,
const char *title, int32_t x, int32_t y,
int32_t w, int32_t h, bool resizable);
// Destroy a window and remove from stack
void wmDestroyWindow(WindowStackT *stack, WindowT *win);
// Raise a window to the top of the stack
void wmRaiseWindow(WindowStackT *stack, DirtyListT *dl, int32_t idx);
// Set focus to a window (by stack index)
void wmSetFocus(WindowStackT *stack, DirtyListT *dl, int32_t idx);
// Recalculate content area geometry from frame dimensions
void wmUpdateContentRect(WindowT *win);
// Reallocate content buffer after resize
int32_t wmReallocContentBuf(WindowT *win, const DisplayT *d);
// Add a menu bar to a window
MenuBarT *wmAddMenuBar(WindowT *win);
// Add a menu to a menu bar
MenuT *wmAddMenu(MenuBarT *bar, const char *label);
// Add an item to a menu
void wmAddMenuItem(MenuT *menu, const char *label, int32_t id);
// Add a separator to a menu
void wmAddMenuSeparator(MenuT *menu);
// Add a vertical scrollbar to a window
ScrollbarT *wmAddVScrollbar(WindowT *win, int32_t min, int32_t max, int32_t pageSize);
// Add a horizontal scrollbar to a window
ScrollbarT *wmAddHScrollbar(WindowT *win, int32_t min, int32_t max, int32_t pageSize);
// Draw window chrome (frame, title bar, menu bar) clipped to a dirty rect
void wmDrawChrome(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font,
const ColorSchemeT *colors, WindowT *win, const RectT *clipTo);
// Draw window content clipped to a dirty rect
void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *clipTo);
// Draw scrollbars for a window
void wmDrawScrollbars(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors,
WindowT *win, const RectT *clipTo);
// Draw minimized window icons at bottom of screen
void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors,
const WindowStackT *stack, const RectT *clipTo);
// Hit test: which part of which window is at screen position (mx, my)?
// Returns stack index or -1 if no window hit
// Sets *hitPart: 0=content, 1=title, 2=close button, 3=resize edge,
// 4=menu bar, 5=vscroll, 6=hscroll, 7=minimize, 8=maximize
int32_t wmHitTest(const WindowStackT *stack, int32_t mx, int32_t my, int32_t *hitPart);
// Detect which resize edge(s) are hit (returns RESIZE_xxx flags)
int32_t wmResizeEdgeHit(const WindowT *win, int32_t mx, int32_t my);
// Handle window drag (call each mouse move during drag)
void wmDragMove(WindowStackT *stack, DirtyListT *dl, int32_t mouseX, int32_t mouseY);
// Handle window resize (call each mouse move during resize)
void wmResizeMove(WindowStackT *stack, DirtyListT *dl, const DisplayT *d,
int32_t mouseX, int32_t mouseY);
// Begin dragging a window
void wmDragBegin(WindowStackT *stack, int32_t idx, int32_t mouseX, int32_t mouseY);
// End dragging
void wmDragEnd(WindowStackT *stack);
// Begin resizing a window
void wmResizeBegin(WindowStackT *stack, int32_t idx, int32_t edge,
int32_t mouseX, int32_t mouseY);
// End resizing
void wmResizeEnd(WindowStackT *stack);
// Set window title
void wmSetTitle(WindowT *win, DirtyListT *dl, const char *title);
// Handle scrollbar click (arrow buttons, trough, or begin thumb drag)
void wmScrollbarClick(WindowStackT *stack, DirtyListT *dl, int32_t idx,
int32_t orient, int32_t mx, int32_t my);
// Handle ongoing scrollbar thumb drag
void wmScrollbarDrag(WindowStackT *stack, DirtyListT *dl, int32_t mx, int32_t my);
// End scrollbar thumb drag
void wmScrollbarEnd(WindowStackT *stack);
// Maximize a window (saves current geometry, expands to screen/max bounds)
void wmMaximize(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
// Minimize a window (hides window, shows icon at bottom of screen)
void wmMinimize(WindowStackT *stack, DirtyListT *dl, WindowT *win);
// Hit-test minimized icons at bottom of screen
// Returns stack index of the minimized window, or -1
int32_t wmMinimizedIconHit(const WindowStackT *stack, const DisplayT *d,
int32_t mx, int32_t my);
// Restore a maximized window to its pre-maximize geometry
void wmRestore(WindowStackT *stack, DirtyListT *dl, const DisplayT *d, WindowT *win);
// Restore a minimized window
void wmRestoreMinimized(WindowStackT *stack, DirtyListT *dl, WindowT *win);
// Load an icon image for a window (converts to display pixel format)
int32_t wmSetIcon(WindowT *win, const char *path, const DisplayT *d);
#endif // DVX_WM_H

7988
dvx/thirdparty/stb_image.h vendored Normal file

File diff suppressed because it is too large Load diff