DVX_GUI/dvxdemo/demo.c

1139 lines
35 KiB
C

// demo.c — DV/X GUI demonstration application
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "thirdparty/stb_image.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_EDIT_CUT 200
#define CMD_EDIT_COPY 201
#define CMD_EDIT_PASTE 202
#define CMD_VIEW_TERM 300
#define CMD_VIEW_CTRL 301
#define CMD_VIEW_ZOOM_IN 302
#define CMD_VIEW_ZOOM_OUT 303
#define CMD_VIEW_ZOOM_FIT 304
#define CMD_VIEW_TOOLBAR 305
#define CMD_VIEW_STATUSBAR 306
#define CMD_VIEW_SIZE_SMALL 307
#define CMD_VIEW_SIZE_MED 308
#define CMD_VIEW_SIZE_LARGE 309
#define CMD_HELP_ABOUT 400
#define CMD_CTX_CUT 500
#define CMD_CTX_COPY 501
#define CMD_CTX_PASTE 502
#define CMD_CTX_DELETE 503
#define CMD_CTX_SELALL 504
#define CMD_CTX_PROPS 505
// ============================================================
// Prototypes
// ============================================================
static uint8_t *loadBmpPixels(AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch);
static void onCloseCb(WindowT *win);
static void onCloseMainCb(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 onToolbarClick(WidgetT *w);
static void setupControlsWindow(AppContextT *ctx);
static void setupMainWindow(AppContextT *ctx);
static void setupTerminalWindow(AppContextT *ctx);
static void setupWidgetDemo(AppContextT *ctx);
// ============================================================
// Globals
// ============================================================
static AppContextT *sCtx = NULL;
// ============================================================
// loadBmpPixels — load a BMP/PNG file into display-format pixels
// ============================================================
static uint8_t *loadBmpPixels(AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch) {
int imgW;
int imgH;
int channels;
uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3);
if (!rgb) {
return NULL;
}
const DisplayT *d = dvxGetDisplay(ctx);
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = imgW * bpp;
uint8_t *data = (uint8_t *)malloc(pitch * imgH);
if (!data) {
stbi_image_free(rgb);
return NULL;
}
for (int32_t y = 0; y < imgH; y++) {
for (int32_t x = 0; x < imgW; x++) {
uint8_t *src = rgb + (y * imgW + x) * 3;
uint32_t color = packColor(d, src[0], src[1], src[2]);
uint8_t *dst = data + y * pitch + x * bpp;
if (bpp == 1) {
*dst = (uint8_t)color;
} else if (bpp == 2) {
*(uint16_t *)dst = (uint16_t)color;
} else {
*(uint32_t *)dst = color;
}
}
}
stbi_image_free(rgb);
*outW = imgW;
*outH = imgH;
*outPitch = pitch;
return data;
}
// ============================================================
// onCloseCb
// ============================================================
static void onCloseCb(WindowT *win) {
AppContextT *ctx = (AppContextT *)win->userData;
if (ctx) {
dvxDestroyWindow(ctx, win);
}
}
// ============================================================
// onCloseMainCb
// ============================================================
static void onCloseMainCb(WindowT *win) {
AppContextT *ctx = (AppContextT *)win->userData;
if (ctx) {
int32_t result = dvxMessageBox(ctx, "Exit",
"Are you sure you want to exit?",
MB_YESNO | MB_ICONQUESTION);
if (result == ID_YES) {
dvxQuit(ctx);
}
}
}
// ============================================================
// onMenuCb
// ============================================================
static const FileFilterT sFileFilters[] = {
{"All Files (*.*)", "*.*"},
{"Text Files (*.txt)", "*.txt"},
{"Batch Files (*.bat)", "*.bat"},
{"Executables (*.exe)", "*.exe"},
{"Bitmap Files (*.bmp)", "*.bmp"}
};
static void onMenuCb(WindowT *win, int32_t menuId) {
AppContextT *ctx = (AppContextT *)win->userData;
switch (menuId) {
case CMD_FILE_OPEN: {
char path[260];
if (dvxFileDialog(ctx, "Open File", FD_OPEN, NULL, sFileFilters, 5, path, sizeof(path))) {
char msg[300];
snprintf(msg, sizeof(msg), "Selected: %s", path);
dvxMessageBox(ctx, "Open", msg, MB_OK | MB_ICONINFO);
}
break;
}
case CMD_FILE_SAVE: {
char path[260];
if (dvxFileDialog(ctx, "Save As", FD_SAVE, NULL, sFileFilters, 5, path, sizeof(path))) {
char msg[300];
snprintf(msg, sizeof(msg), "Save to: %s", path);
dvxMessageBox(ctx, "Save", msg, MB_OK | MB_ICONINFO);
}
break;
}
case CMD_FILE_EXIT:
if (ctx) {
int32_t result = dvxMessageBox(ctx, "Exit",
"Are you sure you want to exit?",
MB_YESNO | MB_ICONQUESTION);
if (result == ID_YES) {
dvxQuit(ctx);
}
}
break;
case CMD_VIEW_TERM:
setupTerminalWindow(ctx);
break;
case CMD_VIEW_CTRL:
setupControlsWindow(ctx);
break;
case CMD_HELP_ABOUT:
dvxMessageBox(sCtx, "About DV/X Demo",
"DV/X GUI Demonstration\n\n"
"A DESQview/X-style windowing system for DOS.",
MB_OK | MB_ICONINFO);
break;
case CMD_CTX_CUT:
dvxMessageBox(ctx, "Context Menu", "Cut selected.", MB_OK | MB_ICONINFO);
break;
case CMD_CTX_COPY:
dvxMessageBox(ctx, "Context Menu", "Copied to clipboard.", MB_OK | MB_ICONINFO);
break;
case CMD_CTX_PASTE:
dvxMessageBox(ctx, "Context Menu", "Pasted from clipboard.", MB_OK | MB_ICONINFO);
break;
case CMD_CTX_DELETE:
dvxMessageBox(ctx, "Context Menu", "Deleted.", MB_OK | MB_ICONINFO);
break;
case CMD_CTX_SELALL:
dvxMessageBox(ctx, "Context Menu", "Selected all.", MB_OK | MB_ICONINFO);
break;
case CMD_CTX_PROPS:
dvxMessageBox(ctx, "Properties", "Window properties dialog would appear here.", MB_OK | MB_ICONINFO);
break;
}
}
// ============================================================
// onOkClick
// ============================================================
static void onOkClick(WidgetT *w) {
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;
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;
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);
}
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/resizable windows",
" - Menu bars with accelerators",
" - Scrollbars",
" - Widget system",
" - ANSI terminal emulator",
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;
}
}
// ============================================================
// onToolbarClick
// ============================================================
static void onToolbarClick(WidgetT *w) {
WidgetT *root = w;
while (root->parent) {
root = root->parent;
}
WidgetT *status = wgtFind(root, "advStatus");
if (status) {
wgtSetText(status, wgtGetText(w));
wgtInvalidate(status);
}
}
// ============================================================
// setupControlsWindow — advanced widgets with tabs
// ============================================================
static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Magenta"};
static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"};
static void setupControlsWindow(AppContextT *ctx) {
WindowT *win = dvxCreateWindow(ctx, "Advanced Widgets", 380, 50, 360, 440, true);
if (!win) {
return;
}
win->userData = ctx;
win->onClose = onCloseCb;
WidgetT *root = wgtInitWindow(ctx, win);
// TabControl at top
WidgetT *tabs = wgtTabControl(root);
// --- Tab 1: Controls ---
WidgetT *page1 = wgtTabPage(tabs, "&Controls");
WidgetT *ddRow = wgtHBox(page1);
wgtLabel(ddRow, "Co&lor:");
WidgetT *dd = wgtDropdown(ddRow);
wgtDropdownSetItems(dd, colorItems, 6);
wgtDropdownSetSelected(dd, 0);
wgtSetTooltip(dd, "Choose a color");
WidgetT *cbRow = wgtHBox(page1);
wgtLabel(cbRow, "Si&ze:");
WidgetT *cb = wgtComboBox(cbRow, 32);
wgtComboBoxSetItems(cb, sizeItems, 4);
wgtComboBoxSetSelected(cb, 1);
wgtSetTooltip(cb, "Type or select a size");
wgtHSeparator(page1);
wgtLabel(page1, "&Progress:");
WidgetT *pb = wgtProgressBar(page1);
wgtProgressBarSetValue(pb, 65);
wgtSetTooltip(pb, "Task progress: 65%");
wgtLabel(page1, "&Volume:");
WidgetT *slider = wgtSlider(page1, 0, 100);
wgtSetTooltip(slider, "Adjust volume level");
WidgetT *spinRow = wgtHBox(page1);
spinRow->maxH = wgtPixels(30);
wgtLabel(spinRow, "&Quantity:");
WidgetT *spin = wgtSpinner(spinRow, 0, 999, 1);
wgtSpinnerSetValue(spin, 42);
spin->weight = 50;
wgtSetTooltip(spin, "Enter quantity (0-999)");
// --- Tab 2: Tree ---
WidgetT *page2 = wgtTabPage(tabs, "&Tree");
WidgetT *tree = wgtTreeView(page2);
wgtTreeViewSetReorderable(tree, true);
WidgetT *docs = wgtTreeItem(tree, "Documents");
wgtTreeItemSetExpanded(docs, true);
wgtTreeItem(docs, "README.md");
wgtTreeItem(docs, "DESIGN.md");
WidgetT *src = wgtTreeItem(docs, "src");
wgtTreeItemSetExpanded(src, true);
wgtTreeItem(src, "main.c");
wgtTreeItem(src, "utils.c");
wgtTreeItem(src, "render.c");
WidgetT *images = wgtTreeItem(tree, "Images");
wgtTreeItem(images, "logo.png");
wgtTreeItem(images, "icon.bmp");
WidgetT *config = wgtTreeItem(tree, "Config");
wgtTreeItem(config, "settings.ini");
wgtTreeItem(config, "palette.dat");
// --- Tab 3: ListView (multi-column list) ---
WidgetT *page3 = wgtTabPage(tabs, "&List");
static const ListViewColT lvCols[] = {
{"Name", (int32_t)(WGT_SIZE_CHARS | 16), ListViewAlignLeftE},
{"Size", (int32_t)(WGT_SIZE_CHARS | 8), ListViewAlignRightE},
{"Type", (int32_t)(WGT_SIZE_CHARS | 12), ListViewAlignLeftE},
{"Modified", (int32_t)(WGT_SIZE_CHARS | 12), ListViewAlignLeftE}
};
static const char *lvData[] = {
"AUTOEXEC.BAT", "412", "Batch File", "03/15/1994",
"CONFIG.SYS", "256", "System File", "03/15/1994",
"COMMAND.COM", "54,645", "Application", "09/30/1993",
"HIMEM.SYS", "29,136", "System Driver", "09/30/1993",
"EMM386.EXE", "120,926", "Application", "09/30/1993",
"MOUSE.COM", "56,408", "Application", "06/01/1994",
"DOSKEY.COM", "5,883", "Application", "09/30/1993",
"EDIT.COM", "413", "Application", "09/30/1993",
"README.TXT", "8,192", "Text File", "01/10/1994",
"DVXDEMO.EXE", "98,304", "Application", "03/15/2026"
};
WidgetT *lv = wgtListView(page3);
wgtListViewSetColumns(lv, lvCols, 4);
wgtListViewSetData(lv, lvData, 10);
wgtListViewSetSelected(lv, 0);
wgtListViewSetMultiSelect(lv, true);
wgtListViewSetReorderable(lv, true);
lv->weight = 100;
// --- Tab 4: ScrollPane ---
WidgetT *page4sp = wgtTabPage(tabs, "&Scroll");
WidgetT *sp = wgtScrollPane(page4sp);
sp->weight = 100;
sp->padding = wgtPixels(4);
sp->spacing = wgtPixels(4);
wgtLabel(sp, "ScrollPane Demo:");
wgtHSeparator(sp);
static const char *spItems[] = {
"Item &1", "Item &2", "Item &3", "Item &4",
"Item &5", "Item &6", "Item &7", "Item &8"
};
for (int32_t i = 0; i < 8; i++) {
wgtCheckbox(sp, spItems[i]);
}
wgtHSeparator(sp);
wgtLabel(sp, "More widgets below:");
WidgetT *spRg = wgtRadioGroup(sp);
static const char *spRadios[] = {
"Radio 1", "Radio 2", "Radio 3", "Radio 4", "Radio 5"
};
for (int32_t i = 0; i < 5; i++) {
wgtRadio(spRg, spRadios[i]);
}
wgtHSeparator(sp);
wgtLabel(sp, "Bottom of scroll area");
wgtButton(sp, "Scrolled &Button");
// --- Tab 5: Toolbar (ImageButtons + VSeparator) ---
WidgetT *page5tb = wgtTabPage(tabs, "Tool&bar");
WidgetT *tb = wgtToolbar(page5tb);
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
uint8_t *newData = loadBmpPixels(ctx, "new.bmp", &imgW, &imgH, &imgPitch);
if (newData) {
WidgetT *btnNew = wgtImageButton(tb, newData, imgW, imgH, imgPitch);
strncpy(btnNew->name, "New", MAX_WIDGET_NAME);
btnNew->onClick = onToolbarClick;
}
uint8_t *openData = loadBmpPixels(ctx, "open.bmp", &imgW, &imgH, &imgPitch);
if (openData) {
WidgetT *btnOpen = wgtImageButton(tb, openData, imgW, imgH, imgPitch);
strncpy(btnOpen->name, "Open", MAX_WIDGET_NAME);
btnOpen->onClick = onToolbarClick;
}
uint8_t *saveData = loadBmpPixels(ctx, "save.bmp", &imgW, &imgH, &imgPitch);
if (saveData) {
WidgetT *btnSave = wgtImageButton(tb, saveData, imgW, imgH, imgPitch);
strncpy(btnSave->name, "Save", MAX_WIDGET_NAME);
btnSave->onClick = onToolbarClick;
}
wgtVSeparator(tb);
WidgetT *btnHelp = wgtButton(tb, "&Help");
btnHelp->onClick = onToolbarClick;
wgtLabel(page5tb, "ImageButtons with VSeparator.");
// --- Tab 6: Media (Image, ImageFromFile) ---
WidgetT *page6m = wgtTabPage(tabs, "&Media");
wgtLabel(page6m, "ImageFromFile (sample.bmp):");
wgtImageFromFile(page6m, "sample.bmp");
wgtHSeparator(page6m);
wgtLabel(page6m, "Image (logo.bmp):");
WidgetT *imgRow = wgtHBox(page6m);
uint8_t *logoData = loadBmpPixels(ctx, "logo.bmp", &imgW, &imgH, &imgPitch);
if (logoData) {
wgtImage(imgRow, logoData, imgW, imgH, imgPitch);
}
wgtVSeparator(imgRow);
wgtLabel(imgRow, "32x32 DV/X logo");
// --- Tab 7: Editor (TextArea, Canvas) ---
WidgetT *page7e = wgtTabPage(tabs, "&Editor");
wgtLabel(page7e, "TextArea:");
WidgetT *ta = wgtTextArea(page7e, 512);
ta->weight = 100;
wgtSetText(ta, "Multi-line text editor.\n\nFeatures:\n- Word wrap\n- Selection\n- Copy/Paste\n- Undo (Ctrl+Z)");
wgtHSeparator(page7e);
wgtLabel(page7e, "Canvas (draw with mouse):");
const DisplayT *d = dvxGetDisplay(ctx);
WidgetT *cv = wgtCanvas(page7e, 280, 80);
wgtCanvasSetPenColor(cv, packColor(d, 200, 0, 0));
wgtCanvasDrawRect(cv, 5, 5, 50, 35);
wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 200));
wgtCanvasFillCircle(cv, 150, 40, 25);
wgtCanvasSetPenColor(cv, packColor(d, 0, 150, 0));
wgtCanvasDrawLine(cv, 70, 5, 130, 70);
wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 0));
// --- Tab 8: Splitter ---
WidgetT *page8s = wgtTabPage(tabs, "S&plit");
// Outer horizontal splitter: explorer on top, detail on bottom
WidgetT *hSplit = wgtSplitter(page8s, false);
hSplit->weight = 100;
wgtSplitterSetPos(hSplit, 120);
// Top pane: vertical splitter (tree | list)
WidgetT *vSplit = wgtSplitter(hSplit, true);
wgtSplitterSetPos(vSplit, 120);
// Left pane: multi-select tree view
WidgetT *leftTree = wgtTreeView(vSplit);
wgtTreeViewSetMultiSelect(leftTree, true);
WidgetT *tiFolders = wgtTreeItem(leftTree, "Folders");
wgtTreeItemSetExpanded(tiFolders, true);
wgtTreeItem(tiFolders, "Documents");
wgtTreeItem(tiFolders, "Pictures");
wgtTreeItem(tiFolders, "Music");
WidgetT *tiSystem = wgtTreeItem(leftTree, "System");
wgtTreeItemSetExpanded(tiSystem, true);
wgtTreeItem(tiSystem, "Config");
wgtTreeItem(tiSystem, "Drivers");
// Right pane: list box
WidgetT *rightList = wgtListBox(vSplit);
static const char *explorerItems[] = {
"README.TXT", "SETUP.EXE", "CONFIG.SYS",
"AUTOEXEC.BAT", "COMMAND.COM", "HIMEM.SYS",
"EMM386.EXE", "MOUSE.COM"
};
wgtListBoxSetItems(rightList, explorerItems, 8);
// Bottom pane: detail/preview area
WidgetT *detailFrame = wgtFrame(hSplit, "Preview");
wgtLabel(detailFrame, "Select a file above to preview.");
// --- Tab 9: Disabled ---
WidgetT *page9d = wgtTabPage(tabs, "&Disabled");
// Left column: enabled widgets Right column: disabled widgets
WidgetT *disRow = wgtHBox(page9d);
disRow->weight = 100;
// Enabled column
WidgetT *enCol = wgtVBox(disRow);
enCol->weight = 100;
wgtLabel(enCol, "Enabled:");
wgtHSeparator(enCol);
wgtLabel(enCol, "A &label");
wgtButton(enCol, "A &button");
WidgetT *enChk = wgtCheckbox(enCol, "&Check me");
enChk->as.checkbox.checked = true;
WidgetT *enRg = wgtRadioGroup(enCol);
wgtRadio(enRg, "&Radio 1");
WidgetT *enR2 = wgtRadio(enRg, "R&adio 2");
enRg->as.radioGroup.selectedIdx = enR2->as.radio.index;
static const char *disItems[] = {"Alpha", "Beta", "Gamma"};
WidgetT *enDd = wgtDropdown(enCol);
wgtDropdownSetItems(enDd, disItems, 3);
wgtDropdownSetSelected(enDd, 0);
WidgetT *enSlider = wgtSlider(enCol, 0, 100);
wgtSliderSetValue(enSlider, 40);
WidgetT *enProg = wgtProgressBar(enCol);
wgtProgressBarSetValue(enProg, 60);
WidgetT *enSpin = wgtSpinner(enCol, 0, 100, 1);
wgtSpinnerSetValue(enSpin, 42);
WidgetT *enText = wgtTextInput(enCol, 64);
wgtSetText(enText, "Editable");
// Disabled column
WidgetT *disCol = wgtVBox(disRow);
disCol->weight = 100;
wgtLabel(disCol, "Disabled:");
wgtHSeparator(disCol);
WidgetT *dLbl = wgtLabel(disCol, "A &label");
wgtSetEnabled(dLbl, false);
WidgetT *dBtn = wgtButton(disCol, "A &button");
wgtSetEnabled(dBtn, false);
WidgetT *dChk = wgtCheckbox(disCol, "&Check me");
dChk->as.checkbox.checked = true;
wgtSetEnabled(dChk, false);
WidgetT *dRg = wgtRadioGroup(disCol);
WidgetT *dR1 = wgtRadio(dRg, "&Radio 1");
WidgetT *dR2 = wgtRadio(dRg, "R&adio 2");
dRg->as.radioGroup.selectedIdx = dR2->as.radio.index;
wgtSetEnabled(dR1, false);
wgtSetEnabled(dR2, false);
WidgetT *dDd = wgtDropdown(disCol);
wgtDropdownSetItems(dDd, disItems, 3);
wgtDropdownSetSelected(dDd, 0);
wgtSetEnabled(dDd, false);
WidgetT *dSlider = wgtSlider(disCol, 0, 100);
wgtSliderSetValue(dSlider, 40);
wgtSetEnabled(dSlider, false);
WidgetT *dProg = wgtProgressBar(disCol);
wgtProgressBarSetValue(dProg, 60);
wgtSetEnabled(dProg, false);
WidgetT *dSpin = wgtSpinner(disCol, 0, 100, 1);
wgtSpinnerSetValue(dSpin, 42);
wgtSetEnabled(dSpin, false);
WidgetT *dText = wgtTextInput(disCol, 64);
wgtSetText(dText, "Read-only");
wgtSetEnabled(dText, false);
// Status bar at bottom (outside tabs)
WidgetT *sb = wgtStatusBar(root);
WidgetT *sbLabel = wgtLabel(sb, "Ready");
sbLabel->weight = 100;
strncpy(sbLabel->name, "advStatus", MAX_WIDGET_NAME);
wgtLabel(sb, "Line 1, Col 1");
wgtInvalidate(root);
}
// ============================================================
// setupMainWindow — info window + paint demos
// ============================================================
static void setupMainWindow(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 = onCloseMainCb;
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, "E&xit", CMD_FILE_EXIT);
}
MenuT *editMenu = wmAddMenu(bar, "&Edit");
if (editMenu) {
wmAddMenuItem(editMenu, "Cu&t", CMD_EDIT_CUT);
wmAddMenuItem(editMenu, "&Copy", CMD_EDIT_COPY);
wmAddMenuItem(editMenu, "&Paste", CMD_EDIT_PASTE);
}
MenuT *viewMenu = wmAddMenu(bar, "&View");
if (viewMenu) {
wmAddMenuItem(viewMenu, "&Terminal", CMD_VIEW_TERM);
wmAddMenuItem(viewMenu, "&Controls", CMD_VIEW_CTRL);
wmAddMenuSeparator(viewMenu);
wmAddMenuCheckItem(viewMenu, "Tool&bar", CMD_VIEW_TOOLBAR, true);
wmAddMenuCheckItem(viewMenu, "&Status Bar", CMD_VIEW_STATUSBAR, true);
wmAddMenuSeparator(viewMenu);
wmAddMenuRadioItem(viewMenu, "S&mall", CMD_VIEW_SIZE_SMALL, false);
wmAddMenuRadioItem(viewMenu, "Me&dium", CMD_VIEW_SIZE_MED, true);
wmAddMenuRadioItem(viewMenu, "&Large", CMD_VIEW_SIZE_LARGE, false);
wmAddMenuSeparator(viewMenu);
MenuT *zoomMenu = wmAddSubMenu(viewMenu, "&Zoom");
if (zoomMenu) {
wmAddMenuItem(zoomMenu, "Zoom &In", CMD_VIEW_ZOOM_IN);
wmAddMenuItem(zoomMenu, "Zoom &Out", CMD_VIEW_ZOOM_OUT);
wmAddMenuSeparator(zoomMenu);
wmAddMenuItem(zoomMenu, "&Fit to Window", CMD_VIEW_ZOOM_FIT);
}
}
MenuT *helpMenu = wmAddMenu(bar, "&Help");
if (helpMenu) {
wmAddMenuItem(helpMenu, "&About...", CMD_HELP_ABOUT);
}
}
// Accelerator table (global hotkeys)
AccelTableT *accel = dvxCreateAccelTable();
dvxAddAccel(accel, 'N', ACCEL_CTRL, CMD_FILE_NEW);
dvxAddAccel(accel, 'O', ACCEL_CTRL, CMD_FILE_OPEN);
dvxAddAccel(accel, 'S', ACCEL_CTRL, CMD_FILE_SAVE);
dvxAddAccel(accel, 'Q', ACCEL_CTRL, CMD_FILE_EXIT);
dvxAddAccel(accel, KEY_F1, 0, CMD_HELP_ABOUT);
win1->accelTable = accel;
// Window-level context menu
MenuT *winCtx = wmCreateMenu();
wmAddMenuItem(winCtx, "Cu&t", CMD_CTX_CUT);
wmAddMenuItem(winCtx, "&Copy", CMD_CTX_COPY);
wmAddMenuItem(winCtx, "&Paste", CMD_CTX_PASTE);
wmAddMenuSeparator(winCtx);
wmAddMenuItem(winCtx, "&Properties...", CMD_CTX_PROPS);
win1->contextMenu = winCtx;
wmUpdateContentRect(win1);
wmReallocContentBuf(win1, &ctx->display);
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);
}
}
// ============================================================
// setupTerminalWindow — ANSI terminal widget demo
// ============================================================
static void setupTerminalWindow(AppContextT *ctx) {
WindowT *win = dvxCreateWindow(ctx, "ANSI Terminal", 60, 60, 660, 420, true);
if (!win) {
return;
}
win->userData = ctx;
win->onClose = onCloseCb;
WidgetT *root = wgtInitWindow(ctx, win);
WidgetT *term = wgtAnsiTerm(root, 80, 25);
term->weight = 100;
wgtAnsiTermSetScrollback(term, 500);
// Feed some ANSI content to demonstrate the terminal
static const uint8_t ansiDemo[] =
"\x1B[2J" // clear screen
"\x1B[1;34m========================================\r\n"
" DV/X ANSI Terminal Emulator\r\n"
"========================================\x1B[0m\r\n"
"\r\n"
"\x1B[1mBold text\x1B[0m, "
"\x1B[7mReverse\x1B[0m, "
"\x1B[5mBlinking\x1B[0m\r\n"
"\r\n"
"Standard colors:\r\n"
" \x1B[30m\x1B[47m Black \x1B[0m"
" \x1B[31m Red \x1B[0m"
" \x1B[32m Green \x1B[0m"
" \x1B[33m Yellow \x1B[0m"
" \x1B[34m Blue \x1B[0m"
" \x1B[35m Magenta \x1B[0m"
" \x1B[36m Cyan \x1B[0m"
" \x1B[37m White \x1B[0m\r\n"
"\r\n"
"Bright colors:\r\n"
" \x1B[1;30m\x1B[47m DkGray \x1B[0m"
" \x1B[1;31m LtRed \x1B[0m"
" \x1B[1;32m LtGreen \x1B[0m"
" \x1B[1;33m LtYellow \x1B[0m"
" \x1B[1;34m LtBlue \x1B[0m"
" \x1B[1;35m LtMagenta \x1B[0m"
" \x1B[1;36m LtCyan \x1B[0m"
" \x1B[1;37m BrWhite \x1B[0m\r\n"
"\r\n"
"Background colors:\r\n"
" \x1B[40m\x1B[37m Black \x1B[0m"
" \x1B[41m\x1B[37m Red \x1B[0m"
" \x1B[42m\x1B[30m Green \x1B[0m"
" \x1B[43m\x1B[30m Yellow \x1B[0m"
" \x1B[44m\x1B[37m Blue \x1B[0m"
" \x1B[45m\x1B[37m Magenta \x1B[0m"
" \x1B[46m\x1B[30m Cyan \x1B[0m"
" \x1B[47m\x1B[30m White \x1B[0m\r\n"
"\r\n"
"CP437 graphics: "
"\x01\x02\x03\x04\x05\x06" // smileys, hearts, diamonds, clubs, spades
" \xB0\xB1\xB2\xDB" // shade blocks
" \xC4\xC5\xB3\xDA\xBF\xC0\xD9" // box drawing
"\r\n"
"\r\n"
"\x1B[1;32mTerminal ready.\x1B[0m "
"\x1B[36m(Not connected to any host)\x1B[0m\r\n";
wgtAnsiTermWrite(term, ansiDemo, (int32_t)(sizeof(ansiDemo) - 1));
WidgetT *sb = wgtStatusBar(root);
wgtLabel(sb, "80x25 [Local]");
dvxFitWindow(ctx, win);
wgtInvalidate(root);
dvxMinimizeWindow(ctx, win);
}
// ============================================================
// setupWidgetDemo — form with accelerators
// ============================================================
static void setupWidgetDemo(AppContextT *ctx) {
WindowT *win = dvxCreateWindow(ctx, "Widget Demo", 80, 200, 280, 360, true);
if (!win) {
return;
}
win->userData = ctx;
win->onClose = onCloseCb;
win->onMenu = onMenuCb;
WidgetT *root = wgtInitWindow(ctx, win);
// 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);
WidgetT *row2 = wgtHBox(frame);
wgtLabel(row2, "&Password:");
wgtPasswordInput(row2, 32);
WidgetT *row3 = wgtHBox(frame);
wgtLabel(row3, "P&hone:");
wgtMaskedInput(row3, "(###) ###-####");
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);
// List boxes
static const char *listItems[] = {"Alpha", "Beta", "Gamma", "Delta", "Epsilon"};
static const char *multiItems[] = {"Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet"};
WidgetT *listRow = wgtHBox(root);
wgtLabel(listRow, "&Items:");
WidgetT *lb = wgtListBox(listRow);
wgtListBoxSetItems(lb, listItems, 5);
wgtListBoxSetReorderable(lb, true);
lb->weight = 100;
// Context menu on the list box
MenuT *lbCtx = wmCreateMenu();
wmAddMenuItem(lbCtx, "Cu&t", CMD_CTX_CUT);
wmAddMenuItem(lbCtx, "&Copy", CMD_CTX_COPY);
wmAddMenuItem(lbCtx, "&Paste", CMD_CTX_PASTE);
wmAddMenuSeparator(lbCtx);
wmAddMenuItem(lbCtx, "&Delete", CMD_CTX_DELETE);
wmAddMenuSeparator(lbCtx);
wmAddMenuItem(lbCtx, "Select &All", CMD_CTX_SELALL);
lb->contextMenu = lbCtx;
wgtLabel(listRow, "&Multi:");
WidgetT *mlb = wgtListBox(listRow);
wgtListBoxSetMultiSelect(mlb, true);
wgtListBoxSetItems(mlb, multiItems, 7);
mlb->weight = 100;
wgtHSeparator(root);
// Button row at bottom
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE;
WidgetT *okBtn = wgtButton(btnRow, "&OK");
okBtn->onClick = onOkClick;
wgtButton(btnRow, "&Cancel");
wgtInvalidate(root);
}
// ============================================================
// main
// ============================================================
int main(int argc, char **argv) {
(void)argc;
// Change to executable's directory so relative BMP paths work
char exeDir[260];
strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
exeDir[sizeof(exeDir) - 1] = '\0';
char *lastSep = strrchr(exeDir, '/');
char *lastBs = strrchr(exeDir, '\\');
if (lastBs > lastSep) {
lastSep = lastBs;
}
if (lastSep) {
*lastSep = '\0';
chdir(exeDir);
}
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;
}
sCtx = &ctx;
setupMainWindow(&ctx);
setupWidgetDemo(&ctx);
setupControlsWindow(&ctx);
setupTerminalWindow(&ctx);
dvxRun(&ctx);
dvxShutdown(&ctx);
printf("DV/X GUI Demo ended.\n");
return 0;
}