Initial commit.
This commit is contained in:
parent
ca6c5dc5c7
commit
67900cbd7f
21 changed files with 17152 additions and 0 deletions
42
dvx/Makefile
Normal file
42
dvx/Makefile
Normal 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
611
dvx/README.md
Normal 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
397
dvx/demo.c
Normal 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
886
dvx/dvxApp.c
Normal 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
87
dvx/dvxApp.h
Normal 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
224
dvx/dvxComp.c
Normal 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
28
dvx/dvxComp.h
Normal 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
261
dvx/dvxCursor.h
Normal 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
642
dvx/dvxDraw.c
Normal 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
46
dvx/dvxDraw.h
Normal 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
1071
dvx/dvxFont.h
Normal file
File diff suppressed because it is too large
Load diff
13
dvx/dvxIcon.c
Normal file
13
dvx/dvxIcon.c
Normal 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
135
dvx/dvxPalette.h
Normal 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
321
dvx/dvxTypes.h
Normal 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
445
dvx/dvxVideo.c
Normal 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
28
dvx/dvxVideo.h
Normal 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
1634
dvx/dvxWidget.c
Normal file
File diff suppressed because it is too large
Load diff
273
dvx/dvxWidget.h
Normal file
273
dvx/dvxWidget.h
Normal 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
1895
dvx/dvxWm.c
Normal file
File diff suppressed because it is too large
Load diff
125
dvx/dvxWm.h
Normal file
125
dvx/dvxWm.h
Normal 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
7988
dvx/thirdparty/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue