Working on keyboard control of the GUI.

This commit is contained in:
Scott Duensing 2026-03-11 22:42:09 -05:00
parent 324382d758
commit 14eca6fcd2
17 changed files with 1507 additions and 144 deletions

File diff suppressed because it is too large Load diff

View file

@ -22,6 +22,8 @@ typedef struct AppContextT {
BitmapFontT font;
ColorSchemeT colors;
PopupStateT popup;
SysMenuStateT sysMenu;
KbMoveResizeT kbMoveResize;
CursorT cursors[5]; // indexed by CURSOR_xxx
int32_t cursorId; // active cursor shape
uint32_t cursorFg; // pre-packed cursor colors

View file

@ -8,6 +8,7 @@
// Prototypes
// ============================================================
char accelParse(const char *text);
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);
@ -18,6 +19,53 @@ static void spanFill16(uint8_t *dst, uint32_t color, int32_t count);
static void spanFill32(uint8_t *dst, uint32_t color, int32_t count);
// ============================================================
// accelParse
// ============================================================
char accelParse(const char *text) {
if (!text) {
return 0;
}
while (*text) {
if (*text == '&') {
text++;
if (*text == '&') {
// Escaped && — literal &, not an accelerator
text++;
continue;
}
if (*text && *text != '&') {
char ch = *text;
if (ch >= 'A' && ch <= 'Z') {
return (char)(ch + 32);
}
if (ch >= 'a' && ch <= 'z') {
return ch;
}
if (ch >= '0' && ch <= '9') {
return ch;
}
return ch;
}
break;
}
text++;
}
return 0;
}
// ============================================================
// clipRect
// ============================================================
@ -353,6 +401,58 @@ void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t
}
// ============================================================
// drawTextAccel
// ============================================================
void drawTextAccel(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) {
if (__builtin_expect(x >= clipX2, 0)) {
break;
}
if (*text == '&') {
text++;
if (*text == '&') {
// Escaped && — draw literal &
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, '&', fg, bg, opaque);
}
x += cw;
text++;
continue;
}
if (*text) {
// Accelerator character — draw it then underline
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
drawHLine(d, ops, x, y + font->charHeight - 1, cw, fg);
}
x += cw;
text++;
continue;
}
break;
}
if (x + cw > d->clipX) {
drawChar(d, ops, font, x, y, *text, fg, bg, opaque);
}
x += cw;
text++;
}
}
// ============================================================
// drawVLine
// ============================================================
@ -640,3 +740,39 @@ int32_t textWidth(const BitmapFontT *font, const char *text) {
return w;
}
// ============================================================
// textWidthAccel
// ============================================================
int32_t textWidthAccel(const BitmapFontT *font, const char *text) {
int32_t w = 0;
while (*text) {
if (*text == '&') {
text++;
if (*text == '&') {
// Escaped && — counts as one character
w += font->charWidth;
text++;
continue;
}
if (*text) {
// Accelerator character — counts as one character, & is skipped
w += font->charWidth;
text++;
continue;
}
break;
}
w += font->charWidth;
text++;
}
return w;
}

View file

@ -25,6 +25,16 @@ void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t
// Measure text width in pixels
int32_t textWidth(const BitmapFontT *font, const char *text);
// Parse accelerator key from text with & markers (e.g. "E&xit" -> 'x')
// Returns the lowercase accelerator character, or 0 if none found.
char accelParse(const char *text);
// Draw text with & accelerator processing (underlines the accelerator character)
void drawTextAccel(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, skipping & markers
int32_t textWidthAccel(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);

View file

@ -153,6 +153,7 @@ typedef struct {
bool separator; // true = this is a separator line, not a clickable item
bool enabled;
bool checked;
char accelKey; // lowercase accelerator character, 0 if none
} MenuItemT;
typedef struct {
@ -161,6 +162,7 @@ typedef struct {
int32_t itemCount;
int32_t barX; // computed position on menu bar
int32_t barW; // computed width on menu bar
char accelKey; // lowercase accelerator character, 0 if none
} MenuT;
typedef struct {
@ -312,6 +314,60 @@ typedef struct {
int32_t hoverItem; // which item is highlighted (-1 = none)
} PopupStateT;
// ============================================================
// System menu (control menu / close box menu)
// ============================================================
typedef enum {
SysMenuRestoreE = 1,
SysMenuMoveE = 2,
SysMenuSizeE = 3,
SysMenuMinimizeE = 4,
SysMenuMaximizeE = 5,
SysMenuCloseE = 6
} SysMenuCmdE;
#define SYS_MENU_MAX_ITEMS 8
typedef struct {
char label[MAX_MENU_LABEL];
int32_t cmd;
bool separator;
bool enabled;
char accelKey;
} SysMenuItemT;
typedef struct {
bool active;
int32_t windowId;
int32_t popupX;
int32_t popupY;
int32_t popupW;
int32_t popupH;
int32_t hoverItem;
SysMenuItemT items[SYS_MENU_MAX_ITEMS];
int32_t itemCount;
} SysMenuStateT;
// ============================================================
// Keyboard move/resize mode
// ============================================================
typedef enum {
KbModeNoneE = 0,
KbModeMoveE = 1,
KbModeResizeE = 2
} KbModeE;
typedef struct {
KbModeE mode;
int32_t windowId;
int32_t origX;
int32_t origY;
int32_t origW;
int32_t origH;
} KbMoveResizeT;
// ============================================================
// Utility macros
// ============================================================

View file

@ -142,6 +142,7 @@ typedef struct WidgetT {
bool visible;
bool enabled;
bool focused;
char accelKey; // lowercase accelerator character, 0 if none
// User data and callbacks
void *userData;

View file

@ -67,7 +67,7 @@ static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
for (int32_t i = 0; i < win->menuBar->menuCount; i++) {
MenuT *menu = &win->menuBar->menus[i];
int32_t labelW = (int32_t)strlen(menu->label) * font->charWidth + CHROME_TITLE_PAD * 2;
int32_t labelW = textWidthAccel(font, menu->label) + CHROME_TITLE_PAD * 2;
menu->barX = x;
menu->barW = labelW;
@ -193,7 +193,7 @@ static void drawMenuBar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *fon
int32_t textX = win->x + menu->barX + CHROME_TITLE_PAD;
int32_t textY = barY + (barH - font->charHeight) / 2;
drawText(d, ops, font, textX, textY, menu->label,
drawTextAccel(d, ops, font, textX, textY, menu->label,
colors->menuFg, colors->menuBg, true);
}
@ -567,6 +567,7 @@ MenuT *wmAddMenu(MenuBarT *bar, const char *label) {
memset(menu, 0, sizeof(*menu));
strncpy(menu->label, label, MAX_MENU_LABEL - 1);
menu->label[MAX_MENU_LABEL - 1] = '\0';
menu->accelKey = accelParse(label);
bar->menuCount++;
return menu;
@ -608,6 +609,7 @@ void wmAddMenuItem(MenuT *menu, const char *label, int32_t id) {
item->separator = false;
item->enabled = true;
item->checked = false;
item->accelKey = accelParse(label);
menu->itemCount++;
}

View file

@ -49,13 +49,13 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
// Draw title centered vertically on the top border line
if (w->as.frame.title && w->as.frame.title[0]) {
int32_t titleW = (int32_t)strlen(w->as.frame.title) * font->charWidth;
int32_t titleW = textWidthAccel(font, w->as.frame.title);
int32_t titleX = w->x + DEFAULT_PADDING + fb;
int32_t titleY = boxY + (fb - font->charHeight) / 2;
rectFill(d, ops, titleX - 2, titleY,
titleW + 4, font->charHeight, bg);
drawText(d, ops, font, titleX, titleY,
drawTextAccel(d, ops, font, titleX, titleY,
w->as.frame.title, fg, bg, true);
}
}
@ -72,6 +72,7 @@ WidgetT *wgtFrame(WidgetT *parent, const char *title) {
w->as.frame.title = title;
w->as.frame.style = FrameInE;
w->as.frame.color = 0;
w->accelKey = accelParse(title);
}
return w;

View file

@ -13,6 +13,7 @@ WidgetT *wgtButton(WidgetT *parent, const char *text) {
if (w) {
w->as.button.text = text;
w->as.button.pressed = false;
w->accelKey = accelParse(text);
}
return w;
@ -24,7 +25,7 @@ WidgetT *wgtButton(WidgetT *parent, const char *text) {
// ============================================================
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
w->calcMinW = (int32_t)strlen(w->as.button.text) * font->charWidth + BUTTON_PAD_H * 2;
w->calcMinW = textWidthAccel(font, w->as.button.text) + BUTTON_PAD_H * 2;
w->calcMinH = font->charHeight + BUTTON_PAD_V * 2;
}
@ -54,7 +55,7 @@ void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
int32_t textW = (int32_t)strlen(w->as.button.text) * font->charWidth;
int32_t textW = textWidthAccel(font, w->as.button.text);
int32_t textX = w->x + (w->w - textW) / 2;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
@ -63,7 +64,7 @@ void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
textY++;
}
drawText(d, ops, font, textX, textY,
drawTextAccel(d, ops, font, textX, textY,
w->as.button.text,
w->enabled ? fg : colors->windowShadow,
bgFace, true);

View file

@ -13,6 +13,7 @@ WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
if (w) {
w->as.checkbox.text = text;
w->as.checkbox.checked = false;
w->accelKey = accelParse(text);
}
return w;
@ -25,7 +26,7 @@ WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
(int32_t)strlen(w->as.checkbox.text) * font->charWidth;
textWidthAccel(font, w->as.checkbox.text);
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
}
@ -73,7 +74,7 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
}
// Draw label
drawText(d, ops, font,
drawTextAccel(d, ops, font,
w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP,
w->y + (w->h - font->charHeight) / 2,
w->as.checkbox.text, fg, bg, false);

View file

@ -57,6 +57,23 @@ WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type) {
}
// ============================================================
// widgetClearFocus
// ============================================================
void widgetClearFocus(WidgetT *root) {
if (!root) {
return;
}
root->focused = false;
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
widgetClearFocus(c);
}
}
// ============================================================
// widgetCountVisibleChildren
// ============================================================
@ -170,6 +187,89 @@ void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t conten
}
// ============================================================
// widgetFindByAccel
// ============================================================
WidgetT *widgetFindByAccel(WidgetT *root, char key) {
if (!root || !root->enabled) {
return NULL;
}
// Invisible tab pages: match the page itself (for tab switching)
// but don't recurse into children (their accels shouldn't be active)
if (!root->visible) {
if (root->type == WidgetTabPageE && root->accelKey == key) {
return root;
}
return NULL;
}
if (root->accelKey == key) {
return root;
}
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
WidgetT *found = widgetFindByAccel(c, key);
if (found) {
return found;
}
}
return NULL;
}
// ============================================================
// widgetFindNextFocusable
// ============================================================
//
// Depth-first walk of the widget tree. Returns the first focusable
// widget found after 'after'. If 'after' is NULL, returns the first
// focusable widget. Wraps around to the beginning if needed.
static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) {
if (!w->visible || !w->enabled) {
return NULL;
}
if (after == NULL) {
*pastAfter = true;
}
if (w == after) {
*pastAfter = true;
} else if (*pastAfter && widgetIsFocusable(w->type)) {
return w;
}
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
WidgetT *found = findNextFocusableImpl(c, after, pastAfter);
if (found) {
return found;
}
}
return NULL;
}
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) {
bool pastAfter = false;
WidgetT *found = findNextFocusableImpl(root, after, &pastAfter);
if (found) {
return found;
}
// Wrap around — search from the beginning
pastAfter = true;
return findNextFocusableImpl(root, NULL, &pastAfter);
}
// ============================================================
// widgetFrameBorderWidth
// ============================================================
@ -220,6 +320,19 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) {
}
// ============================================================
// widgetIsFocusable
// ============================================================
bool widgetIsFocusable(WidgetTypeE type) {
return type == WidgetTextInputE || type == WidgetComboBoxE ||
type == WidgetDropdownE || type == WidgetCheckboxE ||
type == WidgetRadioE || type == WidgetButtonE ||
type == WidgetSliderE || type == WidgetListBoxE ||
type == WidgetTreeViewE || type == WidgetAnsiTermE;
}
// ============================================================
// widgetIsBoxContainer
// ============================================================

View file

@ -57,11 +57,15 @@ extern int32_t sDragOffset;
void widgetAddChild(WidgetT *parent, WidgetT *child);
WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type);
void widgetClearFocus(WidgetT *root);
int32_t widgetCountVisibleChildren(const WidgetT *w);
void widgetDestroyChildren(WidgetT *w);
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
WidgetT *widgetFindByAccel(WidgetT *root, char key);
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
int32_t widgetFrameBorderWidth(const WidgetT *w);
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
bool widgetIsFocusable(WidgetTypeE type);
bool widgetIsBoxContainer(WidgetTypeE type);
bool widgetIsHorizContainer(WidgetTypeE type);
void widgetRemoveChild(WidgetT *parent, WidgetT *child);

View file

@ -12,6 +12,7 @@ WidgetT *wgtLabel(WidgetT *parent, const char *text) {
if (w) {
w->as.label.text = text;
w->accelKey = accelParse(text);
}
return w;
@ -23,8 +24,8 @@ WidgetT *wgtLabel(WidgetT *parent, const char *text) {
// ============================================================
void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
w->calcMinW = (int32_t)strlen(w->as.label.text) * font->charWidth;
w->calcMinH = font->charHeight;
w->calcMinW = textWidthAccel(font, w->as.label.text);
w->calcMinH = font->charHeight + 1;
}
@ -36,6 +37,6 @@ void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
drawText(d, ops, font, w->x, w->y + (w->h - font->charHeight) / 2,
drawTextAccel(d, ops, font, w->x, w->y + (w->h - font->charHeight) / 2,
w->as.label.text, fg, bg, false);
}

View file

@ -396,18 +396,22 @@ void wgtSetText(WidgetT *w, const char *text) {
switch (w->type) {
case WidgetLabelE:
w->as.label.text = text;
w->accelKey = accelParse(text);
break;
case WidgetButtonE:
w->as.button.text = text;
w->accelKey = accelParse(text);
break;
case WidgetCheckboxE:
w->as.checkbox.text = text;
w->accelKey = accelParse(text);
break;
case WidgetRadioE:
w->as.radio.text = text;
w->accelKey = accelParse(text);
break;
case WidgetTextInputE:

View file

@ -12,6 +12,7 @@ WidgetT *wgtRadio(WidgetT *parent, const char *text) {
if (w) {
w->as.radio.text = text;
w->accelKey = accelParse(text);
// Auto-assign index based on position in parent
int32_t idx = 0;
@ -50,7 +51,7 @@ WidgetT *wgtRadioGroup(WidgetT *parent) {
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
(int32_t)strlen(w->as.radio.text) * font->charWidth;
textWidthAccel(font, w->as.radio.text);
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
}
@ -94,7 +95,7 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
CHECKBOX_BOX_SIZE - 6, CHECKBOX_BOX_SIZE - 6, fg);
}
drawText(d, ops, font,
drawTextAccel(d, ops, font,
w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP,
w->y + (w->h - font->charHeight) / 2,
w->as.radio.text, fg, bg, false);

View file

@ -54,6 +54,7 @@ WidgetT *wgtTabPage(WidgetT *parent, const char *title) {
if (w) {
w->as.tabPage.title = title;
w->accelKey = accelParse(title);
}
return w;
@ -79,7 +80,7 @@ void widgetTabControlCalcMinSize(WidgetT *w, const BitmapFontT *font) {
maxPageW = DVX_MAX(maxPageW, c->calcMinW);
maxPageH = DVX_MAX(maxPageH, c->calcMinH);
int32_t labelW = (int32_t)strlen(c->as.tabPage.title) * font->charWidth + TAB_PAD_H * 2;
int32_t labelW = textWidthAccel(font, c->as.tabPage.title) + TAB_PAD_H * 2;
tabHeaderW += labelW;
}
@ -145,7 +146,7 @@ void widgetTabControlOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
continue;
}
int32_t tw = (int32_t)strlen(c->as.tabPage.title) * font->charWidth + TAB_PAD_H * 2;
int32_t tw = textWidthAccel(font, c->as.tabPage.title) + TAB_PAD_H * 2;
if (vx >= tabX && vx < tabX + tw) {
if (tabIdx != hit->as.tabControl.activeTab) {
@ -190,7 +191,7 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
continue;
}
int32_t tw = (int32_t)strlen(c->as.tabPage.title) * font->charWidth + TAB_PAD_H * 2;
int32_t tw = textWidthAccel(font, c->as.tabPage.title) + TAB_PAD_H * 2;
bool isActive = (tabIdx == w->as.tabControl.activeTab);
int32_t ty = isActive ? w->y : w->y + 2;
int32_t th = isActive ? tabH + 2 : tabH;
@ -227,7 +228,7 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
labelY++;
}
drawText(d, ops, font, tabX + TAB_PAD_H, labelY,
drawTextAccel(d, ops, font, tabX + TAB_PAD_H, labelY,
c->as.tabPage.title, colors->contentFg, tabFace, true);
tabX += tw;

View file

@ -14,21 +14,35 @@
#define CMD_FILE_OPEN 101
#define CMD_FILE_SAVE 102
#define CMD_FILE_EXIT 103
#define CMD_HELP_ABOUT 200
#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_HELP_ABOUT 400
// ============================================================
// Prototypes
// ============================================================
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 onToolbarClick(WidgetT *w);
static void onPaintPattern(WindowT *win, RectT *dirtyArea);
static void onPaintText(WindowT *win, RectT *dirtyArea);
static void setupWidgetDemo2(AppContextT *ctx);
static void setupWindows(AppContextT *ctx);
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;
// ============================================================
@ -44,6 +58,19 @@ static void onCloseCb(WindowT *win) {
}
// ============================================================
// onCloseMainCb
// ============================================================
static void onCloseMainCb(WindowT *win) {
AppContextT *ctx = (AppContextT *)win->userData;
if (ctx) {
dvxQuit(ctx);
}
}
// ============================================================
// onMenuCb
// ============================================================
@ -58,8 +85,15 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
}
break;
case CMD_VIEW_TERM:
setupTerminalWindow(ctx);
break;
case CMD_VIEW_CTRL:
setupControlsWindow(ctx);
break;
case CMD_HELP_ABOUT:
// Could create an about dialog window here
break;
}
}
@ -70,7 +104,6 @@ static void onMenuCb(WindowT *win, int32_t menuId) {
// ============================================================
static void onOkClick(WidgetT *w) {
// Find the status label and update it
WidgetT *root = w;
while (root->parent) {
@ -86,26 +119,6 @@ static void onOkClick(WidgetT *w) {
}
// ============================================================
// 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);
}
}
// ============================================================
// onPaintColor
// ============================================================
@ -148,7 +161,7 @@ static void onPaintPattern(WindowT *win, RectT *dirtyArea) {
const DisplayT *d = dvxGetDisplay(ctx);
int32_t bpp = d->format.bytesPerPixel;
int32_t sq = 16; // square size
int32_t sq = 16;
uint32_t c1 = packColor(d, 255, 255, 255);
uint32_t c2 = packColor(d, 0, 0, 180);
@ -187,7 +200,6 @@ static void onPaintText(WindowT *win, RectT *dirtyArea) {
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);
@ -195,7 +207,6 @@ static void onPaintText(WindowT *win, RectT *dirtyArea) {
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",
"",
@ -207,13 +218,11 @@ static void onPaintText(WindowT *win, RectT *dirtyArea) {
" - VESA VBE 2.0+ LFB",
" - Dirty-rect compositing",
" - Beveled Motif-style chrome",
" - Draggable windows",
" - Resizable windows",
" - Menu bars",
" - Draggable/resizable windows",
" - Menu bars with accelerators",
" - Scrollbars",
" - Widget system",
"",
"Press ESC to exit.",
" - ANSI terminal emulator",
NULL
};
@ -272,13 +281,33 @@ static void onPaintText(WindowT *win, RectT *dirtyArea) {
// ============================================================
// setupWidgetDemo2
// 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 setupWidgetDemo2(AppContextT *ctx) {
static void setupControlsWindow(AppContextT *ctx) {
WindowT *win = dvxCreateWindow(ctx, "Advanced Widgets", 380, 50, 320, 400, true);
if (!win) {
@ -294,31 +323,31 @@ static void setupWidgetDemo2(AppContextT *ctx) {
WidgetT *tabs = wgtTabControl(root);
// --- Tab 1: Controls ---
WidgetT *page1 = wgtTabPage(tabs, "Controls");
WidgetT *page1 = wgtTabPage(tabs, "&Controls");
WidgetT *ddRow = wgtHBox(page1);
wgtLabel(ddRow, "Color:");
wgtLabel(ddRow, "Co&lor:");
WidgetT *dd = wgtDropdown(ddRow);
wgtDropdownSetItems(dd, colorItems, 6);
wgtDropdownSetSelected(dd, 0);
WidgetT *cbRow = wgtHBox(page1);
wgtLabel(cbRow, "Size:");
wgtLabel(cbRow, "Si&ze:");
WidgetT *cb = wgtComboBox(cbRow, 32);
wgtComboBoxSetItems(cb, sizeItems, 4);
wgtComboBoxSetSelected(cb, 1);
wgtHSeparator(page1);
wgtLabel(page1, "Progress:");
wgtLabel(page1, "&Progress:");
WidgetT *pb = wgtProgressBar(page1);
wgtProgressBarSetValue(pb, 65);
wgtLabel(page1, "Volume:");
wgtLabel(page1, "&Volume:");
wgtSlider(page1, 0, 100);
// --- Tab 2: Tree ---
WidgetT *page2 = wgtTabPage(tabs, "Tree");
WidgetT *page2 = wgtTabPage(tabs, "&Tree");
WidgetT *tree = wgtTreeView(page2);
WidgetT *docs = wgtTreeItem(tree, "Documents");
@ -338,12 +367,12 @@ static void setupWidgetDemo2(AppContextT *ctx) {
wgtTreeItem(config, "palette.dat");
// --- Tab 3: Toolbar ---
WidgetT *page3 = wgtTabPage(tabs, "Toolbar");
WidgetT *page3 = wgtTabPage(tabs, "Tool&bar");
WidgetT *tb = wgtToolbar(page3);
WidgetT *btnNew = wgtButton(tb, "New");
WidgetT *btnOpen = wgtButton(tb, "Open");
WidgetT *btnSave = wgtButton(tb, "Save");
WidgetT *btnNew = wgtButton(tb, "&New");
WidgetT *btnOpen = wgtButton(tb, "&Open");
WidgetT *btnSave = wgtButton(tb, "&Save");
btnNew->onClick = onToolbarClick;
btnOpen->onClick = onToolbarClick;
btnSave->onClick = onToolbarClick;
@ -362,43 +391,57 @@ static void setupWidgetDemo2(AppContextT *ctx) {
// ============================================================
// setupWindows
// setupMainWindow — info window + paint demos
// ============================================================
static void setupWindows(AppContextT *ctx) {
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 = onCloseCb;
win1->onClose = onCloseMainCb;
win1->onMenu = onMenuCb;
MenuBarT *bar = wmAddMenuBar(win1);
if (bar) {
MenuT *fileMenu = wmAddMenu(bar, "File");
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);
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);
wmAddMenuItem(fileMenu, "E&xit", CMD_FILE_EXIT);
}
MenuT *helpMenu = wmAddMenu(bar, "Help");
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);
}
MenuT *helpMenu = wmAddMenu(bar, "&Help");
if (helpMenu) {
wmAddMenuItem(helpMenu, "About...", CMD_HELP_ABOUT);
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);
}
@ -431,15 +474,105 @@ static void setupWindows(AppContextT *ctx) {
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;
// ============================================================
// setupTerminalWindow — ANSI terminal widget demo
// ============================================================
WidgetT *root = wgtInitWindow(ctx, win4);
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[4mUnderlined\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);
}
// ============================================================
// 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;
WidgetT *root = wgtInitWindow(ctx, win);
// Status label at top
WidgetT *status = wgtLabel(root, "Ready.");
@ -448,38 +581,50 @@ static void setupWindows(AppContextT *ctx) {
wgtHSeparator(root);
// Frame with text input
WidgetT *frame = wgtFrame(root, "User Input");
WidgetT *frame = wgtFrame(root, "&User Input");
WidgetT *row1 = wgtHBox(frame);
wgtLabel(row1, "Name:");
wgtLabel(row1, "&Name:");
wgtTextInput(row1, 64);
WidgetT *row2 = wgtHBox(frame);
wgtLabel(row2, "A&ddress:");
wgtTextInput(row2, 64);
wgtHSeparator(root);
// Checkboxes
wgtCheckbox(root, "Enable feature A");
wgtCheckbox(root, "Enable feature B");
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");
wgtRadio(rg, "Option &1");
wgtRadio(rg, "Option &2");
wgtRadio(rg, "Option &3");
wgtHSeparator(root);
// List box
static const char *listItems[] = {"Alpha", "Beta", "Gamma", "Delta", "Epsilon"};
WidgetT *listRow = wgtHBox(root);
wgtLabel(listRow, "&Items:");
WidgetT *lb = wgtListBox(listRow);
wgtListBoxSetItems(lb, listItems, 5);
lb->weight = 100;
wgtHSeparator(root);
// Button row at bottom
WidgetT *btnRow = wgtHBox(root);
btnRow->align = AlignEndE;
WidgetT *okBtn = wgtButton(btnRow, "OK");
WidgetT *okBtn = wgtButton(btnRow, "&OK");
okBtn->onClick = onOkClick;
wgtButton(btnRow, "Cancel");
wgtButton(btnRow, "&Cancel");
// Layout and paint now that widget tree is complete
wgtInvalidate(root);
}
}
// ============================================================
@ -497,8 +642,12 @@ int main(void) {
return 1;
}
setupWindows(&ctx);
setupWidgetDemo2(&ctx);
sCtx = &ctx;
setupMainWindow(&ctx);
setupWidgetDemo(&ctx);
setupControlsWindow(&ctx);
setupTerminalWindow(&ctx);
dvxRun(&ctx);