MAJOR refactoring. Everything is dynamically loaded now. All new bugs to squash!

This commit is contained in:
Scott Duensing 2026-03-24 23:03:05 -05:00
parent 3b2d87845e
commit 9ee73c8806
111 changed files with 8400 additions and 9686 deletions

View file

@ -1,11 +1,11 @@
# DVX GUI -- Top-level Makefile
#
# Builds the full DVX stack: core library, task switcher,
# bootstrap loader, widgets, shell, and apps.
# bootstrap loader, text help library, widgets, shell, and apps.
.PHONY: all clean core tasks loader widgets shell apps
.PHONY: all clean core tasks loader texthelp listhelp widgets shell apps
all: core tasks loader widgets shell apps
all: core tasks loader texthelp listhelp widgets shell apps
core:
$(MAKE) -C core
@ -16,7 +16,13 @@ tasks:
loader: core tasks
$(MAKE) -C loader
widgets: core tasks
texthelp: core tasks
$(MAKE) -C texthelp
listhelp: core tasks
$(MAKE) -C listhelp
widgets: core tasks texthelp listhelp
$(MAKE) -C widgets
shell: core tasks
@ -29,6 +35,8 @@ clean:
$(MAKE) -C core clean
$(MAKE) -C tasks clean
$(MAKE) -C loader clean
$(MAKE) -C texthelp clean
$(MAKE) -C listhelp clean
$(MAKE) -C widgets clean
$(MAKE) -C shell clean
$(MAKE) -C apps clean

View file

@ -4,7 +4,7 @@ DJGPP_PREFIX = $(HOME)/djgpp/djgpp
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../tasks -I../tasks/thirdparty -I../shell
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../widgets -I../tasks -I../core/thirdparty -I../shell
OBJDIR = ../obj/apps
BINDIR = ../bin/apps

View file

@ -13,6 +13,15 @@
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "dvxWidget.h"
#include "widgetBox.h"
#include "widgetButton.h"
#include "widgetCanvas.h"
#include "widgetDropdown.h"
#include "widgetLabel.h"
#include "widgetListBox.h"
#include "widgetSlider.h"
#include "widgetSpacer.h"
#include "widgetTabControl.h"
#include "dvxWm.h"
#include "dvxPlatform.h"
#include "shellApp.h"

View file

@ -17,6 +17,30 @@
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "widgetAnsiTerm.h"
#include "widgetBox.h"
#include "widgetButton.h"
#include "widgetCanvas.h"
#include "widgetCheckbox.h"
#include "widgetComboBox.h"
#include "widgetDropdown.h"
#include "widgetImage.h"
#include "widgetImageButton.h"
#include "widgetLabel.h"
#include "widgetListBox.h"
#include "widgetProgressBar.h"
#include "widgetRadio.h"
#include "widgetScrollPane.h"
#include "widgetSeparator.h"
#include "widgetSlider.h"
#include "widgetSpacer.h"
#include "widgetSpinner.h"
#include "widgetSplitter.h"
#include "widgetStatusBar.h"
#include "widgetTabControl.h"
#include "widgetTextInput.h"
#include "widgetToolbar.h"
#include "widgetTreeView.h"
#include "dvxWm.h"
#include "shellApp.h"
@ -105,11 +129,8 @@ AppDescriptorT appDescriptor = {
// ============================================================
static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
if (drag && w->as.canvas.lastX >= 0) {
wgtCanvasDrawLine(w, w->as.canvas.lastX, w->as.canvas.lastY, cx, cy);
} else {
wgtCanvasDrawLine(w, cx, cy, cx, cy);
}
(void)drag;
wgtCanvasDrawLine(w, cx, cy, cx, cy);
}
@ -709,12 +730,12 @@ static void setupControlsWindow(void) {
wgtButton(enCol, "A &button");
WidgetT *enChk = wgtCheckbox(enCol, "&Check me");
enChk->as.checkbox.checked = true;
wgtCheckboxSetChecked(enChk, true);
WidgetT *enRg = wgtRadioGroup(enCol);
wgtRadio(enRg, "&Radio 1");
WidgetT *enR2 = wgtRadio(enRg, "R&adio 2");
enRg->as.radioGroup.selectedIdx = enR2->as.radio.index;
wgtRadioGroupSetSelected(enRg, wgtRadioGetIndex(enR2));
static const char *disItems[] = {"Alpha", "Beta", "Gamma"};
@ -747,13 +768,13 @@ static void setupControlsWindow(void) {
wgtSetEnabled(dBtn, false);
WidgetT *dChk = wgtCheckbox(disCol, "&Check me");
dChk->as.checkbox.checked = true;
wgtCheckboxSetChecked(dChk, 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;
wgtRadioGroupSetSelected(dRg, wgtRadioGetIndex(dR2));
wgtSetEnabled(dR1, false);
wgtSetEnabled(dR2, false);

View file

@ -13,6 +13,7 @@
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "widgetTextInput.h"
#include "dvxWm.h"
#include "dvxPlatform.h"
#include "shellApp.h"

View file

@ -22,6 +22,11 @@
#include "dvxDialog.h"
#include "dvxPrefs.h"
#include "dvxWidget.h"
#include "widgetBox.h"
#include "widgetButton.h"
#include "widgetLabel.h"
#include "widgetStatusBar.h"
#include "widgetTextInput.h"
#include "dvxWm.h"
#include "dvxPlatform.h"
#include "shellApp.h"
@ -195,7 +200,6 @@ static void buildPmWindow(void) {
btn->onDblClick = onAppButtonClick;
btn->onClick = onAppButtonClick;
}
}
// Status bar at bottom; weight=100 on the label makes it fill the bar

2
config/combobox.dep Normal file
View file

@ -0,0 +1,2 @@
texthelp
listhelp

1
config/dropdown.dep Normal file
View file

@ -0,0 +1 @@
listhelp

View file

@ -1,14 +1,4 @@
libtasks
libdvx
box
button
checkbox
dropdown
label
listbox
listview
radio
separator
spacer
statbar
textinpt
texthelp
listhelp

1
config/listbox.dep Normal file
View file

@ -0,0 +1 @@
listhelp

2
config/listhelp.dep Normal file
View file

@ -0,0 +1,2 @@
libtasks
libdvx

1
config/listview.dep Normal file
View file

@ -0,0 +1 @@
listhelp

1
config/spinner.dep Normal file
View file

@ -0,0 +1 @@
texthelp

1
config/terminal.dep Normal file
View file

@ -0,0 +1 @@
texthelp

2
config/texthelp.dep Normal file
View file

@ -0,0 +1,2 @@
libtasks
libdvx

1
config/textinpt.dep Normal file
View file

@ -0,0 +1 @@
texthelp

1
config/treeview.dep Normal file
View file

@ -0,0 +1 @@
listhelp

View file

@ -7,13 +7,13 @@
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -Iplatform -I../tasks
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -Iplatform -I../tasks -Ithirdparty
OBJDIR = ../obj/core
LIBSDIR = ../bin/libs
# Core sources
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c \
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxImage.c dvxImageWrite.c \
dvxApp.c dvxDialog.c dvxPrefs.c \
widgetClass.c widgetCore.c widgetScrollbar.c \
widgetLayout.c widgetEvent.c widgetOps.c
@ -28,8 +28,7 @@ DVX_EXPORTS = -E _dvx -E _wgt -E _wm -E _prefs -E _rect -E _draw -E _pack -E _te
-E _sCursor -E _sDbl -E _sDebug -E _sClosed -E _sFocused -E _sKey \
-E _sOpen -E _sPressed -E _sDrag -E _sDrawing -E _sResize \
-E _sListView -E _sSplitter -E _sTreeView \
-E _accelParse \
-E _clearOther -E _clipboard -E _isWord -E _multiClick -E _wordStart -E _wordEnd
-E _accelParse -E _clipboard -E _multiClick
.PHONY: all clean
@ -39,7 +38,8 @@ $(LIBSDIR)/libdvx.dep: ../config/libdvx.dep | $(LIBSDIR)
sed 's/$$/\r/' $< > $@
$(TARGET): $(OBJS) | $(LIBSDIR)
$(DXE3GEN) -o $@ $(DVX_EXPORTS) -U $(OBJS)
$(DXE3GEN) -o $(LIBSDIR)/libdvx.dxe $(DVX_EXPORTS) -U $(OBJS)
mv $(LIBSDIR)/libdvx.dxe $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
@ -56,13 +56,13 @@ $(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h dvxWidget.h thirdparty/stb_image.h
$(OBJDIR)/dvxIcon.o: dvxIcon.c thirdparty/stb_image.h
$(OBJDIR)/dvxImage.o: dvxImage.c thirdparty/stb_image.h
$(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h widgetInternal.h dvxTypes.h dvxDraw.h
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h dvxWidgetPlugin.h dvxTypes.h dvxDraw.h
$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h
WIDGET_DEPS = widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
WIDGET_DEPS = dvxWidgetPlugin.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
$(OBJDIR)/widgetClass.o: widgetClass.c $(WIDGET_DEPS)
$(OBJDIR)/widgetCore.o: widgetCore.c $(WIDGET_DEPS)
$(OBJDIR)/widgetScrollbar.o: widgetScrollbar.c $(WIDGET_DEPS)

View file

@ -75,7 +75,7 @@ DXE modules in `../widgets/`.
| File | Purpose |
|------|---------|
| `widgetInternal.h` | Internal header for widget implementation files: vtable externs, shared state, helper prototypes |
| `dvxWidgetPlugin.h` | Internal header for widget implementation files: vtable externs, shared state, helper prototypes |
| `widgetClass.c` | Widget class table (`widgetClassTable[]`, starts empty) and `wgtRegisterClass()` dynamic registration |
| `widgetCore.c` | Widget allocation, tree ops, hit testing, focus management, shared helpers |
| `widgetScrollbar.c` | Widget-internal scrollbar drawing and hit testing (shared by ListBox, TreeView, etc.) |

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// dvx_app.c -- Layer 5: Application API for DVX GUI
//
// Top-level layer of the DVX windowing system. This is the only layer
@ -31,7 +32,8 @@
#include "dvxApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "../widgets/widgetTimer.h"
#include "dvxFont.h"
#include "dvxCursor.h"
@ -119,8 +121,8 @@ static void openContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t
static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx);
static void openSubMenu(AppContextT *ctx);
static void openSysMenu(AppContextT *ctx, WindowT *win);
static void pollAnsiTermWidgets(AppContextT *ctx);
static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win);
static void pollWidgets(AppContextT *ctx);
static void pollWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win);
static void pollKeyboard(AppContextT *ctx);
static void pollMouse(AppContextT *ctx);
static void refreshMinimizedIcons(AppContextT *ctx);
@ -713,135 +715,31 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
if (win->widgetRoot) {
WidgetT *target = widgetFindByAccel(win->widgetRoot, key);
if (target) {
switch (target->type) {
case WidgetButtonE:
if (target && target->wclass) {
if (target->wclass->flags & WCLASS_FOCUS_FORWARD) {
// Label/Frame: focus-forward to next focusable
WidgetT *next = widgetFindNextFocusable(win->widgetRoot, target);
if (next) {
if (sFocusedWidget) { sFocusedWidget->focused = false; }
sFocusedWidget = target;
target->focused = true;
target->as.button.pressed = true;
sKeyPressedBtn = target;
wgtInvalidate(target);
return true;
sFocusedWidget = next;
next->focused = true;
case WidgetCheckboxE:
if (sFocusedWidget) { sFocusedWidget->focused = false; }
if (target->wclass && target->wclass->onMouse) {
target->wclass->onMouse(target, win->widgetRoot, 0, 0);
}
sFocusedWidget = target;
wgtInvalidate(target);
return true;
case WidgetRadioE:
if (sFocusedWidget) { sFocusedWidget->focused = false; }
if (target->wclass && target->wclass->onMouse) {
target->wclass->onMouse(target, win->widgetRoot, 0, 0);
}
sFocusedWidget = target;
wgtInvalidate(target);
return true;
case WidgetImageButtonE:
if (sFocusedWidget) { sFocusedWidget->focused = false; }
sFocusedWidget = target;
target->focused = true;
target->as.imageButton.pressed = true;
sKeyPressedBtn = target;
wgtInvalidate(target);
return true;
case WidgetTabPageE:
{
// Close any open dropdown/combobox popup
if (sOpenPopup) {
if (sOpenPopup->type == WidgetDropdownE) {
sOpenPopup->as.dropdown.open = false;
} else if (sOpenPopup->type == WidgetComboBoxE) {
sOpenPopup->as.comboBox.open = false;
}
sOpenPopup = NULL;
if (next->wclass && next->wclass->onAccelActivate) {
next->wclass->onAccelActivate(next, win->widgetRoot);
}
// Activate this tab in its parent TabControl
if (target->parent && target->parent->type == WidgetTabControlE) {
int32_t tabIdx = 0;
for (WidgetT *c = target->parent->firstChild; c; c = c->nextSibling) {
if (c == target) {
wgtTabControlSetActive(target->parent, tabIdx);
break;
}
if (c->type == WidgetTabPageE) {
tabIdx++;
}
}
}
return true;
}
case WidgetDropdownE:
target->as.dropdown.open = true;
target->as.dropdown.hoverIdx = target->as.dropdown.selectedIdx;
sOpenPopup = target;
if (sFocusedWidget) { sFocusedWidget->focused = false; }
sFocusedWidget = target;
target->focused = true;
wgtInvalidate(win->widgetRoot);
return true;
case WidgetComboBoxE:
target->as.comboBox.open = true;
target->as.comboBox.hoverIdx = target->as.comboBox.selectedIdx;
sOpenPopup = target;
if (sFocusedWidget) { sFocusedWidget->focused = false; }
sFocusedWidget = target;
target->focused = true;
wgtInvalidate(win->widgetRoot);
return true;
case WidgetLabelE:
case WidgetFrameE:
{
// Focus the next focusable widget
WidgetT *next = widgetFindNextFocusable(win->widgetRoot, target);
if (next) {
if (sFocusedWidget) { sFocusedWidget->focused = false; }
sFocusedWidget = next;
next->focused = true;
// Open dropdown/combobox if that's the focused target
if (next->type == WidgetDropdownE) {
next->as.dropdown.open = true;
next->as.dropdown.hoverIdx = next->as.dropdown.selectedIdx;
sOpenPopup = next;
} else if (next->type == WidgetComboBoxE) {
next->as.comboBox.open = true;
next->as.comboBox.hoverIdx = next->as.comboBox.selectedIdx;
sOpenPopup = next;
}
wgtInvalidate(win->widgetRoot);
}
return true;
}
default:
// For focusable widgets, just focus them
if (widgetIsFocusable(target->type)) {
if (sFocusedWidget) { sFocusedWidget->focused = false; }
sFocusedWidget = target;
target->focused = true;
wgtInvalidate(win->widgetRoot);
return true;
}
break;
} else if (target->wclass->onAccelActivate) {
if (sFocusedWidget) { sFocusedWidget->focused = false; }
sFocusedWidget = target;
target->focused = true;
target->wclass->onAccelActivate(target, win->widgetRoot);
wgtInvalidate(win->widgetRoot);
}
return true;
}
}
@ -1237,20 +1135,12 @@ static void dispatchEvents(AppContextT *ctx) {
// itself or a ScrollPane ancestor that contains it.
WidgetT *target = NULL;
if (focus->type == WidgetListBoxE ||
focus->type == WidgetListViewE ||
focus->type == WidgetTreeViewE ||
focus->type == WidgetTextAreaE ||
focus->type == WidgetAnsiTermE ||
focus->type == WidgetSliderE ||
focus->type == WidgetSpinnerE ||
focus->type == WidgetDropdownE ||
focus->type == WidgetComboBoxE) {
if (focus->wclass && (focus->wclass->flags & WCLASS_SCROLLABLE)) {
target = focus;
} else {
// Walk up to find a ScrollPane ancestor
for (WidgetT *p = focus->parent; p; p = p->parent) {
if (p->type == WidgetScrollPaneE) {
if (p->wclass && (p->wclass->flags & WCLASS_SCROLL_CONTAINER)) {
target = p;
break;
}
@ -1704,7 +1594,7 @@ void dvxResetColorScheme(AppContextT *ctx) {
// Arranges windows in the classic cascade pattern: each window is the same
// size (2/3 of screen), offset diagonally by the title bar height so each
// title bar remains visible. When the cascade would go off-screen, it wraps
// back to (0,0). This matches DESQview/X and Windows 3.x cascade behavior.
// back to (0,0). This matches the Windows 3.x cascade behavior.
// The step size is title_height + border_width so exactly one title bar's
// worth of the previous window peeks out above and to the left.
@ -2692,7 +2582,7 @@ bool dvxUpdate(AppContextT *ctx) {
pollKeyboard(ctx);
dispatchEvents(ctx);
updateTooltip(ctx);
pollAnsiTermWidgets(ctx);
pollWidgets(ctx);
wgtUpdateCursorBlink();
wgtUpdateTimers();
@ -2715,10 +2605,8 @@ bool dvxUpdate(AppContextT *ctx) {
// onClick. The one-frame delay ensures the pressed visual state
// renders before the callback runs (which may open a dialog, etc.).
if (sKeyPressedBtn) {
if (sKeyPressedBtn->type == WidgetImageButtonE) {
sKeyPressedBtn->as.imageButton.pressed = false;
} else {
sKeyPressedBtn->as.button.pressed = false;
if (sKeyPressedBtn->wclass && sKeyPressedBtn->wclass->setPressed) {
sKeyPressedBtn->wclass->setPressed(sKeyPressedBtn, false);
}
if (sKeyPressedBtn->onClick) {
@ -3088,7 +2976,7 @@ static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win) {
// ============================================================
//
// Executes a system menu (window control menu) command. The system menu
// is the DESQview/X equivalent of the Win3.x control-menu box -- it
// is the DVX equivalent of the Win3.x control-menu box -- it
// provides Restore, Move, Size, Minimize, Maximize, and Close. Keyboard
// move/resize mode is entered by setting kbMoveResize state, which causes
// pollKeyboard to intercept arrow keys until Enter/Esc.
@ -3188,7 +3076,7 @@ static WindowT *findWindowById(AppContextT *ctx, int32_t id) {
// hitPart 0: content area (forwarded to window's onMouse callback)
// hitPart 1: title bar (begins mouse drag)
// hitPart 2: close/sys-menu gadget (single-click opens sys menu,
// double-click closes -- DESQview/X convention)
// double-click closes -- DVX convention)
// hitPart 3: resize border (begins edge/corner resize)
// hitPart 4: menu bar (opens popup for clicked menu)
// hitPart 5/6: vertical/horizontal scrollbar
@ -3371,7 +3259,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
// by GEOS Ensemble with Motif-style 3D bevels: teal desktop, grey window
// chrome with white highlights and dark shadows to create the raised/sunken
// illusion. The dark charcoal active title bar distinguishes it from
// GEOS's blue, giving DV/X its own identity.
// GEOS's blue, giving DVX its own identity.
static void initColorScheme(AppContextT *ctx) {
// Load defaults into the RGB source array, then pack all at once
@ -3660,56 +3548,37 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) {
// ============================================================
// pollAnsiTermWidgets -- poll and repaint all ANSI term widgets
// pollWidgets -- poll widgets that need periodic updates
// ============================================================
//
// ANSI terminal widgets have asynchronous data sources (PTYs, serial
// ports) that produce output between frames. This function walks every
// window's widget tree looking for AnsiTerm widgets, polls them for new
// data, and if data arrived, triggers a targeted repaint of just the
// affected rows. The fine-grained dirty rect (just the changed rows
// rather than the whole window) is critical for terminal performance --
// a single character echo should only flush one row to the LFB, not
// the entire terminal viewport.
// Widgets with WCLASS_NEEDS_POLL (e.g. ANSI terminals with asynchronous
// data sources) need periodic polling between frames. This function walks
// every window's widget tree and calls the poll vtable method on matching
// widgets. The poll method handles data ingestion and targeted dirty
// rect generation for efficient repainting.
static void pollAnsiTermWidgets(AppContextT *ctx) {
static void pollWidgets(AppContextT *ctx) {
for (int32_t i = 0; i < ctx->stack.count; i++) {
WindowT *win = ctx->stack.windows[i];
if (win->widgetRoot) {
pollAnsiTermWidgetsWalk(ctx, win->widgetRoot, win);
pollWidgetsWalk(ctx, win->widgetRoot, win);
}
}
}
// ============================================================
// pollAnsiTermWidgetsWalk -- recursive helper
// pollWidgetsWalk -- recursive helper
// ============================================================
static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) {
if (w->type == WidgetAnsiTermE) {
wgtAnsiTermPoll(w);
int32_t dirtyY = 0;
int32_t dirtyH = 0;
if (wgtAnsiTermRepaint(w, &dirtyY, &dirtyH) > 0) {
win->contentDirty = true;
// Dirty only the affected rows (in screen coords) instead of
// the entire window. This dramatically reduces compositor and
// LFB flush work for cursor blink and single-row updates.
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
int32_t rectX = win->x + win->contentX;
int32_t rectY = win->y + win->contentY + dirtyY - scrollY;
int32_t rectW = win->contentW;
dirtyListAdd(&ctx->dirty, rectX, rectY, rectW, dirtyH);
}
static void pollWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) {
if (w->wclass && (w->wclass->flags & WCLASS_NEEDS_POLL) && w->wclass->poll) {
w->wclass->poll(w, win);
}
for (WidgetT *child = w->firstChild; child; child = child->nextSibling) {
pollAnsiTermWidgetsWalk(ctx, child, win);
pollWidgetsWalk(ctx, child, win);
}
}
@ -4273,16 +4142,16 @@ static void pollKeyboard(AppContextT *ctx) {
int32_t popY;
int32_t popW;
int32_t popH;
widgetDropdownPopupRect(sOpenPopup, &ctx->font, popWin->contentH, &popX, &popY, &popW, &popH);
if (sOpenPopup->wclass && sOpenPopup->wclass->getPopupRect) {
sOpenPopup->wclass->getPopupRect(sOpenPopup, &ctx->font, popWin->contentH, &popX, &popY, &popW, &popH);
}
dirtyListAdd(&ctx->dirty, popWin->x + popWin->contentX + popX, popWin->y + popWin->contentY + popY, popW, popH);
WidgetT *closing = sOpenPopup;
sOpenPopup = NULL;
if (closing->type == WidgetDropdownE) {
closing->as.dropdown.open = false;
} else if (closing->type == WidgetComboBoxE) {
closing->as.comboBox.open = false;
if (closing->wclass && closing->wclass->closePopup) {
closing->wclass->closePopup(closing);
}
wgtInvalidate(closing);
@ -4323,8 +4192,8 @@ static void pollKeyboard(AppContextT *ctx) {
WidgetT *w = fstack[--ftop];
if (w->focused && widgetIsFocusable(w->type)) {
// Don't tab out of the terminal -- it swallows Tab
if (w->type == WidgetAnsiTermE) {
// Don't tab out of widgets that swallow Tab
if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
current = NULL;
break;
}
@ -4350,7 +4219,7 @@ static void pollKeyboard(AppContextT *ctx) {
while (ftop > 0) {
WidgetT *w = fstack[--ftop];
if (w->focused && w->type == WidgetAnsiTermE) {
if (w->focused && w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
termFocused = true;
break;
}
@ -4592,7 +4461,9 @@ static void updateCursorShape(AppContextT *ctx) {
}
// Active splitter drag
else if (sDragSplitter) {
newCursor = sDragSplitter->as.splitter.vertical ? CURSOR_RESIZE_H : CURSOR_RESIZE_V;
if (sDragSplitter->wclass && sDragSplitter->wclass->getCursorShape) {
newCursor = sDragSplitter->wclass->getCursorShape(sDragSplitter, 0, 0);
}
}
// Not in an active drag/resize -- check what we're hovering
else if (ctx->stack.dragWindow < 0 && ctx->stack.scrollWindow < 0) {
@ -4632,38 +4503,12 @@ static void updateCursorShape(AppContextT *ctx) {
WidgetT *hit = widgetHitTest(win->widgetRoot, vx, vy);
if (hit && hit->type == WidgetListViewE && sListViewColBorderHitFn && sListViewColBorderHitFn(hit, vx, vy)) {
newCursor = CURSOR_RESIZE_H;
}
if (hit && hit->wclass && hit->wclass->getCursorShape) {
int32_t shape = hit->wclass->getCursorShape(hit, vx, vy);
// Walk into splitters (NO_HIT_RECURSE stops widgetHitTest at the outermost one)
while (hit && hit->type == WidgetSplitterE) {
int32_t pos = hit->as.splitter.dividerPos;
bool onBar;
if (hit->as.splitter.vertical) {
int32_t barX = hit->x + pos;
onBar = (vx >= barX && vx < barX + SPLITTER_BAR_W);
} else {
int32_t barY = hit->y + pos;
onBar = (vy >= barY && vy < barY + SPLITTER_BAR_W);
if (shape > 0) {
newCursor = shape;
}
if (onBar) {
newCursor = hit->as.splitter.vertical ? CURSOR_RESIZE_H : CURSOR_RESIZE_V;
break;
}
// Not on this splitter's bar -- check children for nested splitters
WidgetT *inner = NULL;
for (WidgetT *c = hit->firstChild; c; c = c->nextSibling) {
if (c->visible && vx >= c->x && vx < c->x + c->w && vy >= c->y && vy < c->y + c->h) {
inner = c;
}
}
hit = inner;
}
}
}

View file

@ -72,7 +72,7 @@ typedef struct AppContextT {
// buffer down to icon size.
int32_t iconRefreshIdx; // next minimized icon to refresh (staggered)
int32_t frameCount; // frame counter for periodic tasks
// The idle callback allows the host application (e.g. the DV/X shell)
// The idle callback allows the host application (e.g. the DVX shell)
// to do background work (poll serial ports, service DXE apps) during
// frames where no GUI events occurred, instead of just yielding the CPU.
void (*idleCallback)(void *ctx); // called instead of yield when non-NULL
@ -184,7 +184,7 @@ void dvxShutdown(AppContextT *ctx);
void dvxRun(AppContextT *ctx);
// Process exactly one frame of the event loop. Provided for applications
// that need to integrate the GUI into their own main loop (e.g. the DV/X
// that need to integrate the GUI into their own main loop (e.g. the DVX
// shell polling serial ports between frames). Returns false when the GUI
// wants to exit.
bool dvxUpdate(AppContextT *ctx);

View file

@ -23,7 +23,14 @@
#include "dvxDialog.h"
#include "dvxPlatform.h"
#include "dvxWidget.h"
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "../widgets/widgetBox.h"
#include "../widgets/widgetButton.h"
#include "../widgets/widgetDropdown.h"
#include "../widgets/widgetLabel.h"
#include "../widgets/widgetListBox.h"
#include "../widgets/widgetSpacer.h"
#include "../widgets/widgetTextInput.h"
#include <ctype.h>
#include <dirent.h>
@ -864,7 +871,6 @@ static void fdLoadDir(void) {
wgtListBoxSetItems(sFd.fileList, sFd.listItems, sFd.entryCount);
wgtListBoxSetSelected(sFd.fileList, 0);
sFd.fileList->as.listBox.scrollPos = 0;
// Update path display
wgtSetText(sFd.pathInput, sFd.curDir);

View file

@ -169,16 +169,16 @@ static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *
// drawBevel
// ============================================================
//
// Draws a Motif/DESQview-style beveled rectangular frame. The bevel
// Draws a Motif-style beveled rectangular frame. The bevel
// creates the illusion of a raised or sunken 3D surface by drawing
// lighter "highlight" edges on the top and left, and darker "shadow"
// edges on the bottom and right. Swapping highlight and shadow gives
// a sunken appearance (see BEVEL_RAISED/BEVEL_SUNKEN macros in
// dvxTypes.h).
//
// BevelStyleT.width controls the border thickness. DV/X uses 2px
// bevels for most window chrome (matching the original DESQview/X
// and Motif look), 1px for inner borders and scrollbar elements.
// BevelStyleT.width controls the border thickness. DVX uses 2px
// bevels for most window chrome (matching the Motif look), 1px for
// inner borders and scrollbar elements.
//
// The implementation has special-cased fast paths for bw==2 and bw==1
// that emit exact spans via rectFill rather than looping. This

View file

@ -1,11 +1,11 @@
// dvx_font.h -- Embedded VGA bitmap font data (CP437) for DVX GUI
//
// Contains the raw glyph bitmaps for two standard VGA ROM fonts (8x14 and
// 8x16) covering the full IBM Code Page 437 character set (256 glyphs).
// These are byte-for-byte copies of the fonts stored in the VGA BIOS ROM.
// Contains the raw glyph bitmaps for the 8x16 VGA ROM font covering the
// full IBM Code Page 437 character set (256 glyphs). This is a byte-for-
// byte copy of the font stored in the VGA BIOS ROM.
//
// Embedding them as static const arrays (rather than reading from the VGA
// ROM at INT 10h) serves two purposes:
// Embedding as a static const array (rather than reading from the VGA ROM
// at INT 10h) serves two purposes:
// 1. The data is available on non-DOS platforms (Linux/SDL) where no
// VGA BIOS exists.
// 2. On DOS, reading the VGA ROM font requires a real-mode interrupt
@ -13,13 +13,13 @@
// anyway. Compiling it in is simpler and more portable.
//
// The glyph format is 1 bit per pixel, 8 pixels wide, with MSB = leftmost
// pixel. Each glyph occupies charHeight consecutive bytes (14 or 16).
// The drawing code in dvxDraw.c processes one byte per scanline, testing
// each bit to decide foreground vs. background -- the 8-pixel width means
// no bit-shifting across byte boundaries is ever needed.
// pixel. Each glyph occupies 16 consecutive bytes. The drawing code in
// dvxDraw.c processes one byte per scanline, testing each bit to decide
// foreground vs. background -- the 8-pixel width means no bit-shifting
// across byte boundaries is ever needed.
//
// CP437 includes the full ASCII printable range (32-126) plus box-drawing
// characters (176-223), which are essential for the Motif/DV/X look
// characters (176-223), which are essential for the Motif/DVX look
// (scrollbar arrows, window close gadget, etc.), as well as international
// characters, math symbols, and the classic smiley/card suit characters
// in positions 1-31.
@ -29,529 +29,7 @@
#include "dvxTypes.h"
// ============================================================
// 8x14 VGA ROM font -- standard IBM CP437 character set
// 256 glyphs, 14 bytes per glyph, MSB = leftmost pixel
// ============================================================
static const uint8_t font8x14[256 * 14] = {
// Char 0: NULL (blank)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 1: Smiley (outline)
0x00, 0x00, 0x7E, 0x81, 0xA5, 0x81, 0x81, 0xBD, 0x99, 0x81, 0x7E, 0x00, 0x00, 0x00,
// Char 2: Smiley (filled)
0x00, 0x00, 0x7E, 0xFF, 0xDB, 0xFF, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E, 0x00, 0x00, 0x00,
// Char 3: Heart
0x00, 0x00, 0x00, 0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
// Char 4: Diamond
0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
// Char 5: Club
0x00, 0x00, 0x18, 0x3C, 0x3C, 0xE7, 0xE7, 0xE7, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 6: Spade
0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00,
// Char 7: Bullet
0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 8: Inverse bullet
0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
// Char 9: Circle (outline)
0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 10: Inverse circle
0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0xBD, 0x99, 0xC3, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
// Char 11: Male sign
0x00, 0x00, 0x1E, 0x0E, 0x1A, 0x32, 0x78, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00,
// Char 12: Female sign
0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 13: Note single
0x00, 0x00, 0x3F, 0x33, 0x3F, 0x30, 0x30, 0x30, 0x70, 0xF0, 0xE0, 0x00, 0x00, 0x00,
// Char 14: Note double
0x00, 0x00, 0x7F, 0x63, 0x7F, 0x63, 0x63, 0x63, 0x67, 0xE7, 0xE6, 0xC0, 0x00, 0x00,
// Char 15: Sun
0x00, 0x00, 0x18, 0xDB, 0x3C, 0xE7, 0x3C, 0xDB, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 16: Right triangle
0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00,
// Char 17: Left triangle
0x00, 0x00, 0x02, 0x06, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00,
// Char 18: Up-down arrow
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
// Char 19: Double exclamation
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
// Char 20: Paragraph
0x00, 0x00, 0x7F, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00,
// Char 21: Section
0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0x6C, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00,
// Char 22: Bottom bar
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0xFE, 0x00, 0x00, 0x00,
// Char 23: Up-down arrow with base
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00,
// Char 24: Up arrow
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Char 25: Down arrow
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
// Char 26: Right arrow
0x00, 0x00, 0x00, 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 27: Left arrow
0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 28: Right angle
0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 29: Left-right arrow
0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xFF, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 30: Up triangle
0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7C, 0x7C, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Char 31: Down triangle
0x00, 0x00, 0x00, 0xFE, 0xFE, 0x7C, 0x7C, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
// Char 32: Space
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 33: !
0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 34: "
0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 35: #
0x00, 0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
// Char 36: $
0x00, 0x18, 0x18, 0x7C, 0xC6, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00,
// Char 37: %
0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18, 0x30, 0x66, 0xC6, 0x00, 0x00, 0x00,
// Char 38: &
0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 39: '
0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 40: (
0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00,
// Char 41: )
0x00, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00,
// Char 42: *
0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 43: +
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 44: ,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00,
// Char 45: -
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 46: .
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 47: /
0x00, 0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00,
// Char 48: 0
0x00, 0x00, 0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 49: 1
0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00,
// Char 50: 2
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00,
// Char 51: 3
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 52: 4
0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00,
// Char 53: 5
0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xFC, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 54: 6
0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 55: 7
0x00, 0x00, 0xFE, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00,
// Char 56: 8
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 57: 9
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x0C, 0x78, 0x00, 0x00, 0x00,
// Char 58: :
0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
// Char 59: ;
0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
// Char 60: <
0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00,
// Char 61: =
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 62: >
0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00,
// Char 63: ?
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 64: @
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE, 0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00,
// Char 65: A
0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 66: B
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00,
// Char 67: C
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00,
// Char 68: D
0x00, 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00,
// Char 69: E
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00,
// Char 70: F
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00,
// Char 71: G
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xDE, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00,
// Char 72: H
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 73: I
0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 74: J
0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00,
// Char 75: K
0x00, 0x00, 0xE6, 0x66, 0x6C, 0x6C, 0x78, 0x6C, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00,
// Char 76: L
0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00,
// Char 77: M
0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 78: N
0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 79: O
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00,
// Char 80: P
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00,
// Char 81: Q
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x0C, 0x0E, 0x00, 0x00,
// Char 82: R
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00,
// Char 83: S
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x60, 0x38, 0x0C, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 84: T
0x00, 0x00, 0x7E, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 85: U
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 86: V
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00,
// Char 87: W
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xFE, 0x7C, 0x6C, 0x00, 0x00, 0x00,
// Char 88: X
0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x38, 0x6C, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 89: Y
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 90: Z
0x00, 0x00, 0xFE, 0xC6, 0x8C, 0x18, 0x30, 0x60, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00,
// Char 91: [
0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00,
// Char 92: backslash
0x00, 0x00, 0x80, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00,
// Char 93: ]
0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00,
// Char 94: ^
0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 95: _
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00,
// Char 96: `
0x00, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 97: a
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 98: b
0x00, 0x00, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00,
// Char 99: c
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 100: d
0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 101: e
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 102: f
0x00, 0x00, 0x1C, 0x36, 0x32, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00,
// Char 103: g
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00,
// Char 104: h
0x00, 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00,
// Char 105: i
0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 106: j
0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00,
// Char 107: k
0x00, 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00,
// Char 108: l
0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 109: m
0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xD6, 0xD6, 0xC6, 0x00, 0x00, 0x00,
// Char 110: n
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00,
// Char 111: o
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 112: p
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00,
// Char 113: q
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00,
// Char 114: r
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00,
// Char 115: s
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x70, 0x1C, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 116: t
0x00, 0x00, 0x10, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00,
// Char 117: u
0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 118: v
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00,
// Char 119: w
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00,
// Char 120: x
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00,
// Char 121: y
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0x00,
// Char 122: z
0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCC, 0x18, 0x30, 0x66, 0xFE, 0x00, 0x00, 0x00,
// Char 123: {
0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00,
// Char 124: |
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 125: }
0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00,
// Char 126: ~
0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 127: House/Delete
0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Char 128: C-cedilla (upper)
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, 0xC2, 0x66, 0x3C, 0x0C, 0x06, 0x7C,
// Char 129: u-diaeresis
0x00, 0x00, 0xCC, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 130: e-acute
0x00, 0x0C, 0x18, 0x30, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 131: a-circumflex
0x00, 0x10, 0x38, 0x6C, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 132: a-diaeresis
0x00, 0x00, 0xCC, 0xCC, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 133: a-grave
0x00, 0x60, 0x30, 0x18, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 134: a-ring
0x00, 0x38, 0x6C, 0x38, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 135: c-cedilla
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC0, 0xC6, 0x7C, 0x18, 0x0C, 0x78,
// Char 136: e-circumflex
0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 137: e-diaeresis
0x00, 0x00, 0xC6, 0xC6, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 138: e-grave
0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 139: i-diaeresis
0x00, 0x00, 0x66, 0x66, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 140: i-circumflex
0x00, 0x18, 0x3C, 0x66, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 141: i-grave
0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 142: A-diaeresis
0x00, 0xC6, 0xC6, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 143: A-ring
0x38, 0x6C, 0x38, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 144: E-acute
0x0C, 0x18, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x66, 0xFE, 0x00, 0x00, 0x00,
// Char 145: ae ligature
0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x36, 0x76, 0xDC, 0xD8, 0x6E, 0x00, 0x00, 0x00,
// Char 146: AE ligature
0x00, 0x00, 0x3E, 0x6C, 0xCC, 0xCC, 0xFE, 0xCC, 0xCC, 0xCC, 0xCE, 0x00, 0x00, 0x00,
// Char 147: o-circumflex
0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 148: o-diaeresis
0x00, 0x00, 0xC6, 0xC6, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 149: o-grave
0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 150: u-circumflex
0x00, 0x30, 0x78, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 151: u-grave
0x00, 0x60, 0x30, 0x18, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 152: y-diaeresis
0x00, 0x00, 0xC6, 0xC6, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0x78, 0x00,
// Char 153: O-diaeresis
0x00, 0xC6, 0xC6, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00,
// Char 154: U-diaeresis
0x00, 0xC6, 0xC6, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 155: Cent sign
0x00, 0x18, 0x18, 0x7C, 0xC6, 0xC0, 0xC0, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 156: Pound sterling
0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, 0x60, 0xE6, 0xFC, 0x00, 0x00, 0x00,
// Char 157: Yen sign
0x00, 0x00, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 158: Peseta
0x00, 0xF8, 0xCC, 0xCC, 0xF8, 0xC4, 0xCC, 0xDE, 0xCC, 0xCC, 0xC6, 0x00, 0x00, 0x00,
// Char 159: f-hook
0x00, 0x0E, 0x1B, 0x18, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x18, 0xD8, 0x70, 0x00, 0x00,
// Char 160: a-acute
0x00, 0x18, 0x30, 0x60, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 161: i-acute
0x00, 0x0C, 0x18, 0x30, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
// Char 162: o-acute
0x00, 0x18, 0x30, 0x60, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 163: u-acute
0x00, 0x18, 0x30, 0x60, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
// Char 164: n-tilde
0x00, 0x00, 0x76, 0xDC, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00,
// Char 165: N-tilde
0x76, 0xDC, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 166: Feminine ordinal
0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x3E, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 167: Masculine ordinal
0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 168: Inverted question mark
0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
// Char 169: Reversed not sign (left corner)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00,
// Char 170: Not sign (right corner)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00,
// Char 171: 1/2
0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xCE, 0x9E, 0x06, 0x00, 0x00,
// Char 172: 1/4
0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30, 0x60, 0xDC, 0x86, 0x0C, 0x18, 0x00,
// Char 173: Inverted exclamation mark
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00,
// Char 174: Left-pointing double angle
0x00, 0x00, 0x00, 0x00, 0x36, 0x6C, 0xD8, 0x6C, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 175: Right-pointing double angle
0x00, 0x00, 0x00, 0x00, 0xD8, 0x6C, 0x36, 0x6C, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 176: Light shade
0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44,
// Char 177: Medium shade
0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
// Char 178: Dark shade
0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77,
// Char 179: Box light vertical
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 180: Box light vertical and left
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 181: Box vertical single and left double
0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 182: Box double vertical and left
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 183: Box down single and left double
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 184: Box down double and left single
0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 185: Box double vertical and left
0x36, 0x36, 0x36, 0x36, 0xF6, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 186: Box double vertical
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 187: Box double down and left
0x00, 0x00, 0x00, 0x00, 0xFE, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 188: Box double up and left
0x36, 0x36, 0x36, 0x36, 0xF6, 0x06, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 189: Box double up and right (via left)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 190: Box up single and left double
0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 191: Box light down and left
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 192: Box light up and right
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 193: Box light up and horizontal
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 194: Box light down and horizontal
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 195: Box light vertical and right
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 196: Box light horizontal
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 197: Box light vertical and horizontal
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 198: Box vertical single and right double
0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 199: Box double vertical and right
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 200: Box double up and right
0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 201: Box double down and right
0x00, 0x00, 0x00, 0x00, 0x3F, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 202: Box double up and horizontal
0x36, 0x36, 0x36, 0x36, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 203: Box double down and horizontal
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 204: Box double vertical and right
0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 205: Box double horizontal
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 206: Box double vertical and horizontal
0x36, 0x36, 0x36, 0x36, 0xF7, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 207: Box up double and horizontal single
0x18, 0x18, 0x18, 0x18, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 208: Box double up and horizontal single (upper)
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 209: Box down double and horizontal single
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 210: Box double down and horizontal single (upper)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 211: Box double up and right single
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 212: Box up single and right double
0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 213: Box down single and right double
0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 214: Box double down and right single
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 215: Box double vertical and horizontal single
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFF, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
// Char 216: Box vertical single and horizontal double
0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 217: Box light up and left
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 218: Box light down and right
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 219: Full block
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// Char 220: Lower half block
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// Char 221: Left half block
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
// Char 222: Right half block
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
// Char 223: Upper half block
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 224: alpha
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0xD8, 0xD8, 0xDC, 0x76, 0x00, 0x00, 0x00,
// Char 225: beta (sharp s)
0x00, 0x00, 0x78, 0xCC, 0xCC, 0xD8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC, 0x00, 0x00, 0x00,
// Char 226: Gamma
0x00, 0x00, 0xFE, 0xC6, 0xC6, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00,
// Char 227: pi
0x00, 0x00, 0x00, 0x00, 0xFE, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00,
// Char 228: Sigma (upper)
0x00, 0x00, 0xFE, 0xC6, 0x60, 0x30, 0x18, 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00,
// Char 229: sigma (lower)
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xD8, 0xD8, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00,
// Char 230: mu
0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xC0, 0x00,
// Char 231: tau
0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00,
// Char 232: Phi
0x00, 0x00, 0x7E, 0x18, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00,
// Char 233: Theta
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00,
// Char 234: Omega
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x6C, 0x6C, 0xEE, 0x00, 0x00, 0x00,
// Char 235: delta
0x00, 0x00, 0x1C, 0x30, 0x18, 0x0C, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00,
// Char 236: infinity
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xDB, 0xDB, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 237: phi (lower)
0x00, 0x00, 0x06, 0x0C, 0x7C, 0xDE, 0xD6, 0xF6, 0x7C, 0x60, 0xC0, 0x00, 0x00, 0x00,
// Char 238: epsilon
0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xF8, 0xC0, 0xC0, 0x60, 0x38, 0x00, 0x00, 0x00,
// Char 239: intersection
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
// Char 240: triple bar (equiv)
0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00,
// Char 241: plus-minus
0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
// Char 242: greater-than-or-equal
0x00, 0x00, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x00, 0x7E, 0x00, 0x00, 0x00,
// Char 243: less-than-or-equal
0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x7E, 0x00, 0x00, 0x00,
// Char 244: top of integral
0x00, 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
// Char 245: bottom of integral
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00,
// Char 246: division sign
0x00, 0x00, 0x18, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 247: approximately equal
0x00, 0x00, 0x00, 0x76, 0xDC, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 248: degree
0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 249: bullet (middle dot)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 250: small bullet
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 251: square root
0x00, 0x0F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xEC, 0x6C, 0x3C, 0x1C, 0x00, 0x00, 0x00,
// Char 252: superscript n
0x00, 0xD8, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 253: superscript 2
0x00, 0x70, 0xD8, 0x30, 0x60, 0xC8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 254: filled square
0x00, 0x00, 0x00, 0x00, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
// Char 255: Non-breaking space (blank)
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// ============================================================
// 8x16 VGA ROM font -- standard IBM CP437 character set
// 8x16 VGA ROM font -- standard IBM CP437 character set
// 256 glyphs, 16 bytes per glyph, MSB = leftmost pixel
// ============================================================
@ -1073,23 +551,8 @@ static const uint8_t font8x16[256 * 16] = {
};
// ============================================================
// Font descriptor structs
// Font descriptor struct
// ============================================================
//
// Pre-built BitmapFontT instances ready to pass to dvxInit() or use
// directly with draw functions. The 8x14 font fits more rows on screen
// (34 rows at 480 pixels) while 8x16 matches the standard VGA text mode
// cell height (30 rows at 480). DVX defaults to 8x14 for maximum usable
// content area, but 8x16 is available for applications that want the
// classic DOS text mode look.
static const BitmapFontT dvxFont8x14 = {
.charWidth = 8,
.charHeight = 14,
.firstChar = 0,
.numChars = 256,
.glyphData = font8x14,
};
static const BitmapFontT dvxFont8x16 = {
.charWidth = 8,

View file

@ -121,7 +121,7 @@ typedef struct {
// Bevel style
// ============================================================
//
// Bevels are the defining visual element of the Motif/DESQview/X aesthetic.
// Bevels are the defining visual element of the Motif aesthetic.
// Swapping highlight and shadow colors flips between raised and sunken
// appearance -- the BEVEL_RAISED and BEVEL_SUNKEN macros encode this
// convention so callers don't get the colors backwards.
@ -271,7 +271,7 @@ typedef struct {
// ============================================================
//
// These define the pixel geometry of the window frame (border, title bar,
// menu bar). They're compile-time constants because the DV/X look demands
// menu bar). They're compile-time constants because the DVX look demands
// a fixed, uniform chrome thickness across all windows -- there's no
// per-window customization of frame geometry.
//
@ -484,7 +484,7 @@ typedef struct {
#define SCROLL_HORIZONTAL 1
// Minimized windows display as icons at the bottom of the screen,
// similar to DESQview/X's original icon bar behavior.
// similar to a classic desktop icon bar.
#define ICON_SIZE 64
#define ICON_BORDER 2
#define ICON_TOTAL_SIZE (ICON_SIZE + ICON_BORDER * 2)

View file

@ -8,11 +8,15 @@
//
// Design decisions and rationale:
//
// - Single WidgetT struct with a tagged union: avoids the overhead of a
// class hierarchy and virtual dispatch (no C++ vtable indirection).
// The wclass pointer provides vtable-style dispatch only for the few
// operations that genuinely differ per widget type (paint, layout, events).
// Type-specific data lives in the `as` union to keep the struct compact.
// - Generic WidgetT with void *data: core knows nothing about individual
// widget types. Each widget DXE allocates and manages its own private
// data struct via w->data. The wclass vtable provides polymorphic
// dispatch for paint, layout, events, and all type-specific operations.
// No widget-specific enums, structs, or union members in this header.
//
// - Dynamic type IDs: wgtRegisterClass() returns a runtime ID at load
// time. No compile-time enum. Adding a new widget requires zero core
// changes -- just drop a .wgt DXE in the widgets directory.
//
// - Tagged size values (wgtPixels/wgtChars/wgtPercent): encoding the unit
// in the high bits of a single int32_t avoids extra struct fields for
@ -23,11 +27,6 @@
// this halves the pointer overhead per widget and insertion/removal is
// still O(n) in the worst case, which is acceptable given typical tree
// depths of 5-10 nodes.
//
// - Large widget data (AnsiTermDataT, ListViewDataT) is separately
// allocated and stored as a pointer in the union rather than inlined,
// because these structures are hundreds of bytes and would bloat every
// WidgetT even for simple labels and buttons.
#ifndef DVX_WIDGET_H
#define DVX_WIDGET_H
@ -63,62 +62,14 @@ struct WidgetClassT;
#define wgtChars(v) ((int32_t)(WGT_SIZE_CHARS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
#define wgtPercent(v) ((int32_t)(WGT_SIZE_PERCENT | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
// ============================================================
// Widget type enum
// ============================================================
//
// Used as the index into widgetClassTable[] to look up the vtable for
// each widget type. Built-in types occupy slots 0..WGT_TYPE_BUILTIN_COUNT-1.
// Dynamic types registered via wgtRegisterClass() start at
// WGT_TYPE_DYNAMIC_BASE.
typedef enum {
WidgetVBoxE,
WidgetHBoxE,
WidgetLabelE,
WidgetButtonE,
WidgetCheckboxE,
WidgetRadioGroupE,
WidgetRadioE,
WidgetTextInputE,
WidgetTextAreaE,
WidgetListBoxE,
WidgetSpacerE,
WidgetSeparatorE,
WidgetFrameE,
WidgetDropdownE,
WidgetComboBoxE,
WidgetProgressBarE,
WidgetSliderE,
WidgetTabControlE,
WidgetTabPageE,
WidgetStatusBarE,
WidgetToolbarE,
WidgetTreeViewE,
WidgetTreeItemE,
WidgetImageE,
WidgetImageButtonE,
WidgetCanvasE,
WidgetAnsiTermE,
WidgetListViewE,
WidgetSpinnerE,
WidgetScrollPaneE,
WidgetSplitterE,
WidgetTimerE,
WGT_TYPE_BUILTIN_COUNT // must be last built-in entry
} WidgetTypeE;
// Dynamic widget type IDs start here. External DXEs register new
// widget types via wgtRegisterClass() and receive IDs >= this value.
#define WGT_TYPE_DYNAMIC_BASE 32
#define WGT_MAX_TYPES 64
// Widget type IDs are assigned dynamically by wgtRegisterClass() at
// runtime. There is no compile-time enum. Each widget DXE stores
// its assigned ID(s) in file-scope globals.
// ============================================================
// ListView types
// ListView column types (used by ListView API in widgetListView.h)
// ============================================================
#define LISTVIEW_MAX_COLS 16
typedef enum {
ListViewAlignLeftE,
ListViewAlignCenterE,
@ -152,131 +103,12 @@ typedef enum {
} WidgetAlignE;
// ============================================================
// Frame style enum
// ============================================================
typedef enum {
FrameInE, // beveled inward (sunken) -- default
FrameOutE, // beveled outward (raised)
FrameFlatE // solid color line
} FrameStyleE;
// ============================================================
// Text input mode enum
// ============================================================
typedef enum {
InputNormalE, // default free-form text
InputPasswordE, // displays bullets, no copy
InputMaskedE // format mask (e.g. "(###) ###-####")
} InputModeE;
// ============================================================
// Large widget data (separately allocated to reduce WidgetT size)
// Widget-specific enums and types used by the public API
// ============================================================
//
// AnsiTermDataT and ListViewDataT are heap-allocated and stored as
// pointers in the WidgetT union. Inlining them would add hundreds of
// bytes to every WidgetT instance (even labels and buttons), wasting
// memory on the 30+ simple widgets that don't need terminal or listview
// state. The pointer indirection adds one dereference but saves
// significant memory across a typical widget tree.
// AnsiTermDataT -- full VT100/ANSI terminal emulator state.
// Implements a subset of DEC VT100 escape sequences sufficient for BBS
// and DOS ANSI art rendering: cursor movement, color attributes (16-color
// with bold-as-bright), scrolling regions, and blink. The parser is a
// simple state machine (normal -> ESC -> CSI) with parameter accumulation.
typedef struct {
uint8_t *cells; // character cells: (ch, attr) pairs, cols*rows*2 bytes
int32_t cols; // columns (default 80)
int32_t rows; // rows (default 25)
int32_t cursorRow; // 0-based cursor row
int32_t cursorCol; // 0-based cursor column
bool cursorVisible;
bool wrapMode; // auto-wrap at right margin
bool bold; // SGR bold flag (brightens foreground)
bool originMode; // cursor positioning relative to scroll region
bool csiPrivate; // '?' prefix in CSI sequence
uint8_t curAttr; // current text attribute (fg | bg<<4)
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
int32_t params[8]; // CSI parameter accumulator
int32_t paramCount; // number of CSI params collected
int32_t savedRow; // saved cursor position (SCP)
int32_t savedCol;
// Scrolling region (0-based, inclusive)
int32_t scrollTop; // top row of scroll region
int32_t scrollBot; // bottom row of scroll region
// Scrollback -- circular buffer so old lines age out naturally without
// memmove. Each line is cols*2 bytes (same ch/attr format as cells).
// scrollPos tracks the view offset: when equal to scrollbackCount,
// the user sees the live screen; when less, they're viewing history.
uint8_t *scrollback; // circular buffer of scrollback lines
int32_t scrollbackMax; // max lines in scrollback buffer
int32_t scrollbackCount; // current number of lines stored
int32_t scrollbackHead; // write position (circular index)
int32_t scrollPos; // view position (scrollbackCount = live)
// Blink support
bool blinkVisible; // current blink phase (true = text visible)
clock_t blinkTime; // timestamp of last blink toggle
// Cursor blink
bool cursorOn; // current cursor blink phase
clock_t cursorTime; // timestamp of last cursor toggle
// Dirty tracking -- a 32-bit bitmask where each bit corresponds to one
// terminal row. Only dirty rows are repainted, which is critical because
// the ANSI terminal can receive data every frame (at 9600+ baud) and
// re-rendering all 25 rows of 80 columns each frame would dominate the
// CPU budget. Limits terminal height to 32 rows, which is fine for the
// 25-row default target.
uint32_t dirtyRows; // bitmask of rows needing repaint
int32_t lastCursorRow; // cursor row at last repaint
int32_t lastCursorCol; // cursor col at last repaint
// Pre-packed 16-color palette avoids calling packColor() (which involves
// shift/mask arithmetic) 80*25 = 2000 times per full repaint.
uint32_t packedPalette[16];
bool paletteValid;
// Selection (line indices in scrollback+screen space)
int32_t selStartLine;
int32_t selStartCol;
int32_t selEndLine;
int32_t selEndCol;
bool selecting;
// Communications interface -- abstracted so the terminal can connect to
// different backends (serial port, secLink channel, local pipe) without
// knowing the transport details. When all are NULL, the terminal is in
// offline/disconnected mode (useful for viewing .ANS files).
void *commCtx;
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
} AnsiTermDataT;
// ListViewDataT -- multi-column list with sortable headers and optional
// multi-select. cellData is a flat array of strings indexed as
// cellData[row * colCount + col]. The sortIndex is an indirection array
// that maps displayed row numbers to data row numbers, allowing sort
// without rearranging the actual data. resolvedColW[] caches pixel widths
// after resolving tagged column sizes, avoiding re-resolution on every paint.
typedef struct {
const ListViewColT *cols;
int32_t colCount;
const char **cellData;
int32_t rowCount;
int32_t selectedIdx;
int32_t scrollPos;
int32_t scrollPosH;
int32_t sortCol;
ListViewSortE sortDir;
int32_t resolvedColW[LISTVIEW_MAX_COLS];
int32_t totalColW;
int32_t *sortIndex;
bool multiSelect;
int32_t anchorIdx;
uint8_t *selBits;
bool reorderable;
int32_t dragIdx;
int32_t dropIdx;
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
} ListViewDataT;
// These types are referenced by application code through the wgtApi
// function table. Widget-private types (data structs) are defined
// in their respective widget .c files.
// ============================================================
// Widget class vtable
@ -294,23 +126,79 @@ typedef struct {
// PAINTS_CHILDREN -- widget handles child rendering itself
// NO_HIT_RECURSE -- hit testing stops here, no child recursion
#define WCLASS_FOCUSABLE 0x0001
#define WCLASS_BOX_CONTAINER 0x0002
#define WCLASS_HORIZ_CONTAINER 0x0004
#define WCLASS_PAINTS_CHILDREN 0x0008
#define WCLASS_NO_HIT_RECURSE 0x0010
// Flags encode static properties checked by the framework.
// Widgets set these in their WidgetClassT definition.
#define WCLASS_FOCUSABLE 0x00000001
#define WCLASS_BOX_CONTAINER 0x00000002
#define WCLASS_HORIZ_CONTAINER 0x00000004
#define WCLASS_PAINTS_CHILDREN 0x00000008
#define WCLASS_NO_HIT_RECURSE 0x00000010
#define WCLASS_FOCUS_FORWARD 0x00000020 // accel hit forwards focus to next focusable
#define WCLASS_HAS_POPUP 0x00000040 // has dropdown popup overlay
#define WCLASS_SCROLLABLE 0x00000080 // accepts mouse wheel events
#define WCLASS_SCROLL_CONTAINER 0x00000100 // scroll container (ScrollPane)
#define WCLASS_NEEDS_POLL 0x00000200 // needs periodic polling (AnsiTerm comms)
#define WCLASS_SWALLOWS_TAB 0x00000400 // Tab key goes to widget, not focus nav
#define WCLASS_RELAYOUT_ON_SCROLL 0x00000800 // full relayout on scrollbar drag
#define WCLASS_PRESS_RELEASE 0x00001000 // click = press+release (Button, ImageButton)
#define WCLASS_ACCEL_WHEN_HIDDEN 0x00002000 // accel matching works even when invisible
typedef struct WidgetClassT {
uint32_t flags;
// Rendering
void (*paint)(struct WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void (*paintOverlay)(struct WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
// Layout
void (*calcMinSize)(struct WidgetT *w, const BitmapFontT *font);
void (*layout)(struct WidgetT *w, const BitmapFontT *font);
void (*getLayoutMetrics)(const struct WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW);
// Input
void (*onMouse)(struct WidgetT *w, struct WidgetT *root, int32_t vx, int32_t vy);
void (*onKey)(struct WidgetT *w, int32_t key, int32_t mod);
void (*onAccelActivate)(struct WidgetT *w, struct WidgetT *root);
// Lifecycle
void (*destroy)(struct WidgetT *w);
void (*onChildChanged)(struct WidgetT *parent, struct WidgetT *child);
// Text
const char *(*getText)(const struct WidgetT *w);
void (*setText)(struct WidgetT *w, const char *text);
// Selection / drag-select
bool (*clearSelection)(struct WidgetT *w);
void (*dragSelect)(struct WidgetT *w, struct WidgetT *root, int32_t vx, int32_t vy);
// Popup (dropdown/combobox)
void (*openPopup)(struct WidgetT *w);
void (*closePopup)(struct WidgetT *w);
int32_t (*getPopupItemCount)(const struct WidgetT *w);
void (*getPopupRect)(const struct WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
// Button press state
void (*setPressed)(struct WidgetT *w, bool pressed);
// Drag reorder (listbox/listview/treeview)
void (*reorderDrop)(struct WidgetT *w);
void (*reorderUpdate)(struct WidgetT *w, struct WidgetT *root, int32_t x, int32_t y);
// Cursor shape (returns cursor ID, 0 = default)
int32_t (*getCursorShape)(const struct WidgetT *w, int32_t vx, int32_t vy);
// Polling (AnsiTerm comms)
void (*poll)(struct WidgetT *w, WindowT *win);
// Fast incremental repaint (returns dirty row count, 0 = none)
int32_t (*quickRepaint)(struct WidgetT *w, int32_t *outY, int32_t *outH);
// Text field width (for scroll calculations; 0 = use widget width)
int32_t (*getTextFieldWidth)(const struct WidgetT *w);
// Scrollbar drag update (orient: 0=vert, 1=horiz)
void (*scrollDragUpdate)(struct WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
} WidgetClassT;
// ============================================================
@ -320,17 +208,13 @@ typedef struct WidgetClassT {
#define MAX_WIDGET_NAME 32
typedef struct WidgetT {
WidgetTypeE type;
int32_t type; // assigned by wgtRegisterClass()
// wclass points to the vtable for this widget type. Looked up once at
// creation from widgetClassTable[type]. This avoids a switch on type
// in every paint/layout/event dispatch -- the cost is one pointer per
// widget, which is negligible.
const struct WidgetClassT *wclass;
char name[MAX_WIDGET_NAME];
// djb2 hash of the name string, computed at wgtSetName() time.
// wgtFind() compares hashes before strcmp, making name lookups fast
// for the common no-match case in a tree with many unnamed widgets.
uint32_t nameHash; // djb2 hash of name, 0 if unnamed
// Tree linkage
struct WidgetT *parent;
@ -389,7 +273,7 @@ typedef struct WidgetT {
// Type-specific handlers (e.g. button press animation, listbox
// selection) run first, then these universal callbacks fire.
void *userData;
void *customData; // DXE widget plugin private data (NULL for built-in types)
void *data; // widget-private data (allocated by widget DXE)
const char *tooltip; // tooltip text (NULL = none, caller owns string)
MenuT *contextMenu; // right-click context menu (NULL = none, caller owns)
void (*onClick)(struct WidgetT *w);
@ -398,249 +282,6 @@ typedef struct WidgetT {
void (*onFocus)(struct WidgetT *w);
void (*onBlur)(struct WidgetT *w);
// Type-specific data -- tagged union keyed by the `type` field.
// Only the member corresponding to `type` is valid. This is the C
// equivalent of a discriminated union / variant type. Using a union
// instead of separate structs per widget type keeps all widget data
// in a single allocation, which simplifies memory management and
// avoids pointer chasing during layout/paint traversal.
union {
struct {
const char *text;
WidgetAlignE textAlign; // AlignStartE=left, AlignCenterE, AlignEndE=right
} 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;
// Text input has its own edit buffer (not a pointer to external
// storage) so the widget fully owns its text lifecycle. The undo
// buffer holds a single-level snapshot taken before each edit
// operation -- Ctrl+Z restores to the snapshot. This is simpler
// than a full undo stack but sufficient for single-line fields.
struct {
char *buf;
int32_t bufSize;
int32_t len;
int32_t cursorPos;
int32_t scrollOff;
int32_t selStart; // selection anchor (-1 = none)
int32_t selEnd; // selection end (-1 = none)
char *undoBuf;
int32_t undoLen;
int32_t undoCursor;
InputModeE inputMode;
const char *mask; // format mask for InputMaskedE
} textInput;
// Multi-line text editor. desiredCol implements the "sticky column"
// behavior where pressing Up/Down tries to return to the column
// the cursor was at before traversing shorter lines (standard
// text editor UX). cachedLines/cachedMaxLL cache values that
// require full-buffer scans, invalidated to -1 on any text change.
struct {
char *buf;
int32_t bufSize;
int32_t len;
int32_t cursorRow;
int32_t cursorCol;
int32_t scrollRow;
int32_t scrollCol;
int32_t desiredCol; // sticky column for up/down movement
int32_t selAnchor; // selection anchor byte offset (-1 = none)
int32_t selCursor; // selection cursor byte offset (-1 = none)
char *undoBuf;
int32_t undoLen;
int32_t undoCursor; // byte offset at time of snapshot
int32_t cachedLines; // cached line count (-1 = dirty)
int32_t cachedMaxLL; // cached max line length (-1 = dirty)
} textArea;
struct {
const char **items;
int32_t itemCount;
int32_t selectedIdx; // cursor position (always valid); also sole selection in single-select
int32_t scrollPos;
int32_t maxItemLen; // cached max strlen of items
bool multiSelect;
int32_t anchorIdx; // anchor for shift+click range selection
uint8_t *selBits; // per-item selection flags (multi-select only)
bool reorderable; // allow drag-reorder of items
int32_t dragIdx; // item being dragged (-1 = none)
int32_t dropIdx; // insertion point (-1 = none)
} listBox;
struct {
bool vertical;
} separator;
struct {
const char *title;
FrameStyleE style; // FrameInE (default), FrameOutE, FrameFlatE
uint32_t color; // border color for FrameFlatE (0 = use windowShadow)
} frame;
struct {
const char **items;
int32_t itemCount;
int32_t selectedIdx;
bool open;
int32_t hoverIdx;
int32_t scrollPos;
int32_t maxItemLen; // cached max strlen of items
} dropdown;
struct {
char *buf;
int32_t bufSize;
int32_t len;
int32_t cursorPos;
int32_t scrollOff;
int32_t selStart; // selection anchor (-1 = none)
int32_t selEnd; // selection end (-1 = none)
char *undoBuf;
int32_t undoLen;
int32_t undoCursor;
const char **items;
int32_t itemCount;
int32_t selectedIdx;
bool open;
int32_t hoverIdx;
int32_t listScrollPos;
int32_t maxItemLen; // cached max strlen of items
} comboBox;
struct {
int32_t value;
int32_t maxValue;
bool vertical;
} progressBar;
struct {
int32_t value;
int32_t minValue;
int32_t maxValue;
bool vertical;
} slider;
struct {
int32_t activeTab;
int32_t scrollOffset; // horizontal scroll of tab headers
} tabControl;
struct {
const char *title;
} tabPage;
// TreeView uses widget children (WidgetTreeItemE) as its items,
// unlike ListBox which uses a string array. This allows nested
// hierarchies with expand/collapse state per item. The tree is
// rendered by flattening visible items during paint, with TREE_INDENT
// pixels of indentation per nesting level.
struct {
int32_t scrollPos;
int32_t scrollPosH;
struct WidgetT *selectedItem;
struct WidgetT *anchorItem; // anchor for shift+click range selection
bool multiSelect;
bool reorderable; // allow drag-reorder of items
struct WidgetT *dragItem; // item being dragged (NULL = none)
struct WidgetT *dropTarget; // insertion target (NULL = none)
bool dropAfter; // true = insert after target, false = before
int32_t cachedTotalHeight; // cached result of calcTreeItemsHeight
int32_t cachedMaxWidth; // cached result of calcTreeItemsMaxWidth
bool dimsValid; // false = recalculate on next scrollbar check
} treeView;
struct {
const char *text;
bool expanded;
bool selected; // per-item flag for multi-select
} treeItem;
struct {
uint8_t *data; // pixel buffer in display format
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
bool pressed;
} image;
struct {
uint8_t *data; // pixel buffer in display format
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
bool pressed;
} imageButton;
struct {
uint8_t *data; // pixel buffer in display format
int32_t canvasW;
int32_t canvasH;
int32_t canvasPitch;
int32_t canvasBpp; // cached bytes per pixel (avoids pitch/w division)
uint32_t penColor;
int32_t penSize;
int32_t lastX;
int32_t lastY;
void (*onMouse)(struct WidgetT *w, int32_t cx, int32_t cy, bool drag);
} canvas;
AnsiTermDataT *ansiTerm;
ListViewDataT *listView;
struct {
int32_t value;
int32_t minValue;
int32_t maxValue;
int32_t step;
char buf[16]; // formatted value text
int32_t len;
int32_t cursorPos;
int32_t scrollOff;
int32_t selStart; // selection anchor (-1 = none)
int32_t selEnd; // selection end (-1 = none)
char undoBuf[16]; // undo snapshot
int32_t undoLen;
int32_t undoCursor;
bool editing; // true when user is typing
} spinner;
struct {
int32_t scrollPosV;
int32_t scrollPosH;
} scrollPane;
struct {
int32_t dividerPos; // pixels from left/top edge
bool vertical; // true = vertical divider (left|right panes)
} splitter;
struct {
int32_t intervalMs; // timer period in milliseconds
bool repeat; // true = repeating, false = one-shot
bool running; // true = active
clock_t lastFire; // clock() value at last fire (or start)
} timer;
} as;
} WidgetT;
// ============================================================
@ -655,299 +296,6 @@ typedef struct WidgetT {
// root container. The window's userData is set to the AppContextT pointer.
WidgetT *wgtInitWindow(struct AppContextT *ctx, WindowT *win);
// ============================================================
// Container creation
// ============================================================
//
// VBox and HBox are the primary layout containers, analogous to CSS
// flexbox with column/row direction. Children are laid out sequentially
// along the main axis (vertical for VBox, horizontal for HBox) with
// spacing between them. Extra space is distributed according to each
// child's weight. Frame is a titled groupbox container with a bevel border.
WidgetT *wgtVBox(WidgetT *parent);
WidgetT *wgtHBox(WidgetT *parent);
WidgetT *wgtFrame(WidgetT *parent, const char *title);
// ============================================================
// Basic widgets
// ============================================================
WidgetT *wgtLabel(WidgetT *parent, const char *text);
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align);
WidgetT *wgtButton(WidgetT *parent, const char *text);
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
WidgetT *wgtPasswordInput(WidgetT *parent, int32_t maxLen);
WidgetT *wgtMaskedInput(WidgetT *parent, const char *mask);
// ============================================================
// Radio buttons
// ============================================================
//
// Radio buttons must be children of a RadioGroup container. The group
// tracks which child is selected (by index). Clicking a radio button
// automatically deselects the previously selected sibling. This parent-
// tracking approach avoids explicit "radio group ID" parameters.
WidgetT *wgtRadioGroup(WidgetT *parent);
WidgetT *wgtRadio(WidgetT *parent, const char *text);
// ============================================================
// Spacing and visual dividers
// ============================================================
//
// Spacer is an invisible flexible widget (weight=100) that absorbs
// extra space -- useful for pushing subsequent siblings to the end of
// a container (like CSS flex: 1 auto). Separators are thin beveled
// lines for visual grouping.
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);
// ============================================================
// Dropdown and ComboBox
// ============================================================
WidgetT *wgtDropdown(WidgetT *parent);
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count);
int32_t wgtDropdownGetSelected(const WidgetT *w);
void wgtDropdownSetSelected(WidgetT *w, int32_t idx);
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen);
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count);
int32_t wgtComboBoxGetSelected(const WidgetT *w);
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx);
// ============================================================
// ProgressBar
// ============================================================
WidgetT *wgtProgressBar(WidgetT *parent);
WidgetT *wgtProgressBarV(WidgetT *parent);
void wgtProgressBarSetValue(WidgetT *w, int32_t value);
int32_t wgtProgressBarGetValue(const WidgetT *w);
// ============================================================
// Slider (TrackBar)
// ============================================================
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal);
void wgtSliderSetValue(WidgetT *w, int32_t value);
int32_t wgtSliderGetValue(const WidgetT *w);
// ============================================================
// Spinner (numeric input)
// ============================================================
WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step);
void wgtSpinnerSetValue(WidgetT *w, int32_t value);
int32_t wgtSpinnerGetValue(const WidgetT *w);
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal);
void wgtSpinnerSetStep(WidgetT *w, int32_t step);
// ============================================================
// TabControl
// ============================================================
WidgetT *wgtTabControl(WidgetT *parent);
WidgetT *wgtTabPage(WidgetT *parent, const char *title);
void wgtTabControlSetActive(WidgetT *w, int32_t idx);
int32_t wgtTabControlGetActive(const WidgetT *w);
// ============================================================
// StatusBar and Toolbar
// ============================================================
WidgetT *wgtStatusBar(WidgetT *parent);
WidgetT *wgtToolbar(WidgetT *parent);
// ============================================================
// TreeView
// ============================================================
WidgetT *wgtTreeView(WidgetT *parent);
WidgetT *wgtTreeViewGetSelected(const WidgetT *w);
void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item);
void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi);
void wgtTreeViewSetReorderable(WidgetT *w, bool reorderable);
WidgetT *wgtTreeItem(WidgetT *parent, const char *text);
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded);
bool wgtTreeItemIsExpanded(const WidgetT *w);
bool wgtTreeItemIsSelected(const WidgetT *w);
void wgtTreeItemSetSelected(WidgetT *w, bool selected);
// ============================================================
// ListView (multi-column list)
// ============================================================
WidgetT *wgtListView(WidgetT *parent);
void wgtListViewSetColumns(WidgetT *w, const ListViewColT *cols, int32_t count);
void wgtListViewSetData(WidgetT *w, const char **cellData, int32_t rowCount);
int32_t wgtListViewGetSelected(const WidgetT *w);
void wgtListViewSetSelected(WidgetT *w, int32_t idx);
void wgtListViewSetSort(WidgetT *w, int32_t col, ListViewSortE dir);
void wgtListViewSetHeaderClickCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir));
void wgtListViewSetMultiSelect(WidgetT *w, bool multi);
bool wgtListViewIsItemSelected(const WidgetT *w, int32_t idx);
void wgtListViewSetItemSelected(WidgetT *w, int32_t idx, bool selected);
void wgtListViewSelectAll(WidgetT *w);
void wgtListViewClearSelection(WidgetT *w);
void wgtListViewSetReorderable(WidgetT *w, bool reorderable);
// ============================================================
// ScrollPane
// ============================================================
WidgetT *wgtScrollPane(WidgetT *parent);
// ============================================================
// Splitter (draggable divider between two child regions)
// ============================================================
// Create a splitter. If vertical is true, children are arranged left|right;
// if false, top|bottom. Add exactly two children.
WidgetT *wgtSplitter(WidgetT *parent, bool vertical);
void wgtSplitterSetPos(WidgetT *w, int32_t pos);
int32_t wgtSplitterGetPos(const WidgetT *w);
// ============================================================
// Timer (invisible, callback-driven)
// ============================================================
// Create a timer widget. The timer is invisible and takes no space in
// layout. When running, it calls onChange at the specified interval.
// intervalMs: period in milliseconds. repeat: true for repeating,
// false for one-shot (auto-stops after first fire).
WidgetT *wgtTimer(WidgetT *parent, int32_t intervalMs, bool repeat);
// Start/restart the timer. Resets the elapsed time.
void wgtTimerStart(WidgetT *w);
// Stop the timer.
void wgtTimerStop(WidgetT *w);
// Change the interval (takes effect on next fire).
void wgtTimerSetInterval(WidgetT *w, int32_t intervalMs);
// Check if the timer is currently running.
bool wgtTimerIsRunning(const WidgetT *w);
// Called once per frame by dvxUpdate. Checks all active timers.
void wgtUpdateTimers(void);
// ============================================================
// ImageButton
// ============================================================
// Create an image button from raw pixel data (display format).
// Takes ownership of the data buffer (freed on destroy).
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
// Load an image button from a file (BMP, PNG, JPEG, GIF).
// Returns NULL on load failure; falls through gracefully.
WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path);
// Replace the image data. Takes ownership of the new buffer.
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
// ============================================================
// Image
// ============================================================
// Create an image widget from raw pixel data (display format).
// Takes ownership of the data buffer (freed on destroy).
WidgetT *wgtImage(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
// Load an image widget from a file (BMP, PNG, JPEG, GIF).
// Returns NULL on load failure.
WidgetT *wgtImageFromFile(WidgetT *parent, const char *path);
// Replace the image data. Takes ownership of the new buffer.
void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
// ============================================================
// Canvas
// ============================================================
// Create a drawable canvas widget with the given dimensions.
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h);
// Clear the canvas to the specified color.
void wgtCanvasClear(WidgetT *w, uint32_t color);
// Set the pen color (in display pixel format).
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color);
// Set the pen size in pixels (diameter).
void wgtCanvasSetPenSize(WidgetT *w, int32_t size);
// Set the mouse callback. Called on click (drag=false) and drag (drag=true)
// with canvas-relative coordinates. If NULL (default), mouse events are ignored.
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
// Save the canvas to a PNG file. Returns 0 on success, -1 on failure.
int32_t wgtCanvasSave(WidgetT *w, const char *path);
// Load a PNG file onto the canvas. Returns 0 on success, -1 on failure.
int32_t wgtCanvasLoad(WidgetT *w, const char *path);
// Programmatic drawing (coordinates are in canvas space)
void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color);
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y);
// ============================================================
// ANSI Terminal
// ============================================================
//
// A VT100/ANSI terminal emulator widget. Supports the subset of escape
// sequences needed for DOS BBS programs and ANSI art: cursor positioning,
// SGR color/attribute codes, scrolling regions, and text blink. Pairs
// with the secLink communications layer for remote serial connections
// or can be used standalone for viewing .ANS files.
// Create an ANSI terminal widget (0 for cols/rows = 80x25 default)
WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows);
// Write raw data through the ANSI escape sequence parser. Used for
// loading .ANS files or feeding data from a source that isn't using
// the comm interface.
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len);
// Clear the terminal screen and reset cursor to home
void wgtAnsiTermClear(WidgetT *w);
// Set the communications interface (NULL function pointers = disconnected)
void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t));
// Set the scrollback buffer size in lines (default 500). Clears existing scrollback.
void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines);
// Poll the comm interface for incoming data and process it. Returns bytes processed.
int32_t wgtAnsiTermPoll(WidgetT *w);
// Fast-path repaint for the terminal widget. Instead of going through the
// full widget paint pipeline (which would repaint the entire widget), this
// renders only the dirty rows (tracked via the dirtyRows bitmask) directly
// into the window's content buffer. This is essential for responsive terminal
// output -- incoming serial data can dirty a few rows per frame, and
// repainting only those rows keeps the cost proportional to the actual
// change rather than the full 80x25 grid. Returns the number of rows
// repainted; outY/outH report the affected region for dirty-rect tracking.
int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH);
// ============================================================
// Operations
// ============================================================
@ -958,11 +306,6 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH);
// function call.
struct AppContextT *wgtGetContext(const WidgetT *w);
// Update text cursor blink state. Call once per frame from dvxUpdate.
// Toggles the cursor visibility at 250ms intervals, matching the ANSI
// terminal cursor rate.
void wgtUpdateCursorBlink(void);
// Mark a widget as needing both re-layout (measure + position) and
// repaint. Propagates upward to ancestors since a child's size change
// can affect parent layout. Use this after structural changes (adding/
@ -1000,20 +343,6 @@ 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);
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi);
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx);
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected);
void wgtListBoxSelectAll(WidgetT *w);
void wgtListBoxClearSelection(WidgetT *w);
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable);
// ============================================================
// Tooltip
// ============================================================
@ -1034,7 +363,7 @@ void wgtSetDebugLayout(struct AppContextT *ctx, bool enabled);
// ============================================================
// Register a new widget class at runtime. Returns the assigned type ID
// (>= WGT_TYPE_DYNAMIC_BASE) on success, or -1 if the table is full.
// Appends wclass to widgetClassTable and returns the assigned type ID.
// The WidgetClassT must remain valid for the lifetime of the process
// (typically a static const in the registering DXE).
int32_t wgtRegisterClass(const WidgetClassT *wclass);
@ -1063,4 +392,20 @@ void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT
// after the main tree so they render on top of everything.
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
// ============================================================
// Widget API registry
// ============================================================
//
// Each widget DXE registers a small API struct under a name
// during wgtRegister(). Callers retrieve it via wgtGetApi()
// and cast to the widget-specific API type. Per-widget headers
// (e.g. widgetButton.h) provide typed accessors and convenience
// macros so callers don't need manual casts.
//
// This replaces the monolithic WidgetApiT -- adding a new widget
// requires zero changes to dvxWidget.h.
void wgtRegisterApi(const char *name, const void *api);
const void *wgtGetApi(const char *name);
#endif // DVX_WIDGET_H

213
core/dvxWidgetPlugin.h Normal file
View file

@ -0,0 +1,213 @@
// dvxWidgetPlugin.h -- Plugin API for DVX widget DXE modules
//
// Included by widget .c files to access core infrastructure.
// Provides: widget allocation, tree ops, class table, shared
// interaction state, scrollbar helpers, layout helpers, and
// common utility functions.
//
// Does NOT contain any per-widget prototypes. Each widget's
// internal functions (paint, onMouse, onKey, calcMinSize, etc.)
// are private to its own .c file and referenced only through
// the WidgetClassT vtable.
#ifndef DVX_WIDGET_PLUGIN_H
#define DVX_WIDGET_PLUGIN_H
#include "dvxWidget.h"
#include "dvxApp.h"
#include "dvxDraw.h"
#include "dvxWm.h"
#include "dvxVideo.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// ============================================================
// Widget class table
// ============================================================
// stb_ds dynamic array. Each widget DXE appends its class(es)
// via wgtRegisterClass() during wgtRegister().
extern const WidgetClassT **widgetClassTable;
// ============================================================
// Validation macros
// ============================================================
#define VALIDATE_WIDGET(w, wtype, retval) \
do { if (!(w) || (w)->type != (wtype)) { return (retval); } } while (0)
#define VALIDATE_WIDGET_VOID(w, wtype) \
do { if (!(w) || (w)->type != (wtype)) { return; } } while (0)
// ============================================================
// Cursor shape IDs (mirrors dvxCursor.h defines)
// ============================================================
#define CURSOR_ARROW 0
#define CURSOR_RESIZE_H 1
#define CURSOR_RESIZE_V 2
#define CURSOR_RESIZE_DIAG_NWSE 3
#define CURSOR_RESIZE_DIAG_NESW 4
// ============================================================
// Keyboard modifier flags
// ============================================================
#define KEY_MOD_SHIFT 0x03
#define KEY_MOD_CTRL 0x04
#define KEY_MOD_ALT 0x08
// ============================================================
// Core layout constants
// ============================================================
#define DEFAULT_SPACING 4
#define DEFAULT_PADDING 4
#define SB_MIN_THUMB 14
#define WGT_SB_W 14
// ============================================================
// Inline helpers
// ============================================================
static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
if (val < lo) { return lo; }
if (val > hi) { return hi; }
return val;
}
static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
drawText(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
drawText(d, ops, font, x, y, text, colors->windowShadow, 0, false);
}
static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
drawTextAccel(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
drawTextAccel(d, ops, font, x, y, text, colors->windowShadow, 0, false);
}
// ============================================================
// Shared interaction state (defined in widgetCore.c)
// ============================================================
extern bool sCursorBlinkOn;
extern clock_t sDblClickTicks;
extern bool sDebugLayout;
extern WidgetT *sClosedPopup;
extern WidgetT *sFocusedWidget;
extern WidgetT *sKeyPressedBtn;
extern WidgetT *sOpenPopup;
extern WidgetT *sPressedButton;
extern WidgetT *sDragSlider;
extern WidgetT *sDrawingCanvas;
extern WidgetT *sDragTextSelect;
extern int32_t sDragOffset;
extern WidgetT *sResizeListView;
extern WidgetT *sDragSplitter;
extern int32_t sDragSplitStart;
extern WidgetT *sDragReorder;
extern WidgetT *sDragScrollbar;
extern int32_t sDragScrollbarOff;
extern int32_t sDragScrollbarOrient;
// ============================================================
// Core widget functions (widgetCore.c)
// ============================================================
// Tree manipulation
void widgetAddChild(WidgetT *parent, WidgetT *child);
void widgetRemoveChild(WidgetT *parent, WidgetT *child);
void widgetDestroyChildren(WidgetT *w);
// Allocation
WidgetT *widgetAlloc(WidgetT *parent, int32_t type);
// Focus management
void widgetClearFocus(WidgetT *root);
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
WidgetT *widgetFindByAccel(WidgetT *root, char key);
// Utility queries
int32_t widgetCountVisibleChildren(const WidgetT *w);
int32_t widgetFrameBorderWidth(const WidgetT *w);
bool widgetIsFocusable(int32_t type);
bool widgetIsBoxContainer(int32_t type);
bool widgetIsHorizContainer(int32_t type);
int32_t multiClickDetect(int32_t vx, int32_t vy);
// Clipboard
void clipboardCopy(const char *text, int32_t len);
const char *clipboardGet(int32_t *outLen);
int32_t clipboardMaxLen(void);
// Hit testing
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
// Scrollbar helpers
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize);
// ============================================================
// Scrollbar drawing and hit testing (widgetScrollbar.c)
// ============================================================
typedef enum {
ScrollHitNoneE,
ScrollHitArrowDecE,
ScrollHitArrowIncE,
ScrollHitPageDecE,
ScrollHitPageIncE,
ScrollHitThumbE
} ScrollHitE;
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
// ============================================================
// Layout functions (widgetLayout.c)
// ============================================================
void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font);
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font);
void widgetLayoutBox(WidgetT *w, const BitmapFontT *font);
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
// ============================================================
// Event dispatch (widgetEvent.c)
// ============================================================
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
// ============================================================
// Paint helpers (widgetOps.c)
// ============================================================
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
// Shared helper libraries:
// texthelp/textHelp.h -- clipboard, text editing, word boundaries
// listhelp/listHelp.h -- dropdown arrow, popup list, keyboard nav
// Widget DXEs that use these include the headers directly.
// widgetTextDragUpdate stays in core (widgetCore.c)
void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
// ============================================================
// Drag-reorder helpers (widgetEvent.c)
// ============================================================
void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
void widgetReorderDrop(WidgetT *w);
#endif // DVX_WIDGET_PLUGIN_H

View file

@ -144,9 +144,9 @@ static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
// Compute title bar gadget positions. Used by both drawTitleBar()
// and wmHitTest() to keep geometry in sync.
//
// Layout follows the DESQview/X + GEOS Ensemble convention:
// Layout follows the Motif + GEOS Ensemble convention:
// [Close] ---- Title Text ---- [Minimize] [Maximize]
// Close is on the far left (also doubles as system menu in DV/X style),
// Close is on the far left (also doubles as system menu),
// minimize and maximize pack right-to-left from the far right edge.
// Modal windows suppress the minimize gadget since they must stay visible.
// Non-resizable windows suppress the maximize gadget.
@ -1410,8 +1410,8 @@ void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *
// 2. A live thumbnail of the window's content buffer (also scaled)
// 3. A grey fill if neither is available
//
// The live thumbnail approach (option 2) is a DESQview/X homage -- DV/X
// showed miniature window contents in its icon/task view. The thumbnail
// The live thumbnail (option 2) shows miniature window contents in the
// icon/task view, giving a quick preview of each minimized window. The thumbnail
// is rendered from the existing content buffer, so no extra rendering
// pass is needed. contentDirty tracks whether the content has changed
// since the last icon refresh.
@ -1441,30 +1441,30 @@ void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
continue;
}
// Draw beveled border
BevelStyleT bevel;
bevel.highlight = colors->windowHighlight;
bevel.shadow = colors->windowShadow;
bevel.face = colors->windowFace;
bevel.width = ICON_BORDER;
drawBevel(d, ops, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE, &bevel);
// Draw icon content
int32_t contentX = ix + ICON_BORDER;
int32_t contentY = iy + ICON_BORDER;
int32_t bpp = d->format.bytesPerPixel;
int32_t bpp = d->format.bytesPerPixel;
if (win->iconData && win->iconW > 0 && win->iconH > 0) {
// Draw loaded icon image, scaled to ICON_SIZE x ICON_SIZE
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
// Provided icon image -- draw edge-to-edge, no bevel
drawScaledRect(d, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE,
win->iconData, win->iconW, win->iconH, win->iconPitch, bpp);
} else if (win->contentBuf && win->contentW > 0 && win->contentH > 0) {
// Draw scaled copy of window contents
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
win->contentBuf, win->contentW, win->contentH, win->contentPitch, bpp);
} else {
// No content -- draw grey fill
rectFill(d, ops, contentX, contentY, ICON_SIZE, ICON_SIZE, colors->windowFace);
// Live thumbnail or grey fill -- draw with beveled border
BevelStyleT bevel;
bevel.highlight = colors->windowHighlight;
bevel.shadow = colors->windowShadow;
bevel.face = colors->windowFace;
bevel.width = ICON_BORDER;
drawBevel(d, ops, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE, &bevel);
int32_t contentX = ix + ICON_BORDER;
int32_t contentY = iy + ICON_BORDER;
if (win->contentBuf && win->contentW > 0 && win->contentH > 0) {
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
win->contentBuf, win->contentW, win->contentH, win->contentPitch, bpp);
} else {
rectFill(d, ops, contentX, contentY, ICON_SIZE, ICON_SIZE, colors->windowFace);
}
}
}
}

View file

@ -111,7 +111,7 @@ void wmDrawScrollbars(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colo
// Draw icons for all minimized windows along the bottom of the screen.
// Each icon shows a scaled-down preview of the window's content (or a
// default icon) with a beveled border, similar to DESQview/X's icon bar.
// default icon) with a beveled border, similar to a classic desktop icon bar.
void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, const WindowStackT *stack, const RectT *clipTo);
// Determine which window and which part of that window is under the given

View file

@ -24,6 +24,8 @@
#include "dvxTypes.h"
#include <setjmp.h>
// ============================================================
// Keyboard event
// ============================================================
@ -47,7 +49,7 @@ void platformInit(void);
// Cooperative yield -- give up the CPU timeslice when the event loop has
// nothing to do. On DOS this calls __dpmi_yield() to be friendly to
// multitaskers (Windows 3.x, OS/2, DESQview). On Linux this calls
// multitaskers (Windows 3.x, OS/2). On Linux this calls
// SDL_Delay(1) to avoid busy-spinning at 100% CPU.
void platformYield(void);
@ -241,4 +243,41 @@ int32_t platformStripLineEndings(char *buf, int32_t len);
// export table. On platforms without DXE (Linux/SDL), this is a no-op.
void platformRegisterDxeExports(void);
// ============================================================
// Crash recovery
// ============================================================
// Log function signature for crash diagnostics.
typedef void (*PlatformLogFnT)(const char *fmt, ...);
// Install signal handlers for fatal exceptions (SIGSEGV, SIGFPE, SIGILL).
// On crash, logs platform-specific diagnostics (register dump on DJGPP)
// via logFn, stores the signal number in *crashSignal, then longjmps to
// recoveryBuf. The caller is responsible for app-level diagnostics in
// the setjmp recovery block.
void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn);
// Log platform-specific crash diagnostics (signal name and register dump
// on DJGPP). Called internally by the crash handler before longjmp;
// also available for manual invocation if needed.
void platformLogCrashDetail(int sig, PlatformLogFnT logFn);
// ============================================================
// DXE symbol overrides
// ============================================================
// A name/function-pointer pair for registering symbol overrides with
// the dynamic module loader. On DJGPP this maps directly to the
// dxe_symbol_table struct used by dlregsym().
typedef struct {
const char *name;
void *func;
} PlatformSymOverrideT;
// Register function pointer overrides for subsequently loaded modules.
// entries is an array of {name, funcPtr} pairs terminated by {NULL, NULL}.
// On DJGPP this wraps dlregsym(). On platforms without DXE, this is a
// no-op (RTLD_GLOBAL or equivalent handles symbol resolution).
void platformRegisterSymOverrides(const PlatformSymOverrideT *entries);
#endif // DVX_PLATFORM_H

View file

@ -51,6 +51,7 @@
#include <dpmi.h>
#include <go32.h>
#include <pc.h>
#include <sys/exceptn.h>
#include <sys/nearptr.h>
#include <sys/farptr.h>
@ -71,6 +72,7 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pre
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
static bool hasCpuid(void);
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
static void platformCrashHandler(int sig);
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
static int32_t setVesaMode(uint16_t mode);
static void sysInfoAppend(const char *fmt, ...);
@ -1891,7 +1893,7 @@ void platformVideoShutdown(DisplayT *d) {
// ============================================================
//
// Cooperative yield to the DPMI host. In a multitasking DOS
// environment (Windows 3.x DOS box, OS/2 VDM, or DESQview),
// environment (Windows 3.x DOS box or OS/2 VDM),
// this gives other tasks a chance to run. Under a single-tasking
// DPMI server (CWSDPMI) it's essentially a no-op, but it doesn't
// hurt. Called once per event loop iteration when idle.
@ -1926,6 +1928,103 @@ static int32_t setVesaMode(uint16_t mode) {
}
// ============================================================
// Crash recovery state
// ============================================================
static jmp_buf *sPlatformCrashJmp = NULL;
static volatile int *sPlatformCrashSignal = NULL;
static PlatformLogFnT sPlatformLogFn = NULL;
// ============================================================
// platformCrashHandler -- signal handler for fatal exceptions
// ============================================================
//
// DJGPP uses System V signal semantics where the handler is reset to
// SIG_DFL after each delivery, so we must re-install before doing
// anything else. After logging platform-specific diagnostics and
// storing the signal number, we longjmp back to the caller's recovery
// point (typically the shell's main loop).
static void platformCrashHandler(int sig) {
if (sPlatformLogFn) {
platformLogCrashDetail(sig, sPlatformLogFn);
}
signal(sig, platformCrashHandler);
if (sPlatformCrashSignal) {
*sPlatformCrashSignal = sig;
}
if (sPlatformCrashJmp) {
longjmp(*sPlatformCrashJmp, 1);
}
}
// ============================================================
// platformInstallCrashHandler
// ============================================================
void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn) {
sPlatformCrashJmp = recoveryBuf;
sPlatformCrashSignal = crashSignal;
sPlatformLogFn = logFn;
signal(SIGSEGV, platformCrashHandler);
signal(SIGFPE, platformCrashHandler);
signal(SIGILL, platformCrashHandler);
}
// ============================================================
// platformLogCrashDetail -- log signal name and CPU register dump
// ============================================================
void platformLogCrashDetail(int sig, PlatformLogFnT logFn) {
const char *sigName = "UNKNOWN";
if (sig == SIGSEGV) {
sigName = "SIGSEGV (page fault)";
} else if (sig == SIGFPE) {
sigName = "SIGFPE (floating point exception)";
} else if (sig == SIGILL) {
sigName = "SIGILL (illegal instruction)";
}
logFn("=== CRASH ===");
logFn("Signal: %d (%s)", sig, sigName);
// __djgpp_exception_state_ptr captures the full CPU register state
// at the point of the exception. This gives us the faulting EIP,
// stack pointer, and all GPRs -- invaluable for post-mortem debugging.
jmp_buf *estate = __djgpp_exception_state_ptr;
if (estate) {
struct __jmp_buf *regs = &(*estate)[0];
logFn("EIP: 0x%08lx CS: 0x%04x", regs->__eip, regs->__cs);
logFn("EAX: 0x%08lx EBX: 0x%08lx ECX: 0x%08lx EDX: 0x%08lx", regs->__eax, regs->__ebx, regs->__ecx, regs->__edx);
logFn("ESI: 0x%08lx EDI: 0x%08lx EBP: 0x%08lx ESP: 0x%08lx", regs->__esi, regs->__edi, regs->__ebp, regs->__esp);
logFn("DS: 0x%04x ES: 0x%04x FS: 0x%04x GS: 0x%04x SS: 0x%04x", regs->__ds, regs->__es, regs->__fs, regs->__gs, regs->__ss);
logFn("EFLAGS: 0x%08lx", regs->__eflags);
}
}
// ============================================================
// platformRegisterSymOverrides
// ============================================================
//
// PlatformSymOverrideT has the same layout as dxe_symbol_table
// ({const char *name, void *ptr}), so we cast directly.
void platformRegisterSymOverrides(const PlatformSymOverrideT *entries) {
dlregsym((const dxe_symbol_table *)entries);
}
// ============================================================
// DXE3 export table
// ============================================================
@ -1952,8 +2051,8 @@ extern unsigned long long __udivdi3(unsigned long long, unsigned long long);
extern unsigned long long __udivmoddi4(unsigned long long, unsigned long long, unsigned long long *);
extern unsigned long long __umoddi3(unsigned long long, unsigned long long);
// DJGPP runtime internals
extern void *__djgpp_exception_state_ptr;
// DJGPP runtime internals (__djgpp_exception_state_ptr comes from
// <sys/exceptn.h> included above, with proper jmp_buf* typing)
extern void __dj_assert(const char *, const char *, int);
extern unsigned short __dj_ctype_flags[];
extern unsigned char __dj_ctype_tolower[];
@ -1961,6 +2060,22 @@ extern unsigned char __dj_ctype_tolower[];
// GCC emulated thread-local storage
extern void *__emutls_get_address(void *);
// stb_ds internals (implementation compiled into dvx.exe via loaderMain.c)
extern void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap);
extern void stbds_arrfreef(void *a);
extern void stbds_hmfree_func(void *a, size_t elemsize);
extern void *stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
extern void *stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode);
extern void *stbds_hmput_default(void *a, size_t elemsize);
extern void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
extern void *stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode);
extern void *stbds_shmode_func(size_t elemsize, int mode);
extern size_t stbds_hash_string(char *str, size_t seed);
extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
extern void stbds_rand_seed(size_t seed);
extern char *stbds_stralloc(void *a, char *str);
extern void stbds_strreset(void *a);
DXE_EXPORT_TABLE(sDxeExportTable)
// --- platform ---
@ -1970,9 +2085,11 @@ DXE_EXPORT_TABLE(sDxeExportTable)
DXE_EXPORT(platformGetMemoryInfo)
DXE_EXPORT(platformGetSystemInfo)
DXE_EXPORT(platformInit)
DXE_EXPORT(platformInstallCrashHandler)
DXE_EXPORT(platformKeyboardGetModifiers)
DXE_EXPORT(platformKeyboardRead)
DXE_EXPORT(platformLineEnding)
DXE_EXPORT(platformLogCrashDetail)
DXE_EXPORT(platformMkdirRecursive)
DXE_EXPORT(platformMouseInit)
DXE_EXPORT(platformMousePoll)
@ -1982,6 +2099,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
DXE_EXPORT(platformMouseWheelPoll)
DXE_EXPORT(platformPathDirEnd)
DXE_EXPORT(platformRegisterDxeExports)
DXE_EXPORT(platformRegisterSymOverrides)
DXE_EXPORT(platformSpanCopy8)
DXE_EXPORT(platformSpanCopy16)
DXE_EXPORT(platformSpanCopy32)
@ -2187,6 +2305,22 @@ DXE_EXPORT_TABLE(sDxeExportTable)
DXE_EXPORT(dlerror)
DXE_EXPORT(dlregsym)
// --- stb_ds (implementation lives in dvx.exe) ---
DXE_EXPORT(stbds_arrgrowf)
DXE_EXPORT(stbds_arrfreef)
DXE_EXPORT(stbds_hmfree_func)
DXE_EXPORT(stbds_hmget_key)
DXE_EXPORT(stbds_hmget_key_ts)
DXE_EXPORT(stbds_hmput_default)
DXE_EXPORT(stbds_hmput_key)
DXE_EXPORT(stbds_hmdel_key)
DXE_EXPORT(stbds_shmode_func)
DXE_EXPORT(stbds_hash_string)
DXE_EXPORT(stbds_hash_bytes)
DXE_EXPORT(stbds_rand_seed)
DXE_EXPORT(stbds_stralloc)
DXE_EXPORT(stbds_strreset)
DXE_EXPORT_END

View file

@ -1,49 +1,86 @@
// widgetClass.c -- Widget class table and dynamic registration
#define DVX_WIDGET_IMPL
// widgetClass.c -- Widget class table, API registry, and dynamic registration
//
// The widgetClassTable[] is the central dispatch table for the widget
// system. All entries start NULL and are filled at runtime by widget
// DXE registration functions (wgtBoxRegister, wgtButtonRegister, etc.).
// The loader loads each widget DXE and calls its register function
// before starting the shell.
// widgetClassTable is a stb_ds dynamic array. Widget DXEs call
// wgtRegisterClass() during wgtRegister() to append their class
// definition and receive a runtime type ID. No widget types are
// known at compile time.
//
// This file contains only the table and the dynamic registration API.
// All class definitions live in their respective widget .c files.
// The API registry uses an stb_ds string hashmap for O(1) lookup.
// Each widget DXE calls wgtRegisterApi("name", &sApi) during
// wgtRegister(). App/core code calls wgtGetApi("name") to get
// the API pointer, then casts and calls through it.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "stb_ds.h"
// ============================================================
// Class table -- indexed by WidgetTypeE
// ============================================================
//
// Every entry is NULL until the corresponding widget DXE calls its
// registration function (e.g. wgtBoxRegister sets VBox/HBox/Frame).
// Sized WGT_MAX_TYPES to accommodate both built-in widget types
// (0..WGT_TYPE_BUILTIN_COUNT-1) and dynamically registered types
// (WGT_TYPE_DYNAMIC_BASE..WGT_MAX_TYPES-1).
#include <string.h>
const WidgetClassT *widgetClassTable[WGT_MAX_TYPES];
// stb_ds dynamic array of class pointers. Grows on each
// wgtRegisterClass() call. Index = type ID.
const WidgetClassT **widgetClassTable = NULL;
// stb_ds string hashmap: key = widget name, value = API pointer
typedef struct {
char *key; // stb_ds string key (heap-allocated by shput)
const void *value;
} ApiMapEntryT;
static ApiMapEntryT *sApiMap = NULL;
// ============================================================
// wgtRegisterClass -- register a dynamic widget type at runtime
// wgtGetApi
// ============================================================
//
// External DXE plugins call this to add new widget types without
// modifying the core library. Returns the assigned type ID
// (>= WGT_TYPE_DYNAMIC_BASE) so the caller can use it with
// widgetAlloc(). Returns -1 if the table is full.
// Look up a widget API by name. O(1) via stb_ds string hashmap.
// Returns NULL if the widget is not loaded. Callers should still
// cache the result in a static local to skip even the hash.
const void *wgtGetApi(const char *name) {
if (!name) {
return NULL;
}
int32_t idx = shgeti(sApiMap, name);
if (idx < 0) {
return NULL;
}
return sApiMap[idx].value;
}
// ============================================================
// wgtRegisterApi
// ============================================================
//
// Register a widget's public API struct under a name. Called by
// each widget DXE during wgtRegister().
void wgtRegisterApi(const char *name, const void *api) {
if (!name || !api) {
return;
}
shput(sApiMap, name, api);
}
// ============================================================
// wgtRegisterClass
// ============================================================
//
// Appends a class to the table and returns the assigned type ID.
// The ID is simply the array index.
int32_t wgtRegisterClass(const WidgetClassT *wclass) {
if (!wclass) {
return -1;
}
for (int32_t i = WGT_TYPE_DYNAMIC_BASE; i < WGT_MAX_TYPES; i++) {
if (!widgetClassTable[i]) {
widgetClassTable[i] = wclass;
return i;
}
}
return -1;
int32_t id = arrlen(widgetClassTable);
arrput(widgetClassTable, wclass);
return id;
}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetEvent.c -- Window event handlers for widget system
//
// This file routes window-level events (mouse, keyboard, paint, resize,
@ -17,7 +18,7 @@
// still adjusts the value, because sDragSlider captures the event
// before hit testing runs.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
// Widget whose popup was just closed by click-outside -- prevents
// immediate re-open on the same click. Without this, clicking the
@ -206,10 +207,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Close popups from other windows
if (sOpenPopup && sOpenPopup->window != win) {
if (sOpenPopup->type == WidgetDropdownE) {
sOpenPopup->as.dropdown.open = false;
} else if (sOpenPopup->type == WidgetComboBoxE) {
sOpenPopup->as.comboBox.open = false;
if (sOpenPopup->wclass && sOpenPopup->wclass->closePopup) {
sOpenPopup->wclass->closePopup(sOpenPopup);
}
sOpenPopup = NULL;
@ -225,13 +224,13 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
if (sDragTextSelect && (buttons & MOUSE_LEFT)) {
widgetTextDragUpdate(sDragTextSelect, root, x, y);
if (sDragTextSelect->type == WidgetAnsiTermE) {
// Fast path: repaint only dirty terminal rows into the
// content buffer, then dirty just that screen stripe.
if (sDragTextSelect->wclass && sDragTextSelect->wclass->quickRepaint) {
// Fast path: repaint only dirty rows into the content
// buffer, then dirty just that screen stripe.
int32_t dirtyY = 0;
int32_t dirtyH = 0;
if (wgtAnsiTermRepaint(sDragTextSelect, &dirtyY, &dirtyH) > 0) {
if (sDragTextSelect->wclass->quickRepaint(sDragTextSelect, &dirtyY, &dirtyH) > 0) {
AppContextT *ctx = (AppContextT *)root->userData;
int32_t scrollY2 = win->vScroll ? win->vScroll->value : 0;
int32_t rectX = win->x + win->contentX;
@ -249,8 +248,6 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Handle canvas drawing release
if (sDrawingCanvas && !(buttons & MOUSE_LEFT)) {
sDrawingCanvas->as.canvas.lastX = -1;
sDrawingCanvas->as.canvas.lastY = -1;
sDrawingCanvas = NULL;
wgtInvalidatePaint(root);
return;
@ -274,38 +271,9 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Handle slider drag (mouse move while pressed)
if (sDragSlider && (buttons & MOUSE_LEFT)) {
int32_t range = sDragSlider->as.slider.maxValue - sDragSlider->as.slider.minValue;
if (range > 0) {
int32_t newVal;
if (sDragSlider->as.slider.vertical) {
int32_t thumbRange = sDragSlider->h - SLIDER_THUMB_W;
int32_t relY = y - sDragSlider->y - sDragOffset;
newVal = sDragSlider->as.slider.minValue + (relY * range) / thumbRange;
} else {
int32_t thumbRange = sDragSlider->w - SLIDER_THUMB_W;
int32_t relX = x - sDragSlider->x - sDragOffset;
newVal = sDragSlider->as.slider.minValue + (relX * range) / thumbRange;
}
if (newVal < sDragSlider->as.slider.minValue) {
newVal = sDragSlider->as.slider.minValue;
}
if (newVal > sDragSlider->as.slider.maxValue) {
newVal = sDragSlider->as.slider.maxValue;
}
if (newVal != sDragSlider->as.slider.value) {
sDragSlider->as.slider.value = newVal;
if (sDragSlider->onChange) {
sDragSlider->onChange(sDragSlider);
}
wgtInvalidatePaint(root);
}
if (sDragSlider->wclass && sDragSlider->wclass->scrollDragUpdate) {
sDragSlider->wclass->scrollDragUpdate(sDragSlider, 0, sDragOffset, x, y);
wgtInvalidatePaint(root);
}
return;
@ -314,44 +282,13 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Handle ListView column resize release
if (sResizeListView && !(buttons & MOUSE_LEFT)) {
sResizeListView = NULL;
sResizeCol = -1;
sResizeDragging = false;
return;
}
// Handle ListView column resize drag. The drag is deferred: on the
// initial click sResizeDragging is false and no resize happens until
// the mouse actually moves from the click point. This allows double-
// click auto-size to work (the first click sets sResizeListView but
// doesn't start dragging; the release clears it; the second click
// reaches widgetListViewOnMouse where multiClickDetect returns 2).
// Handle ListView column resize drag via vtable
if (sResizeListView && (buttons & MOUSE_LEFT)) {
if (!sResizeDragging) {
if (x == sResizeStartX) {
return;
}
sResizeDragging = true;
}
int32_t delta = x - sResizeStartX;
int32_t newW = sResizeOrigW + delta;
if (newW < LISTVIEW_MIN_COL_W) {
newW = LISTVIEW_MIN_COL_W;
}
if (newW != sResizeListView->as.listView->resolvedColW[sResizeCol]) {
sResizeListView->as.listView->resolvedColW[sResizeCol] = newW;
// Recalculate totalColW
int32_t total = 0;
for (int32_t c = 0; c < sResizeListView->as.listView->colCount; c++) {
total += sResizeListView->as.listView->resolvedColW[c];
}
sResizeListView->as.listView->totalColW = total;
if (sResizeListView->wclass && sResizeListView->wclass->scrollDragUpdate) {
sResizeListView->wclass->scrollDragUpdate(sResizeListView, -1, 0, x, y);
wgtInvalidatePaint(root);
}
@ -379,28 +316,10 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
return;
}
// Handle splitter drag
// Handle splitter drag via vtable
if (sDragSplitter && (buttons & MOUSE_LEFT)) {
int32_t pos;
if (sDragSplitter->as.splitter.vertical) {
pos = x - sDragSplitter->x - sDragSplitStart;
} else {
pos = y - sDragSplitter->y - sDragSplitStart;
}
if (sSplitterClampPosFn) {
sSplitterClampPosFn(sDragSplitter, &pos);
}
if (pos != sDragSplitter->as.splitter.dividerPos) {
sDragSplitter->as.splitter.dividerPos = pos;
if (sDragSplitter->onChange) {
sDragSplitter->onChange(sDragSplitter);
}
wgtInvalidate(sDragSplitter);
if (sDragSplitter->wclass && sDragSplitter->wclass->scrollDragUpdate) {
sDragSplitter->wclass->scrollDragUpdate(sDragSplitter, 0, sDragSplitStart, x, y);
}
return;
@ -417,9 +336,9 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
if (sDragScrollbar && (buttons & MOUSE_LEFT)) {
widgetScrollbarDragUpdate(sDragScrollbar, sDragScrollbarOrient, sDragScrollbarOff, x, y);
// ScrollPane needs full relayout because it positions children
// Scroll containers need full relayout because they position children
// at absolute coordinates incorporating the scroll offset.
if (sDragScrollbar->type == WidgetScrollPaneE) {
if (sDragScrollbar->wclass && (sDragScrollbar->wclass->flags & WCLASS_RELAYOUT_ON_SCROLL)) {
wgtInvalidate(sDragScrollbar);
} else {
wgtInvalidatePaint(root);
@ -430,10 +349,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Handle button press release
if (sPressedButton && !(buttons & MOUSE_LEFT)) {
if (sPressedButton->type == WidgetImageButtonE) {
sPressedButton->as.imageButton.pressed = false;
} else {
sPressedButton->as.button.pressed = false;
if (sPressedButton->wclass && sPressedButton->wclass->setPressed) {
sPressedButton->wclass->setPressed(sPressedButton, false);
}
// Fire onClick if released over the same button in the same window
@ -460,20 +377,11 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
y >= sPressedButton->y && y < sPressedButton->y + sPressedButton->h);
}
bool curPressed = (sPressedButton->type == WidgetImageButtonE)
? sPressedButton->as.imageButton.pressed
: sPressedButton->as.button.pressed;
if (curPressed != over) {
if (sPressedButton->type == WidgetImageButtonE) {
sPressedButton->as.imageButton.pressed = over;
} else {
sPressedButton->as.button.pressed = over;
}
wgtInvalidatePaint(sPressedButton);
if (sPressedButton->wclass && sPressedButton->wclass->setPressed) {
sPressedButton->wclass->setPressed(sPressedButton, over);
}
wgtInvalidatePaint(sPressedButton);
return;
}
@ -487,47 +395,17 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
int32_t popW;
int32_t popH;
widgetDropdownPopupRect(sOpenPopup, font, win->contentH, &popX, &popY, &popW, &popH);
if (!sOpenPopup->wclass || !sOpenPopup->wclass->getPopupRect) {
sOpenPopup = NULL;
return;
}
sOpenPopup->wclass->getPopupRect(sOpenPopup, font, win->contentH, &popX, &popY, &popW, &popH);
if (x >= popX && x < popX + popW && y >= popY && y < popY + popH) {
// Click on popup item
int32_t itemIdx = (y - popY - 2) / font->charHeight;
int32_t scrollP = 0;
if (sOpenPopup->type == WidgetDropdownE) {
scrollP = sOpenPopup->as.dropdown.scrollPos;
} else {
scrollP = sOpenPopup->as.comboBox.listScrollPos;
}
itemIdx += scrollP;
if (sOpenPopup->type == WidgetDropdownE) {
if (itemIdx >= 0 && itemIdx < sOpenPopup->as.dropdown.itemCount) {
sOpenPopup->as.dropdown.selectedIdx = itemIdx;
sOpenPopup->as.dropdown.open = false;
if (sOpenPopup->onChange) {
sOpenPopup->onChange(sOpenPopup);
}
}
} else if (sOpenPopup->type == WidgetComboBoxE) {
if (itemIdx >= 0 && itemIdx < sOpenPopup->as.comboBox.itemCount) {
sOpenPopup->as.comboBox.selectedIdx = itemIdx;
sOpenPopup->as.comboBox.open = false;
// Copy selected item text to buffer
const char *itemText = sOpenPopup->as.comboBox.items[itemIdx];
strncpy(sOpenPopup->as.comboBox.buf, itemText, sOpenPopup->as.comboBox.bufSize - 1);
sOpenPopup->as.comboBox.buf[sOpenPopup->as.comboBox.bufSize - 1] = '\0';
sOpenPopup->as.comboBox.len = (int32_t)strlen(sOpenPopup->as.comboBox.buf);
sOpenPopup->as.comboBox.cursorPos = sOpenPopup->as.comboBox.len;
sOpenPopup->as.comboBox.scrollOff = 0;
if (sOpenPopup->onChange) {
sOpenPopup->onChange(sOpenPopup);
}
}
// Click on popup item -- dispatch to widget's onMouse
if (sOpenPopup->wclass && sOpenPopup->wclass->onMouse) {
sOpenPopup->wclass->onMouse(sOpenPopup, root, x, y);
}
sOpenPopup = NULL;
@ -538,10 +416,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// Click outside popup -- close it and remember which widget it was
sClosedPopup = sOpenPopup;
if (sOpenPopup->type == WidgetDropdownE) {
sOpenPopup->as.dropdown.open = false;
} else if (sOpenPopup->type == WidgetComboBoxE) {
sOpenPopup->as.comboBox.open = false;
if (sOpenPopup->wclass && sOpenPopup->wclass->closePopup) {
sOpenPopup->wclass->closePopup(sOpenPopup);
}
sOpenPopup = NULL;
@ -587,7 +463,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
// in the release handler above. They still get onDblClick here.
if (hit->enabled) {
int32_t clicks = multiClickDetect(vx, vy);
bool isBtn = (hit->type == WidgetButtonE || hit->type == WidgetImageButtonE);
bool isBtn = (hit->wclass && (hit->wclass->flags & WCLASS_PRESS_RELEASE));
if (clicks >= 2 && hit->onDblClick) {
hit->onDblClick(hit);
@ -755,223 +631,12 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
// widgetReorderDrop -- finalize drag-reorder on mouse release
// ============================================================
//
// Completes a drag-reorder operation for ListBox, ListView, or TreeView.
// Moves the dragged item from its original position to the drop position
// by shifting intermediate elements. This is an in-place array rotation
// for ListBox/ListView (O(n) element moves) and a linked-list splice
// for TreeView.
//
// For ListBox and ListView, the item array, selection bits, and sort
// indices are all shifted together to maintain consistency. The selected
// index is updated to follow the moved item.
//
// For TreeView, the operation is a tree node re-parenting: unlink the
// dragged item from its old parent's child list, then insert it before
// or after the drop target in the target's parent's child list.
// Dispatches to the widget's reorderDrop vtable method, which handles
// the type-specific logic for completing the drag-reorder operation.
void widgetReorderDrop(WidgetT *w) {
if (w->type == WidgetListBoxE) {
int32_t drag = w->as.listBox.dragIdx;
int32_t drop = w->as.listBox.dropIdx;
w->as.listBox.dragIdx = -1;
w->as.listBox.dropIdx = -1;
if (drag < 0 || drop < 0 || drag == drop || drag == drop - 1) {
return;
}
// Move item at dragIdx to before dropIdx
const char *temp = w->as.listBox.items[drag];
uint8_t selBit = 0;
if (w->as.listBox.multiSelect && w->as.listBox.selBits) {
selBit = w->as.listBox.selBits[drag];
}
if (drag < drop) {
for (int32_t i = drag; i < drop - 1; i++) {
w->as.listBox.items[i] = w->as.listBox.items[i + 1];
if (w->as.listBox.selBits) {
w->as.listBox.selBits[i] = w->as.listBox.selBits[i + 1];
}
}
w->as.listBox.items[drop - 1] = temp;
if (w->as.listBox.selBits) {
w->as.listBox.selBits[drop - 1] = selBit;
}
w->as.listBox.selectedIdx = drop - 1;
} else {
for (int32_t i = drag; i > drop; i--) {
w->as.listBox.items[i] = w->as.listBox.items[i - 1];
if (w->as.listBox.selBits) {
w->as.listBox.selBits[i] = w->as.listBox.selBits[i - 1];
}
}
w->as.listBox.items[drop] = temp;
if (w->as.listBox.selBits) {
w->as.listBox.selBits[drop] = selBit;
}
w->as.listBox.selectedIdx = drop;
}
if (w->onChange) {
w->onChange(w);
}
} else if (w->type == WidgetListViewE) {
int32_t drag = w->as.listView->dragIdx;
int32_t drop = w->as.listView->dropIdx;
int32_t colCnt = w->as.listView->colCount;
w->as.listView->dragIdx = -1;
w->as.listView->dropIdx = -1;
if (drag < 0 || drop < 0 || drag == drop || drag == drop - 1) {
return;
}
// Move row at dragIdx to before dropIdx
const char *temp[LISTVIEW_MAX_COLS];
for (int32_t c = 0; c < colCnt; c++) {
temp[c] = w->as.listView->cellData[drag * colCnt + c];
}
uint8_t selBit = 0;
if (w->as.listView->multiSelect && w->as.listView->selBits) {
selBit = w->as.listView->selBits[drag];
}
int32_t sortVal = 0;
if (w->as.listView->sortIndex) {
sortVal = w->as.listView->sortIndex[drag];
}
if (drag < drop) {
for (int32_t i = drag; i < drop - 1; i++) {
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView->cellData[i * colCnt + c] = w->as.listView->cellData[(i + 1) * colCnt + c];
}
if (w->as.listView->selBits) {
w->as.listView->selBits[i] = w->as.listView->selBits[i + 1];
}
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i + 1];
}
}
int32_t dest = drop - 1;
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView->cellData[dest * colCnt + c] = temp[c];
}
if (w->as.listView->selBits) {
w->as.listView->selBits[dest] = selBit;
}
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[dest] = sortVal;
}
w->as.listView->selectedIdx = dest;
} else {
for (int32_t i = drag; i > drop; i--) {
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView->cellData[i * colCnt + c] = w->as.listView->cellData[(i - 1) * colCnt + c];
}
if (w->as.listView->selBits) {
w->as.listView->selBits[i] = w->as.listView->selBits[i - 1];
}
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i - 1];
}
}
for (int32_t c = 0; c < colCnt; c++) {
w->as.listView->cellData[drop * colCnt + c] = temp[c];
}
if (w->as.listView->selBits) {
w->as.listView->selBits[drop] = selBit;
}
if (w->as.listView->sortIndex) {
w->as.listView->sortIndex[drop] = sortVal;
}
w->as.listView->selectedIdx = drop;
}
if (w->onChange) {
w->onChange(w);
}
} else if (w->type == WidgetTreeViewE) {
WidgetT *drag = w->as.treeView.dragItem;
WidgetT *target = w->as.treeView.dropTarget;
bool after = w->as.treeView.dropAfter;
w->as.treeView.dragItem = NULL;
w->as.treeView.dropTarget = NULL;
if (!drag || !target || drag == target) {
return;
}
w->as.treeView.dimsValid = false;
// Unlink drag from its current parent
WidgetT *oldParent = drag->parent;
if (oldParent->firstChild == drag) {
oldParent->firstChild = drag->nextSibling;
} else {
for (WidgetT *c = oldParent->firstChild; c; c = c->nextSibling) {
if (c->nextSibling == drag) {
c->nextSibling = drag->nextSibling;
break;
}
}
}
drag->nextSibling = NULL;
// Insert drag before or after target (same parent level)
WidgetT *newParent = target->parent;
drag->parent = newParent;
if (after) {
drag->nextSibling = target->nextSibling;
target->nextSibling = drag;
} else {
if (newParent->firstChild == target) {
drag->nextSibling = target;
newParent->firstChild = drag;
} else {
for (WidgetT *c = newParent->firstChild; c; c = c->nextSibling) {
if (c->nextSibling == target) {
c->nextSibling = drag;
drag->nextSibling = target;
break;
}
}
}
}
if (w->onChange) {
w->onChange(w);
}
if (w->wclass && w->wclass->reorderDrop) {
w->wclass->reorderDrop(w);
}
}
@ -980,141 +645,11 @@ void widgetReorderDrop(WidgetT *w) {
// widgetReorderUpdate -- update drop position during drag
// ============================================================
//
// Tracks the mouse during a drag-reorder, updating the drop indicator
// position and auto-scrolling when the mouse is near the widget's edges.
//
// The drop position is computed from the mouse Y relative to item
// boundaries: if the mouse is in the top half of an item, the drop
// indicator goes before that item; if in the bottom half, it goes
// after. This gives intuitive "insert between items" behavior.
//
// Auto-scrolling happens when the mouse is within one row-height of
// the top or bottom edge, allowing the user to drag items to positions
// not currently visible.
// Dispatches to the widget's reorderUpdate vtable method, which handles
// type-specific drop indicator positioning and auto-scrolling.
void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
(void)x;
AppContextT *ctx = (AppContextT *)root->userData;
const BitmapFontT *font = &ctx->font;
if (w->type == WidgetListBoxE) {
int32_t innerY = w->y + LISTBOX_BORDER;
int32_t innerH = w->h - LISTBOX_BORDER * 2;
int32_t visibleRows = innerH / font->charHeight;
int32_t maxScroll = w->as.listBox.itemCount - visibleRows;
if (maxScroll < 0) {
maxScroll = 0;
}
// Auto-scroll when dragging near edges
if (y < innerY + font->charHeight && w->as.listBox.scrollPos > 0) {
w->as.listBox.scrollPos--;
} else if (y > innerY + innerH - font->charHeight && w->as.listBox.scrollPos < maxScroll) {
w->as.listBox.scrollPos++;
}
int32_t relY = y - innerY;
int32_t row = w->as.listBox.scrollPos + relY / font->charHeight;
int32_t halfRow = (relY % font->charHeight) >= font->charHeight / 2 ? 1 : 0;
int32_t drop = row + halfRow;
if (drop < 0) {
drop = 0;
}
if (drop > w->as.listBox.itemCount) {
drop = w->as.listBox.itemCount;
}
w->as.listBox.dropIdx = drop;
} else if (w->type == WidgetListViewE) {
int32_t headerH = font->charHeight + 4;
int32_t innerY = w->y + LISTVIEW_BORDER + headerH;
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;
int32_t visibleRows = innerH / font->charHeight;
int32_t maxScroll = w->as.listView->rowCount - visibleRows;
if (maxScroll < 0) {
maxScroll = 0;
}
// Auto-scroll when dragging near edges
if (y < innerY + font->charHeight && w->as.listView->scrollPos > 0) {
w->as.listView->scrollPos--;
} else if (y > innerY + innerH - font->charHeight && w->as.listView->scrollPos < maxScroll) {
w->as.listView->scrollPos++;
}
int32_t relY = y - innerY;
int32_t row = w->as.listView->scrollPos + relY / font->charHeight;
int32_t halfRow = (relY % font->charHeight) >= font->charHeight / 2 ? 1 : 0;
int32_t drop = row + halfRow;
if (drop < 0) {
drop = 0;
}
if (drop > w->as.listView->rowCount) {
drop = w->as.listView->rowCount;
}
w->as.listView->dropIdx = drop;
} else if (w->type == WidgetTreeViewE) {
int32_t innerY = w->y + TREE_BORDER;
int32_t innerH = w->h - TREE_BORDER * 2;
// Auto-scroll when dragging near edges (pixel-based scroll)
if (y < innerY + font->charHeight && w->as.treeView.scrollPos > 0) {
w->as.treeView.scrollPos -= font->charHeight;
if (w->as.treeView.scrollPos < 0) {
w->as.treeView.scrollPos = 0;
}
} else if (y > innerY + innerH - font->charHeight) {
w->as.treeView.scrollPos += font->charHeight;
// Paint will clamp to actual max
}
// Find which visible item the mouse is over
int32_t curY = w->y + TREE_BORDER - w->as.treeView.scrollPos;
// Walk visible items to find drop target
WidgetT *first = NULL;
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
if (c->type == WidgetTreeItemE && c->visible) {
first = c;
break;
}
}
WidgetT *target = NULL;
bool after = false;
for (WidgetT *v = first; v; v = sTreeViewNextVisibleFn ? sTreeViewNextVisibleFn(v, w) : NULL) {
int32_t itemBot = curY + font->charHeight;
int32_t mid = curY + font->charHeight / 2;
if (y < mid) {
target = v;
after = false;
break;
}
curY = itemBot;
// Check if mouse is between this item and next
WidgetT *next = sTreeViewNextVisibleFn ? sTreeViewNextVisibleFn(v, w) : NULL;
if (!next || y < itemBot) {
target = v;
after = true;
break;
}
}
w->as.treeView.dropTarget = target;
w->as.treeView.dropAfter = after;
if (w->wclass && w->wclass->reorderUpdate) {
w->wclass->reorderUpdate(w, root, x, y);
}
}

View file

@ -1,494 +0,0 @@
// widgetInternal.h -- Shared internal header for widget implementation files
//
// This header is included only by widget implementation .c files (in the
// widgets/ directory), never by application code. It exposes the vtable
// system, shared module-level state, and internal helper functions that
// widget implementations need but the public API should not expose.
//
// The widget system is split across multiple .c files by concern:
// widgetCore.c -- allocation, tree ops, focus management, shared helpers
// widgetLayout.c -- measure + layout algorithms for box containers
// widgetEvent.c -- mouse/keyboard dispatch, scrollbar management
// widgetOps.c -- paint dispatch, overlay rendering
// widget*.c -- per-type paint, event, and layout implementations
#ifndef WIDGET_INTERNAL_H
#define WIDGET_INTERNAL_H
#include "dvxWidget.h"
#include "dvxApp.h"
#include "dvxDraw.h"
#include "dvxWm.h"
#include "dvxVideo.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// ============================================================
// Widget class table
// ============================================================
//
// WidgetClassT and WCLASS_* flags are now in dvxWidget.h (public API)
// so that DXE plugins can register new widget types.
//
// Global vtable array -- sized WGT_MAX_TYPES. Built-in entries
// (0..WGT_TYPE_BUILTIN_COUNT-1) are initialized at startup. Dynamic
// entries (WGT_TYPE_DYNAMIC_BASE..WGT_MAX_TYPES-1) are filled by
// wgtRegisterClass(). Defined in widgetClass.c.
extern const WidgetClassT *widgetClassTable[WGT_MAX_TYPES];
// ============================================================
// Validation macros
// ============================================================
//
// Defensive type-checking at the top of public widget API functions.
// Since WidgetT is a tagged union, calling a ListBox function with a
// Button widget would access the wrong union member and corrupt state.
// These macros provide a consistent early-return guard pattern.
#define VALIDATE_WIDGET(w, wtype, retval) \
do { if (!(w) || (w)->type != (wtype)) { return (retval); } } while (0)
#define VALIDATE_WIDGET_VOID(w, wtype) \
do { if (!(w) || (w)->type != (wtype)) { return; } } while (0)
// ============================================================
// Constants
// ============================================================
// Modifier flags (BIOS INT 16h shift state bits). Duplicated from
// dvxTypes.h ACCEL_xxx constants because widget code references them
// frequently and the KEY_MOD_ prefix reads better in event handlers.
#define KEY_MOD_SHIFT 0x03
#define KEY_MOD_CTRL 0x04
#define KEY_MOD_ALT 0x08
// Layout and geometry constants. These define the visual metrics of
// each widget type and are tuned for readability at 640x480 with 8px
// wide fonts. Changing these affects every instance of the widget type.
#define DEFAULT_SPACING 4
#define DEFAULT_PADDING 4
#define SEPARATOR_THICKNESS 2
#define BUTTON_PAD_H 8
#define BUTTON_PAD_V 4
#define BUTTON_FOCUS_INSET 3 // focus rect inset from button edge
#define BUTTON_PRESS_OFFSET 1 // text/focus shift when button is pressed
#define CHECKBOX_BOX_SIZE 12
#define CHECKBOX_GAP 4
#define FRAME_BEVEL_BORDER 2
#define FRAME_FLAT_BORDER 1
#define TEXT_INPUT_PAD 3
#define DROPDOWN_BTN_WIDTH 16
#define DROPDOWN_MAX_VISIBLE 8
#define SLIDER_TRACK_H 4
#define SLIDER_THUMB_W 11
#define TAB_PAD_H 8
#define TAB_PAD_V 4
#define TAB_BORDER 2
#define LISTBOX_BORDER 2
#define LISTVIEW_BORDER 2
#define LISTVIEW_MIN_COL_W 20 // minimum column width during resize
#define TREE_INDENT 16
#define TREE_EXPAND_SIZE 9
#define TREE_ICON_GAP 4
#define TREE_BORDER 2
// WGT_SB_W is the widget-internal scrollbar width (slightly narrower than
// the window-level SCROLLBAR_WIDTH) to fit within widget content areas.
#define WGT_SB_W 14
#define TREE_MIN_ROWS 4
#define SB_MIN_THUMB 14
#define SPLITTER_BAR_W 5
#define SPLITTER_MIN_PANE 20
#define TOOLBAR_PAD 2 // padding inside toolbar/statusbar
#define TOOLBAR_GAP 2 // gap between toolbar/statusbar children
// ============================================================
// Inline helpers
// ============================================================
static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
if (val < lo) { return lo; }
if (val > hi) { return hi; }
return val;
}
// Classic Windows 3.1 embossed (etched) text for disabled widgets.
// The illusion works by drawing the text twice: first offset +1,+1 in
// the highlight color (creating a light "shadow" behind the text), then
// at the original position in the shadow color. The result is text that
// appears chiseled into the surface -- the universal visual indicator for
// "greyed out" in Motif and Windows 3.x era GUIs.
static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
drawText(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
drawText(d, ops, font, x, y, text, colors->windowShadow, 0, false);
}
static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
drawTextAccel(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
drawTextAccel(d, ops, font, x, y, text, colors->windowShadow, 0, false);
}
// ============================================================
// Shared state (defined in widgetCore.c)
// ============================================================
//
// Module-level globals for tracking active interactive operations.
// Only one of these can be active at a time (e.g. you can't drag a
// slider and resize a listview column simultaneously). Using globals
// rather than per-widget state avoids bloating every widget with fields
// that are only relevant during a single drag operation. The tradeoff
// is that these are truly global -- but since the GUI is single-threaded
// and only one mouse can exist, this is safe.
extern bool sCursorBlinkOn; // text cursor blink phase (toggled by wgtUpdateCursorBlink)
extern clock_t sDblClickTicks; // double-click threshold (set from AppContextT.dblClickTicks)
extern bool sDebugLayout;
extern WidgetT *sClosedPopup; // popup that was just closed (prevents immediate reopen)
extern WidgetT *sFocusedWidget; // currently focused widget across all windows
extern WidgetT *sKeyPressedBtn; // button being held via keyboard (Space/Enter)
extern WidgetT *sOpenPopup; // dropdown/combobox with open popup list
extern WidgetT *sPressedButton; // button/imagebutton being held via mouse
extern WidgetT *sDragSlider; // slider being dragged
extern WidgetT *sDrawingCanvas; // canvas receiving drag events
extern WidgetT *sDragTextSelect; // text widget with active mouse selection drag
extern int32_t sDragOffset; // mouse offset within thumb/handle at drag start
extern WidgetT *sResizeListView; // listview whose column is being resized
extern int32_t sResizeCol; // which column is being resized
extern int32_t sResizeStartX; // mouse X at start of column resize
extern int32_t sResizeOrigW; // original column width at start of resize
extern bool sResizeDragging; // true once mouse has moved from click point
extern WidgetT *sDragSplitter; // splitter being dragged
extern int32_t sDragSplitStart; // mouse position at start of splitter drag
extern WidgetT *sDragReorder; // listbox/treeview item being drag-reordered
extern WidgetT *sDragScrollbar; // widget whose scrollbar thumb is being dragged
// Hook for cursor shape change over ListView column borders.
// Set by wgtListViewRegister(); NULL if ListView DXE not loaded.
extern bool (*sListViewColBorderHitFn)(const WidgetT *w, int32_t vx, int32_t vy);
// Hook for splitter divider clamping during drag.
// Set by wgtSplitterRegister(); NULL if Splitter DXE not loaded.
extern void (*sSplitterClampPosFn)(WidgetT *w, int32_t *pos);
// Hook for TreeView visible item iteration during drag-reorder.
// Set by wgtTreeViewRegister(); NULL if TreeView DXE not loaded.
extern WidgetT *(*sTreeViewNextVisibleFn)(WidgetT *item, WidgetT *treeView);
extern int32_t sDragScrollbarOff; // mouse offset within thumb at drag start
extern int32_t sDragScrollbarOrient; // 0=vertical, 1=horizontal
// ============================================================
// Core functions (widgetCore.c)
// ============================================================
// Tree manipulation
void widgetAddChild(WidgetT *parent, WidgetT *child);
void widgetRemoveChild(WidgetT *parent, WidgetT *child);
void widgetDestroyChildren(WidgetT *w);
// Allocate a new widget, set its type and vtable, and add it as a child.
WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type);
// Focus management -- Tab/Shift-Tab navigation walks the tree in creation
// order, skipping non-focusable and hidden widgets.
void widgetClearFocus(WidgetT *root);
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
// Alt+key accelerator lookup -- searches the tree for a widget whose
// accelKey matches, used for keyboard navigation of labeled controls.
WidgetT *widgetFindByAccel(WidgetT *root, char key);
// Utility queries
int32_t widgetCountVisibleChildren(const WidgetT *w);
int32_t widgetFrameBorderWidth(const WidgetT *w);
bool widgetIsFocusable(WidgetTypeE type);
bool widgetIsBoxContainer(WidgetTypeE type);
bool widgetIsHorizContainer(WidgetTypeE type);
// Hit testing -- find the deepest widget containing the point (x,y).
// Respects WCLASS_NO_HIT_RECURSE to stop at list/tree widgets.
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
// Translate arrow/PgUp/PgDn/Home/End keys into list index changes.
// Shared by ListBox, ListView, Dropdown, and TreeView.
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
// Compute popup position for a dropdown/combobox, clamped to screen bounds.
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
// Draw the small downward-pointing triangle glyph used by dropdown buttons.
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color);
// Compute the maximum strlen across an array of string items.
int32_t widgetMaxItemLen(const char **items, int32_t count);
// Paint a generic popup item list (used by dropdown, combobox, and popup menus).
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
// Compute scrollbar thumb position and size for a given track length,
// content size, visible size, and scroll position. Used by both widget-
// internal scrollbars and the window-level scrollbar drawing code.
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize);
// ============================================================
// Shared scrollbar functions (widgetScrollbar.c)
// ============================================================
//
// Widget-internal scrollbar rendering and hit testing, shared across
// ListBox, ListView, TreeView, TextArea, ScrollPane, and AnsiTerm.
// These operate on abstract track coordinates (position + length) so
// the same code handles both horizontal and vertical scrollbars.
typedef enum {
ScrollHitNoneE,
ScrollHitArrowDecE,
ScrollHitArrowIncE,
ScrollHitPageDecE,
ScrollHitPageIncE,
ScrollHitThumbE
} ScrollHitE;
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
// ============================================================
// Layout functions (widgetLayout.c)
// ============================================================
//
// The layout algorithm is a two-pass flexbox-like system:
//
// calcMinSize (bottom-up): each leaf widget reports its minimum size
// (e.g. button = text width + padding). Containers sum their children's
// minimums plus spacing along the main axis, and take the maximum of
// children's minimums on the cross axis.
//
// layout (top-down): starting from the available space (window content
// area), each container distributes space to children. Children that fit
// at their minimum get their minimum. Remaining space is distributed
// proportionally by weight to flexible children. This happens recursively
// down the tree.
void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font);
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font);
void widgetLayoutBox(WidgetT *w, const BitmapFontT *font);
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
// ============================================================
// Event functions (widgetEvent.c)
// ============================================================
//
// These are the WindowT callback implementations installed by wgtInitWindow().
// They bridge the window manager's callback interface to the widget tree's
// event dispatch. Each one locates the appropriate widget (via hit-test for
// mouse events, or the focused widget for keyboard events) and calls the
// widget's vtable handler.
// Sync window-level scrollbar ranges to match the widget tree's overflow.
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
// ============================================================
// Paint/ops functions (widgetOps.c)
// ============================================================
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
// ============================================================
// Per-widget paint functions
// ============================================================
void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetImagePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
bool widgetListViewColBorderHit(const WidgetT *w, int32_t vx, int32_t vy);
void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetSeparatorPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetStatusBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetToolbarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
// ============================================================
// Per-widget calcMinSize functions
// ============================================================
void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetListViewCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetSplitterCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetSpinnerCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetSpacerCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetTabControlCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetTextAreaCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetTextInputCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font);
// ============================================================
// Per-widget layout functions (for special containers)
// ============================================================
void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font);
void widgetSplitterLayout(WidgetT *w, const BitmapFontT *font);
void widgetTabControlLayout(WidgetT *w, const BitmapFontT *font);
void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font);
// ============================================================
// Per-widget getText/setText functions
// ============================================================
const char *widgetButtonGetText(const WidgetT *w);
void widgetButtonSetText(WidgetT *w, const char *text);
const char *widgetCheckboxGetText(const WidgetT *w);
void widgetCheckboxSetText(WidgetT *w, const char *text);
const char *widgetComboBoxGetText(const WidgetT *w);
void widgetComboBoxSetText(WidgetT *w, const char *text);
const char *widgetDropdownGetText(const WidgetT *w);
const char *widgetLabelGetText(const WidgetT *w);
void widgetLabelSetText(WidgetT *w, const char *text);
const char *widgetRadioGetText(const WidgetT *w);
void widgetRadioSetText(WidgetT *w, const char *text);
const char *widgetTextAreaGetText(const WidgetT *w);
void widgetTextAreaSetText(WidgetT *w, const char *text);
const char *widgetTextInputGetText(const WidgetT *w);
void widgetTextInputSetText(WidgetT *w, const char *text);
const char *widgetTreeItemGetText(const WidgetT *w);
void widgetTreeItemSetText(WidgetT *w, const char *text);
// ============================================================
// Per-widget destroy functions
// ============================================================
void widgetAnsiTermDestroy(WidgetT *w);
void widgetCanvasDestroy(WidgetT *w);
void widgetComboBoxDestroy(WidgetT *w);
void widgetImageButtonDestroy(WidgetT *w);
void widgetImageDestroy(WidgetT *w);
void widgetListBoxDestroy(WidgetT *w);
void widgetListViewDestroy(WidgetT *w);
void widgetTextAreaDestroy(WidgetT *w);
void widgetTextInputDestroy(WidgetT *w);
// ============================================================
// Per-widget mouse/key functions
// ============================================================
//
// Shared text editing helpers used by TextInput, TextArea, ComboBox, and
// Spinner. The widgetTextEditOnKey function implements the common subset
// of single-line text editing behavior (cursor movement, selection,
// copy/paste, undo) shared across all text-editing widgets, so changes
// to editing behavior only need to happen in one place.
// Clear text selections in all widgets except the given one (ensures only
// one text selection is active at a time across the entire GUI).
void clearOtherSelections(WidgetT *except);
void clipboardCopy(const char *text, int32_t len);
const char *clipboardGet(int32_t *outLen);
bool isWordChar(char c);
// Detect double/triple clicks based on timing and position proximity.
// Returns 1 for single, 2 for double, 3 for triple click.
int32_t multiClickDetect(int32_t vx, int32_t vy);
void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetAnsiTermOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetCanvasOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetListBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetListViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetScrollPaneOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetSplitterClampPos(WidgetT *w, int32_t *pos);
void widgetSplitterOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetSliderOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetSpinnerOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
const char *widgetSpinnerGetText(const WidgetT *w);
void widgetSpinnerSetText(WidgetT *w, const char *text);
void widgetTabControlOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetTabControlOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
// Shared single-line text editing logic. Takes pointers to all the edit
// state fields so the same code works for TextInput, ComboBox, and Spinner
// (which store these fields in different union members). This avoids
// duplicating the full editing logic (cursor movement, word selection,
// clipboard, undo) three times.
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
void widgetTextInputOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
int32_t wordStart(const char *buf, int32_t pos);
void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod);
void widgetTreeViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
// Timer internals
void widgetTimerCalcMinSize(WidgetT *w, const BitmapFontT *font);
void widgetTimerDestroy(WidgetT *w);
// Drag-reorder helpers -- generic drag-and-drop reordering shared by
// ListBox, ListView, and TreeView. The update function tracks the mouse
// position and computes the drop target, the drop function commits the
// reorder by relinking the item in the data structure.
void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
void widgetReorderDrop(WidgetT *w);
// Iterate to the next visible item in a tree view (skipping collapsed
// subtrees). Used for keyboard navigation and rendering.
WidgetT *widgetTreeViewNextVisible(WidgetT *item, WidgetT *treeView);
#endif // WIDGET_INTERNAL_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetLayout.c -- Layout engine (measure + arrange)
//
// Implements a two-pass layout algorithm inspired by CSS flexbox:
@ -26,7 +27,7 @@
// unit types into a single int32_t without needing a separate struct.
// The wgtResolveSize() function decodes the tag and converts to pixels.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
// ============================================================
@ -58,21 +59,12 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
gap = DEFAULT_SPACING;
}
// Frame reserves extra vertical space at the top so the frame title
// text can sit centered on the top border line (like Windows group boxes).
// Without this, the first child would overlap the title.
// Widgets with getLayoutMetrics override default padding/gap/extraTop
int32_t frameExtraTop = 0;
if (w->type == WidgetFrameE) {
frameExtraTop = font->charHeight / 2;
pad = DEFAULT_PADDING;
}
// Toolbar and StatusBar use minimal padding to pack controls tightly,
// matching the compact feel of classic Windows/Motif toolbars.
if (w->type == WidgetToolbarE || w->type == WidgetStatusBarE) {
pad = TOOLBAR_PAD;
gap = TOOLBAR_GAP;
if (w->wclass && w->wclass->getLayoutMetrics) {
int32_t metricBorderW = 0;
w->wclass->getLayoutMetrics(w, font, &pad, &gap, &frameExtraTop, &metricBorderW);
}
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
@ -110,8 +102,8 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
w->calcMinH = mainSize + frameExtraTop;
}
// Frame border
if (w->type == WidgetFrameE) {
// Border (Frame and similar containers with getLayoutMetrics)
if (w->wclass && w->wclass->getLayoutMetrics) {
int32_t fb = widgetFrameBorderWidth(w);
w->calcMinW += fb * 2;
w->calcMinH += fb * 2;
@ -205,20 +197,12 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
gap = DEFAULT_SPACING;
}
// Frame adjustments
// Widgets with getLayoutMetrics override default padding/gap/extraTop/border
int32_t frameExtraTop = 0;
int32_t fb = 0;
if (w->type == WidgetFrameE) {
frameExtraTop = font->charHeight / 2;
fb = widgetFrameBorderWidth(w);
pad = DEFAULT_PADDING;
}
// Toolbar and StatusBar use tighter padding
if (w->type == WidgetToolbarE || w->type == WidgetStatusBarE) {
pad = TOOLBAR_PAD;
gap = TOOLBAR_GAP;
if (w->wclass && w->wclass->getLayoutMetrics) {
w->wclass->getLayoutMetrics(w, font, &pad, &gap, &frameExtraTop, &fb);
}
int32_t innerX = w->x + pad + fb;

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetOps.c -- Paint dispatcher and public widget operations
//
// This file contains two categories of functions:
@ -10,7 +11,8 @@
// because they share the same invalidation infrastructure (wgtInvalidate
// and wgtInvalidatePaint).
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "../widgets/widgetBox.h"
// ============================================================
@ -153,11 +155,11 @@ void wgtDestroy(WidgetT *w) {
return;
}
// Invalidate tree dimension cache if destroying a tree item
if (w->type == WidgetTreeItemE && w->parent) {
// Notify parent chain of child destruction via onChildChanged vtable
if (w->parent) {
for (WidgetT *p = w->parent; p; p = p->parent) {
if (p->type == WidgetTreeViewE) {
p->as.treeView.dimsValid = false;
if (p->wclass && p->wclass->onChildChanged) {
p->wclass->onChildChanged(p, w);
break;
}
}
@ -200,43 +202,16 @@ void wgtDestroy(WidgetT *w) {
// ============================================================
// widgetHashName -- djb2 hash for widget name lookup
// wgtFind
// ============================================================
//
// The djb2 hash function (Dan Bernstein) is used for fast name-based
// widget lookup. The hash is stored in each widget's nameHash field
// so that wgtFind() can reject non-matches without calling strcmp()
// on every node. In a tree of 100 widgets, this typically reduces
// the search from 100 strcmp calls to 1-2.
static uint32_t widgetHashName(const char *s) {
uint32_t h = 5381;
while (*s) {
h = ((h << 5) + h) + (uint8_t)*s;
s++;
static WidgetT *wgtFindImpl(WidgetT *w, const char *name) {
if (w->name[0] && strcmp(w->name, name) == 0) {
return w;
}
return h;
}
// ============================================================
// wgtFindByHash -- recursive search with hash fast-reject
// ============================================================
//
// Depth-first search by name with hash pre-screening. The hash
// comparison (integer ==) is much cheaper than strcmp and eliminates
// most non-matches. A full strcmp is only done when hashes match,
// which should be the target widget or a rare collision.
static WidgetT *wgtFindByHash(WidgetT *root, const char *name, uint32_t hash) {
if (root->nameHash == hash && root->name[0] && strcmp(root->name, name) == 0) {
return root;
}
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
WidgetT *found = wgtFindByHash(c, name, hash);
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
WidgetT *found = wgtFindImpl(c, name);
if (found) {
return found;
@ -246,19 +221,12 @@ static WidgetT *wgtFindByHash(WidgetT *root, const char *name, uint32_t hash) {
return NULL;
}
// ============================================================
// wgtFind
// ============================================================
WidgetT *wgtFind(WidgetT *root, const char *name) {
if (!root || !name) {
return NULL;
}
uint32_t hash = widgetHashName(name);
return wgtFindByHash(root, name, hash);
return wgtFindImpl(root, name);
}
@ -273,7 +241,6 @@ void wgtSetName(WidgetT *w, const char *name) {
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
w->name[MAX_WIDGET_NAME - 1] = '\0';
w->nameHash = widgetHashName(w->name);
}
@ -339,7 +306,7 @@ const char *wgtGetText(const WidgetT *w) {
// widget function -- any widget can retrieve it via wgtGetContext().
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
WidgetT *root = widgetAlloc(NULL, WidgetVBoxE);
WidgetT *root = dvxBoxApi() ? dvxBoxApi()->vBox(NULL) : NULL;
if (!root) {
return NULL;
@ -566,11 +533,11 @@ void wgtSetVisible(WidgetT *w, bool visible) {
if (w) {
w->visible = visible;
// Invalidate tree dimension cache if this is a tree item
if (w->type == WidgetTreeItemE && w->parent) {
// Notify parent chain of child visibility change via onChildChanged vtable
if (w->parent) {
for (WidgetT *p = w->parent; p; p = p->parent) {
if (p->type == WidgetTreeViewE) {
p->as.treeView.dimsValid = false;
if (p->wclass && p->wclass->onChildChanged) {
p->wclass->onChildChanged(p, w);
break;
}
}

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetScrollbar.c -- Shared scrollbar painting and hit-testing
//
// These are not widgets themselves -- they are stateless rendering and
@ -20,17 +21,7 @@
// If the container is too small, the scrollbar is simply not drawn
// rather than rendering a corrupted mess.
#include "widgetInternal.h"
// Constants duplicated from widgetTextInput.c and widgetScrollPane.c
// for use in widgetScrollbarDragUpdate. These are file-local in their
// source files, so we repeat the values here to avoid cross-file
// coupling. They must stay in sync with the originals.
#define TEXTAREA_BORDER 2
#define TEXTAREA_PAD 2
#define TEXTAREA_SB_W 14
#define SP_BORDER 2
#define SP_SB_W 14
#include "dvxWidgetPlugin.h"
// ============================================================
@ -150,395 +141,14 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
// ============================================================
// Handles ongoing scrollbar thumb drag for widget-internal scrollbars.
// Converts the mouse pixel position into a scroll value using linear
// interpolation, matching the WM-level wmScrollbarDrag logic.
// orient: 0=vertical, 1=horizontal.
// dragOff: mouse offset within thumb captured at drag start.
//
// Each widget type stores scroll state differently (row counts vs
// pixel offsets, different struct fields), so this function switches
// on widget type to extract the scrollbar geometry and update the
// correct scroll field.
// Dispatches to the widget's scrollDragUpdate vtable method, which
// handles type-specific geometry extraction and scroll position update.
void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) {
// Determine scrollbar geometry per widget type.
// sbOrigin: screen coordinate of the scrollbar's top/left edge
// sbLen: total length of the scrollbar (including arrow buttons)
// totalSize: total content size (items, pixels, or columns)
// visibleSize: visible portion of content
// scrollPos: current scroll position (pointer to update)
// maxScroll: maximum scroll value
int32_t sbOrigin = 0;
int32_t sbLen = 0;
int32_t totalSize = 0;
int32_t visibleSize = 0;
int32_t maxScroll = 0;
int32_t sbWidth = WGT_SB_W;
if (w->type == WidgetTextAreaE) {
sbWidth = TEXTAREA_SB_W;
if (orient == 0) {
// Vertical scrollbar
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
int32_t visCols = innerW / font->charWidth;
int32_t maxLL = 0;
// Compute max line length by scanning lines
const char *buf = w->as.textArea.buf;
int32_t len = w->as.textArea.len;
int32_t lineStart = 0;
for (int32_t i = 0; i <= len; i++) {
if (i == len || buf[i] == '\n') {
int32_t ll = i - lineStart;
if (ll > maxLL) {
maxLL = ll;
}
lineStart = i + 1;
}
}
bool needHSb = (maxLL > visCols);
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
int32_t visRows = innerH / font->charHeight;
if (visRows < 1) {
visRows = 1;
}
// Count total lines
int32_t totalLines = 1;
for (int32_t i = 0; i < len; i++) {
if (buf[i] == '\n') {
totalLines++;
}
}
sbOrigin = w->y + TEXTAREA_BORDER;
sbLen = innerH;
totalSize = totalLines;
visibleSize = visRows;
maxScroll = totalLines - visRows;
} else {
// Horizontal scrollbar
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
int32_t visCols = innerW / font->charWidth;
int32_t maxLL = 0;
const char *buf = w->as.textArea.buf;
int32_t len = w->as.textArea.len;
int32_t lineStart = 0;
for (int32_t i = 0; i <= len; i++) {
if (i == len || buf[i] == '\n') {
int32_t ll = i - lineStart;
if (ll > maxLL) {
maxLL = ll;
}
lineStart = i + 1;
}
}
int32_t hsbW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_SB_W;
sbOrigin = w->x + TEXTAREA_BORDER;
sbLen = hsbW;
totalSize = maxLL;
visibleSize = visCols;
maxScroll = maxLL - visCols;
}
} else if (w->type == WidgetListBoxE) {
// Vertical only
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t innerH = w->h - LISTBOX_BORDER * 2;
int32_t visibleRows = innerH / font->charHeight;
sbOrigin = w->y + LISTBOX_BORDER;
sbLen = innerH;
totalSize = w->as.listBox.itemCount;
visibleSize = visibleRows;
maxScroll = totalSize - visibleSize;
} else if (w->type == WidgetListViewE) {
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t headerH = font->charHeight + 4;
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;
int32_t innerW = w->w - LISTVIEW_BORDER * 2;
int32_t visibleRows = innerH / font->charHeight;
int32_t totalColW = w->as.listView->totalColW;
bool needVSb = (w->as.listView->rowCount > visibleRows);
if (needVSb) {
innerW -= WGT_SB_W;
}
if (totalColW > innerW) {
innerH -= WGT_SB_W;
visibleRows = innerH / font->charHeight;
if (!needVSb && w->as.listView->rowCount > visibleRows) {
innerW -= WGT_SB_W;
}
}
if (visibleRows < 1) {
visibleRows = 1;
}
if (orient == 0) {
// Vertical
sbOrigin = w->y + LISTVIEW_BORDER + headerH;
sbLen = innerH;
totalSize = w->as.listView->rowCount;
visibleSize = visibleRows;
maxScroll = totalSize - visibleSize;
} else {
// Horizontal
sbOrigin = w->x + LISTVIEW_BORDER;
sbLen = innerW;
totalSize = totalColW;
visibleSize = innerW;
maxScroll = totalColW - innerW;
}
} else if (w->type == WidgetTreeViewE) {
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t totalH;
int32_t totalW;
int32_t innerH;
int32_t innerW;
bool needVSb;
// Walk the visible tree to compute total content dimensions.
// This duplicates treeCalcScrollbarNeeds which is file-static,
// so we compute it inline using the same logic.
int32_t treeH = 0;
int32_t treeW = 0;
// Count visible items and max width
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
if (c->type == WidgetTreeItemE && c->visible) {
// Walk visible items
WidgetT *item = c;
while (item) {
treeH += font->charHeight;
// Compute depth
int32_t depth = 0;
WidgetT *p = item->parent;
while (p && p != w) {
depth++;
p = p->parent;
}
int32_t itemW = depth * TREE_INDENT + TREE_EXPAND_SIZE + TREE_ICON_GAP;
if (item->as.treeItem.text) {
itemW += (int32_t)strlen(item->as.treeItem.text) * font->charWidth;
}
if (itemW > treeW) {
treeW = itemW;
}
item = widgetTreeViewNextVisible(item, w);
}
break; // Only process first top-level visible chain
}
}
totalH = treeH;
totalW = treeW;
innerH = w->h - TREE_BORDER * 2;
innerW = w->w - TREE_BORDER * 2;
needVSb = (totalH > innerH);
if (needVSb) {
innerW -= WGT_SB_W;
}
if (totalW > innerW) {
innerH -= WGT_SB_W;
if (!needVSb && totalH > innerH) {
needVSb = true;
innerW -= WGT_SB_W;
}
}
if (orient == 0) {
// Vertical (pixel-based scroll)
sbOrigin = w->y + TREE_BORDER;
sbLen = innerH;
totalSize = totalH;
visibleSize = innerH;
maxScroll = totalH - innerH;
} else {
// Horizontal (pixel-based scroll)
sbOrigin = w->x + TREE_BORDER;
sbLen = innerW;
totalSize = totalW;
visibleSize = innerW;
maxScroll = totalW - innerW;
}
} else if (w->type == WidgetScrollPaneE) {
sbWidth = SP_SB_W;
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
// Compute content min size (must match spCalcNeeds in widgetScrollPane.c)
int32_t contentMinW = 0;
int32_t contentMinH = 0;
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
int32_t count = 0;
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
if (c->visible) {
if (c->wclass && c->wclass->calcMinSize) {
c->wclass->calcMinSize(c, font);
}
if (c->calcMinW > contentMinW) {
contentMinW = c->calcMinW;
}
contentMinH += c->calcMinH;
count++;
}
}
if (count > 1) {
contentMinH += gap * (count - 1);
}
contentMinW += pad * 2;
contentMinH += pad * 2;
int32_t innerH = w->h - SP_BORDER * 2;
int32_t innerW = w->w - SP_BORDER * 2;
bool needVSb = (contentMinH > innerH);
if (needVSb) {
innerW -= SP_SB_W;
}
if (contentMinW > innerW) {
innerH -= SP_SB_W;
if (!needVSb && contentMinH > innerH) {
needVSb = true;
innerW -= SP_SB_W;
}
}
if (orient == 0) {
// Vertical
sbOrigin = w->y + SP_BORDER;
sbLen = innerH;
totalSize = contentMinH;
visibleSize = innerH;
maxScroll = contentMinH - innerH;
} else {
// Horizontal
sbOrigin = w->x + SP_BORDER;
sbLen = innerW;
totalSize = contentMinW;
visibleSize = innerW;
maxScroll = contentMinW - innerW;
}
} else {
return;
}
if (maxScroll < 0) {
maxScroll = 0;
}
if (maxScroll == 0) {
return;
}
// Compute thumb geometry
int32_t trackLen = sbLen - sbWidth * 2;
if (trackLen <= 0) {
return;
}
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalSize, visibleSize, 0, &thumbPos, &thumbSize);
if (trackLen <= thumbSize) {
return;
}
// Convert mouse position to scroll value
int32_t mousePos;
if (orient == 0) {
mousePos = mouseY - sbOrigin - sbWidth - dragOff;
} else {
mousePos = mouseX - sbOrigin - sbWidth - dragOff;
}
int32_t newScroll = (mousePos * maxScroll) / (trackLen - thumbSize);
if (newScroll < 0) {
newScroll = 0;
}
if (newScroll > maxScroll) {
newScroll = maxScroll;
}
// Update the widget's scroll position
if (w->type == WidgetTextAreaE) {
if (orient == 0) {
w->as.textArea.scrollRow = newScroll;
} else {
w->as.textArea.scrollCol = newScroll;
}
} else if (w->type == WidgetListBoxE) {
w->as.listBox.scrollPos = newScroll;
} else if (w->type == WidgetListViewE) {
if (orient == 0) {
w->as.listView->scrollPos = newScroll;
} else {
w->as.listView->scrollPosH = newScroll;
}
} else if (w->type == WidgetTreeViewE) {
if (orient == 0) {
w->as.treeView.scrollPos = newScroll;
} else {
w->as.treeView.scrollPosH = newScroll;
}
} else if (w->type == WidgetScrollPaneE) {
if (orient == 0) {
w->as.scrollPane.scrollPosV = newScroll;
} else {
w->as.scrollPane.scrollPosH = newScroll;
}
// Dispatch to the widget's scrollDragUpdate vtable method, which
// handles type-specific geometry extraction, thumb computation,
// and scroll position update.
if (w->wclass && w->wclass->scrollDragUpdate) {
w->wclass->scrollDragUpdate(w, orient, dragOff, mouseX, mouseY);
}
}

42
listhelp/Makefile Normal file
View file

@ -0,0 +1,42 @@
# List Help Library Makefile for DJGPP cross-compilation
#
# Builds listhelp.lib -- shared list/dropdown helper infrastructure
# (dropdown arrow, item length, keyboard navigation, popup list painting).
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -I../core -I../core/platform -I../tasks -I../core/thirdparty
OBJDIR = ../obj/listhelp
LIBSDIR = ../bin/libs
SRCS = listHelp.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
TARGET = $(LIBSDIR)/listhelp.lib
.PHONY: all clean
all: $(TARGET) $(LIBSDIR)/listhelp.dep
$(LIBSDIR)/listhelp.dep: ../config/listhelp.dep | $(LIBSDIR)
sed 's/$$/\r/' $< > $@
$(TARGET): $(OBJS) | $(LIBSDIR)
$(DXE3GEN) -o $(LIBSDIR)/listhelp.dxe -E _widgetDraw -E _widgetDropdown -E _widgetMax -E _widgetNavigate -E _widgetPaint -U $(OBJS)
mv $(LIBSDIR)/listhelp.dxe $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
$(LIBSDIR):
mkdir -p $(LIBSDIR)
# Dependencies
$(OBJDIR)/listHelp.o: listHelp.c listHelp.h ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
clean:
rm -f $(OBJS) $(TARGET) $(LIBSDIR)/listhelp.dep

187
listhelp/listHelp.c Normal file
View file

@ -0,0 +1,187 @@
// listHelp.c -- Shared list/dropdown helper functions
//
// Implements dropdown arrow drawing, item length scanning, keyboard
// navigation, and popup list painting used by ListBox, Dropdown,
// ComboBox, ListView, and TreeView widgets.
#include "listHelp.h"
#include "../texthelp/textHelp.h"
#include <string.h>
// ============================================================
// widgetDrawDropdownArrow
// ============================================================
//
// Draws a small downward-pointing filled triangle (7, 5, 3, 1 pixels
// wide across 4 rows) centered at the given position. Used by both
// Dropdown and ComboBox for the drop button arrow glyph.
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color) {
for (int32_t i = 0; i < 4; i++) {
drawHLine(d, ops, centerX - 3 + i, centerY + i, 7 - i * 2, color);
}
}
// ============================================================
// widgetMaxItemLen
// ============================================================
//
// Scans an array of string items and returns the maximum strlen.
// Shared by ListBox, Dropdown, and ComboBox to cache the widest
// item length for calcMinSize without duplicating the loop.
int32_t widgetMaxItemLen(const char **items, int32_t count) {
int32_t maxLen = 0;
for (int32_t i = 0; i < count; i++) {
int32_t slen = (int32_t)strlen(items[i]);
if (slen > maxLen) {
maxLen = slen;
}
}
return maxLen;
}
// ============================================================
// widgetNavigateIndex
// ============================================================
//
// Shared keyboard navigation for list-like widgets (ListBox, Dropdown,
// ListView, etc.). Encapsulates the Up/Down/Home/End/PgUp/PgDn logic
// so each widget doesn't have to reimplement index clamping.
//
// Key values use the 0x100 flag to mark extended scan codes (arrow
// keys, Home, End, etc.) -- this is the DVX convention for passing
// scan codes through the same int32_t channel as ASCII values.
//
// Returns -1 for unrecognized keys so callers can check whether the
// key was consumed.
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize) {
if (key == (0x50 | 0x100)) {
// Down arrow
if (current < count - 1) {
return current + 1;
}
return current < 0 ? 0 : current;
}
if (key == (0x48 | 0x100)) {
// Up arrow
if (current > 0) {
return current - 1;
}
return current < 0 ? 0 : current;
}
if (key == (0x47 | 0x100)) {
// Home
return 0;
}
if (key == (0x4F | 0x100)) {
// End
return count - 1;
}
if (key == (0x51 | 0x100)) {
// Page Down
int32_t n = current + pageSize;
return n >= count ? count - 1 : n;
}
if (key == (0x49 | 0x100)) {
// Page Up
int32_t n = current - pageSize;
return n < 0 ? 0 : n;
}
return -1;
}
// ============================================================
// widgetPaintPopupList
// ============================================================
//
// Shared popup list painting for Dropdown and ComboBox.
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos) {
// Draw popup border
BevelStyleT bevel;
bevel.highlight = colors->windowHighlight;
bevel.shadow = colors->windowShadow;
bevel.face = colors->contentBg;
bevel.width = 2;
drawBevel(d, ops, popX, popY, popW, popH, &bevel);
// Draw items
int32_t visibleItems = popH / font->charHeight;
int32_t textX = popX + TEXT_INPUT_PAD;
int32_t textY = popY + 2;
int32_t textW = popW - TEXT_INPUT_PAD * 2 - 4;
for (int32_t i = 0; i < visibleItems && (scrollPos + i) < itemCount; i++) {
int32_t idx = scrollPos + i;
int32_t iy = textY + i * font->charHeight;
uint32_t ifg = colors->contentFg;
uint32_t ibg = colors->contentBg;
if (idx == hoverIdx) {
ifg = colors->menuHighlightFg;
ibg = colors->menuHighlightBg;
rectFill(d, ops, popX + 2, iy, textW + TEXT_INPUT_PAD * 2, font->charHeight, ibg);
}
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
}
}
// ============================================================
// widgetDropdownPopupRect
// ============================================================
//
// Calculates the screen rectangle for a dropdown/combobox popup list.
// Shared between Dropdown and ComboBox since they have identical
// popup positioning logic.
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
int32_t itemCount = 0;
if (w->wclass && w->wclass->getPopupItemCount) {
itemCount = w->wclass->getPopupItemCount(w);
}
int32_t visibleItems = itemCount;
if (visibleItems > DROPDOWN_MAX_VISIBLE) {
visibleItems = DROPDOWN_MAX_VISIBLE;
}
if (visibleItems < 1) {
visibleItems = 1;
}
*popX = w->x;
*popW = w->w;
*popH = visibleItems * font->charHeight + 4;
if (w->y + w->h + *popH <= contentH) {
*popY = w->y + w->h;
} else {
*popY = w->y - *popH;
if (*popY < 0) {
*popY = 0;
}
}
}

45
listhelp/listHelp.h Normal file
View file

@ -0,0 +1,45 @@
// listHelp.h -- Public API for the shared list/dropdown helper library
//
// Declares dropdown arrow drawing, item length scanning, keyboard
// navigation, and popup list painting shared across widget DXEs
// (ListBox, Dropdown, ComboBox, ListView, TreeView).
#ifndef LIST_HELP_H
#define LIST_HELP_H
#include "../core/dvxWidgetPlugin.h"
#define DROPDOWN_BTN_WIDTH 16
#define DROPDOWN_MAX_VISIBLE 8
// ============================================================
// Dropdown arrow glyph
// ============================================================
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color);
// ============================================================
// Item measurement
// ============================================================
int32_t widgetMaxItemLen(const char **items, int32_t count);
// ============================================================
// Keyboard navigation (Up/Down/Home/End/PgUp/PgDn)
// ============================================================
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
// ============================================================
// Popup rect calculation
// ============================================================
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
// ============================================================
// Popup list painting
// ============================================================
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
#endif // LIST_HELP_H

View file

@ -9,7 +9,7 @@ DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
EXE2COFF = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff
CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../core/thirdparty
LDFLAGS = -lm
OBJDIR = ../obj/loader

View file

@ -15,6 +15,7 @@
#include <ctype.h>
#include <dirent.h>
#include <dlfcn.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
@ -29,6 +30,26 @@
#define LIBS_DIR "LIBS"
#define WIDGET_DIR "WIDGETS"
#define LOG_PATH "dvx.log"
// ============================================================
// loaderLog -- write to dvx.log (before shell is loaded)
// ============================================================
static void loaderLog(const char *fmt, ...) {
FILE *f = fopen(LOG_PATH, "a");
if (!f) {
return;
}
va_list ap;
va_start(ap, fmt);
vfprintf(f, fmt, ap);
va_end(ap);
fprintf(f, "\n");
fclose(f);
}
// ============================================================
// Module entry for dependency resolution
@ -49,8 +70,10 @@ typedef struct {
static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods);
static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize);
static void *findSymbol(void **handles, const char *symbol);
static void freeMods(ModuleT *mods);
static void **loadAllModules(void);
static void loadInOrder(ModuleT *mods);
static void logAndReadDeps(ModuleT *mods);
static void readDeps(ModuleT *mod);
static void scanDir(const char *dirPath, const char *ext, ModuleT **mods);
@ -246,14 +269,25 @@ static void loadInOrder(ModuleT *mods) {
continue;
}
loaderLog("Loading: %s", mods[i].path);
mods[i].handle = dlopen(mods[i].path, RTLD_GLOBAL);
if (mods[i].handle) {
RegFnT regFn = (RegFnT)dlsym(mods[i].handle, "_wgtRegister");
if (!mods[i].handle) {
const char *err = dlerror();
loaderLog(" FAILED: %s", err ? err : "(unknown)");
mods[i].loaded = true;
loaded++;
progress = true;
continue;
}
if (regFn) {
regFn();
}
loaderLog(" OK");
RegFnT regFn = (RegFnT)dlsym(mods[i].handle, "_wgtRegister");
if (regFn) {
loaderLog(" Calling wgtRegister()");
regFn();
}
mods[i].loaded = true;
@ -280,31 +314,28 @@ static void loadInOrder(ModuleT *mods) {
//
// Returns a stb_ds dynamic array of dlopen handles.
static void **loadAllModules(void) {
ModuleT *mods = NULL;
static void logAndReadDeps(ModuleT *mods) {
loaderLog("Discovered %d modules:", arrlen(mods));
// Discover all modules
scanDir(LIBS_DIR, ".lib", &mods);
scanDir(WIDGET_DIR, ".wgt", &mods);
for (int32_t i = 0; i < arrlen(mods); i++) {
loaderLog(" [%d] %s (base: %s)", i, mods[i].path, mods[i].baseName);
}
// Read dependency files
for (int32_t i = 0; i < arrlen(mods); i++) {
readDeps(&mods[i]);
}
// Load in dependency order
loadInOrder(mods);
if (arrlen(mods[i].deps) > 0) {
loaderLog(" %s deps:", mods[i].baseName);
// Collect handles
void **handles = NULL;
for (int32_t i = 0; i < arrlen(mods); i++) {
if (mods[i].handle) {
arrput(handles, mods[i].handle);
for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
loaderLog(" %s", mods[i].deps[d]);
}
}
}
}
// Free dep arrays
static void freeMods(ModuleT *mods) {
for (int32_t i = 0; i < arrlen(mods); i++) {
for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
free(mods[i].deps[d]);
@ -314,6 +345,40 @@ static void **loadAllModules(void) {
}
arrfree(mods);
}
static void **loadAllModules(void) {
void **handles = NULL;
// Phase 1: load libraries in dependency order (libs before widgets)
ModuleT *libs = NULL;
scanDir(LIBS_DIR, ".lib", &libs);
logAndReadDeps(libs);
loadInOrder(libs);
for (int32_t i = 0; i < arrlen(libs); i++) {
if (libs[i].handle) {
arrput(handles, libs[i].handle);
}
}
freeMods(libs);
// Phase 2: load widgets in dependency order (all libs already loaded)
ModuleT *widgets = NULL;
scanDir(WIDGET_DIR, ".wgt", &widgets);
logAndReadDeps(widgets);
loadInOrder(widgets);
for (int32_t i = 0; i < arrlen(widgets); i++) {
if (widgets[i].handle) {
arrput(handles, widgets[i].handle);
}
}
freeMods(widgets);
return handles;
}
@ -353,8 +418,18 @@ int main(int argc, char *argv[]) {
platformChdir(exeDir);
}
// Truncate log, then use append-per-write
FILE *logInit = fopen(LOG_PATH, "w");
if (logInit) {
fclose(logInit);
}
loaderLog("DVX Loader starting...");
// Register platform + libc/libm/runtime symbols for DXE resolution
platformRegisterDxeExports();
loaderLog("Platform exports registered.");
// Load all modules from libs/ and widgets/ in dependency order.
// Each module may have a .dep file specifying load-before deps.
@ -372,7 +447,7 @@ int main(int argc, char *argv[]) {
ShellMainFnT shellMain = (ShellMainFnT)findSymbol(handles, "_shellMain");
if (!shellMain) {
fprintf(stderr, "No module exports shellMain\n");
loaderLog("ERROR: No module exports shellMain");
for (int32_t i = arrlen(handles) - 1; i >= 0; i--) {
dlclose(handles[i]);

View file

@ -5,7 +5,7 @@
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../widgets -I../tasks -I../core/thirdparty
OBJDIR = ../obj/shell
LIBSDIR = ../bin/libs
@ -28,7 +28,8 @@ $(LIBSDIR)/dvxshell.dep: ../config/dvxshell.dep | $(LIBSDIR)
sed 's/$$/\r/' $< > $@
$(TARGET): $(OBJS) | $(LIBSDIR)
$(DXE3GEN) -o $@ -E _shell -U $(OBJS)
$(DXE3GEN) -o $(LIBSDIR)/dvxshell.dxe -E _shell -U $(OBJS)
mv $(LIBSDIR)/dvxshell.dxe $@
$(CONFIGDIR)/dvx.ini: ../config/dvx.ini | $(CONFIGDIR)
sed 's/$$/\r/' $< > $@

View file

@ -6,6 +6,7 @@
#include "shellApp.h"
#include "dvxDialog.h"
#include "dvxPlatform.h"
#include "stb_ds.h"
#include <dlfcn.h>
#include <errno.h>
@ -18,13 +19,12 @@
// Module state
// ============================================================
// Fixed-size app slot table. Using a static array (not dynamic) because:
// 1. The number of concurrent apps is bounded by available DOS memory
// 2. App IDs are slot indices, so a fixed array gives O(1) lookup
// 3. No fragmentation concerns from repeated malloc/free cycles
// Slot 0 is reserved (represents the shell itself); apps use slots 1..31.
static ShellAppT sApps[SHELL_MAX_APPS];
int32_t sCurrentAppId = 0;
// Dynamic app slot table (stb_ds array). App IDs are array indices.
// Slot 0 is reserved (represents the shell itself); apps use slots 1+.
// New slots are appended as needed; freed slots are recycled.
static ShellAppT *sApps = NULL;
static int32_t sAppsCap = 0; // number of slots allocated (including slot 0)
int32_t sCurrentAppId = 0;
// ============================================================
// Prototypes
@ -52,15 +52,20 @@ void shellTerminateAllApps(AppContextT *ctx);
// Find the first free slot, starting at 1 (slot 0 is the shell).
// Returns the slot index which also serves as the app's unique ID.
// This linear scan is fine for 32 slots.
// If no free slot exists, appends a new one.
static int32_t allocSlot(void) {
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
for (int32_t i = 1; i < arrlen(sApps); i++) {
if (sApps[i].state == AppStateFreeE) {
return i;
}
}
return -1;
// No free slot -- grow the array
ShellAppT newSlot;
memset(&newSlot, 0, sizeof(newSlot));
arrput(sApps, newSlot);
return arrlen(sApps) - 1;
}
@ -144,7 +149,7 @@ static int32_t copyFile(const char *src, const char *dst) {
// Find an active slot with the given DXE path. Returns the slot pointer,
// or NULL if no running app was loaded from this path.
static ShellAppT *findLoadedPath(const char *path) {
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
for (int32_t i = 1; i < arrlen(sApps); i++) {
if (sApps[i].state != AppStateFreeE && strcmp(sApps[i].path, path) == 0) {
return &sApps[i];
}
@ -194,7 +199,11 @@ static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t
// ============================================================
void shellAppInit(void) {
memset(sApps, 0, sizeof(sApps));
// Seed slot 0 (reserved for the shell)
ShellAppT slot0;
memset(&slot0, 0, sizeof(slot0));
arrput(sApps, slot0);
sAppsCap = 1;
}
@ -255,7 +264,7 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
ShellAppT *shellGetApp(int32_t appId) {
if (appId < 1 || appId >= SHELL_MAX_APPS) {
if (appId < 1 || appId >= arrlen(sApps)) {
return NULL;
}
@ -508,7 +517,7 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
bool shellReapApps(AppContextT *ctx) {
bool reaped = false;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
for (int32_t i = 1; i < arrlen(sApps); i++) {
if (sApps[i].state == AppStateTerminatingE) {
shellReapApp(ctx, &sApps[i]);
reaped = true;
@ -519,10 +528,15 @@ bool shellReapApps(AppContextT *ctx) {
}
int32_t shellAppSlotCount(void) {
return arrlen(sApps);
}
int32_t shellRunningAppCount(void) {
int32_t count = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
for (int32_t i = 1; i < arrlen(sApps); i++) {
if (sApps[i].state == AppStateRunningE || sApps[i].state == AppStateLoadedE) {
count++;
}
@ -533,7 +547,7 @@ int32_t shellRunningAppCount(void) {
void shellTerminateAllApps(AppContextT *ctx) {
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
for (int32_t i = 1; i < arrlen(sApps); i++) {
if (sApps[i].state != AppStateFreeE) {
shellForceKillApp(ctx, &sApps[i]);
}

View file

@ -34,10 +34,6 @@
// ============================================================
#define SHELL_APP_NAME_MAX 64
// Fixed array size for app slots. A small fixed limit avoids dynamic
// allocation complexity and matches the practical limit of a DOS desktop.
// Slot 0 is reserved for the shell itself, so 31 app slots are usable.
#define SHELL_MAX_APPS 32
// Every DXE app exports a global AppDescriptorT named "appDescriptor".
// The shell reads it at load time to determine how to launch the app.
@ -148,6 +144,9 @@ void shellTerminateAllApps(AppContextT *ctx);
// Get app slot by ID (returns NULL if invalid/free)
ShellAppT *shellGetApp(int32_t appId);
// Total number of app slots (for iteration bounds)
int32_t shellAppSlotCount(void);
// Count running apps (not counting the shell itself)
int32_t shellRunningAppCount(void);

View file

@ -1,26 +1,26 @@
// shellExport.c -- DXE wrapper overrides for resource tracking
//
// Registers three wrapper functions via dlregsym that override the
// real dvxCreateWindow, dvxCreateWindowCentered, and dvxDestroyWindow
// for all subsequently loaded app DXEs.
// Registers three wrapper functions that override the real
// dvxCreateWindow, dvxCreateWindowCentered, and dvxDestroyWindow
// for all subsequently loaded app modules.
//
// The key mechanic: dlregsym takes precedence over RTLD_GLOBAL exports.
// Since libdvx.dxe (which has the real functions) was loaded before
// shellExportInit() registers these wrappers, libdvx.dxe keeps the
// real implementations. But any app DXE loaded afterward gets the
// The key mechanic: symbol overrides registered via
// platformRegisterSymOverrides take precedence over previously loaded
// symbols. Since libdvx (which has the real functions) was loaded
// before shellExportInit() registers these wrappers, libdvx keeps the
// real implementations. But any app module loaded afterward gets the
// wrappers, which add resource tracking (appId stamping, last-window
// reaping) transparently.
//
// All other symbol exports (dvx*, wgt*, platform*, libc) are handled
// by the DXE modules and the loader's dlregsym table -- they no longer
// need to be listed here.
// by the loaded modules and the platform layer's export table -- they
// no longer need to be listed here.
#include "shellApp.h"
#include "dvxApp.h"
#include "dvxPlatform.h"
#include "dvxWm.h"
#include <sys/dxe.h>
// ============================================================
// Prototypes
// ============================================================
@ -33,7 +33,7 @@ static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win);
// Wrapper: dvxCreateWindow -- stamps win->appId
// ============================================================
// The wrapper calls the real dvxCreateWindow (resolved from libdvx.dxe
// The wrapper calls the real dvxCreateWindow (resolved from libdvx
// at shellcore load time), then tags the result with sCurrentAppId.
// This is how the shell knows which app owns which window, enabling
// per-app window cleanup on crash/termination.
@ -101,26 +101,19 @@ static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win) {
}
// ============================================================
// Wrapper export table
// ============================================================
// Only three entries: the resource-tracking wrappers exported under
// the original function names. All other symbols come from the
// loaded DXE modules (via RTLD_GLOBAL) and the loader's dlregsym.
DXE_EXPORT_TABLE(sWrapperTable)
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
DXE_EXPORT_END
// ============================================================
// shellExportInit
// ============================================================
// Register the wrapper overrides. Must be called before any
// shellLoadApp() -- wrappers only affect subsequently loaded DXEs.
// shellLoadApp() -- wrappers only affect subsequently loaded modules.
void shellExportInit(void) {
dlregsym(sWrapperTable);
static const PlatformSymOverrideT sOverrides[] = {
{"_dvxCreateWindow", (void *)shellWrapCreateWindow},
{"_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered},
{"_dvxDestroyWindow", (void *)shellWrapDestroyWindow},
{NULL, NULL}
};
platformRegisterSymOverrides(sOverrides);
}

View file

@ -16,11 +16,13 @@
// apps were reaped, so it can refresh its task list.
//
// Crash recovery uses setjmp/longjmp:
// The shell installs signal handlers for SIGSEGV, SIGFPE, SIGILL. If a
// crash occurs in an app task, the handler longjmps back to the setjmp
// point in main(). This works because longjmp restores the main task's
// stack frame regardless of which task was running. tsRecoverToMain()
// then fixes the scheduler's bookkeeping, and the crashed app is killed.
// The platform layer installs signal handlers for SIGSEGV, SIGFPE, SIGILL
// via platformInstallCrashHandler(). If a crash occurs in an app task, the
// handler logs platform-specific diagnostics (register dump on DJGPP),
// then longjmps back to the setjmp point in main(). This works because
// longjmp restores the main task's stack frame regardless of which task
// was running. tsRecoverToMain() then fixes the scheduler's bookkeeping,
// the shell logs app-specific info, and the crashed app is killed.
// This gives the shell Windows 3.1-style fault tolerance -- one bad app
// doesn't take down the whole system.
@ -34,12 +36,8 @@
#include <stdarg.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
// DJGPP-specific: provides __djgpp_exception_state_ptr for accessing
// CPU register state at the point of the exception
#include <sys/exceptn.h>
// ============================================================
// Module state
@ -61,37 +59,9 @@ static DesktopUpdateFnT *sDesktopUpdateFns = NULL;
// Prototypes
// ============================================================
static void crashHandler(int sig);
static void idleYield(void *ctx);
static void installCrashHandler(void);
static void logCrash(int sig);
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
// ============================================================
// crashHandler -- catch page faults and other fatal signals
// ============================================================
// Signal handler for fatal exceptions. DJGPP uses System V signal
// semantics where the handler is reset to SIG_DFL after each delivery,
// so we must re-install it before doing anything else.
//
// The longjmp is the key to crash recovery: it unwinds whatever stack
// we're on (potentially a crashed app's task stack) and restores the
// main task's stack frame to the setjmp point in main(). This is safe
// because cooperative switching means the main task's stack is always
// intact -- it was cleanly suspended at a yield point. The crashed
// task's stack is abandoned (and later freed by tsKill).
static void crashHandler(int sig) {
logCrash(sig);
// Re-install handler (DJGPP resets to SIG_DFL after delivery)
signal(sig, crashHandler);
sCrashSignal = sig;
longjmp(sCrashJmp, 1);
}
// ============================================================
// shellDesktopUpdate -- notify desktop app of state change
// ============================================================
@ -141,71 +111,6 @@ static void idleYield(void *ctx) {
}
// ============================================================
// installCrashHandler
// ============================================================
static void installCrashHandler(void) {
signal(SIGSEGV, crashHandler);
signal(SIGFPE, crashHandler);
signal(SIGILL, crashHandler);
}
// ============================================================
// logCrash -- write exception details to the log
// ============================================================
// Dump as much diagnostic info as possible before longjmp destroys the
// crash context. This runs inside the signal handler, so only
// async-signal-safe functions should be used -- but since we're in
// DJGPP (single-threaded DOS), reentrancy isn't a practical concern
// and vfprintf/fflush are safe to call here.
static void logCrash(int sig) {
const char *sigName = "UNKNOWN";
if (sig == SIGSEGV) {
sigName = "SIGSEGV (page fault)";
} else if (sig == SIGFPE) {
sigName = "SIGFPE (floating point exception)";
} else if (sig == SIGILL) {
sigName = "SIGILL (illegal instruction)";
}
shellLog("=== CRASH ===");
shellLog("Signal: %d (%s)", sig, sigName);
shellLog("Current app ID: %ld", (long)sCurrentAppId);
if (sCurrentAppId > 0) {
ShellAppT *app = shellGetApp(sCurrentAppId);
if (app) {
shellLog("App name: %s", app->name);
shellLog("App path: %s", app->path);
shellLog("Has main loop: %s", app->hasMainLoop ? "yes" : "no");
shellLog("Task ID: %lu", (unsigned long)app->mainTaskId);
}
} else {
shellLog("Crashed in shell (task 0)");
}
// __djgpp_exception_state_ptr is a DJGPP extension that captures the
// full CPU register state at the point of the exception. This gives
// us the faulting EIP, stack pointer, and all GPRs -- invaluable for
// post-mortem debugging of app crashes from the log file.
jmp_buf *estate = __djgpp_exception_state_ptr;
if (estate) {
struct __jmp_buf *regs = &(*estate)[0];
shellLog("EIP: 0x%08lx CS: 0x%04x", regs->__eip, regs->__cs);
shellLog("EAX: 0x%08lx EBX: 0x%08lx ECX: 0x%08lx EDX: 0x%08lx", regs->__eax, regs->__ebx, regs->__ecx, regs->__edx);
shellLog("ESI: 0x%08lx EDI: 0x%08lx EBP: 0x%08lx ESP: 0x%08lx", regs->__esi, regs->__edi, regs->__ebp, regs->__esp);
shellLog("DS: 0x%04x ES: 0x%04x FS: 0x%04x GS: 0x%04x SS: 0x%04x", regs->__ds, regs->__es, regs->__fs, regs->__gs, regs->__ss);
shellLog("EFLAGS: 0x%08lx", regs->__eflags);
}
}
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData) {
(void)userData;
shellLog(" %ldx%ld %ldbpp", (long)w, (long)h, (long)bpp);
@ -268,14 +173,10 @@ int shellMain(int argc, char *argv[]) {
(void)argc;
(void)argv;
// Truncate the log file, then use append-per-write so the file
// isn't held open (allows Notepad to read it while the shell runs).
// The loader already truncates and writes boot info to dvx.log.
// We just append from here on, opening/closing per-write so the
// file isn't held open (allows Notepad to read it while the shell runs).
sLogPath = "dvx.log";
FILE *logInit = fopen(sLogPath, "w");
if (logInit) {
fclose(logInit);
}
shellLog("DVX Shell starting...");
@ -403,21 +304,22 @@ int shellMain(int argc, char *argv[]) {
sCtx.onTitleChange = titleChangeHandler;
sCtx.titleChangeCtx = &sCtx;
// Load the desktop app
int32_t desktopId = shellLoadApp(&sCtx, SHELL_DESKTOP_APP);
// Load the desktop app (configurable via [shell] desktop= in dvx.ini)
const char *desktopApp = prefsGetString("shell", "desktop", SHELL_DESKTOP_APP);
int32_t desktopId = shellLoadApp(&sCtx, desktopApp);
if (desktopId < 0) {
shellLog("Failed to load desktop app '%s'", SHELL_DESKTOP_APP);
shellLog("Failed to load desktop app '%s'", desktopApp);
tsShutdown();
dvxShutdown(&sCtx);
return 1;
}
// Install crash handler after everything is initialized -- if
// initialization itself crashes, we want the default DJGPP behavior
// initialization itself crashes, we want the default behavior
// (abort with register dump) rather than our recovery path, because
// the system isn't in a recoverable state yet.
installCrashHandler();
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, shellLog);
shellLog("DVX Shell ready.");
@ -432,6 +334,23 @@ int shellMain(int argc, char *argv[]) {
// Fix it before doing anything else so the scheduler is consistent.
tsRecoverToMain();
// Platform handler already logged signal name and register dump.
// Log app-specific info here.
shellLog("Current app ID: %ld", (long)sCurrentAppId);
if (sCurrentAppId > 0) {
ShellAppT *crashedApp = shellGetApp(sCurrentAppId);
if (crashedApp) {
shellLog("App name: %s", crashedApp->name);
shellLog("App path: %s", crashedApp->path);
shellLog("Has main loop: %s", crashedApp->hasMainLoop ? "yes" : "no");
shellLog("Task ID: %lu", (unsigned long)crashedApp->mainTaskId);
}
} else {
shellLog("Crashed in shell (task 0)");
}
shellLog("Recovering from crash, killing app %ld", (long)sCurrentAppId);
if (sCurrentAppId > 0) {

View file

@ -8,6 +8,10 @@
#include "shellApp.h"
#include "dvxDialog.h"
#include "dvxWidget.h"
#include "widgetBox.h"
#include "widgetButton.h"
#include "widgetLabel.h"
#include "widgetListView.h"
#include "dvxWm.h"
#include "dvxPlatform.h"
#include "stb_ds.h"
@ -65,7 +69,7 @@ static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings
static ShellAppT *getRunningAppByIndex(int32_t sel) {
int32_t idx = 0;
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
for (int32_t i = 1; i < shellAppSlotCount(); i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {
@ -202,7 +206,7 @@ static void refreshTaskList(void) {
static int32_t *appIds = NULL;
arrsetlen(appIds, 0);
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
for (int32_t i = 1; i < shellAppSlotCount(); i++) {
ShellAppT *app = shellGetApp(i);
if (app && app->state == AppStateRunningE) {

View file

@ -19,7 +19,8 @@ TARGET = $(LIBSDIR)/libtasks.lib
all: $(TARGET)
$(TARGET): $(OBJS) | $(LIBSDIR)
$(DXE3GEN) -o $@ -E _ts -E _stbds_ -U $(OBJS)
$(DXE3GEN) -o $(LIBSDIR)/libtasks.dxe -E _ts -U $(OBJS)
mv $(LIBSDIR)/libtasks.dxe $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
@ -31,7 +32,7 @@ $(LIBSDIR):
mkdir -p $(LIBSDIR)
# Dependencies
$(OBJDIR)/taskswitch.o: taskswitch.c taskswitch.h thirdparty/stb_ds.h
$(OBJDIR)/taskswitch.o: taskswitch.c taskswitch.h ../core/thirdparty/stb_ds.h
clean:
rm -f $(OBJS) $(TARGET)

View file

@ -1,144 +0,0 @@
// demo.c -- Demonstration of the cooperative task switching library
//
// Shows priority scheduling, round-robin, pausing/resuming, dynamic
// priority changes, and slot reuse after task termination.
//
// This is a standalone test harness that runs on the host (Linux x86_64)
// or DJGPP target to verify the task switcher without the GUI stack.
// Each phase exercises a different scheduler feature and prints output
// so you can verify the scheduling order visually.
#include <stdio.h>
#include "taskswitch.h"
// Forward declarations
static void pauseTarget(void *arg);
static void shortLived(void *arg);
static void worker(void *arg);
static void pauseTarget(void *arg) {
(void)arg;
for (int32_t i = 0; i < 10; i++) {
printf(" [pauser] iteration %d\n", (int)i);
tsYield();
}
printf(" [pauser] finished\n");
}
static void shortLived(void *arg) {
int32_t id = (int32_t)(intptr_t)arg;
printf(" [short-%d] running (id=%d)\n", (int)id, (int)tsCurrentId());
tsYield();
printf(" [short-%d] done\n", (int)id);
}
static void worker(void *arg) {
const char *name = (const char *)arg;
int32_t pri = tsGetPriority(tsCurrentId());
for (int32_t i = 0; i < 5; i++) {
printf(" [%s] step %d (priority %d)\n", name, (int)i, (int)pri);
tsYield();
}
printf(" [%s] finished\n", name);
}
int main(void) {
printf("=== Cooperative Task Switching Demo ===\n\n");
if (tsInit() != TS_OK) {
printf("ERROR: failed to initialize task system\n");
return 1;
}
int32_t tHigh = tsCreate("high", worker, "high", 0, TS_PRIORITY_HIGH);
int32_t tNorm = tsCreate("normal", worker, "normal", 0, TS_PRIORITY_NORMAL);
int32_t tLow = tsCreate("low", worker, "low", 0, TS_PRIORITY_LOW);
int32_t tPauser = tsCreate("pauser", pauseTarget, NULL, 0, TS_PRIORITY_NORMAL);
if (tHigh < 0 || tNorm < 0 || tLow < 0 || tPauser < 0) {
printf("ERROR: failed to create tasks\n");
tsShutdown();
return 1;
}
printf("Created: high=%d normal=%d low=%d pauser=%d\n\n", (int)tHigh, (int)tNorm, (int)tLow, (int)tPauser);
// Phase 1: all tasks run, but high-priority tasks get more turns per round.
// Low (priority 0) gets 1 turn, normal (5) gets 6, high (10) gets 11.
// In the output you'll see "high" appearing much more frequently than
// "low", demonstrating the weighted fair-share scheduling.
printf("--- Phase 1: Priority scheduling ---\n");
for (int32_t i = 0; i < 6; i++) {
printf("[main] yield %d\n", (int)i);
tsYield();
}
// Phase 2: pause the pauser task so it stops being scheduled
printf("\n--- Phase 2: Pause 'pauser' ---\n");
tsPause((uint32_t)tPauser);
printf("[main] pauser state: %d (expect %d=paused)\n", tsGetState((uint32_t)tPauser), TaskStatePaused);
for (int32_t i = 0; i < 4; i++) {
printf("[main] yield %d (pauser is paused)\n", (int)i);
tsYield();
}
// Phase 3: resume pauser
printf("\n--- Phase 3: Resume 'pauser' ---\n");
tsResume((uint32_t)tPauser);
for (int32_t i = 0; i < 4; i++) {
printf("[main] yield %d\n", (int)i);
tsYield();
}
// Phase 4: boost low to the highest priority so it gets more turns
printf("\n--- Phase 4: Boost 'low' to highest ---\n");
tsSetPriority((uint32_t)tLow, TS_PRIORITY_HIGH + 5);
for (int32_t i = 0; i < 6; i++) {
printf("[main] yield %d\n", (int)i);
tsYield();
}
// Let remaining tasks finish
printf("\n--- Draining remaining tasks ---\n");
while (tsActiveCount() > 1) {
tsYield();
}
// Phase 5: dynamic creation with slot reuse.
// After all tasks from phases 1-4 have terminated, their slots are free.
// Creating new tasks should recycle those slots (IDs 1-4) rather than
// growing the array. Each "wave" creates 3 tasks, lets them finish,
// then creates 3 more -- the IDs should repeat across waves.
printf("\n--- Phase 5: Slot reuse ---\n");
printf("[main] active before: %u\n", (unsigned)tsActiveCount());
// Create short-lived tasks that will terminate and free their slots
for (int32_t wave = 0; wave < 3; wave++) {
printf("[main] wave %d: creating 3 tasks\n", (int)wave);
int32_t ids[3];
for (int32_t j = 0; j < 3; j++) {
int32_t tag = wave * 3 + j;
char name[TS_NAME_MAX];
snprintf(name, sizeof(name), "short-%d", (int)tag);
ids[j] = tsCreate(name, shortLived, (void *)(intptr_t)tag, 0, TS_PRIORITY_NORMAL);
printf("[main] created id=%d\n", (int)ids[j]);
}
// Let them all finish
while (tsActiveCount() > 1) {
tsYield();
}
printf("[main] wave %d complete, active=%u\n", (int)wave, (unsigned)tsActiveCount());
}
printf("\nActive tasks: %u\n", (unsigned)tsActiveCount());
printf("=== Demo complete ===\n");
tsShutdown();
return 0;
}

View file

@ -21,8 +21,7 @@
// scheduler's linear scan. The downside (holes after termination) is
// mitigated by slot recycling in findFreeSlot().
#define STB_DS_IMPLEMENTATION
#include "thirdparty/stb_ds.h"
#include "../core/thirdparty/stb_ds.h"
#include "taskswitch.h"
#include <stdlib.h>
@ -219,7 +218,7 @@ static void __attribute__((noinline)) contextSwitch(TaskContextT *save, TaskCont
// Find a free (terminated or unallocated) slot in the task array.
// Returns the index, or -1 if no free slot exists.
// Starts at 1 because slot 0 is always the main task and cannot be reused.
// Linear scan is fine -- SHELL_MAX_APPS caps the practical limit at ~32 tasks.
// Linear scan is fine for the typical task count (tens, not hundreds).
static int32_t findFreeSlot(void) {
ptrdiff_t count = arrlen(tasks);
for (ptrdiff_t i = 1; i < count; i++) {

42
texthelp/Makefile Normal file
View file

@ -0,0 +1,42 @@
# Text Help Library Makefile for DJGPP cross-compilation
#
# Builds texthelp.lib -- shared text editing infrastructure
# (clipboard, multi-click, word boundaries, single-line editing engine).
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -I../core -I../core/platform -I../tasks -I../core/thirdparty
OBJDIR = ../obj/texthelp
LIBSDIR = ../bin/libs
SRCS = textHelp.c
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
TARGET = $(LIBSDIR)/texthelp.lib
.PHONY: all clean
all: $(TARGET) $(LIBSDIR)/texthelp.dep
$(LIBSDIR)/texthelp.dep: ../config/texthelp.dep | $(LIBSDIR)
sed 's/$$/\r/' $< > $@
$(TARGET): $(OBJS) | $(LIBSDIR)
$(DXE3GEN) -o $(LIBSDIR)/texthelp.dxe -E _clipboard -E _clearOther -E _isWordChar -E _multiClick -E _widgetText -E _wordStart -E _wordEnd -U $(OBJS)
mv $(LIBSDIR)/texthelp.dxe $@
$(OBJDIR)/%.o: %.c | $(OBJDIR)
$(CC) $(CFLAGS) -c -o $@ $<
$(OBJDIR):
mkdir -p $(OBJDIR)
$(LIBSDIR):
mkdir -p $(LIBSDIR)
# Dependencies
$(OBJDIR)/textHelp.o: textHelp.c textHelp.h ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
clean:
rm -f $(OBJS) $(TARGET) $(LIBSDIR)/texthelp.dep

739
texthelp/textHelp.c Normal file
View file

@ -0,0 +1,739 @@
// textHelp.c -- Shared text editing infrastructure
//
// Provides clipboard, multi-click detection, word boundary logic,
// cross-widget selection clearing, and the single-line text editing
// engine. Shared across multiple widget DXEs (TextInput, TextArea,
// ComboBox, Spinner, AnsiTerm) via the texthelp.lib DXE.
#include "textHelp.h"
#include <ctype.h>
#include <time.h>
// ============================================================
// Static state
// ============================================================
// Track the widget that last had an active selection so we can
// clear it in O(1) instead of walking every widget in every window.
static WidgetT *sLastSelectedWidget = NULL;
// ============================================================
// Static helper prototypes
// ============================================================
static bool clearSelectionOnWidget(WidgetT *w);
static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd);
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
static int32_t wordBoundaryLeft(const char *buf, int32_t pos);
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
// ============================================================
// clearOtherSelections
// ============================================================
//
// Clears selection on the previously-selected widget (if different
// from the newly-focused one). Validates that the previous widget's
// window is still in the window stack before touching it -- the
// window may have been closed since sLastSelectedWidget was set.
// If the previous widget was in a different window, that window
// gets a full repaint to clear the stale selection highlight.
void clearOtherSelections(WidgetT *except) {
if (!except || !except->window || !except->window->widgetRoot) {
return;
}
WidgetT *prev = sLastSelectedWidget;
sLastSelectedWidget = except;
if (!prev || prev == except) {
return;
}
// Verify the widget is still alive (its window still in the stack)
WindowT *prevWin = prev->window;
if (!prevWin) {
return;
}
AppContextT *ctx = wgtGetContext(except);
if (!ctx) {
return;
}
bool found = false;
for (int32_t i = 0; i < ctx->stack.count; i++) {
if (ctx->stack.windows[i] == prevWin) {
found = true;
break;
}
}
if (!found) {
return;
}
if (clearSelectionOnWidget(prev) && prevWin != except->window) {
dvxInvalidateWindow(ctx, prevWin);
}
}
static bool clearSelectionOnWidget(WidgetT *w) {
if (w->wclass && w->wclass->clearSelection) {
return w->wclass->clearSelection(w);
}
return false;
}
bool isWordChar(char c) {
return isalnum((unsigned char)c) || c == '_';
}
// ============================================================
// Shared undo/selection helpers
// ============================================================
static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd) {
int32_t lo = *pSelStart < *pSelEnd ? *pSelStart : *pSelEnd;
int32_t hi = *pSelStart < *pSelEnd ? *pSelEnd : *pSelStart;
if (lo < 0) {
lo = 0;
}
if (hi > *pLen) {
hi = *pLen;
}
if (lo >= hi) {
*pSelStart = -1;
*pSelEnd = -1;
return;
}
memmove(buf + lo, buf + hi, *pLen - hi + 1);
*pLen -= (hi - lo);
*pCursor = lo;
*pSelStart = -1;
*pSelEnd = -1;
}
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize) {
if (!undoBuf) {
return;
}
int32_t copyLen = len < bufSize ? len : bufSize - 1;
memcpy(undoBuf, buf, copyLen);
undoBuf[copyLen] = '\0';
*pUndoLen = copyLen;
*pUndoCursor = cursor;
}
// ============================================================
// widgetTextEditDragUpdateLine
// ============================================================
//
// Called during mouse drag to extend the selection for single-line
// text widgets. Auto-scrolls when the mouse moves past the visible
// text edges.
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd) {
int32_t rightEdge = leftEdge + maxChars * font->charWidth;
if (vx < leftEdge && *pScrollOff > 0) {
(*pScrollOff)--;
} else if (vx >= rightEdge && *pScrollOff + maxChars < len) {
(*pScrollOff)++;
}
int32_t relX = vx - leftEdge;
int32_t charPos = relX / font->charWidth + *pScrollOff;
if (charPos < 0) {
charPos = 0;
}
if (charPos > len) {
charPos = len;
}
*pCursorPos = charPos;
*pSelEnd = charPos;
}
// ============================================================
// widgetTextEditMouseClick
// ============================================================
//
// Computes cursor position from pixel coordinates, handles multi-click
// (double = word select, triple = select all), and optionally starts
// drag-select.
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect) {
int32_t relX = vx - textLeftX;
int32_t charPos = relX / font->charWidth + scrollOff;
if (charPos < 0) {
charPos = 0;
}
if (charPos > len) {
charPos = len;
}
int32_t clicks = multiClickDetect(vx, vy);
if (clicks >= 3) {
*pSelStart = 0;
*pSelEnd = len;
*pCursorPos = len;
sDragTextSelect = NULL;
return;
}
if (clicks == 2) {
if (wordSelect && buf) {
int32_t ws = wordStart(buf, charPos);
int32_t we = wordEnd(buf, len, charPos);
*pSelStart = ws;
*pSelEnd = we;
*pCursorPos = we;
} else {
*pSelStart = 0;
*pSelEnd = len;
*pCursorPos = len;
}
sDragTextSelect = NULL;
return;
}
// Single click: place cursor
*pCursorPos = charPos;
*pSelStart = charPos;
*pSelEnd = charPos;
sDragTextSelect = dragSelect ? w : NULL;
}
// ============================================================
// widgetTextEditOnKey -- shared single-line text editing logic
// ============================================================
//
// This is the core single-line text editing engine, parameterized by
// pointer to allow reuse across TextInput, Spinner, and ComboBox.
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor) {
bool shift = (mod & KEY_MOD_SHIFT) != 0;
bool hasSel = (pSelStart && pSelEnd && *pSelStart >= 0 && *pSelEnd >= 0 && *pSelStart != *pSelEnd);
int32_t selLo = hasSel ? (*pSelStart < *pSelEnd ? *pSelStart : *pSelEnd) : -1;
int32_t selHi = hasSel ? (*pSelStart < *pSelEnd ? *pSelEnd : *pSelStart) : -1;
// Clamp selection to buffer bounds
if (hasSel) {
if (selLo < 0) {
selLo = 0;
}
if (selHi > *pLen) {
selHi = *pLen;
}
if (selLo >= selHi) {
hasSel = false;
selLo = -1;
selHi = -1;
}
}
// Ctrl+A -- select all
if (key == 1 && pSelStart && pSelEnd) {
*pSelStart = 0;
*pSelEnd = *pLen;
*pCursor = *pLen;
goto adjustScroll;
}
// Ctrl+C -- copy
if (key == 3) {
if (hasSel) {
clipboardCopy(buf + selLo, selHi - selLo);
}
return;
}
// Ctrl+V -- paste
if (key == 22) {
int32_t cbLen = 0;
const char *cb = clipboardGet(&cbLen);
if (cbLen > 0) {
if (undoBuf) {
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
}
if (hasSel) {
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
}
int32_t canFit = bufSize - 1 - *pLen;
// For single-line, skip newlines in clipboard
int32_t paste = 0;
for (int32_t i = 0; i < cbLen && paste < canFit; i++) {
if (cb[i] != '\n' && cb[i] != '\r') {
paste++;
}
}
if (paste > 0) {
int32_t pos = *pCursor;
memmove(buf + pos + paste, buf + pos, *pLen - pos + 1);
int32_t j = 0;
for (int32_t i = 0; i < cbLen && j < paste; i++) {
if (cb[i] != '\n' && cb[i] != '\r') {
buf[pos + j] = cb[i];
j++;
}
}
*pLen += paste;
*pCursor += paste;
}
if (w->onChange) {
w->onChange(w);
}
}
goto adjustScroll;
}
// Ctrl+X -- cut
if (key == 24) {
if (hasSel) {
clipboardCopy(buf + selLo, selHi - selLo);
if (undoBuf) {
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
}
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
if (w->onChange) {
w->onChange(w);
}
}
goto adjustScroll;
}
// Ctrl+Z -- undo
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
// Swap current and undo
char tmpBuf[clipboardMaxLen() + 1];
int32_t tmpLen = *pLen;
int32_t tmpCursor = *pCursor;
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
memcpy(tmpBuf, buf, copyLen);
tmpBuf[copyLen] = '\0';
int32_t restLen = *pUndoLen < bufSize - 1 ? *pUndoLen : bufSize - 1;
memcpy(buf, undoBuf, restLen);
buf[restLen] = '\0';
*pLen = restLen;
int32_t restoreCursor = *pUndoCursor < *pLen ? *pUndoCursor : *pLen;
// Save old as new undo
int32_t saveLen = copyLen < bufSize - 1 ? copyLen : bufSize - 1;
memcpy(undoBuf, tmpBuf, saveLen);
undoBuf[saveLen] = '\0';
*pUndoLen = saveLen;
*pUndoCursor = tmpCursor;
*pCursor = restoreCursor;
if (pSelStart) {
*pSelStart = -1;
}
if (pSelEnd) {
*pSelEnd = -1;
}
if (w->onChange) {
w->onChange(w);
}
goto adjustScroll;
}
if (key >= 32 && key < 127) {
// Printable character
if (undoBuf) {
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
}
if (hasSel) {
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
}
if (*pLen < bufSize - 1) {
int32_t pos = *pCursor;
memmove(buf + pos + 1, buf + pos, *pLen - pos + 1);
buf[pos] = (char)key;
(*pLen)++;
(*pCursor)++;
if (w->onChange) {
w->onChange(w);
}
}
} else if (key == 8) {
// Backspace
if (hasSel) {
if (undoBuf) {
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
}
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
if (w->onChange) {
w->onChange(w);
}
} else if (*pCursor > 0) {
if (undoBuf) {
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
}
int32_t pos = *pCursor;
memmove(buf + pos - 1, buf + pos, *pLen - pos + 1);
(*pLen)--;
(*pCursor)--;
if (w->onChange) {
w->onChange(w);
}
}
} else if (key == (0x4B | 0x100)) {
// Left arrow
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
if (*pCursor > 0) {
(*pCursor)--;
}
*pSelEnd = *pCursor;
} else {
if (pSelStart) {
*pSelStart = -1;
}
if (pSelEnd) {
*pSelEnd = -1;
}
if (*pCursor > 0) {
(*pCursor)--;
}
}
} else if (key == (0x4D | 0x100)) {
// Right arrow
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
if (*pCursor < *pLen) {
(*pCursor)++;
}
*pSelEnd = *pCursor;
} else {
if (pSelStart) {
*pSelStart = -1;
}
if (pSelEnd) {
*pSelEnd = -1;
}
if (*pCursor < *pLen) {
(*pCursor)++;
}
}
} else if (key == (0x73 | 0x100)) {
// Ctrl+Left -- word left
int32_t newPos = wordBoundaryLeft(buf, *pCursor);
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = newPos;
*pSelEnd = newPos;
} else {
if (pSelStart) {
*pSelStart = -1;
}
if (pSelEnd) {
*pSelEnd = -1;
}
*pCursor = newPos;
}
} else if (key == (0x74 | 0x100)) {
// Ctrl+Right -- word right
int32_t newPos = wordBoundaryRight(buf, *pLen, *pCursor);
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = newPos;
*pSelEnd = newPos;
} else {
if (pSelStart) {
*pSelStart = -1;
}
if (pSelEnd) {
*pSelEnd = -1;
}
*pCursor = newPos;
}
} else if (key == (0x47 | 0x100)) {
// Home
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = 0;
*pSelEnd = 0;
} else {
if (pSelStart) {
*pSelStart = -1;
}
if (pSelEnd) {
*pSelEnd = -1;
}
*pCursor = 0;
}
} else if (key == (0x4F | 0x100)) {
// End
if (shift && pSelStart && pSelEnd) {
if (*pSelStart < 0) {
*pSelStart = *pCursor;
*pSelEnd = *pCursor;
}
*pCursor = *pLen;
*pSelEnd = *pLen;
} else {
if (pSelStart) {
*pSelStart = -1;
}
if (pSelEnd) {
*pSelEnd = -1;
}
*pCursor = *pLen;
}
} else if (key == (0x53 | 0x100)) {
// Delete
if (hasSel) {
if (undoBuf) {
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
}
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
if (w->onChange) {
w->onChange(w);
}
} else if (*pCursor < *pLen) {
if (undoBuf) {
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
}
int32_t pos = *pCursor;
memmove(buf + pos, buf + pos + 1, *pLen - pos);
(*pLen)--;
if (w->onChange) {
w->onChange(w);
}
}
} else {
return;
}
adjustScroll:
// Adjust scroll offset to keep cursor visible
{
AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font;
int32_t fieldW = w->w;
if (w->wclass && w->wclass->getTextFieldWidth) {
fieldW = w->wclass->getTextFieldWidth(w);
}
int32_t visibleChars = (fieldW - TEXT_INPUT_PAD * 2) / font->charWidth;
if (*pCursor < *pScrollOff) {
*pScrollOff = *pCursor;
}
if (*pCursor >= *pScrollOff + visibleChars) {
*pScrollOff = *pCursor - visibleChars + 1;
}
}
wgtInvalidatePaint(w);
}
// ============================================================
// widgetTextEditPaintLine
// ============================================================
//
// Renders a single line of text with optional selection highlighting
// and a blinking cursor.
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX) {
// Normalize selection to low/high
int32_t selLo = -1;
int32_t selHi = -1;
if (selStart >= 0 && selEnd >= 0 && selStart != selEnd) {
selLo = selStart < selEnd ? selStart : selEnd;
selHi = selStart < selEnd ? selEnd : selStart;
}
// Map selection to visible range
int32_t visSelLo = selLo - scrollOff;
int32_t visSelHi = selHi - scrollOff;
if (visSelLo < 0) { visSelLo = 0; }
if (visSelHi > visLen) { visSelHi = visLen; }
if (selLo >= 0 && visSelLo < visSelHi) {
if (visSelLo > 0) {
drawTextN(d, ops, font, textX, textY, buf, visSelLo, fg, bg, true);
}
drawTextN(d, ops, font, textX + visSelLo * font->charWidth, textY, buf + visSelLo, visSelHi - visSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
if (visSelHi < visLen) {
drawTextN(d, ops, font, textX + visSelHi * font->charWidth, textY, buf + visSelHi, visLen - visSelHi, fg, bg, true);
}
} else if (visLen > 0) {
drawTextN(d, ops, font, textX, textY, buf, visLen, fg, bg, true);
}
// Blinking cursor
if (showCursor && sCursorBlinkOn) {
int32_t cursorX = textX + (cursorPos - scrollOff) * font->charWidth;
if (cursorX >= cursorMinX && cursorX < cursorMaxX) {
drawVLine(d, ops, cursorX, textY, font->charHeight, fg);
}
}
}
// ============================================================
// Word boundary helpers
// ============================================================
static int32_t wordBoundaryLeft(const char *buf, int32_t pos) {
if (pos <= 0) {
return 0;
}
// Skip non-word characters
while (pos > 0 && !isalnum((unsigned char)buf[pos - 1]) && buf[pos - 1] != '_') {
pos--;
}
// Skip word characters
while (pos > 0 && (isalnum((unsigned char)buf[pos - 1]) || buf[pos - 1] == '_')) {
pos--;
}
return pos;
}
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos) {
if (pos >= len) {
return len;
}
// Skip word characters
while (pos < len && (isalnum((unsigned char)buf[pos]) || buf[pos] == '_')) {
pos++;
}
// Skip non-word characters
while (pos < len && !isalnum((unsigned char)buf[pos]) && buf[pos] != '_') {
pos++;
}
return pos;
}
int32_t wordEnd(const char *buf, int32_t len, int32_t pos) {
while (pos < len && isWordChar(buf[pos])) {
pos++;
}
return pos;
}
int32_t wordStart(const char *buf, int32_t pos) {
while (pos > 0 && isWordChar(buf[pos - 1])) {
pos--;
}
return pos;
}

38
texthelp/textHelp.h Normal file
View file

@ -0,0 +1,38 @@
// textHelp.h -- Public API for the shared text editing infrastructure library
//
// Declares clipboard operations, multi-click detection, word boundary
// logic, cross-widget selection clearing, and the single-line text
// editing engine shared across widget DXEs (TextInput, Spinner,
// ComboBox, AnsiTerm, etc.).
#ifndef TEXT_HELP_H
#define TEXT_HELP_H
#include "../core/dvxWidgetPlugin.h"
#define TEXT_INPUT_PAD 3
// ============================================================
// Selection management
// ============================================================
void clearOtherSelections(WidgetT *except);
// ============================================================
// Word / character helpers
// ============================================================
bool isWordChar(char c);
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
int32_t wordStart(const char *buf, int32_t pos);
// ============================================================
// Single-line text editing engine
// ============================================================
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
#endif // TEXT_HELP_H

View file

@ -6,7 +6,7 @@
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../core/thirdparty
OBJDIR = ../obj/widgets
WGTDIR = ../bin/widgets
@ -69,11 +69,18 @@ WGT_MODS = $(WGTDIR)/box.wgt \
.PHONY: all clean
all: $(WGT_MODS)
DEPFILES = textinpt combobox spinner terminal listbox dropdown listview treeview
WGT_DEPS = $(DEPFILES:%=$(WGTDIR)/%.dep)
# Generic widget module rule: -E _wgt exports all public API + wgtRegister
all: $(WGT_MODS) $(WGT_DEPS)
$(WGTDIR)/%.dep: ../config/%.dep | $(WGTDIR)
sed 's/$$/\r/' $< > $@
# Generic widget module rule: export only the registration function
$(WGTDIR)/%.wgt: | $(WGTDIR)
$(DXE3GEN) -o $@ -E _wgt -U $<
$(DXE3GEN) -o $(WGTDIR)/$*.dxe -E _wgtRegister -U $<
mv $(WGTDIR)/$*.dxe $@
# Map .wgt name to .o file
$(WGTDIR)/box.wgt: $(OBJDIR)/widgetBox.o
@ -114,7 +121,7 @@ $(WGTDIR):
mkdir -p $(WGTDIR)
# Dependencies
WIDGET_DEPS = ../core/widgetInternal.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
WIDGET_DEPS = ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
$(OBJDIR)/widgetAnsiTerm.o: widgetAnsiTerm.c $(WIDGET_DEPS)
$(OBJDIR)/widgetBox.o: widgetBox.c $(WIDGET_DEPS)
$(OBJDIR)/widgetButton.o: widgetButton.c $(WIDGET_DEPS)
@ -143,5 +150,5 @@ $(OBJDIR)/widgetToolbar.o: widgetToolbar.c $(WIDGET_DEPS)
$(OBJDIR)/widgetTreeView.o: widgetTreeView.c $(WIDGET_DEPS)
clean:
rm -f $(OBJS) $(WGT_MODS)
rm -f $(OBJS) $(WGT_MODS) $(WGT_DEPS)
-rmdir $(WGTDIR) 2>/dev/null

View file

@ -66,7 +66,7 @@ multiple related types).
## Writing a New Widget
1. Create `widgets/myWidget.c`
2. Include `"widgetInternal.h"` for infrastructure access
2. Include `"dvxWidgetPlugin.h"` for infrastructure access
3. Implement paint, calcMinSize, and event handlers as needed
4. Define a `static const WidgetClassT` with your vtable
5. Add a `wgtRegister()` function:

File diff suppressed because it is too large Load diff

31
widgets/widgetAnsiTerm.h Normal file
View file

@ -0,0 +1,31 @@
// widgetAnsiTerm.h -- ANSI terminal widget API
#ifndef WIDGET_ANSITERM_H
#define WIDGET_ANSITERM_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, int32_t cols, int32_t rows);
void (*write)(WidgetT *w, const uint8_t *data, int32_t len);
void (*clear)(WidgetT *w);
void (*setComm)(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t));
void (*setScrollback)(WidgetT *w, int32_t maxLines);
void (*poll)(WidgetT *w);
int32_t (*repaint)(WidgetT *w, int32_t *outY, int32_t *outH);
} AnsiTermApiT;
static inline const AnsiTermApiT *dvxAnsiTermApi(void) {
static const AnsiTermApiT *sApi;
if (!sApi) { sApi = (const AnsiTermApiT *)wgtGetApi("ansiterm"); }
return sApi;
}
#define wgtAnsiTerm(parent, cols, rows) dvxAnsiTermApi()->create(parent, cols, rows)
#define wgtAnsiTermWrite(w, data, len) dvxAnsiTermApi()->write(w, data, len)
#define wgtAnsiTermClear(w) dvxAnsiTermApi()->clear(w)
#define wgtAnsiTermSetComm(w, ctx, readFn, writeFn) dvxAnsiTermApi()->setComm(w, ctx, readFn, writeFn)
#define wgtAnsiTermSetScrollback(w, maxLines) dvxAnsiTermApi()->setScrollback(w, maxLines)
#define wgtAnsiTermPoll(w) dvxAnsiTermApi()->poll(w)
#define wgtAnsiTermRepaint(w, outY, outH) dvxAnsiTermApi()->repaint(w, outY, outH)
#endif // WIDGET_ANSITERM_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetBox.c -- VBox, HBox, and Frame container widgets
//
// VBox and HBox are the primary layout containers. They have no visual
@ -18,7 +19,23 @@
// "break" the border behind the title -- this is the classic Win3.1/Motif
// group box appearance.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
static int32_t sVBoxTypeId = -1;
static int32_t sHBoxTypeId = -1;
static int32_t sFrameTypeId = -1;
typedef enum {
FrameInE,
FrameOutE,
FrameFlatE
} FrameStyleE;
typedef struct {
const char *title;
FrameStyleE style;
uint32_t color;
} FrameDataT;
// ============================================================
@ -37,15 +54,16 @@
// FrameOutE -- Motif "ridge" (outset bevel: highlight-then-shadow)
// The groove/ridge are each two nested 1px bevels with swapped colors.
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
int32_t fb = widgetFrameBorderWidth(w);
int32_t boxY = w->y + font->charHeight / 2;
int32_t boxH = w->h - font->charHeight / 2;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
FrameDataT *fd = (FrameDataT *)w->data;
int32_t fb = widgetFrameBorderWidth(w);
int32_t boxY = w->y + font->charHeight / 2;
int32_t boxH = w->h - font->charHeight / 2;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
if (w->as.frame.style == FrameFlatE) {
if (fd->style == FrameFlatE) {
// Flat: solid color rectangle outline
uint32_t fc = w->as.frame.color ? w->as.frame.color : colors->windowShadow;
uint32_t fc = fd->color ? fd->color : colors->windowShadow;
drawHLine(d, ops, w->x, boxY, w->w, fc);
drawHLine(d, ops, w->x, boxY + boxH - 1, w->w, fc);
@ -56,7 +74,7 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
BevelStyleT outer;
BevelStyleT inner;
if (w->as.frame.style == FrameInE) {
if (fd->style == FrameInE) {
outer.highlight = colors->windowShadow;
outer.shadow = colors->windowHighlight;
inner.highlight = colors->windowHighlight;
@ -77,8 +95,8 @@ 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 = textWidthAccel(font, w->as.frame.title);
if (fd->title && fd->title[0]) {
int32_t titleW = textWidthAccel(font, fd->title);
int32_t titleX = w->x + DEFAULT_PADDING + fb;
int32_t titleY = boxY + (fb - font->charHeight) / 2;
@ -86,50 +104,33 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
titleW + 4, font->charHeight, bg);
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, titleX, titleY, w->as.frame.title, colors);
drawTextAccelEmbossed(d, ops, font, titleX, titleY, fd->title, colors);
} else {
drawTextAccel(d, ops, font, titleX, titleY, w->as.frame.title, fg, bg, true);
drawTextAccel(d, ops, font, titleX, titleY, fd->title, fg, bg, true);
}
}
}
// ============================================================
// wgtFrame
// widgetFrameGetLayoutMetrics
// ============================================================
// Create a Frame container. The title string supports accelerator keys
// (prefixed with '&') which are parsed by accelParse. The title pointer
// is stored directly (not copied) -- the caller must ensure it remains valid.
WidgetT *wgtFrame(WidgetT *parent, const char *title) {
WidgetT *w = widgetAlloc(parent, WidgetFrameE);
if (w) {
w->as.frame.title = title;
w->as.frame.style = FrameInE;
w->as.frame.color = 0;
w->accelKey = accelParse(title);
}
return w;
void widgetFrameGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW) {
FrameDataT *fd = (FrameDataT *)w->data;
*pad = DEFAULT_PADDING;
*gap = 0;
*extraTop = font ? font->charHeight / 2 : 0;
*borderW = (fd && fd->style == FrameFlatE) ? 1 : 2;
}
// ============================================================
// wgtHBox
// widgetFrameDestroy
// ============================================================
WidgetT *wgtHBox(WidgetT *parent) {
return widgetAlloc(parent, WidgetHBoxE);
}
// ============================================================
// wgtVBox
// ============================================================
WidgetT *wgtVBox(WidgetT *parent) {
return widgetAlloc(parent, WidgetVBoxE);
void widgetFrameDestroy(WidgetT *w) {
free(w->data);
}
@ -165,20 +166,68 @@ static const WidgetClassT sClassHBox = {
};
static const WidgetClassT sClassFrame = {
.flags = WCLASS_BOX_CONTAINER,
.paint = widgetFramePaint,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
.flags = WCLASS_BOX_CONTAINER | WCLASS_FOCUS_FORWARD,
.paint = widgetFramePaint,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = widgetFrameDestroy,
.getText = NULL,
.setText = NULL,
.getLayoutMetrics = widgetFrameGetLayoutMetrics
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtFrame(WidgetT *parent, const char *title) {
WidgetT *w = widgetAlloc(parent, sFrameTypeId);
if (w) {
FrameDataT *fd = calloc(1, sizeof(FrameDataT));
fd->title = title;
fd->style = FrameInE;
fd->color = 0;
w->data = fd;
w->accelKey = accelParse(title);
}
return w;
}
WidgetT *wgtHBox(WidgetT *parent) {
return widgetAlloc(parent, sHBoxTypeId);
}
WidgetT *wgtVBox(WidgetT *parent) {
return widgetAlloc(parent, sVBoxTypeId);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*vBox)(WidgetT *parent);
WidgetT *(*hBox)(WidgetT *parent);
WidgetT *(*frame)(WidgetT *parent, const char *title);
} sApi = {
.vBox = wgtVBox,
.hBox = wgtHBox,
.frame = wgtFrame
};
void wgtRegister(void) {
widgetClassTable[WidgetVBoxE] = &sClassVBox;
widgetClassTable[WidgetHBoxE] = &sClassHBox;
widgetClassTable[WidgetFrameE] = &sClassFrame;
sVBoxTypeId = wgtRegisterClass(&sClassVBox);
sHBoxTypeId = wgtRegisterClass(&sClassHBox);
sFrameTypeId = wgtRegisterClass(&sClassFrame);
wgtRegisterApi("box", &sApi);
}

23
widgets/widgetBox.h Normal file
View file

@ -0,0 +1,23 @@
// widgetBox.h -- Box container widget API (VBox, HBox, Frame)
#ifndef WIDGET_BOX_H
#define WIDGET_BOX_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*vBox)(WidgetT *parent);
WidgetT *(*hBox)(WidgetT *parent);
WidgetT *(*frame)(WidgetT *parent, const char *title);
} BoxApiT;
static inline const BoxApiT *dvxBoxApi(void) {
static const BoxApiT *sApi;
if (!sApi) { sApi = (const BoxApiT *)wgtGetApi("box"); }
return sApi;
}
#define wgtVBox(parent) dvxBoxApi()->vBox(parent)
#define wgtHBox(parent) dvxBoxApi()->hBox(parent)
#define wgtFrame(parent, title) dvxBoxApi()->frame(parent, title)
#endif // WIDGET_BOX_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetButton.c -- Button widget
//
// Standard push button with text label, Motif-style 2px beveled border,
@ -19,42 +20,38 @@
// Text supports accelerator keys via '&' prefix (e.g., "&OK" underlines 'O'
// and Alt+O activates the button).
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#define BUTTON_PAD_H 8
#define BUTTON_PAD_V 4
#define BUTTON_FOCUS_INSET 3
#define BUTTON_PRESS_OFFSET 1
static int32_t sTypeId = -1;
typedef struct {
const char *text;
bool pressed;
} ButtonDataT;
// ============================================================
// wgtButton
// Prototypes
// ============================================================
WidgetT *wgtButton(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, WidgetButtonE);
if (w) {
w->as.button.text = text;
w->as.button.pressed = false;
w->accelKey = accelParse(text);
}
return w;
}
static void widgetButtonDestroy(WidgetT *w);
// ============================================================
// widgetButtonGetText
// widgetButtonAccelActivate
// ============================================================
const char *widgetButtonGetText(const WidgetT *w) {
return w->as.button.text;
}
// ============================================================
// widgetButtonSetText
// ============================================================
void widgetButtonSetText(WidgetT *w, const char *text) {
w->as.button.text = text;
w->accelKey = accelParse(text);
void widgetButtonAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
ButtonDataT *d = (ButtonDataT *)w->data;
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
@ -66,11 +63,32 @@ void widgetButtonSetText(WidgetT *w, const char *text) {
// width is computed by textWidthAccel which excludes the '&' accelerator prefix
// character from the measurement.
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
w->calcMinW = textWidthAccel(font, w->as.button.text) + BUTTON_PAD_H * 2;
ButtonDataT *d = (ButtonDataT *)w->data;
w->calcMinW = textWidthAccel(font, d->text) + BUTTON_PAD_H * 2;
w->calcMinH = font->charHeight + BUTTON_PAD_V * 2;
}
// ============================================================
// widgetButtonDestroy
// ============================================================
static void widgetButtonDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetButtonGetText
// ============================================================
const char *widgetButtonGetText(const WidgetT *w) {
ButtonDataT *d = (ButtonDataT *)w->data;
return d->text;
}
// ============================================================
// widgetButtonOnKey
// ============================================================
@ -79,7 +97,8 @@ void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
if (key == ' ' || key == 0x0D) {
w->as.button.pressed = true;
ButtonDataT *d = (ButtonDataT *)w->data;
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
@ -94,8 +113,9 @@ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
ButtonDataT *d = (ButtonDataT *)w->data;
w->focused = true;
w->as.button.pressed = true;
d->pressed = true;
sPressedButton = w;
}
@ -111,56 +131,112 @@ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// shadow color at 0,0) to create a chiseled/etched look -- this is the
// standard Windows 3.1 disabled control appearance.
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
ButtonDataT *bd = (ButtonDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
BevelStyleT bevel;
bevel.highlight = w->as.button.pressed ? colors->windowShadow : colors->windowHighlight;
bevel.shadow = w->as.button.pressed ? colors->windowHighlight : colors->windowShadow;
bevel.highlight = bd->pressed ? colors->windowShadow : colors->windowHighlight;
bevel.shadow = bd->pressed ? colors->windowHighlight : colors->windowShadow;
bevel.face = bgFace;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
int32_t textW = textWidthAccel(font, w->as.button.text);
int32_t textW = textWidthAccel(font, bd->text);
int32_t textX = w->x + (w->w - textW) / 2;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
if (w->as.button.pressed) {
if (bd->pressed) {
textX += BUTTON_PRESS_OFFSET;
textY += BUTTON_PRESS_OFFSET;
}
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, textX, textY, w->as.button.text, colors);
drawTextAccelEmbossed(d, ops, font, textX, textY, bd->text, colors);
} else {
drawTextAccel(d, ops, font, textX, textY, w->as.button.text, fg, bgFace, true);
drawTextAccel(d, ops, font, textX, textY, bd->text, fg, bgFace, true);
}
if (w->focused) {
int32_t off = w->as.button.pressed ? BUTTON_PRESS_OFFSET : 0;
int32_t off = bd->pressed ? BUTTON_PRESS_OFFSET : 0;
drawFocusRect(d, ops, w->x + BUTTON_FOCUS_INSET + off, w->y + BUTTON_FOCUS_INSET + off, w->w - BUTTON_FOCUS_INSET * 2, w->h - BUTTON_FOCUS_INSET * 2, fg);
}
}
// ============================================================
// widgetButtonSetPressed
// ============================================================
void widgetButtonSetPressed(WidgetT *w, bool pressed) {
ButtonDataT *d = (ButtonDataT *)w->data;
d->pressed = pressed;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetButtonSetText
// ============================================================
void widgetButtonSetText(WidgetT *w, const char *text) {
ButtonDataT *d = (ButtonDataT *)w->data;
d->text = text;
w->accelKey = accelParse(text);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassButton = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetButtonPaint,
.paintOverlay = NULL,
.calcMinSize = widgetButtonCalcMinSize,
.layout = NULL,
.onMouse = widgetButtonOnMouse,
.onKey = widgetButtonOnKey,
.destroy = NULL,
.getText = widgetButtonGetText,
.setText = widgetButtonSetText
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
.paint = widgetButtonPaint,
.paintOverlay = NULL,
.calcMinSize = widgetButtonCalcMinSize,
.layout = NULL,
.onMouse = widgetButtonOnMouse,
.onKey = widgetButtonOnKey,
.onAccelActivate = widgetButtonAccelActivate,
.destroy = widgetButtonDestroy,
.getText = widgetButtonGetText,
.setText = widgetButtonSetText,
.setPressed = widgetButtonSetPressed
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtButton(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
ButtonDataT *d = calloc(1, sizeof(ButtonDataT));
w->data = d;
d->text = text;
w->accelKey = accelParse(text);
}
return w;
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
} sApi = {
.create = wgtButton
};
void wgtRegister(void) {
widgetClassTable[WidgetButtonE] = &sClassButton;
sTypeId = wgtRegisterClass(&sClassButton);
wgtRegisterApi("button", &sApi);
}

19
widgets/widgetButton.h Normal file
View file

@ -0,0 +1,19 @@
// widgetButton.h -- Button widget API
#ifndef WIDGET_BUTTON_H
#define WIDGET_BUTTON_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
} ButtonApiT;
static inline const ButtonApiT *dvxButtonApi(void) {
static const ButtonApiT *sApi;
if (!sApi) { sApi = (const ButtonApiT *)wgtGetApi("button"); }
return sApi;
}
#define wgtButton(parent, text) dvxButtonApi()->create(parent, text)
#endif // WIDGET_BUTTON_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetCanvas.c -- Drawable canvas widget (freehand draw, PNG save/load)
//
// The canvas widget provides a pixel buffer in the display's native pixel
@ -21,17 +22,32 @@
// top-left of the canvas content, not the widget. Mouse events translate
// widget-space coordinates to canvas-space by subtracting the border offset.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
static int32_t sTypeId = -1;
#define CANVAS_BORDER 2
typedef struct {
uint8_t *pixelData; // pixel buffer in display format
int32_t canvasW;
int32_t canvasH;
int32_t canvasPitch;
int32_t canvasBpp; // cached bytes per pixel
uint32_t penColor;
int32_t penSize;
int32_t lastX;
int32_t lastY;
void (*onMouse)(struct WidgetT *w, int32_t cx, int32_t cy, bool drag);
} CanvasDataT;
// ============================================================
// Prototypes
// ============================================================
static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy);
static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
static void canvasDrawDot(CanvasDataT *cd, int32_t cx, int32_t cy);
static void canvasDrawLine(CanvasDataT *cd, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
// ============================================================
@ -72,14 +88,14 @@ static inline void canvasPutPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
//
// Draw a filled circle of diameter penSize at (cx, cy) in canvas coords.
static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
int32_t bpp = w->as.canvas.canvasBpp;
int32_t pitch = w->as.canvas.canvasPitch;
uint8_t *data = w->as.canvas.data;
int32_t cw = w->as.canvas.canvasW;
int32_t ch = w->as.canvas.canvasH;
uint32_t color = w->as.canvas.penColor;
int32_t rad = w->as.canvas.penSize / 2;
static void canvasDrawDot(CanvasDataT *cd, int32_t cx, int32_t cy) {
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
int32_t rad = cd->penSize / 2;
if (rad < 1) {
// Single pixel
@ -161,7 +177,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
// chosen over DDA because it's pure integer arithmetic -- no floating point
// needed, which matters on 486SX (no FPU).
static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
static void canvasDrawLine(CanvasDataT *cd, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
int32_t dx = x1 - x0;
int32_t dy = y1 - y0;
int32_t sx = (dx >= 0) ? 1 : -1;
@ -173,7 +189,7 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
int32_t err = dx - dy;
for (;;) {
canvasDrawDot(w, x0, y0);
canvasDrawDot(cd, x0, y0);
if (x0 == x1 && y0 == y1) {
break;
@ -194,79 +210,18 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
}
// ============================================================
// wgtCanvas
// ============================================================
// Create a canvas widget with the specified dimensions. The canvas buffer
// is allocated in the display's native pixel format by walking up the widget
// tree to find the AppContextT (which holds the display format info). This
// tree-walk pattern is necessary because the widget doesn't have direct access
// to the display -- only the root widget's userData points to the AppContextT.
// The buffer is initialized to white using spanFill for performance.
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
if (!parent || w <= 0 || h <= 0) {
return NULL;
}
// Find the AppContextT to get display format
WidgetT *root = parent;
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
if (!ctx) {
return NULL;
}
const DisplayT *d = &ctx->display;
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = w * bpp;
uint8_t *data = (uint8_t *)malloc(pitch * h);
if (!data) {
return NULL;
}
// Fill with white using span fill for performance
uint32_t white = packColor(d, 255, 255, 255);
BlitOpsT canvasOps;
drawInit(&canvasOps, d);
for (int32_t y = 0; y < h; y++) {
canvasOps.spanFill(data + y * pitch, white, w);
}
WidgetT *wgt = widgetAlloc(parent, WidgetCanvasE);
if (wgt) {
wgt->as.canvas.data = data;
wgt->as.canvas.canvasW = w;
wgt->as.canvas.canvasH = h;
wgt->as.canvas.canvasPitch = pitch;
wgt->as.canvas.canvasBpp = bpp;
wgt->as.canvas.penColor = packColor(d, 0, 0, 0);
wgt->as.canvas.penSize = 2;
wgt->as.canvas.lastX = -1;
wgt->as.canvas.lastY = -1;
} else {
free(data);
}
return wgt;
}
// ============================================================
// wgtCanvasClear
// ============================================================
void wgtCanvasClear(WidgetT *w, uint32_t color) {
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
@ -280,12 +235,12 @@ void wgtCanvasClear(WidgetT *w, uint32_t color) {
return;
}
int32_t pitch = w->as.canvas.canvasPitch;
int32_t cw = w->as.canvas.canvasW;
int32_t ch = w->as.canvas.canvasH;
int32_t pitch = cd->canvasPitch;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
for (int32_t y = 0; y < ch; y++) {
ctx->blitOps.spanFill(w->as.canvas.data + y * pitch, color, cw);
ctx->blitOps.spanFill(cd->pixelData + y * pitch, color, cw);
}
}
@ -297,11 +252,17 @@ void wgtCanvasClear(WidgetT *w, uint32_t color) {
// Draw a line using the current pen color and pen size.
void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
if (!w || w->type != sTypeId) {
return;
}
canvasDrawLine(w, x0, y0, x1, y1);
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
canvasDrawLine(cd, x0, y0, x1, y1);
}
@ -312,7 +273,13 @@ void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y
// Draw a 1px outlined rectangle using the current pen color.
void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height) {
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
@ -320,12 +287,12 @@ void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
return;
}
int32_t bpp = w->as.canvas.canvasBpp;
int32_t pitch = w->as.canvas.canvasPitch;
uint8_t *data = w->as.canvas.data;
int32_t cw = w->as.canvas.canvasW;
int32_t ch = w->as.canvas.canvasH;
uint32_t color = w->as.canvas.penColor;
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
// Top and bottom edges
for (int32_t px = x; px < x + width; px++) {
@ -378,7 +345,13 @@ void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
// Draw a filled circle using the current pen color.
void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
@ -386,12 +359,12 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
return;
}
int32_t bpp = w->as.canvas.canvasBpp;
int32_t pitch = w->as.canvas.canvasPitch;
uint8_t *data = w->as.canvas.data;
int32_t cw = w->as.canvas.canvasW;
int32_t ch = w->as.canvas.canvasH;
uint32_t color = w->as.canvas.penColor;
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
int32_t r2 = radius * radius;
for (int32_t dy = -radius; dy <= radius; dy++) {
@ -449,7 +422,13 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
// Draw a filled rectangle using the current pen color.
void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height) {
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
if (!w || w->type != sTypeId) {
return;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
@ -457,12 +436,12 @@ void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
return;
}
int32_t bpp = w->as.canvas.canvasBpp;
int32_t pitch = w->as.canvas.canvasPitch;
uint8_t *data = w->as.canvas.data;
int32_t cw = w->as.canvas.canvasW;
int32_t ch = w->as.canvas.canvasH;
uint32_t color = w->as.canvas.penColor;
int32_t bpp = cd->canvasBpp;
int32_t pitch = cd->canvasPitch;
uint8_t *data = cd->pixelData;
int32_t cw = cd->canvasW;
int32_t ch = cd->canvasH;
uint32_t color = cd->penColor;
// Clip to canvas bounds
int32_t x0 = x < 0 ? 0 : x;
@ -505,129 +484,48 @@ void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
// ============================================================
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y) {
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
if (!w || w->type != sTypeId) {
return 0;
}
if (x < 0 || x >= w->as.canvas.canvasW || y < 0 || y >= w->as.canvas.canvasH) {
const CanvasDataT *cd = (const CanvasDataT *)w->data;
if (!cd->pixelData) {
return 0;
}
int32_t bpp = w->as.canvas.canvasBpp;
const uint8_t *src = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
if (x < 0 || x >= cd->canvasW || y < 0 || y >= cd->canvasH) {
return 0;
}
int32_t bpp = cd->canvasBpp;
const uint8_t *src = cd->pixelData + y * cd->canvasPitch + x * bpp;
return canvasGetPixel(src, bpp);
}
// ============================================================
// wgtCanvasLoad
// ============================================================
// Load an image file into the canvas, replacing the current content.
// Delegates to dvxLoadImage for format decoding and pixel conversion.
// The old buffer is freed and replaced with the new one -- canvas
// dimensions change to match the loaded image.
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
if (!w || w->type != WidgetCanvasE || !path) {
return -1;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return -1;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *data = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!data) {
return -1;
}
free(w->as.canvas.data);
w->as.canvas.data = data;
w->as.canvas.canvasW = imgW;
w->as.canvas.canvasH = imgH;
w->as.canvas.canvasPitch = pitch;
w->as.canvas.canvasBpp = ctx->display.format.bytesPerPixel;
return 0;
}
// ============================================================
// wgtCanvasSave
// ============================================================
// Save the canvas content to a PNG file. Delegates to dvxSaveImage
// which handles native-to-RGB conversion and PNG encoding.
int32_t wgtCanvasSave(WidgetT *w, const char *path) {
if (!w || w->type != WidgetCanvasE || !path || !w->as.canvas.data) {
return -1;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return -1;
}
return dvxSaveImage(ctx, w->as.canvas.data, w->as.canvas.canvasW, w->as.canvas.canvasH, w->as.canvas.canvasPitch, path);
}
// ============================================================
// wgtCanvasSetMouseCallback
// ============================================================
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag)) {
if (w && w->type == WidgetCanvasE) {
w->as.canvas.onMouse = cb;
}
}
// ============================================================
// wgtCanvasSetPenColor
// ============================================================
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color) {
if (w && w->type == WidgetCanvasE) {
w->as.canvas.penColor = color;
}
}
// ============================================================
// wgtCanvasSetPenSize
// ============================================================
void wgtCanvasSetPenSize(WidgetT *w, int32_t size) {
if (w && w->type == WidgetCanvasE && size > 0) {
w->as.canvas.penSize = size;
}
}
// ============================================================
// wgtCanvasSetPixel
// ============================================================
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color) {
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
if (!w || w->type != sTypeId) {
return;
}
if (x < 0 || x >= w->as.canvas.canvasW || y < 0 || y >= w->as.canvas.canvasH) {
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return;
}
int32_t bpp = w->as.canvas.canvasBpp;
uint8_t *dst = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
if (x < 0 || x >= cd->canvasW || y < 0 || y >= cd->canvasH) {
return;
}
int32_t bpp = cd->canvasBpp;
uint8_t *dst = cd->pixelData + y * cd->canvasPitch + x * bpp;
canvasPutPixel(dst, color, bpp);
}
@ -638,7 +536,10 @@ void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color) {
// ============================================================
void widgetCanvasDestroy(WidgetT *w) {
free(w->as.canvas.data);
CanvasDataT *cd = (CanvasDataT *)w->data;
free(cd->pixelData);
free(cd);
}
@ -652,8 +553,10 @@ void widgetCanvasDestroy(WidgetT *w) {
// minimum, and the layout engine should respect that.
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
w->calcMinW = w->as.canvas.canvasW + CANVAS_BORDER * 2;
w->calcMinH = w->as.canvas.canvasH + CANVAS_BORDER * 2;
CanvasDataT *cd = (CanvasDataT *)w->data;
w->calcMinW = cd->canvasW + CANVAS_BORDER * 2;
w->calcMinH = cd->canvasH + CANVAS_BORDER * 2;
}
@ -668,8 +571,9 @@ void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// and continuing one.
void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
CanvasDataT *cd = (CanvasDataT *)hit->data;
if (!hit->as.canvas.onMouse) {
if (!cd->onMouse) {
return;
}
@ -681,12 +585,14 @@ void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
if (!drag) {
sDrawingCanvas = hit;
cd->lastX = -1;
cd->lastY = -1;
}
hit->as.canvas.lastX = cx;
hit->as.canvas.lastY = cy;
cd->lastX = cx;
cd->lastY = cy;
hit->as.canvas.onMouse(hit, cx, cy, drag);
cd->onMouse(hit, cx, cy, drag);
wgtInvalidatePaint(hit);
}
@ -701,8 +607,9 @@ void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// repaint essentially free relative to the display bandwidth.
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!w->as.canvas.data) {
if (!cd->pixelData) {
return;
}
@ -715,13 +622,13 @@ void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
drawBevel(d, ops, w->x, w->y, w->w, w->h, &sunken);
// Blit the canvas data inside the border
int32_t imgW = w->as.canvas.canvasW;
int32_t imgH = w->as.canvas.canvasH;
int32_t imgW = cd->canvasW;
int32_t imgH = cd->canvasH;
int32_t dx = w->x + CANVAS_BORDER;
int32_t dy = w->y + CANVAS_BORDER;
rectCopy(d, ops, dx, dy,
w->as.canvas.data, w->as.canvas.canvasPitch,
cd->pixelData, cd->canvasPitch,
0, 0, imgW, imgH);
}
@ -744,6 +651,188 @@ static const WidgetClassT sClassCanvas = {
};
void wgtRegister(void) {
widgetClassTable[WidgetCanvasE] = &sClassCanvas;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
if (!parent || w <= 0 || h <= 0) {
return NULL;
}
// Find the AppContextT to get display format
WidgetT *root = parent;
while (root->parent) {
root = root->parent;
}
AppContextT *ctx = (AppContextT *)root->userData;
if (!ctx) {
return NULL;
}
const DisplayT *d = &ctx->display;
int32_t bpp = d->format.bytesPerPixel;
int32_t pitch = w * bpp;
uint8_t *data = (uint8_t *)malloc(pitch * h);
if (!data) {
return NULL;
}
// Fill with white using span fill for performance
uint32_t white = packColor(d, 255, 255, 255);
BlitOpsT canvasOps;
drawInit(&canvasOps, d);
for (int32_t y = 0; y < h; y++) {
canvasOps.spanFill(data + y * pitch, white, w);
}
WidgetT *wgt = widgetAlloc(parent, sTypeId);
if (wgt) {
CanvasDataT *cd = (CanvasDataT *)calloc(1, sizeof(CanvasDataT));
cd->pixelData = data;
cd->canvasW = w;
cd->canvasH = h;
cd->canvasPitch = pitch;
cd->canvasBpp = bpp;
cd->penColor = packColor(d, 0, 0, 0);
cd->penSize = 2;
cd->lastX = -1;
cd->lastY = -1;
wgt->data = cd;
} else {
free(data);
}
return wgt;
}
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
if (!w || w->type != sTypeId || !path) {
return -1;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return -1;
}
int32_t imgW;
int32_t imgH;
int32_t pitch;
uint8_t *data = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
if (!data) {
return -1;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
free(cd->pixelData);
cd->pixelData = data;
cd->canvasW = imgW;
cd->canvasH = imgH;
cd->canvasPitch = pitch;
cd->canvasBpp = ctx->display.format.bytesPerPixel;
return 0;
}
int32_t wgtCanvasSave(WidgetT *w, const char *path) {
if (!w || w->type != sTypeId || !path) {
return -1;
}
CanvasDataT *cd = (CanvasDataT *)w->data;
if (!cd->pixelData) {
return -1;
}
AppContextT *ctx = wgtGetContext(w);
if (!ctx) {
return -1;
}
return dvxSaveImage(ctx, cd->pixelData, cd->canvasW, cd->canvasH, cd->canvasPitch, path);
}
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag)) {
if (w && w->type == sTypeId) {
CanvasDataT *cd = (CanvasDataT *)w->data;
cd->onMouse = cb;
}
}
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color) {
if (w && w->type == sTypeId) {
CanvasDataT *cd = (CanvasDataT *)w->data;
cd->penColor = color;
}
}
void wgtCanvasSetPenSize(WidgetT *w, int32_t size) {
if (w && w->type == sTypeId && size > 0) {
CanvasDataT *cd = (CanvasDataT *)w->data;
cd->penSize = size;
}
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, int32_t w, int32_t h);
void (*clear)(WidgetT *w, uint32_t color);
void (*setPenColor)(WidgetT *w, uint32_t color);
void (*setPenSize)(WidgetT *w, int32_t size);
void (*setMouseCallback)(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
int32_t (*save)(WidgetT *w, const char *path);
int32_t (*load)(WidgetT *w, const char *path);
void (*drawLine)(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
void (*drawRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void (*fillRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void (*fillCircle)(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
void (*setPixel)(WidgetT *w, int32_t x, int32_t y, uint32_t color);
uint32_t (*getPixel)(const WidgetT *w, int32_t x, int32_t y);
} sApi = {
.create = wgtCanvas,
.clear = wgtCanvasClear,
.setPenColor = wgtCanvasSetPenColor,
.setPenSize = wgtCanvasSetPenSize,
.setMouseCallback = wgtCanvasSetMouseCallback,
.save = wgtCanvasSave,
.load = wgtCanvasLoad,
.drawLine = wgtCanvasDrawLine,
.drawRect = wgtCanvasDrawRect,
.fillRect = wgtCanvasFillRect,
.fillCircle = wgtCanvasFillCircle,
.setPixel = wgtCanvasSetPixel,
.getPixel = wgtCanvasGetPixel
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassCanvas);
wgtRegisterApi("canvas", &sApi);
}

43
widgets/widgetCanvas.h Normal file
View file

@ -0,0 +1,43 @@
// widgetCanvas.h -- Canvas widget API
#ifndef WIDGET_CANVAS_H
#define WIDGET_CANVAS_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, int32_t w, int32_t h);
void (*clear)(WidgetT *w, uint32_t color);
void (*setPenColor)(WidgetT *w, uint32_t color);
void (*setPenSize)(WidgetT *w, int32_t size);
void (*setMouseCallback)(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
void (*save)(WidgetT *w, const char *path);
void (*load)(WidgetT *w, const char *path);
void (*drawLine)(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
void (*drawRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void (*fillRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
void (*fillCircle)(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
void (*setPixel)(WidgetT *w, int32_t x, int32_t y, uint32_t color);
uint32_t (*getPixel)(const WidgetT *w, int32_t x, int32_t y);
} CanvasApiT;
static inline const CanvasApiT *dvxCanvasApi(void) {
static const CanvasApiT *sApi;
if (!sApi) { sApi = (const CanvasApiT *)wgtGetApi("canvas"); }
return sApi;
}
#define wgtCanvas(parent, w, h) dvxCanvasApi()->create(parent, w, h)
#define wgtCanvasClear(w, color) dvxCanvasApi()->clear(w, color)
#define wgtCanvasSetPenColor(w, color) dvxCanvasApi()->setPenColor(w, color)
#define wgtCanvasSetPenSize(w, size) dvxCanvasApi()->setPenSize(w, size)
#define wgtCanvasSetMouseCallback(w, cb) dvxCanvasApi()->setMouseCallback(w, cb)
#define wgtCanvasSave(w, path) dvxCanvasApi()->save(w, path)
#define wgtCanvasLoad(w, path) dvxCanvasApi()->load(w, path)
#define wgtCanvasDrawLine(w, x0, y0, x1, y1) dvxCanvasApi()->drawLine(w, x0, y0, x1, y1)
#define wgtCanvasDrawRect(w, x, y, width, height) dvxCanvasApi()->drawRect(w, x, y, width, height)
#define wgtCanvasFillRect(w, x, y, width, height) dvxCanvasApi()->fillRect(w, x, y, width, height)
#define wgtCanvasFillCircle(w, cx, cy, radius) dvxCanvasApi()->fillCircle(w, cx, cy, radius)
#define wgtCanvasSetPixel(w, x, y, color) dvxCanvasApi()->setPixel(w, x, y, color)
#define wgtCanvasGetPixel(w, x, y) dvxCanvasApi()->getPixel(w, x, y)
#endif // WIDGET_CANVAS_H

View file

@ -1,9 +1,10 @@
#define DVX_WIDGET_IMPL
// widgetCheckbox.c -- Checkbox widget
//
// Classic checkbox: a small box with a sunken bevel (1px) on the left, a text
// label to the right. The check mark is drawn as two diagonal lines forming an
// "X" pattern rather than a traditional checkmark glyph -- this is simpler to
// render with drawHLine primitives and matches the DV/X aesthetic.
// render with drawHLine primitives and matches the DVX aesthetic.
//
// State management is simple: a boolean 'checked' flag toggles on each click
// or Space/Enter keypress. The onChange callback fires after each toggle so
@ -12,42 +13,34 @@
// Focus is shown via a dotted rectangle around the label text (not the box),
// matching the Win3.1 convention where the focus indicator wraps the label.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#define CHECKBOX_BOX_SIZE 12
#define CHECKBOX_GAP 4
static int32_t sTypeId = -1;
typedef struct {
const char *text;
bool checked;
} CheckboxDataT;
// ============================================================
// wgtCheckbox
// Prototypes
// ============================================================
WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, WidgetCheckboxE);
if (w) {
w->as.checkbox.text = text;
w->as.checkbox.checked = false;
w->accelKey = accelParse(text);
}
return w;
}
static void widgetCheckboxDestroy(WidgetT *w);
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
// ============================================================
// widgetCheckboxGetText
// widgetCheckboxAccelActivate
// ============================================================
const char *widgetCheckboxGetText(const WidgetT *w) {
return w->as.checkbox.text;
}
// ============================================================
// widgetCheckboxSetText
// ============================================================
void widgetCheckboxSetText(WidgetT *w, const char *text) {
w->as.checkbox.text = text;
w->accelKey = accelParse(text);
void widgetCheckboxAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
widgetCheckboxOnMouse(w, NULL, 0, 0);
}
@ -59,12 +52,33 @@ void widgetCheckboxSetText(WidgetT *w, const char *text) {
// the font). This ensures the box and text are always vertically centered
// relative to each other regardless of font size.
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
textWidthAccel(font, w->as.checkbox.text);
textWidthAccel(font, d->text);
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
}
// ============================================================
// widgetCheckboxDestroy
// ============================================================
static void widgetCheckboxDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetCheckboxGetText
// ============================================================
const char *widgetCheckboxGetText(const WidgetT *w) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
return d->text;
}
// ============================================================
// widgetCheckboxOnKey
// ============================================================
@ -73,7 +87,8 @@ void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
if (key == ' ' || key == 0x0D) {
w->as.checkbox.checked = !w->as.checkbox.checked;
CheckboxDataT *d = (CheckboxDataT *)w->data;
d->checked = !d->checked;
if (w->onChange) {
w->onChange(w);
@ -92,8 +107,9 @@ void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
CheckboxDataT *d = (CheckboxDataT *)w->data;
w->focused = true;
w->as.checkbox.checked = !w->as.checkbox.checked;
d->checked = !d->checked;
if (w->onChange) {
w->onChange(w);
@ -106,9 +122,10 @@ void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// ============================================================
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
int32_t boxY = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;
CheckboxDataT *cd = (CheckboxDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
int32_t boxY = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;
// Draw checkbox box
BevelStyleT bevel;
@ -123,7 +140,7 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// instead of a real line drawing algorithm avoids Bresenham overhead
// for what's always a small fixed-size glyph (6x6 pixels). The 3px
// inset from the box edge keeps the mark visually centered.
if (w->as.checkbox.checked) {
if (cd->checked) {
int32_t cx = w->x + 3;
int32_t cy = boxY + 3;
int32_t cs = CHECKBOX_BOX_SIZE - 6;
@ -138,12 +155,12 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// Draw label
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
int32_t labelW = textWidthAccel(font, w->as.checkbox.text);
int32_t labelW = textWidthAccel(font, cd->text);
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, labelX, labelY, w->as.checkbox.text, colors);
drawTextAccelEmbossed(d, ops, font, labelX, labelY, cd->text, colors);
} else {
drawTextAccel(d, ops, font, labelX, labelY, w->as.checkbox.text, fg, bg, false);
drawTextAccel(d, ops, font, labelX, labelY, cd->text, fg, bg, false);
}
if (w->focused) {
@ -152,24 +169,88 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
}
// ============================================================
// widgetCheckboxSetText
// ============================================================
void widgetCheckboxSetText(WidgetT *w, const char *text) {
CheckboxDataT *d = (CheckboxDataT *)w->data;
d->text = text;
w->accelKey = accelParse(text);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassCheckbox = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetCheckboxPaint,
.paintOverlay = NULL,
.calcMinSize = widgetCheckboxCalcMinSize,
.layout = NULL,
.onMouse = widgetCheckboxOnMouse,
.onKey = widgetCheckboxOnKey,
.destroy = NULL,
.getText = widgetCheckboxGetText,
.setText = widgetCheckboxSetText
.flags = WCLASS_FOCUSABLE,
.paint = widgetCheckboxPaint,
.paintOverlay = NULL,
.calcMinSize = widgetCheckboxCalcMinSize,
.layout = NULL,
.onMouse = widgetCheckboxOnMouse,
.onKey = widgetCheckboxOnKey,
.onAccelActivate = widgetCheckboxAccelActivate,
.destroy = widgetCheckboxDestroy,
.getText = widgetCheckboxGetText,
.setText = widgetCheckboxSetText
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
CheckboxDataT *d = calloc(1, sizeof(CheckboxDataT));
w->data = d;
d->text = text;
w->accelKey = accelParse(text);
}
return w;
}
bool wgtCheckboxIsChecked(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, false);
CheckboxDataT *d = (CheckboxDataT *)w->data;
return d->checked;
}
void wgtCheckboxSetChecked(WidgetT *w, bool checked) {
VALIDATE_WIDGET_VOID(w, sTypeId);
CheckboxDataT *d = (CheckboxDataT *)w->data;
d->checked = checked;
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
bool (*isChecked)(const WidgetT *w);
void (*setChecked)(WidgetT *w, bool checked);
} sApi = {
.create = wgtCheckbox,
.isChecked = wgtCheckboxIsChecked,
.setChecked = wgtCheckboxSetChecked
};
void wgtRegister(void) {
widgetClassTable[WidgetCheckboxE] = &sClassCheckbox;
sTypeId = wgtRegisterClass(&sClassCheckbox);
wgtRegisterApi("checkbox", &sApi);
}

23
widgets/widgetCheckbox.h Normal file
View file

@ -0,0 +1,23 @@
// widgetCheckbox.h -- Checkbox widget API
#ifndef WIDGET_CHECKBOX_H
#define WIDGET_CHECKBOX_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
bool (*isChecked)(const WidgetT *w);
void (*setChecked)(WidgetT *w, bool checked);
} CheckboxApiT;
static inline const CheckboxApiT *dvxCheckboxApi(void) {
static const CheckboxApiT *sApi;
if (!sApi) { sApi = (const CheckboxApiT *)wgtGetApi("checkbox"); }
return sApi;
}
#define wgtCheckbox(parent, text) dvxCheckboxApi()->create(parent, text)
#define wgtCheckboxIsChecked(w) dvxCheckboxApi()->isChecked(w)
#define wgtCheckboxSetChecked(w, checked) dvxCheckboxApi()->setChecked(w, checked)
#endif // WIDGET_CHECKBOX_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetComboBox.c -- ComboBox widget (editable text + dropdown list)
//
// Combines a single-line text input with a dropdown list. The text area
@ -20,104 +21,31 @@
// double-click (word select), and triple-click (select all). Drag-select
// is tracked via the sDragTextSelect global.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "../texthelp/textHelp.h"
#include "../listhelp/listHelp.h"
static int32_t sTypeId = -1;
// ============================================================
// wgtComboBox
// ============================================================
// Create a combo box. maxLen controls the edit buffer size (0 = default 256).
// Two buffers are allocated: the edit buffer and an undo buffer (for Ctrl+Z
// single-level undo). Selection indices start at -1 (nothing selected).
// Default weight=100 makes combo boxes expand to fill available space in
// a layout container, which is the typical desired behavior.
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
WidgetT *w = widgetAlloc(parent, WidgetComboBoxE);
if (w) {
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
w->as.comboBox.buf = (char *)malloc(bufSize);
w->as.comboBox.undoBuf = (char *)malloc(bufSize);
w->as.comboBox.bufSize = bufSize;
if (!w->as.comboBox.buf || !w->as.comboBox.undoBuf) {
free(w->as.comboBox.buf);
free(w->as.comboBox.undoBuf);
w->as.comboBox.buf = NULL;
w->as.comboBox.undoBuf = NULL;
} else {
w->as.comboBox.buf[0] = '\0';
}
w->as.comboBox.selStart = -1;
w->as.comboBox.selEnd = -1;
w->as.comboBox.selectedIdx = -1;
w->weight = 100;
}
return w;
}
// ============================================================
// wgtComboBoxGetSelected
// ============================================================
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
VALIDATE_WIDGET(w, WidgetComboBoxE, -1);
return w->as.comboBox.selectedIdx;
}
// ============================================================
// wgtComboBoxSetItems
// ============================================================
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
w->as.comboBox.items = items;
w->as.comboBox.itemCount = count;
// Cache max item string length so calcMinSize doesn't need to re-scan
// the entire item array on every layout pass. Items are stored as
// external pointers (not copied) -- the caller owns the string data.
w->as.comboBox.maxItemLen = widgetMaxItemLen(items, count);
if (w->as.comboBox.selectedIdx >= count) {
w->as.comboBox.selectedIdx = -1;
}
// wgtInvalidate (not wgtInvalidatePaint) triggers a full relayout because
// changing items may change the widget's minimum width
wgtInvalidate(w);
}
// ============================================================
// wgtComboBoxSetSelected
// ============================================================
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
w->as.comboBox.selectedIdx = idx;
// Copy selected item text to buffer
if (idx >= 0 && idx < w->as.comboBox.itemCount && w->as.comboBox.buf) {
strncpy(w->as.comboBox.buf, w->as.comboBox.items[idx], w->as.comboBox.bufSize - 1);
w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0';
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
w->as.comboBox.cursorPos = w->as.comboBox.len;
w->as.comboBox.scrollOff = 0;
w->as.comboBox.selStart = -1;
w->as.comboBox.selEnd = -1;
}
wgtInvalidatePaint(w);
}
typedef struct {
char *buf;
int32_t bufSize;
int32_t len;
int32_t cursorPos;
int32_t scrollOff;
int32_t selStart;
int32_t selEnd;
char *undoBuf;
int32_t undoLen;
int32_t undoCursor;
const char **items;
int32_t itemCount;
int32_t selectedIdx;
bool open;
int32_t hoverIdx;
int32_t listScrollPos;
int32_t maxItemLen;
} ComboBoxDataT;
// ============================================================
@ -125,7 +53,8 @@ void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
// ============================================================
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
int32_t maxItemW = w->as.comboBox.maxItemLen * font->charWidth;
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
int32_t maxItemW = d->maxItemLen * font->charWidth;
int32_t minW = font->charWidth * 8;
if (maxItemW < minW) {
@ -142,8 +71,14 @@ void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// ============================================================
void widgetComboBoxDestroy(WidgetT *w) {
free(w->as.comboBox.buf);
free(w->as.comboBox.undoBuf);
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
if (d) {
free(d->buf);
free(d->undoBuf);
free(d);
w->data = NULL;
}
}
@ -152,7 +87,8 @@ void widgetComboBoxDestroy(WidgetT *w) {
// ============================================================
const char *widgetComboBoxGetText(const WidgetT *w) {
return w->as.comboBox.buf ? w->as.comboBox.buf : "";
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
return d->buf ? d->buf : "";
}
@ -167,13 +103,15 @@ const char *widgetComboBoxGetText(const WidgetT *w) {
// and a list selector depending on popup state.
// Key codes: 0x48|0x100 = Up, 0x50|0x100 = Down (BIOS scan codes with extended bit).
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (w->as.comboBox.open) {
if (key == (0x48 | 0x100)) {
if (w->as.comboBox.hoverIdx > 0) {
w->as.comboBox.hoverIdx--;
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
if (w->as.comboBox.hoverIdx < w->as.comboBox.listScrollPos) {
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx;
if (d->open) {
if (key == (0x48 | 0x100)) {
if (d->hoverIdx > 0) {
d->hoverIdx--;
if (d->hoverIdx < d->listScrollPos) {
d->listScrollPos = d->hoverIdx;
}
}
@ -182,11 +120,11 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
if (key == (0x50 | 0x100)) {
if (w->as.comboBox.hoverIdx < w->as.comboBox.itemCount - 1) {
w->as.comboBox.hoverIdx++;
if (d->hoverIdx < d->itemCount - 1) {
d->hoverIdx++;
if (w->as.comboBox.hoverIdx >= w->as.comboBox.listScrollPos + DROPDOWN_MAX_VISIBLE) {
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
}
}
@ -195,22 +133,22 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
if (key == 0x0D) {
int32_t idx = w->as.comboBox.hoverIdx;
int32_t idx = d->hoverIdx;
if (idx >= 0 && idx < w->as.comboBox.itemCount) {
w->as.comboBox.selectedIdx = idx;
if (idx >= 0 && idx < d->itemCount) {
d->selectedIdx = idx;
const char *itemText = w->as.comboBox.items[idx];
strncpy(w->as.comboBox.buf, itemText, w->as.comboBox.bufSize - 1);
w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0';
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
w->as.comboBox.cursorPos = w->as.comboBox.len;
w->as.comboBox.scrollOff = 0;
w->as.comboBox.selStart = -1;
w->as.comboBox.selEnd = -1;
const char *itemText = d->items[idx];
strncpy(d->buf, itemText, d->bufSize - 1);
d->buf[d->bufSize - 1] = '\0';
d->len = (int32_t)strlen(d->buf);
d->cursorPos = d->len;
d->scrollOff = 0;
d->selStart = -1;
d->selEnd = -1;
}
w->as.comboBox.open = false;
d->open = false;
sOpenPopup = NULL;
if (w->onChange) {
@ -223,17 +161,17 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
// Down arrow on closed combobox opens the popup
if (!w->as.comboBox.open && key == (0x50 | 0x100)) {
w->as.comboBox.open = true;
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
if (!d->open && key == (0x50 | 0x100)) {
d->open = true;
d->hoverIdx = d->selectedIdx;
sOpenPopup = w;
if (w->as.comboBox.hoverIdx >= w->as.comboBox.listScrollPos + DROPDOWN_MAX_VISIBLE) {
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
}
if (w->as.comboBox.hoverIdx < w->as.comboBox.listScrollPos) {
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx;
if (d->hoverIdx < d->listScrollPos) {
d->listScrollPos = d->hoverIdx;
}
wgtInvalidatePaint(w);
@ -241,18 +179,18 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
// Text editing (when popup is closed, or non-navigation keys with popup open)
if (!w->as.comboBox.buf) {
if (!d->buf) {
return;
}
clearOtherSelections(w);
widgetTextEditOnKey(w, key, mod, w->as.comboBox.buf, w->as.comboBox.bufSize,
&w->as.comboBox.len, &w->as.comboBox.cursorPos,
&w->as.comboBox.scrollOff,
&w->as.comboBox.selStart, &w->as.comboBox.selEnd,
w->as.comboBox.undoBuf, &w->as.comboBox.undoLen,
&w->as.comboBox.undoCursor);
widgetTextEditOnKey(w, key, mod, d->buf, d->bufSize,
&d->len, &d->cursorPos,
&d->scrollOff,
&d->selStart, &d->selEnd,
d->undoBuf, &d->undoLen,
&d->undoCursor);
}
@ -262,6 +200,7 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
w->focused = true;
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
// Check if click is on the button area
int32_t textAreaW = w->w - DROPDOWN_BTN_WIDTH;
@ -273,15 +212,15 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
}
// Button click -- toggle popup
w->as.comboBox.open = !w->as.comboBox.open;
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
sOpenPopup = w->as.comboBox.open ? w : NULL;
d->open = !d->open;
d->hoverIdx = d->selectedIdx;
sOpenPopup = d->open ? w : NULL;
} else {
// Text area click -- focus for editing
clearOtherSelections(w);
AppContextT *ctx = (AppContextT *)root->userData;
widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, w->as.comboBox.buf, w->as.comboBox.len, w->as.comboBox.scrollOff, &w->as.comboBox.cursorPos, &w->as.comboBox.selStart, &w->as.comboBox.selEnd, true, true);
widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, d->buf, d->len, d->scrollOff, &d->cursorPos, &d->selStart, &d->selEnd, true, true);
}
}
@ -296,7 +235,8 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// post-selection). The dropdown button has a small triangular arrow glyph
// drawn as horizontal lines of decreasing width. When the popup is open,
// the button bevel is inverted (sunken) to show it's active.
void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
void widgetComboBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->contentFg) : colors->windowShadow;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
@ -307,36 +247,36 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
bevel.shadow = colors->windowHighlight;
bevel.face = bg;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, textAreaW, w->h, &bevel);
drawBevel(disp, ops, w->x, w->y, textAreaW, w->h, &bevel);
// Draw text content
if (w->as.comboBox.buf) {
if (d->buf) {
int32_t textX = w->x + TEXT_INPUT_PAD;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
int32_t maxChars = (textAreaW - TEXT_INPUT_PAD * 2 - 4) / font->charWidth;
int32_t off = w->as.comboBox.scrollOff;
int32_t len = w->as.comboBox.len - off;
int32_t off = d->scrollOff;
int32_t len = d->len - off;
if (len > maxChars) {
len = maxChars;
}
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, w->as.comboBox.buf + off, len, off, w->as.comboBox.cursorPos, w->as.comboBox.selStart, w->as.comboBox.selEnd, fg, bg, w->focused && w->enabled && !w->as.comboBox.open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, d->buf + off, len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w->focused && w->enabled && !d->open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
}
// Drop button
BevelStyleT btnBevel;
btnBevel.highlight = w->as.comboBox.open ? colors->windowShadow : colors->windowHighlight;
btnBevel.shadow = w->as.comboBox.open ? colors->windowHighlight : colors->windowShadow;
btnBevel.highlight = d->open ? colors->windowShadow : colors->windowHighlight;
btnBevel.shadow = d->open ? colors->windowHighlight : colors->windowShadow;
btnBevel.face = colors->buttonFace;
btnBevel.width = 2;
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
drawBevel(disp, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
// Down arrow
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2;
int32_t arrowY = w->y + w->h / 2 - 1;
widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg);
widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg);
}
@ -344,14 +284,15 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// widgetComboBoxPaintPopup
// ============================================================
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
int32_t popX;
int32_t popY;
int32_t popW;
int32_t popH;
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
widgetPaintPopupList(d, ops, font, colors, popX, popY, popW, popH, w->as.comboBox.items, w->as.comboBox.itemCount, w->as.comboBox.hoverIdx, w->as.comboBox.listScrollPos);
widgetDropdownPopupRect(w, font, disp->clipH, &popX, &popY, &popW, &popH);
widgetPaintPopupList(disp, ops, font, colors, popX, popY, popW, popH, d->items, d->itemCount, d->hoverIdx, d->listScrollPos);
}
@ -360,36 +301,199 @@ void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
// ============================================================
void widgetComboBoxSetText(WidgetT *w, const char *text) {
if (w->as.comboBox.buf) {
strncpy(w->as.comboBox.buf, text, w->as.comboBox.bufSize - 1);
w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0';
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
w->as.comboBox.cursorPos = w->as.comboBox.len;
w->as.comboBox.scrollOff = 0;
w->as.comboBox.selStart = -1;
w->as.comboBox.selEnd = -1;
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
if (d->buf) {
strncpy(d->buf, text, d->bufSize - 1);
d->buf[d->bufSize - 1] = '\0';
d->len = (int32_t)strlen(d->buf);
d->cursorPos = d->len;
d->scrollOff = 0;
d->selStart = -1;
d->selEnd = -1;
}
}
// ============================================================
// widgetComboBoxAccelActivate
// ============================================================
void widgetComboBoxAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
d->open = true;
d->hoverIdx = d->selectedIdx;
sOpenPopup = w;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetComboBoxClosePopup
// ============================================================
void widgetComboBoxClosePopup(WidgetT *w) {
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
d->open = false;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetComboBoxGetPopupItemCount
// ============================================================
int32_t widgetComboBoxGetPopupItemCount(const WidgetT *w) {
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
return d->itemCount;
}
// ============================================================
// widgetComboBoxOpenPopup
// ============================================================
void widgetComboBoxOpenPopup(WidgetT *w) {
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
d->open = true;
d->hoverIdx = d->selectedIdx;
sOpenPopup = w;
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static void widgetComboBoxGetPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
widgetDropdownPopupRect((WidgetT *)w, font, contentH, popX, popY, popW, popH);
}
static const WidgetClassT sClassComboBox = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetComboBoxPaint,
.paintOverlay = widgetComboBoxPaintPopup,
.calcMinSize = widgetComboBoxCalcMinSize,
.layout = NULL,
.onMouse = widgetComboBoxOnMouse,
.onKey = widgetComboBoxOnKey,
.destroy = widgetComboBoxDestroy,
.getText = widgetComboBoxGetText,
.setText = widgetComboBoxSetText
.flags = WCLASS_FOCUSABLE | WCLASS_HAS_POPUP | WCLASS_SCROLLABLE,
.paint = widgetComboBoxPaint,
.paintOverlay = widgetComboBoxPaintPopup,
.calcMinSize = widgetComboBoxCalcMinSize,
.layout = NULL,
.onMouse = widgetComboBoxOnMouse,
.onKey = widgetComboBoxOnKey,
.onAccelActivate = widgetComboBoxAccelActivate,
.destroy = widgetComboBoxDestroy,
.getText = widgetComboBoxGetText,
.setText = widgetComboBoxSetText,
.openPopup = widgetComboBoxOpenPopup,
.closePopup = widgetComboBoxClosePopup,
.getPopupItemCount = widgetComboBoxGetPopupItemCount,
.getPopupRect = widgetComboBoxGetPopupRect
};
void wgtRegister(void) {
widgetClassTable[WidgetComboBoxE] = &sClassComboBox;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
ComboBoxDataT *d = (ComboBoxDataT *)calloc(1, sizeof(ComboBoxDataT));
if (!d) {
return w;
}
w->data = d;
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
d->buf = (char *)malloc(bufSize);
d->undoBuf = (char *)malloc(bufSize);
d->bufSize = bufSize;
if (!d->buf || !d->undoBuf) {
free(d->buf);
free(d->undoBuf);
d->buf = NULL;
d->undoBuf = NULL;
} else {
d->buf[0] = '\0';
}
d->selStart = -1;
d->selEnd = -1;
d->selectedIdx = -1;
w->weight = 100;
}
return w;
}
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, -1);
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
return d->selectedIdx;
}
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
d->items = items;
d->itemCount = count;
d->maxItemLen = widgetMaxItemLen(items, count);
if (d->selectedIdx >= count) {
d->selectedIdx = -1;
}
wgtInvalidate(w);
}
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
d->selectedIdx = idx;
// Copy selected item text to buffer
if (idx >= 0 && idx < d->itemCount && d->buf) {
strncpy(d->buf, d->items[idx], d->bufSize - 1);
d->buf[d->bufSize - 1] = '\0';
d->len = (int32_t)strlen(d->buf);
d->cursorPos = d->len;
d->scrollOff = 0;
d->selStart = -1;
d->selEnd = -1;
}
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
void (*setItems)(WidgetT *w, const char **items, int32_t count);
int32_t (*getSelected)(const WidgetT *w);
void (*setSelected)(WidgetT *w, int32_t index);
} sApi = {
.create = wgtComboBox,
.setItems = wgtComboBoxSetItems,
.getSelected = wgtComboBoxGetSelected,
.setSelected = wgtComboBoxSetSelected
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassComboBox);
wgtRegisterApi("combobox", &sApi);
}

25
widgets/widgetComboBox.h Normal file
View file

@ -0,0 +1,25 @@
// widgetComboBox.h -- ComboBox widget API
#ifndef WIDGET_COMBOBOX_H
#define WIDGET_COMBOBOX_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
void (*setItems)(WidgetT *w, const char **items, int32_t count);
int32_t (*getSelected)(const WidgetT *w);
void (*setSelected)(WidgetT *w, int32_t idx);
} ComboBoxApiT;
static inline const ComboBoxApiT *dvxComboBoxApi(void) {
static const ComboBoxApiT *sApi;
if (!sApi) { sApi = (const ComboBoxApiT *)wgtGetApi("combobox"); }
return sApi;
}
#define wgtComboBox(parent, maxLen) dvxComboBoxApi()->create(parent, maxLen)
#define wgtComboBoxSetItems(w, items, count) dvxComboBoxApi()->setItems(w, items, count)
#define wgtComboBoxGetSelected(w) dvxComboBoxApi()->getSelected(w)
#define wgtComboBoxSetSelected(w, idx) dvxComboBoxApi()->setSelected(w, idx)
#endif // WIDGET_COMBOBOX_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetDropdown.c -- Dropdown (select) widget
//
// A non-editable dropdown list (HTML <select> equivalent). Unlike ComboBox,
@ -18,66 +19,29 @@
// Popup overlay painting and hit testing are shared with ComboBox via
// widgetDropdownPopupRect and widgetPaintPopupList in widgetCore.c.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "../texthelp/textHelp.h"
#include "../listhelp/listHelp.h"
static int32_t sTypeId = -1;
typedef struct {
const char **items;
int32_t itemCount;
int32_t selectedIdx;
bool open;
int32_t hoverIdx;
int32_t scrollPos;
int32_t maxItemLen;
} DropdownDataT;
// ============================================================
// wgtDropdown
// widgetDropdownDestroy
// ============================================================
WidgetT *wgtDropdown(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetDropdownE);
if (w) {
w->as.dropdown.selectedIdx = -1;
w->as.dropdown.hoverIdx = -1;
}
return w;
}
// ============================================================
// wgtDropdownGetSelected
// ============================================================
int32_t wgtDropdownGetSelected(const WidgetT *w) {
VALIDATE_WIDGET(w, WidgetDropdownE, -1);
return w->as.dropdown.selectedIdx;
}
// ============================================================
// wgtDropdownSetItems
// ============================================================
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
w->as.dropdown.items = items;
w->as.dropdown.itemCount = count;
// Cache max item strlen to avoid recomputing in calcMinSize
w->as.dropdown.maxItemLen = widgetMaxItemLen(items, count);
if (w->as.dropdown.selectedIdx >= count) {
w->as.dropdown.selectedIdx = -1;
}
wgtInvalidate(w);
}
// ============================================================
// wgtDropdownSetSelected
// ============================================================
void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
w->as.dropdown.selectedIdx = idx;
wgtInvalidatePaint(w);
void widgetDropdownDestroy(WidgetT *w) {
free(w->data);
}
@ -86,8 +50,10 @@ void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
// ============================================================
const char *widgetDropdownGetText(const WidgetT *w) {
if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) {
return w->as.dropdown.items[w->as.dropdown.selectedIdx];
const DropdownDataT *d = (const DropdownDataT *)w->data;
if (d->selectedIdx >= 0 && d->selectedIdx < d->itemCount) {
return d->items[d->selectedIdx];
}
return "";
@ -99,8 +65,10 @@ const char *widgetDropdownGetText(const WidgetT *w) {
// ============================================================
void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
DropdownDataT *d = (DropdownDataT *)w->data;
// Width: widest item + button width + border
int32_t maxItemW = w->as.dropdown.maxItemLen * font->charWidth;
int32_t maxItemW = d->maxItemLen * font->charWidth;
int32_t minW = font->charWidth * 8;
if (maxItemW < minW) {
@ -123,28 +91,29 @@ void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// arrow-key cycling doesn't require the popup to be visible.
void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
DropdownDataT *d = (DropdownDataT *)w->data;
if (w->as.dropdown.open) {
if (d->open) {
// Popup is open -- navigate items
if (key == (0x48 | 0x100)) {
if (w->as.dropdown.hoverIdx > 0) {
w->as.dropdown.hoverIdx--;
if (d->hoverIdx > 0) {
d->hoverIdx--;
if (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) {
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx;
if (d->hoverIdx < d->scrollPos) {
d->scrollPos = d->hoverIdx;
}
}
} else if (key == (0x50 | 0x100)) {
if (w->as.dropdown.hoverIdx < w->as.dropdown.itemCount - 1) {
w->as.dropdown.hoverIdx++;
if (d->hoverIdx < d->itemCount - 1) {
d->hoverIdx++;
if (w->as.dropdown.hoverIdx >= w->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) {
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
if (d->hoverIdx >= d->scrollPos + DROPDOWN_MAX_VISIBLE) {
d->scrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
}
}
} else if (key == 0x0D || key == ' ') {
w->as.dropdown.selectedIdx = w->as.dropdown.hoverIdx;
w->as.dropdown.open = false;
d->selectedIdx = d->hoverIdx;
d->open = false;
sOpenPopup = NULL;
if (w->onChange) {
@ -154,20 +123,20 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
} else {
// Popup is closed
if (key == (0x50 | 0x100) || key == ' ' || key == 0x0D) {
w->as.dropdown.open = true;
w->as.dropdown.hoverIdx = w->as.dropdown.selectedIdx;
d->open = true;
d->hoverIdx = d->selectedIdx;
sOpenPopup = w;
if (w->as.dropdown.hoverIdx >= w->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) {
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
if (d->hoverIdx >= d->scrollPos + DROPDOWN_MAX_VISIBLE) {
d->scrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
}
if (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) {
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx;
if (d->hoverIdx < d->scrollPos) {
d->scrollPos = d->hoverIdx;
}
} else if (key == (0x48 | 0x100)) {
if (w->as.dropdown.selectedIdx > 0) {
w->as.dropdown.selectedIdx--;
if (d->selectedIdx > 0) {
d->selectedIdx--;
if (w->onChange) {
w->onChange(w);
@ -201,9 +170,10 @@ void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
return;
}
w->as.dropdown.open = !w->as.dropdown.open;
w->as.dropdown.hoverIdx = w->as.dropdown.selectedIdx;
sOpenPopup = w->as.dropdown.open ? w : NULL;
DropdownDataT *d = (DropdownDataT *)w->data;
d->open = !d->open;
d->hoverIdx = d->selectedIdx;
sOpenPopup = d->open ? w : NULL;
}
@ -211,7 +181,8 @@ void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// widgetDropdownPaint
// ============================================================
void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
void widgetDropdownPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
DropdownDataT *d = (DropdownDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
@ -222,36 +193,36 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
bevel.shadow = colors->windowHighlight;
bevel.face = bg;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, textAreaW, w->h, &bevel);
drawBevel(disp, ops, w->x, w->y, textAreaW, w->h, &bevel);
// Draw selected item text
if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) {
if (d->selectedIdx >= 0 && d->selectedIdx < d->itemCount) {
int32_t textX = w->x + TEXT_INPUT_PAD;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
if (!w->enabled) {
drawTextEmbossed(d, ops, font, textX, textY, w->as.dropdown.items[w->as.dropdown.selectedIdx], colors);
drawTextEmbossed(disp, ops, font, textX, textY, d->items[d->selectedIdx], colors);
} else {
drawText(d, ops, font, textX, textY, w->as.dropdown.items[w->as.dropdown.selectedIdx], fg, bg, true);
drawText(disp, ops, font, textX, textY, d->items[d->selectedIdx], fg, bg, true);
}
}
// Drop button
BevelStyleT btnBevel;
btnBevel.highlight = w->as.dropdown.open ? colors->windowShadow : colors->windowHighlight;
btnBevel.shadow = w->as.dropdown.open ? colors->windowHighlight : colors->windowShadow;
btnBevel.highlight = d->open ? colors->windowShadow : colors->windowHighlight;
btnBevel.shadow = d->open ? colors->windowHighlight : colors->windowShadow;
btnBevel.face = colors->buttonFace;
btnBevel.width = 2;
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
drawBevel(disp, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
// Down arrow glyph in button
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2;
int32_t arrowY = w->y + w->h / 2 - 1;
widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg);
widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg);
if (w->focused) {
drawFocusRect(d, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg);
drawFocusRect(disp, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg);
}
}
@ -265,14 +236,63 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
// on-screen) and widgetPaintPopupList (which renders the bordered, scrollable
// item list with hover highlighting). These shared helpers are also used by
// ComboBox, keeping the popup rendering consistent between both widgets.
void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
void widgetDropdownPaintPopup(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
DropdownDataT *d = (DropdownDataT *)w->data;
int32_t popX;
int32_t popY;
int32_t popW;
int32_t popH;
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
widgetPaintPopupList(d, ops, font, colors, popX, popY, popW, popH, w->as.dropdown.items, w->as.dropdown.itemCount, w->as.dropdown.hoverIdx, w->as.dropdown.scrollPos);
widgetDropdownPopupRect(w, font, disp->clipH, &popX, &popY, &popW, &popH);
widgetPaintPopupList(disp, ops, font, colors, popX, popY, popW, popH, d->items, d->itemCount, d->hoverIdx, d->scrollPos);
}
// ============================================================
// widgetDropdownAccelActivate
// ============================================================
void widgetDropdownAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
DropdownDataT *d = (DropdownDataT *)w->data;
d->open = true;
d->hoverIdx = d->selectedIdx;
sOpenPopup = w;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetDropdownClosePopup
// ============================================================
void widgetDropdownClosePopup(WidgetT *w) {
DropdownDataT *d = (DropdownDataT *)w->data;
d->open = false;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetDropdownGetPopupItemCount
// ============================================================
int32_t widgetDropdownGetPopupItemCount(const WidgetT *w) {
const DropdownDataT *d = (const DropdownDataT *)w->data;
return d->itemCount;
}
// ============================================================
// widgetDropdownOpenPopup
// ============================================================
void widgetDropdownOpenPopup(WidgetT *w) {
DropdownDataT *d = (DropdownDataT *)w->data;
d->open = true;
d->hoverIdx = d->selectedIdx;
sOpenPopup = w;
wgtInvalidatePaint(w);
}
@ -281,19 +301,104 @@ void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
// ============================================================
static void widgetDropdownGetPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
widgetDropdownPopupRect((WidgetT *)w, font, contentH, popX, popY, popW, popH);
}
static const WidgetClassT sClassDropdown = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetDropdownPaint,
.paintOverlay = widgetDropdownPaintPopup,
.calcMinSize = widgetDropdownCalcMinSize,
.layout = NULL,
.onMouse = widgetDropdownOnMouse,
.onKey = widgetDropdownOnKey,
.destroy = NULL,
.getText = widgetDropdownGetText,
.setText = NULL
.flags = WCLASS_FOCUSABLE | WCLASS_HAS_POPUP | WCLASS_SCROLLABLE,
.paint = widgetDropdownPaint,
.paintOverlay = widgetDropdownPaintPopup,
.calcMinSize = widgetDropdownCalcMinSize,
.layout = NULL,
.onMouse = widgetDropdownOnMouse,
.onKey = widgetDropdownOnKey,
.onAccelActivate = widgetDropdownAccelActivate,
.destroy = widgetDropdownDestroy,
.getText = widgetDropdownGetText,
.setText = NULL,
.openPopup = widgetDropdownOpenPopup,
.closePopup = widgetDropdownClosePopup,
.getPopupItemCount = widgetDropdownGetPopupItemCount,
.getPopupRect = widgetDropdownGetPopupRect
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtDropdown(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
DropdownDataT *d = (DropdownDataT *)calloc(1, sizeof(DropdownDataT));
if (!d) {
return w;
}
w->data = d;
d->selectedIdx = -1;
d->hoverIdx = -1;
}
return w;
}
int32_t wgtDropdownGetSelected(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, -1);
const DropdownDataT *d = (const DropdownDataT *)w->data;
return d->selectedIdx;
}
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
VALIDATE_WIDGET_VOID(w, sTypeId);
DropdownDataT *d = (DropdownDataT *)w->data;
d->items = items;
d->itemCount = count;
d->maxItemLen = widgetMaxItemLen(items, count);
if (d->selectedIdx >= count) {
d->selectedIdx = -1;
}
wgtInvalidate(w);
}
void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, sTypeId);
DropdownDataT *d = (DropdownDataT *)w->data;
d->selectedIdx = idx;
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent);
void (*setItems)(WidgetT *w, const char **items, int32_t count);
int32_t (*getSelected)(const WidgetT *w);
void (*setSelected)(WidgetT *w, int32_t index);
} sApi = {
.create = wgtDropdown,
.setItems = wgtDropdownSetItems,
.getSelected = wgtDropdownGetSelected,
.setSelected = wgtDropdownSetSelected
};
void wgtRegister(void) {
widgetClassTable[WidgetDropdownE] = &sClassDropdown;
sTypeId = wgtRegisterClass(&sClassDropdown);
wgtRegisterApi("dropdown", &sApi);
}

25
widgets/widgetDropdown.h Normal file
View file

@ -0,0 +1,25 @@
// widgetDropdown.h -- Dropdown widget API
#ifndef WIDGET_DROPDOWN_H
#define WIDGET_DROPDOWN_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent);
void (*setItems)(WidgetT *w, const char **items, int32_t count);
int32_t (*getSelected)(const WidgetT *w);
void (*setSelected)(WidgetT *w, int32_t idx);
} DropdownApiT;
static inline const DropdownApiT *dvxDropdownApi(void) {
static const DropdownApiT *sApi;
if (!sApi) { sApi = (const DropdownApiT *)wgtGetApi("dropdown"); }
return sApi;
}
#define wgtDropdown(parent) dvxDropdownApi()->create(parent)
#define wgtDropdownSetItems(w, items, count) dvxDropdownApi()->setItems(w, items, count)
#define wgtDropdownGetSelected(w) dvxDropdownApi()->getSelected(w)
#define wgtDropdownSetSelected(w, idx) dvxDropdownApi()->setSelected(w, idx)
#endif // WIDGET_DROPDOWN_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetImage.c -- Image widget (displays bitmap, responds to clicks)
//
// Displays a bitmap image, optionally responding to clicks. The image data
@ -16,38 +17,141 @@
// No border or bevel is drawn -- the image fills its widget bounds with
// centering if the widget is larger than the image.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
static int32_t sTypeId = -1;
typedef struct {
uint8_t *pixelData;
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
bool pressed;
} ImageDataT;
// ============================================================
// wgtImage
// widgetImageDestroy
// ============================================================
//
// Create an image widget from raw pixel data already in display format.
// Takes ownership of the data buffer (freed on destroy).
WidgetT *wgtImage(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch) {
WidgetT *wgt = widgetAlloc(parent, WidgetImageE);
void widgetImageDestroy(WidgetT *w) {
ImageDataT *d = (ImageDataT *)w->data;
if (d) {
free(d->pixelData);
free(d);
}
}
// ============================================================
// widgetImageCalcMinSize
// ============================================================
void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
ImageDataT *d = (ImageDataT *)w->data;
w->calcMinW = d->imgW;
w->calcMinH = d->imgH;
}
// ============================================================
// widgetImageOnMouse
// ============================================================
// Mouse click: briefly sets pressed=true (for the 1px offset effect),
// invalidates to show the press, fires onClick, then immediately clears
// pressed. The press is purely visual -- there's no release tracking like
// Button has, since Image clicks are meant for instant actions (e.g.,
// clicking a logo or icon area).
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
ImageDataT *d = (ImageDataT *)w->data;
d->pressed = true;
wgtInvalidatePaint(w);
if (w->onClick) {
w->onClick(w);
}
d->pressed = false;
}
// ============================================================
// widgetImagePaint
// ============================================================
void widgetImagePaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
(void)colors;
ImageDataT *d = (ImageDataT *)w->data;
if (!d->pixelData) {
return;
}
// Center the image within the widget bounds
int32_t imgW = d->imgW;
int32_t imgH = d->imgH;
int32_t dx = w->x + (w->w - imgW) / 2;
int32_t dy = w->y + (w->h - imgH) / 2;
// Offset by 1px when pressed (button-press effect)
if (d->pressed) {
dx++;
dy++;
}
rectCopy(disp, ops, dx, dy,
d->pixelData, d->imgPitch,
0, 0, imgW, imgH);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassImage = {
.flags = 0,
.paint = widgetImagePaint,
.paintOverlay = NULL,
.calcMinSize = widgetImageCalcMinSize,
.layout = NULL,
.onMouse = widgetImageOnMouse,
.onKey = NULL,
.destroy = widgetImageDestroy,
.getText = NULL,
.setText = NULL
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtImage(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch) {
WidgetT *wgt = widgetAlloc(parent, sTypeId);
if (wgt) {
wgt->as.image.data = data;
wgt->as.image.imgW = w;
wgt->as.image.imgH = h;
wgt->as.image.imgPitch = pitch;
wgt->as.image.pressed = false;
ImageDataT *d = calloc(1, sizeof(ImageDataT));
d->pixelData = pixelData;
d->imgW = w;
d->imgH = h;
d->imgPitch = pitch;
d->pressed = false;
wgt->data = d;
}
return wgt;
}
// ============================================================
// wgtImageFromFile
// ============================================================
//
// Load an image from a file (BMP, PNG, JPEG, GIF), convert to
// display pixel format, and create an image widget. Delegates to
// dvxLoadImage for format decoding and pixel conversion.
WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) {
if (!parent || !path) {
return NULL;
@ -72,114 +176,35 @@ WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) {
}
// ============================================================
// wgtImageSetData
// ============================================================
void wgtImageSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ImageDataT *d = (ImageDataT *)w->data;
void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
VALIDATE_WIDGET_VOID(w, WidgetImageE);
free(w->as.image.data);
w->as.image.data = data;
w->as.image.imgW = imgW;
w->as.image.imgH = imgH;
w->as.image.imgPitch = pitch;
free(d->pixelData);
d->pixelData = pixelData;
d->imgW = imgW;
d->imgH = imgH;
d->imgPitch = pitch;
wgtInvalidate(w);
}
// ============================================================
// widgetImageDestroy
// ============================================================
void widgetImageDestroy(WidgetT *w) {
free(w->as.image.data);
}
// ============================================================
// widgetImageCalcMinSize
// ============================================================
void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
w->calcMinW = w->as.image.imgW;
w->calcMinH = w->as.image.imgH;
}
// ============================================================
// widgetImageOnMouse
// ============================================================
// Mouse click: briefly sets pressed=true (for the 1px offset effect),
// invalidates to show the press, fires onClick, then immediately clears
// pressed. The press is purely visual -- there's no release tracking like
// Button has, since Image clicks are meant for instant actions (e.g.,
// clicking a logo or icon area).
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
w->as.image.pressed = true;
wgtInvalidatePaint(w);
if (w->onClick) {
w->onClick(w);
}
w->as.image.pressed = false;
}
// ============================================================
// widgetImagePaint
// ============================================================
void widgetImagePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
(void)colors;
if (!w->as.image.data) {
return;
}
// Center the image within the widget bounds
int32_t imgW = w->as.image.imgW;
int32_t imgH = w->as.image.imgH;
int32_t dx = w->x + (w->w - imgW) / 2;
int32_t dy = w->y + (w->h - imgH) / 2;
// Offset by 1px when pressed (button-press effect)
if (w->as.image.pressed) {
dx++;
dy++;
}
rectCopy(d, ops, dx, dy,
w->as.image.data, w->as.image.imgPitch,
0, 0, imgW, imgH);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassImage = {
.flags = 0,
.paint = widgetImagePaint,
.paintOverlay = NULL,
.calcMinSize = widgetImageCalcMinSize,
.layout = NULL,
.onMouse = widgetImageOnMouse,
.onKey = NULL,
.destroy = widgetImageDestroy,
.getText = NULL,
.setText = NULL
static const struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
} sApi = {
.create = wgtImage,
.fromFile = wgtImageFromFile,
.setData = wgtImageSetData
};
void wgtRegister(void) {
widgetClassTable[WidgetImageE] = &sClassImage;
sTypeId = wgtRegisterClass(&sClassImage);
wgtRegisterApi("image", &sApi);
}

23
widgets/widgetImage.h Normal file
View file

@ -0,0 +1,23 @@
// widgetImage.h -- Image widget API
#ifndef WIDGET_IMAGE_H
#define WIDGET_IMAGE_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
} ImageApiT;
static inline const ImageApiT *dvxImageApi(void) {
static const ImageApiT *sApi;
if (!sApi) { sApi = (const ImageApiT *)wgtGetApi("image"); }
return sApi;
}
#define wgtImage(parent, data, w, h, pitch) dvxImageApi()->create(parent, data, w, h, pitch)
#define wgtImageFromFile(parent, path) dvxImageApi()->fromFile(parent, path)
#define wgtImageSetData(w, data, imgW, imgH, pitch) dvxImageApi()->setData(w, data, imgW, imgH, pitch)
#endif // WIDGET_IMAGE_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetImageButton.c -- Image button widget (button with image instead of text)
//
// Combines a Button's beveled border and press behavior with an Image's
@ -16,41 +17,190 @@
// the 2px bevel on each side -- no extra padding is added beyond that,
// keeping image buttons compact for toolbar use.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
static int32_t sTypeId = -1;
typedef struct {
uint8_t *pixelData;
int32_t imgW;
int32_t imgH;
int32_t imgPitch;
bool pressed;
} ImageButtonDataT;
// ============================================================
// wgtImageButton
// widgetImageButtonDestroy
// ============================================================
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch) {
if (!parent || !data || w <= 0 || h <= 0) {
void widgetImageButtonDestroy(WidgetT *w) {
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
if (d) {
free(d->pixelData);
free(d);
}
}
// ============================================================
// widgetImageButtonCalcMinSize
// ============================================================
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
// Bevel border only, no extra padding
w->calcMinW = d->imgW + 4;
w->calcMinH = d->imgH + 4;
}
// ============================================================
// widgetImageButtonOnKey
// ============================================================
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
if (key == ' ' || key == 0x0D) {
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
}
// ============================================================
// widgetImageButtonOnMouse
// ============================================================
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
w->focused = true;
d->pressed = true;
sPressedButton = w;
}
// ============================================================
// widgetImageButtonPaint
// ============================================================
void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
bool pressed = d->pressed && w->enabled;
BevelStyleT bevel;
bevel.highlight = pressed ? colors->windowShadow : colors->windowHighlight;
bevel.shadow = pressed ? colors->windowHighlight : colors->windowShadow;
bevel.face = bgFace;
bevel.width = 2;
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
if (d->pixelData) {
int32_t imgX = w->x + (w->w - d->imgW) / 2;
int32_t imgY = w->y + (w->h - d->imgH) / 2;
if (pressed) {
imgX++;
imgY++;
}
rectCopy(disp, ops, imgX, imgY,
d->pixelData, d->imgPitch,
0, 0, d->imgW, d->imgH);
}
if (w->focused) {
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
int32_t off = pressed ? 1 : 0;
drawFocusRect(disp, ops, w->x + 3 + off, w->y + 3 + off, w->w - 6, w->h - 6, fg);
}
}
// ============================================================
// widgetImageButtonAccelActivate
// ============================================================
void widgetImageButtonAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
d->pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
// ============================================================
// widgetImageButtonSetPressed
// ============================================================
void widgetImageButtonSetPressed(WidgetT *w, bool pressed) {
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
d->pressed = pressed;
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassImageButton = {
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
.paint = widgetImageButtonPaint,
.paintOverlay = NULL,
.calcMinSize = widgetImageButtonCalcMinSize,
.layout = NULL,
.onMouse = widgetImageButtonOnMouse,
.onKey = widgetImageButtonOnKey,
.onAccelActivate = widgetImageButtonAccelActivate,
.destroy = widgetImageButtonDestroy,
.getText = NULL,
.setText = NULL,
.setPressed = widgetImageButtonSetPressed
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch) {
if (!parent || !pixelData || w <= 0 || h <= 0) {
return NULL;
}
WidgetT *wgt = widgetAlloc(parent, WidgetImageButtonE);
WidgetT *wgt = widgetAlloc(parent, sTypeId);
if (wgt) {
wgt->as.imageButton.data = data;
wgt->as.imageButton.imgW = w;
wgt->as.imageButton.imgH = h;
wgt->as.imageButton.imgPitch = pitch;
wgt->as.imageButton.pressed = false;
ImageButtonDataT *d = calloc(1, sizeof(ImageButtonDataT));
d->pixelData = pixelData;
d->imgW = w;
d->imgH = h;
d->imgPitch = pitch;
d->pressed = false;
wgt->data = d;
} else {
free(data);
free(pixelData);
}
return wgt;
}
// ============================================================
// wgtImageButtonFromFile
// ============================================================
//
// Load an image from disk and create an ImageButton widget.
// Delegates to dvxLoadImage for format decoding and pixel conversion.
WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path) {
if (!parent || !path) {
return NULL;
@ -75,130 +225,35 @@ WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path) {
}
// ============================================================
// wgtImageButtonSetData
// ============================================================
void wgtImageButtonSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
VALIDATE_WIDGET_VOID(w, WidgetImageButtonE);
free(w->as.imageButton.data);
w->as.imageButton.data = data;
w->as.imageButton.imgW = imgW;
w->as.imageButton.imgH = imgH;
w->as.imageButton.imgPitch = pitch;
free(d->pixelData);
d->pixelData = pixelData;
d->imgW = imgW;
d->imgH = imgH;
d->imgPitch = pitch;
wgtInvalidate(w);
}
// ============================================================
// widgetImageButtonDestroy
// ============================================================
void widgetImageButtonDestroy(WidgetT *w) {
free(w->as.imageButton.data);
}
// ============================================================
// widgetImageButtonCalcMinSize
// ============================================================
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
// Bevel border only, no extra padding
w->calcMinW = w->as.imageButton.imgW + 4;
w->calcMinH = w->as.imageButton.imgH + 4;
}
// ============================================================
// widgetImageButtonOnKey
// ============================================================
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
if (key == ' ' || key == 0x0D) {
w->as.imageButton.pressed = true;
sKeyPressedBtn = w;
wgtInvalidatePaint(w);
}
}
// ============================================================
// widgetImageButtonOnMouse
// ============================================================
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
(void)vx;
(void)vy;
w->focused = true;
w->as.imageButton.pressed = true;
sPressedButton = w;
}
// ============================================================
// widgetImageButtonPaint
// ============================================================
void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
bool pressed = w->as.imageButton.pressed && w->enabled;
BevelStyleT bevel;
bevel.highlight = pressed ? colors->windowShadow : colors->windowHighlight;
bevel.shadow = pressed ? colors->windowHighlight : colors->windowShadow;
bevel.face = bgFace;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
if (w->as.imageButton.data) {
int32_t imgX = w->x + (w->w - w->as.imageButton.imgW) / 2;
int32_t imgY = w->y + (w->h - w->as.imageButton.imgH) / 2;
if (pressed) {
imgX++;
imgY++;
}
rectCopy(d, ops, imgX, imgY,
w->as.imageButton.data, w->as.imageButton.imgPitch,
0, 0, w->as.imageButton.imgW, w->as.imageButton.imgH);
}
if (w->focused) {
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
int32_t off = pressed ? 1 : 0;
drawFocusRect(d, ops, w->x + 3 + off, w->y + 3 + off, w->w - 6, w->h - 6, fg);
}
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassImageButton = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetImageButtonPaint,
.paintOverlay = NULL,
.calcMinSize = widgetImageButtonCalcMinSize,
.layout = NULL,
.onMouse = widgetImageButtonOnMouse,
.onKey = widgetImageButtonOnKey,
.destroy = widgetImageButtonDestroy,
.getText = NULL,
.setText = NULL
static const struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
} sApi = {
.create = wgtImageButton,
.fromFile = wgtImageButtonFromFile,
.setData = wgtImageButtonSetData
};
void wgtRegister(void) {
widgetClassTable[WidgetImageButtonE] = &sClassImageButton;
sTypeId = wgtRegisterClass(&sClassImageButton);
wgtRegisterApi("imagebutton", &sApi);
}

View file

@ -0,0 +1,23 @@
// widgetImageButton.h -- ImageButton widget API
#ifndef WIDGET_IMAGEBUTTON_H
#define WIDGET_IMAGEBUTTON_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
} ImageButtonApiT;
static inline const ImageButtonApiT *dvxImageButtonApi(void) {
static const ImageButtonApiT *sApi;
if (!sApi) { sApi = (const ImageButtonApiT *)wgtGetApi("imagebutton"); }
return sApi;
}
#define wgtImageButton(parent, data, w, h, pitch) dvxImageButtonApi()->create(parent, data, w, h, pitch)
#define wgtImageButtonFromFile(parent, path) dvxImageButtonApi()->fromFile(parent, path)
#define wgtImageButtonSetData(w, data, imgW, imgH, pitch) dvxImageButtonApi()->setData(w, data, imgW, imgH, pitch)
#endif // WIDGET_IMAGEBUTTON_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetLabel.c -- Label widget
//
// Static text display -- the simplest widget. Not focusable, not interactive.
@ -15,22 +16,41 @@
// content background color from the color scheme). The +1 in calcMinH adds a
// pixel of breathing room below the text baseline.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
static int32_t sTypeId = -1;
typedef struct {
const char *text;
WidgetAlignE textAlign;
} LabelDataT;
// ============================================================
// wgtLabel
// Prototypes
// ============================================================
WidgetT *wgtLabel(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, WidgetLabelE);
static void widgetLabelDestroy(WidgetT *w);
if (w) {
w->as.label.text = text;
w->accelKey = accelParse(text);
}
return w;
// ============================================================
// widgetLabelCalcMinSize
// ============================================================
void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
LabelDataT *d = (LabelDataT *)w->data;
w->calcMinW = textWidthAccel(font, d->text);
w->calcMinH = font->charHeight + 1;
}
// ============================================================
// widgetLabelDestroy
// ============================================================
static void widgetLabelDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
@ -39,27 +59,8 @@ WidgetT *wgtLabel(WidgetT *parent, const char *text) {
// ============================================================
const char *widgetLabelGetText(const WidgetT *w) {
return w->as.label.text;
}
// ============================================================
// widgetLabelSetText
// ============================================================
void widgetLabelSetText(WidgetT *w, const char *text) {
w->as.label.text = text;
w->accelKey = accelParse(text);
}
// ============================================================
// widgetLabelCalcMinSize
// ============================================================
void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
w->calcMinW = textWidthAccel(font, w->as.label.text);
w->calcMinH = font->charHeight + 1;
LabelDataT *d = (LabelDataT *)w->data;
return d->text;
}
@ -68,36 +69,37 @@ void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// ============================================================
void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
int32_t textW = textWidthAccel(font, w->as.label.text);
int32_t textX = w->x;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
LabelDataT *ld = (LabelDataT *)w->data;
int32_t textW = textWidthAccel(font, ld->text);
int32_t textX = w->x;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
if (w->as.label.textAlign == AlignCenterE) {
if (ld->textAlign == AlignCenterE) {
textX = w->x + (w->w - textW) / 2;
} else if (w->as.label.textAlign == AlignEndE) {
} else if (ld->textAlign == AlignEndE) {
textX = w->x + w->w - textW;
}
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, textX, textY, w->as.label.text, colors);
drawTextAccelEmbossed(d, ops, font, textX, textY, ld->text, colors);
return;
}
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
drawTextAccel(d, ops, font, textX, textY, w->as.label.text, fg, bg, false);
drawTextAccel(d, ops, font, textX, textY, ld->text, fg, bg, false);
}
// ============================================================
// wgtLabelSetAlign
// widgetLabelSetText
// ============================================================
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
if (w && w->type == WidgetLabelE) {
w->as.label.textAlign = align;
}
void widgetLabelSetText(WidgetT *w, const char *text) {
LabelDataT *d = (LabelDataT *)w->data;
d->text = text;
w->accelKey = accelParse(text);
}
@ -107,18 +109,59 @@ void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
static const WidgetClassT sClassLabel = {
.flags = 0,
.flags = WCLASS_FOCUS_FORWARD,
.paint = widgetLabelPaint,
.paintOverlay = NULL,
.calcMinSize = widgetLabelCalcMinSize,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.destroy = widgetLabelDestroy,
.getText = widgetLabelGetText,
.setText = widgetLabelSetText
};
void wgtRegister(void) {
widgetClassTable[WidgetLabelE] = &sClassLabel;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtLabel(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
LabelDataT *d = calloc(1, sizeof(LabelDataT));
w->data = d;
d->text = text;
w->accelKey = accelParse(text);
}
return w;
}
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
if (w && w->type == sTypeId) {
LabelDataT *d = (LabelDataT *)w->data;
d->textAlign = align;
}
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
void (*setAlign)(WidgetT *w, WidgetAlignE align);
} sApi = {
.create = wgtLabel,
.setAlign = wgtLabelSetAlign
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassLabel);
wgtRegisterApi("label", &sApi);
}

21
widgets/widgetLabel.h Normal file
View file

@ -0,0 +1,21 @@
// widgetLabel.h -- Label widget API
#ifndef WIDGET_LABEL_H
#define WIDGET_LABEL_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, const char *text);
void (*setAlign)(WidgetT *w, WidgetAlignE align);
} LabelApiT;
static inline const LabelApiT *dvxLabelApi(void) {
static const LabelApiT *sApi;
if (!sApi) { sApi = (const LabelApiT *)wgtGetApi("label"); }
return sApi;
}
#define wgtLabel(parent, text) dvxLabelApi()->create(parent, text)
#define wgtLabelSetAlign(w, align) dvxLabelApi()->setAlign(w, align)
#endif // WIDGET_LABEL_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetListBox.c -- ListBox widget (single and multi-select)
//
// Scrollable list of text items with single-select or multi-select modes.
@ -27,7 +28,26 @@
// and a 2px horizontal line indicator shows the insertion point. The actual
// reordering is handled by the application's onReorder callback.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "../listhelp/listHelp.h"
#define LISTBOX_BORDER 2
static int32_t sTypeId = -1;
typedef struct {
const char **items;
int32_t itemCount;
int32_t selectedIdx;
int32_t scrollPos;
int32_t maxItemLen;
bool multiSelect;
int32_t anchorIdx;
uint8_t *selBits;
bool reorderable;
int32_t dragIdx;
int32_t dropIdx;
} ListBoxDataT;
#include <stdlib.h>
#include <string.h>
@ -40,32 +60,10 @@
// Prototypes
// ============================================================
static void allocSelBits(WidgetT *w);
static void ensureScrollVisible(WidgetT *w, int32_t idx);
static void selectRange(WidgetT *w, int32_t from, int32_t to);
// ============================================================
// allocSelBits
// ============================================================
// Allocate (or re-allocate) the selection bits array. Only allocated in
// multi-select mode -- in single-select, selectedIdx alone tracks the
// selection. calloc initializes all bits to 0 (nothing selected).
static void allocSelBits(WidgetT *w) {
if (w->as.listBox.selBits) {
free(w->as.listBox.selBits);
w->as.listBox.selBits = NULL;
}
int32_t count = w->as.listBox.itemCount;
if (count > 0 && w->as.listBox.multiSelect) {
w->as.listBox.selBits = (uint8_t *)calloc(count, 1);
}
}
// ============================================================
// ensureScrollVisible
// ============================================================
@ -78,6 +76,7 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) {
return;
}
ListBoxDataT *d = (ListBoxDataT *)w->data;
AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font;
int32_t innerH = w->h - LISTBOX_BORDER * 2;
@ -87,10 +86,10 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) {
visibleRows = 1;
}
if (idx < w->as.listBox.scrollPos) {
w->as.listBox.scrollPos = idx;
} else if (idx >= w->as.listBox.scrollPos + visibleRows) {
w->as.listBox.scrollPos = idx - visibleRows + 1;
if (idx < d->scrollPos) {
d->scrollPos = idx;
} else if (idx >= d->scrollPos + visibleRows) {
d->scrollPos = idx - visibleRows + 1;
}
}
@ -102,7 +101,9 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) {
// Set selection bits for all items in the range [from, to] (inclusive,
// order-independent). Used for shift-click and shift-arrow range selection.
static void selectRange(WidgetT *w, int32_t from, int32_t to) {
if (!w->as.listBox.selBits) {
ListBoxDataT *d = (ListBoxDataT *)w->data;
if (!d->selBits) {
return;
}
@ -113,12 +114,12 @@ static void selectRange(WidgetT *w, int32_t from, int32_t to) {
lo = 0;
}
if (hi >= w->as.listBox.itemCount) {
hi = w->as.listBox.itemCount - 1;
if (hi >= d->itemCount) {
hi = d->itemCount - 1;
}
for (int32_t i = lo; i <= hi; i++) {
w->as.listBox.selBits[i] = 1;
d->selBits[i] = 1;
}
}
@ -128,191 +129,16 @@ static void selectRange(WidgetT *w, int32_t from, int32_t to) {
// ============================================================
void widgetListBoxDestroy(WidgetT *w) {
if (w->as.listBox.selBits) {
free(w->as.listBox.selBits);
w->as.listBox.selBits = NULL;
ListBoxDataT *d = (ListBoxDataT *)w->data;
if (d) {
free(d->selBits);
free(d);
w->data = NULL;
}
}
// ============================================================
// wgtListBox
// ============================================================
WidgetT *wgtListBox(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetListBoxE);
if (w) {
w->as.listBox.selectedIdx = -1;
w->as.listBox.anchorIdx = -1;
w->as.listBox.dragIdx = -1;
w->as.listBox.dropIdx = -1;
}
return w;
}
// ============================================================
// wgtListBoxClearSelection
// ============================================================
void wgtListBoxClearSelection(WidgetT *w) {
if (!w || w->type != WidgetListBoxE || !w->as.listBox.selBits) {
return;
}
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
wgtInvalidatePaint(w);
}
// ============================================================
// wgtListBoxGetSelected
// ============================================================
int32_t wgtListBoxGetSelected(const WidgetT *w) {
VALIDATE_WIDGET(w, WidgetListBoxE, -1);
return w->as.listBox.selectedIdx;
}
// ============================================================
// wgtListBoxIsItemSelected
// ============================================================
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
VALIDATE_WIDGET(w, WidgetListBoxE, false);
if (!w->as.listBox.multiSelect) {
return idx == w->as.listBox.selectedIdx;
}
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
return false;
}
return w->as.listBox.selBits[idx] != 0;
}
// ============================================================
// wgtListBoxSelectAll
// ============================================================
void wgtListBoxSelectAll(WidgetT *w) {
if (!w || w->type != WidgetListBoxE || !w->as.listBox.selBits) {
return;
}
memset(w->as.listBox.selBits, 1, w->as.listBox.itemCount);
wgtInvalidatePaint(w);
}
// ============================================================
// wgtListBoxSetItemSelected
// ============================================================
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
return;
}
w->as.listBox.selBits[idx] = selected ? 1 : 0;
wgtInvalidatePaint(w);
}
// ============================================================
// wgtListBoxSetItems
// ============================================================
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.items = items;
w->as.listBox.itemCount = count;
// Cache max item strlen to avoid recomputing in calcMinSize
w->as.listBox.maxItemLen = widgetMaxItemLen(items, count);
if (w->as.listBox.selectedIdx >= count) {
w->as.listBox.selectedIdx = count > 0 ? 0 : -1;
}
if (w->as.listBox.selectedIdx < 0 && count > 0) {
w->as.listBox.selectedIdx = 0;
}
w->as.listBox.anchorIdx = w->as.listBox.selectedIdx;
// Reallocate selection bits
allocSelBits(w);
// Pre-select the cursor item
if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) {
w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1;
}
wgtInvalidate(w);
}
// ============================================================
// wgtListBoxSetReorderable
// ============================================================
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.reorderable = reorderable;
}
// ============================================================
// wgtListBoxSetMultiSelect
// ============================================================
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.multiSelect = multi;
allocSelBits(w);
// Sync: mark current selection
if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) {
w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1;
}
}
// ============================================================
// wgtListBoxSetSelected
// ============================================================
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
w->as.listBox.selectedIdx = idx;
w->as.listBox.anchorIdx = idx;
// In multi-select, clear all then select just this one
if (w->as.listBox.selBits) {
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
if (idx >= 0 && idx < w->as.listBox.itemCount) {
w->as.listBox.selBits[idx] = 1;
}
}
wgtInvalidatePaint(w);
}
// ============================================================
// widgetListBoxCalcMinSize
// ============================================================
@ -322,8 +148,9 @@ void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
// minimum height even when empty. The 8-character minimum width prevents
// the listbox from collapsing too narrow when items are short or absent.
void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
int32_t maxItemW = w->as.listBox.maxItemLen * font->charWidth;
int32_t minW = font->charWidth * 8;
ListBoxDataT *d = (ListBoxDataT *)w->data;
int32_t maxItemW = d->maxItemLen * font->charWidth;
int32_t minW = font->charWidth * 8;
if (maxItemW < minW) {
maxItemW = minW;
@ -343,14 +170,16 @@ void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// navigation. This shared helper ensures consistent keyboard navigation
// across ListBox, ListView, and other scrollable widgets.
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (!w || w->type != WidgetListBoxE || w->as.listBox.itemCount == 0) {
ListBoxDataT *d = (ListBoxDataT *)w->data;
if (!w || w->type != sTypeId || d->itemCount == 0) {
return;
}
bool multi = w->as.listBox.multiSelect;
bool multi = d->multiSelect;
bool shift = (mod & KEY_MOD_SHIFT) != 0;
bool ctrl = (mod & KEY_MOD_CTRL) != 0;
int32_t sel = w->as.listBox.selectedIdx;
int32_t sel = d->selectedIdx;
// Ctrl+A -- select all (multi-select only)
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
@ -360,9 +189,9 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Space -- toggle current item (multi-select only)
if (multi && key == ' ') {
if (sel >= 0 && w->as.listBox.selBits) {
w->as.listBox.selBits[sel] ^= 1;
w->as.listBox.anchorIdx = sel;
if (sel >= 0 && d->selBits) {
d->selBits[sel] ^= 1;
d->anchorIdx = sel;
}
if (w->onChange) {
@ -390,7 +219,7 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
visibleRows = 1;
}
int32_t newSel = widgetNavigateIndex(key, sel, w->as.listBox.itemCount, visibleRows);
int32_t newSel = widgetNavigateIndex(key, sel, d->itemCount, visibleRows);
if (newSel < 0) {
return;
@ -400,14 +229,14 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
return;
}
w->as.listBox.selectedIdx = newSel;
d->selectedIdx = newSel;
// Update selection
if (multi && w->as.listBox.selBits) {
if (multi && d->selBits) {
if (shift) {
// Shift+arrow: range from anchor to new cursor
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
selectRange(w, w->as.listBox.anchorIdx, newSel);
memset(d->selBits, 0, d->itemCount);
selectRange(w, d->anchorIdx, newSel);
}
// Plain arrow: just move cursor, leave selections untouched
}
@ -432,21 +261,22 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
// regions, and thumb based on the click position. Item clicks are translated
// from pixel coordinates to item index using integer division by charHeight.
void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
ListBoxDataT *d = (ListBoxDataT *)hit->data;
AppContextT *ctx = (AppContextT *)root->userData;
const BitmapFontT *font = &ctx->font;
int32_t innerH = hit->h - LISTBOX_BORDER * 2;
int32_t visibleRows = innerH / font->charHeight;
bool needSb = (hit->as.listBox.itemCount > visibleRows);
bool needSb = (d->itemCount > visibleRows);
// Clamp scroll
int32_t maxScroll = hit->as.listBox.itemCount - visibleRows;
int32_t maxScroll = d->itemCount - visibleRows;
if (maxScroll < 0) {
maxScroll = 0;
}
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
d->scrollPos = clampInt(d->scrollPos, 0, maxScroll);
// Check if click is on the scrollbar
if (needSb) {
@ -454,27 +284,27 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
if (vx >= sbX) {
int32_t relY = vy - (hit->y + LISTBOX_BORDER);
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos);
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, d->itemCount, visibleRows, d->scrollPos);
if (sh == ScrollHitArrowDecE) {
if (hit->as.listBox.scrollPos > 0) {
hit->as.listBox.scrollPos--;
if (d->scrollPos > 0) {
d->scrollPos--;
}
} else if (sh == ScrollHitArrowIncE) {
if (hit->as.listBox.scrollPos < maxScroll) {
hit->as.listBox.scrollPos++;
if (d->scrollPos < maxScroll) {
d->scrollPos++;
}
} else if (sh == ScrollHitPageDecE) {
hit->as.listBox.scrollPos -= visibleRows;
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
d->scrollPos -= visibleRows;
d->scrollPos = clampInt(d->scrollPos, 0, maxScroll);
} else if (sh == ScrollHitPageIncE) {
hit->as.listBox.scrollPos += visibleRows;
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
d->scrollPos += visibleRows;
d->scrollPos = clampInt(d->scrollPos, 0, maxScroll);
} else if (sh == ScrollHitThumbE) {
int32_t trackLen = innerH - WGT_SB_W * 2;
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos, &thumbPos, &thumbSize);
widgetScrollbarThumb(trackLen, d->itemCount, visibleRows, d->scrollPos, &thumbPos, &thumbSize);
sDragScrollbar = hit;
sDragScrollbarOrient = 0;
@ -495,33 +325,33 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
}
int32_t clickedRow = relY / font->charHeight;
int32_t idx = hit->as.listBox.scrollPos + clickedRow;
int32_t idx = d->scrollPos + clickedRow;
if (idx < 0 || idx >= hit->as.listBox.itemCount) {
if (idx < 0 || idx >= d->itemCount) {
return;
}
bool multi = hit->as.listBox.multiSelect;
bool multi = d->multiSelect;
bool shift = (ctx->keyModifiers & KEY_MOD_SHIFT) != 0;
bool ctrl = (ctx->keyModifiers & KEY_MOD_CTRL) != 0;
hit->as.listBox.selectedIdx = idx;
d->selectedIdx = idx;
hit->focused = true;
if (multi && hit->as.listBox.selBits) {
if (multi && d->selBits) {
if (ctrl) {
// Ctrl+click: toggle item, update anchor
hit->as.listBox.selBits[idx] ^= 1;
hit->as.listBox.anchorIdx = idx;
d->selBits[idx] ^= 1;
d->anchorIdx = idx;
} else if (shift) {
// Shift+click: range from anchor to clicked
memset(hit->as.listBox.selBits, 0, hit->as.listBox.itemCount);
selectRange(hit, hit->as.listBox.anchorIdx, idx);
memset(d->selBits, 0, d->itemCount);
selectRange(hit, d->anchorIdx, idx);
} else {
// Plain click: select only this item, update anchor
memset(hit->as.listBox.selBits, 0, hit->as.listBox.itemCount);
hit->as.listBox.selBits[idx] = 1;
hit->as.listBox.anchorIdx = idx;
memset(d->selBits, 0, d->itemCount);
d->selBits[idx] = 1;
d->anchorIdx = idx;
}
}
@ -532,9 +362,9 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// onDblClick is handled by the central dispatcher in widgetEvent.c
// Initiate drag-reorder if enabled (not from modifier clicks)
if (hit->as.listBox.reorderable && !shift && !ctrl) {
hit->as.listBox.dragIdx = idx;
hit->as.listBox.dropIdx = idx;
if (d->reorderable && !shift && !ctrl) {
d->dragIdx = idx;
d->dropIdx = idx;
sDragReorder = hit;
}
}
@ -552,12 +382,13 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// items are selected. The scrollbar is drawn only when needed (item count
// exceeds visible rows), and the content width is reduced accordingly.
void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
ListBoxDataT *lb = (ListBoxDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
int32_t innerH = w->h - LISTBOX_BORDER * 2;
int32_t visibleRows = innerH / font->charHeight;
bool needSb = (w->as.listBox.itemCount > visibleRows);
bool needSb = (lb->itemCount > visibleRows);
int32_t contentW = w->w - LISTBOX_BORDER * 2;
if (needSb) {
@ -569,22 +400,22 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
// Clamp scroll position
int32_t maxScroll = w->as.listBox.itemCount - visibleRows;
int32_t maxScroll = lb->itemCount - visibleRows;
if (maxScroll < 0) {
maxScroll = 0;
}
w->as.listBox.scrollPos = clampInt(w->as.listBox.scrollPos, 0, maxScroll);
lb->scrollPos = clampInt(lb->scrollPos, 0, maxScroll);
// Draw items
int32_t innerX = w->x + LISTBOX_BORDER + LISTBOX_PAD;
int32_t innerY = w->y + LISTBOX_BORDER;
int32_t scrollPos = w->as.listBox.scrollPos;
bool multi = w->as.listBox.multiSelect;
uint8_t *selBits = w->as.listBox.selBits;
int32_t scrollPos = lb->scrollPos;
bool multi = lb->multiSelect;
uint8_t *selBits = lb->selBits;
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < w->as.listBox.itemCount; i++) {
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < lb->itemCount; i++) {
int32_t idx = scrollPos + i;
int32_t iy = innerY + i * font->charHeight;
uint32_t ifg = fg;
@ -595,7 +426,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
if (multi && selBits) {
isSelected = selBits[idx] != 0;
} else {
isSelected = (idx == w->as.listBox.selectedIdx);
isSelected = (idx == lb->selectedIdx);
}
if (isSelected) {
@ -604,10 +435,10 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
rectFill(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, ibg);
}
drawText(d, ops, font, innerX, iy, w->as.listBox.items[idx], ifg, ibg, false);
drawText(d, ops, font, innerX, iy, lb->items[idx], ifg, ibg, false);
// Draw cursor indicator in multi-select (dotted focus rect on cursor item)
if (multi && idx == w->as.listBox.selectedIdx && w->focused) {
if (multi && idx == lb->selectedIdx && w->focused) {
drawFocusRect(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, fg);
}
}
@ -615,8 +446,8 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
// Draw drag-reorder insertion indicator: a 2px horizontal line at the
// drop position. The line is drawn between items (not on top of them)
// so it's clear where the dragged item will be inserted.
if (w->as.listBox.reorderable && w->as.listBox.dragIdx >= 0 && w->as.listBox.dropIdx >= 0) {
int32_t drop = w->as.listBox.dropIdx;
if (lb->reorderable && lb->dragIdx >= 0 && lb->dropIdx >= 0) {
int32_t drop = lb->dropIdx;
int32_t lineY = innerY + (drop - scrollPos) * font->charHeight;
if (lineY >= innerY && lineY <= innerY + innerH) {
@ -629,7 +460,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
if (needSb) {
int32_t sbX = w->x + w->w - LISTBOX_BORDER - WGT_SB_W;
int32_t sbY = w->y + LISTBOX_BORDER;
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, w->as.listBox.itemCount, visibleRows, w->as.listBox.scrollPos);
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, lb->itemCount, visibleRows, lb->scrollPos);
}
if (w->focused) {
@ -644,7 +475,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
static const WidgetClassT sClassListBox = {
.flags = WCLASS_FOCUSABLE,
.flags = WCLASS_FOCUSABLE | WCLASS_SCROLLABLE,
.paint = widgetListBoxPaint,
.paintOverlay = NULL,
.calcMinSize = widgetListBoxCalcMinSize,
@ -656,6 +487,227 @@ static const WidgetClassT sClassListBox = {
.setText = NULL
};
void wgtRegister(void) {
widgetClassTable[WidgetListBoxE] = &sClassListBox;
// ============================================================
// Widget creation functions and accessors
// ============================================================
// Allocate (or re-allocate) the selection bits array for multi-select mode.
static void listBoxAllocSelBits(WidgetT *w) {
ListBoxDataT *d = (ListBoxDataT *)w->data;
free(d->selBits);
d->selBits = NULL;
int32_t count = d->itemCount;
if (count > 0 && d->multiSelect) {
d->selBits = (uint8_t *)calloc(count, 1);
}
}
WidgetT *wgtListBox(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
ListBoxDataT *d = (ListBoxDataT *)calloc(1, sizeof(ListBoxDataT));
if (!d) {
return w;
}
w->data = d;
d->selectedIdx = -1;
d->anchorIdx = -1;
d->dragIdx = -1;
d->dropIdx = -1;
}
return w;
}
void wgtListBoxClearSelection(WidgetT *w) {
if (!w || w->type != sTypeId) {
return;
}
ListBoxDataT *d = (ListBoxDataT *)w->data;
if (!d->selBits) {
return;
}
memset(d->selBits, 0, d->itemCount);
wgtInvalidatePaint(w);
}
int32_t wgtListBoxGetSelected(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, -1);
ListBoxDataT *d = (ListBoxDataT *)w->data;
return d->selectedIdx;
}
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
VALIDATE_WIDGET(w, sTypeId, false);
ListBoxDataT *d = (ListBoxDataT *)w->data;
if (!d->multiSelect) {
return idx == d->selectedIdx;
}
if (!d->selBits || idx < 0 || idx >= d->itemCount) {
return false;
}
return d->selBits[idx] != 0;
}
void wgtListBoxSelectAll(WidgetT *w) {
if (!w || w->type != sTypeId) {
return;
}
ListBoxDataT *d = (ListBoxDataT *)w->data;
if (!d->selBits) {
return;
}
memset(d->selBits, 1, d->itemCount);
wgtInvalidatePaint(w);
}
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ListBoxDataT *d = (ListBoxDataT *)w->data;
if (!d->selBits || idx < 0 || idx >= d->itemCount) {
return;
}
d->selBits[idx] = selected ? 1 : 0;
wgtInvalidatePaint(w);
}
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ListBoxDataT *d = (ListBoxDataT *)w->data;
d->items = items;
d->itemCount = count;
d->maxItemLen = widgetMaxItemLen(items, count);
if (d->selectedIdx >= count) {
d->selectedIdx = count > 0 ? 0 : -1;
}
if (d->selectedIdx < 0 && count > 0) {
d->selectedIdx = 0;
}
d->anchorIdx = d->selectedIdx;
// Reallocate selection bits
listBoxAllocSelBits(w);
// Pre-select the cursor item
if (d->selBits && d->selectedIdx >= 0) {
d->selBits[d->selectedIdx] = 1;
}
wgtInvalidate(w);
}
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ListBoxDataT *d = (ListBoxDataT *)w->data;
d->multiSelect = multi;
listBoxAllocSelBits(w);
// Sync: mark current selection
if (d->selBits && d->selectedIdx >= 0) {
d->selBits[d->selectedIdx] = 1;
}
}
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ListBoxDataT *d = (ListBoxDataT *)w->data;
d->reorderable = reorderable;
}
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ListBoxDataT *d = (ListBoxDataT *)w->data;
d->selectedIdx = idx;
d->anchorIdx = idx;
d->scrollPos = 0;
// In multi-select, clear all then select just this one
if (d->selBits) {
memset(d->selBits, 0, d->itemCount);
if (idx >= 0 && idx < d->itemCount) {
d->selBits[idx] = 1;
}
}
// Scroll to make the selected item visible
ensureScrollVisible(w, idx);
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent);
void (*setItems)(WidgetT *w, const char **items, int32_t count);
int32_t (*getSelected)(const WidgetT *w);
void (*setSelected)(WidgetT *w, int32_t idx);
void (*setMultiSelect)(WidgetT *w, bool multi);
bool (*isItemSelected)(const WidgetT *w, int32_t idx);
void (*setItemSelected)(WidgetT *w, int32_t idx, bool selected);
void (*selectAll)(WidgetT *w);
void (*clearSelection)(WidgetT *w);
void (*setReorderable)(WidgetT *w, bool reorderable);
} sApi = {
.create = wgtListBox,
.setItems = wgtListBoxSetItems,
.getSelected = wgtListBoxGetSelected,
.setSelected = wgtListBoxSetSelected,
.setMultiSelect = wgtListBoxSetMultiSelect,
.isItemSelected = wgtListBoxIsItemSelected,
.setItemSelected = wgtListBoxSetItemSelected,
.selectAll = wgtListBoxSelectAll,
.clearSelection = wgtListBoxClearSelection,
.setReorderable = wgtListBoxSetReorderable
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassListBox);
wgtRegisterApi("listbox", &sApi);
}

37
widgets/widgetListBox.h Normal file
View file

@ -0,0 +1,37 @@
// widgetListBox.h -- ListBox widget API
#ifndef WIDGET_LISTBOX_H
#define WIDGET_LISTBOX_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent);
void (*setItems)(WidgetT *w, const char **items, int32_t count);
int32_t (*getSelected)(const WidgetT *w);
void (*setSelected)(WidgetT *w, int32_t idx);
void (*setMultiSelect)(WidgetT *w, bool multi);
bool (*isItemSelected)(const WidgetT *w, int32_t idx);
void (*setItemSelected)(WidgetT *w, int32_t idx, bool selected);
void (*selectAll)(WidgetT *w);
void (*clearSelection)(WidgetT *w);
void (*setReorderable)(WidgetT *w, bool reorderable);
} ListBoxApiT;
static inline const ListBoxApiT *dvxListBoxApi(void) {
static const ListBoxApiT *sApi;
if (!sApi) { sApi = (const ListBoxApiT *)wgtGetApi("listbox"); }
return sApi;
}
#define wgtListBox(parent) dvxListBoxApi()->create(parent)
#define wgtListBoxSetItems(w, items, count) dvxListBoxApi()->setItems(w, items, count)
#define wgtListBoxGetSelected(w) dvxListBoxApi()->getSelected(w)
#define wgtListBoxSetSelected(w, idx) dvxListBoxApi()->setSelected(w, idx)
#define wgtListBoxSetMultiSelect(w, multi) dvxListBoxApi()->setMultiSelect(w, multi)
#define wgtListBoxIsItemSelected(w, idx) dvxListBoxApi()->isItemSelected(w, idx)
#define wgtListBoxSetItemSelected(w, idx, selected) dvxListBoxApi()->setItemSelected(w, idx, selected)
#define wgtListBoxSelectAll(w) dvxListBoxApi()->selectAll(w)
#define wgtListBoxClearSelection(w) dvxListBoxApi()->clearSelection(w)
#define wgtListBoxSetReorderable(w, reorderable) dvxListBoxApi()->setReorderable(w, reorderable)
#endif // WIDGET_LISTBOX_H

File diff suppressed because it is too large Load diff

47
widgets/widgetListView.h Normal file
View file

@ -0,0 +1,47 @@
// widgetListView.h -- ListView widget API
//
// Include this header in app/shell code that uses the ListView widget.
// Column types (ListViewColT, ListViewAlignE, ListViewSortE) are
// defined in dvxWidget.h so widget DXEs can reference them.
#ifndef WIDGET_LISTVIEW_H
#define WIDGET_LISTVIEW_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent);
void (*setColumns)(WidgetT *w, const ListViewColT *cols, int32_t count);
void (*setData)(WidgetT *w, const char **cellData, int32_t rowCount);
int32_t (*getSelected)(const WidgetT *w);
void (*setSelected)(WidgetT *w, int32_t idx);
void (*setSort)(WidgetT *w, int32_t col, ListViewSortE dir);
void (*setHeaderClickCallback)(WidgetT *w, void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir));
void (*setMultiSelect)(WidgetT *w, bool multi);
bool (*isItemSelected)(const WidgetT *w, int32_t idx);
void (*setItemSelected)(WidgetT *w, int32_t idx, bool selected);
void (*selectAll)(WidgetT *w);
void (*clearSelection)(WidgetT *w);
void (*setReorderable)(WidgetT *w, bool reorderable);
} ListViewApiT;
static inline const ListViewApiT *dvxListViewApi(void) {
static const ListViewApiT *sApi;
if (!sApi) { sApi = (const ListViewApiT *)wgtGetApi("listview"); }
return sApi;
}
#define wgtListView(parent) dvxListViewApi()->create(parent)
#define wgtListViewSetColumns(w, cols, count) dvxListViewApi()->setColumns(w, cols, count)
#define wgtListViewSetData(w, cellData, rowCount) dvxListViewApi()->setData(w, cellData, rowCount)
#define wgtListViewGetSelected(w) dvxListViewApi()->getSelected(w)
#define wgtListViewSetSelected(w, idx) dvxListViewApi()->setSelected(w, idx)
#define wgtListViewSetSort(w, col, dir) dvxListViewApi()->setSort(w, col, dir)
#define wgtListViewSetHeaderClickCallback(w, cb) dvxListViewApi()->setHeaderClickCallback(w, cb)
#define wgtListViewSetMultiSelect(w, multi) dvxListViewApi()->setMultiSelect(w, multi)
#define wgtListViewIsItemSelected(w, idx) dvxListViewApi()->isItemSelected(w, idx)
#define wgtListViewSetItemSelected(w, idx, selected) dvxListViewApi()->setItemSelected(w, idx, selected)
#define wgtListViewSelectAll(w) dvxListViewApi()->selectAll(w)
#define wgtListViewClearSelection(w) dvxListViewApi()->clearSelection(w)
#define wgtListViewSetReorderable(w, reorderable) dvxListViewApi()->setReorderable(w, reorderable)
#endif // WIDGET_LISTVIEW_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetProgressBar.c -- ProgressBar widget
//
// A non-interactive fill-bar widget for displaying bounded progress.
@ -14,72 +15,15 @@
// The widget has no mouse or keyboard handlers -- it is purely display.
// Value is controlled externally through the set/get API.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
static int32_t sTypeId = -1;
// ============================================================
// wgtProgressBar
// ============================================================
WidgetT *wgtProgressBar(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetProgressBarE);
if (w) {
w->as.progressBar.value = 0;
w->as.progressBar.maxValue = 100;
w->as.progressBar.vertical = false;
}
return w;
}
// ============================================================
// wgtProgressBarV
// ============================================================
WidgetT *wgtProgressBarV(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetProgressBarE);
if (w) {
w->as.progressBar.value = 0;
w->as.progressBar.maxValue = 100;
w->as.progressBar.vertical = true;
}
return w;
}
// ============================================================
// wgtProgressBarGetValue
// ============================================================
int32_t wgtProgressBarGetValue(const WidgetT *w) {
VALIDATE_WIDGET(w, WidgetProgressBarE, 0);
return w->as.progressBar.value;
}
// ============================================================
// wgtProgressBarSetValue
// ============================================================
void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
VALIDATE_WIDGET_VOID(w, WidgetProgressBarE);
if (value < 0) {
value = 0;
}
if (value > w->as.progressBar.maxValue) {
value = w->as.progressBar.maxValue;
}
w->as.progressBar.value = value;
wgtInvalidatePaint(w);
}
typedef struct {
int32_t value;
int32_t maxValue;
bool vertical;
} ProgressBarDataT;
// ============================================================
@ -91,7 +35,9 @@ void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
// bar plus 2px bevel on each side). The main axis uses 12 char widths
// so the bar is wide enough to show meaningful progress granularity.
void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font) {
if (w->as.progressBar.vertical) {
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
if (d->vertical) {
w->calcMinW = font->charHeight + 4;
w->calcMinH = font->charWidth * 12;
} else {
@ -113,8 +59,9 @@ void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font) {
//
// Vertical bars fill from bottom-up (natural "filling" metaphor),
// which requires computing the Y offset as (innerH - fillH).
void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->activeTitleBg) : colors->windowShadow;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
@ -125,10 +72,10 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
bevel.shadow = colors->windowHighlight;
bevel.face = bg;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
// Fill bar
int32_t maxVal = w->as.progressBar.maxValue;
int32_t maxVal = d->maxValue;
if (maxVal <= 0) {
maxVal = 100;
@ -137,30 +84,39 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
int32_t innerW = w->w - 4;
int32_t innerH = w->h - 4;
if (w->as.progressBar.vertical) {
int32_t fillH = (innerH * w->as.progressBar.value) / maxVal;
if (d->vertical) {
int32_t fillH = (innerH * d->value) / maxVal;
if (fillH > innerH) {
fillH = innerH;
}
if (fillH > 0) {
rectFill(d, ops, w->x + 2, w->y + 2 + innerH - fillH, innerW, fillH, fg);
rectFill(disp, ops, w->x + 2, w->y + 2 + innerH - fillH, innerW, fillH, fg);
}
} else {
int32_t fillW = (innerW * w->as.progressBar.value) / maxVal;
int32_t fillW = (innerW * d->value) / maxVal;
if (fillW > innerW) {
fillW = innerW;
}
if (fillW > 0) {
rectFill(d, ops, w->x + 2, w->y + 2, fillW, innerH, fg);
rectFill(disp, ops, w->x + 2, w->y + 2, fillW, innerH, fg);
}
}
}
// ============================================================
// widgetProgressBarDestroy
// ============================================================
void widgetProgressBarDestroy(WidgetT *w) {
free(w->data);
}
// ============================================================
// DXE registration
// ============================================================
@ -173,12 +129,90 @@ static const WidgetClassT sClassProgressBar = {
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.destroy = widgetProgressBarDestroy,
.getText = NULL,
.setText = NULL
};
void wgtRegister(void) {
widgetClassTable[WidgetProgressBarE] = &sClassProgressBar;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtProgressBar(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT));
d->value = 0;
d->maxValue = 100;
d->vertical = false;
w->data = d;
}
return w;
}
WidgetT *wgtProgressBarV(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT));
d->value = 0;
d->maxValue = 100;
d->vertical = true;
w->data = d;
}
return w;
}
int32_t wgtProgressBarGetValue(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
return d->value;
}
void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
VALIDATE_WIDGET_VOID(w, sTypeId);
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
if (value < 0) {
value = 0;
}
if (value > d->maxValue) {
value = d->maxValue;
}
d->value = value;
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent);
WidgetT *(*createV)(WidgetT *parent);
void (*setValue)(WidgetT *w, int32_t value);
int32_t (*getValue)(const WidgetT *w);
} sApi = {
.create = wgtProgressBar,
.createV = wgtProgressBarV,
.setValue = wgtProgressBarSetValue,
.getValue = wgtProgressBarGetValue
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassProgressBar);
wgtRegisterApi("progressbar", &sApi);
}

View file

@ -0,0 +1,25 @@
// widgetProgressBar.h -- ProgressBar widget API
#ifndef WIDGET_PROGRESSBAR_H
#define WIDGET_PROGRESSBAR_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent);
WidgetT *(*createV)(WidgetT *parent);
void (*setValue)(WidgetT *w, int32_t value);
int32_t (*getValue)(const WidgetT *w);
} ProgressBarApiT;
static inline const ProgressBarApiT *dvxProgressBarApi(void) {
static const ProgressBarApiT *sApi;
if (!sApi) { sApi = (const ProgressBarApiT *)wgtGetApi("progressbar"); }
return sApi;
}
#define wgtProgressBar(parent) dvxProgressBarApi()->create(parent)
#define wgtProgressBarV(parent) dvxProgressBarApi()->createV(parent)
#define wgtProgressBarSetValue(w, value) dvxProgressBarApi()->setValue(w, value)
#define wgtProgressBarGetValue(w) dvxProgressBarApi()->getValue(w)
#endif // WIDGET_PROGRESSBAR_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetRadio.c -- RadioGroup and Radio button widgets
//
// Two-level architecture: RadioGroupE is an invisible container that
@ -21,70 +22,40 @@
// focus AND selection together (standard radio group behavior --
// focus and selection are coupled, unlike checkboxes).
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#define CHECKBOX_BOX_SIZE 12
#define CHECKBOX_GAP 4
static int32_t sRadioGroupTypeId = -1;
static int32_t sRadioTypeId = -1;
typedef struct {
const char *text;
int32_t index;
} RadioDataT;
typedef struct {
int32_t selectedIdx;
} RadioGroupDataT;
// ============================================================
// wgtRadio
// Prototypes
// ============================================================
// Auto-index is computed by counting RadioE siblings before this one.
// This means radio buttons must be added in order and not reordered
// after construction -- which is the normal pattern for radio groups.
WidgetT *wgtRadio(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, WidgetRadioE);
if (w) {
w->as.radio.text = text;
w->accelKey = accelParse(text);
// Auto-assign index based on position in parent
int32_t idx = 0;
for (WidgetT *c = parent->firstChild; c != w; c = c->nextSibling) {
if (c->type == WidgetRadioE) {
idx++;
}
}
w->as.radio.index = idx;
}
return w;
}
static void widgetRadioDestroy(WidgetT *w);
static void widgetRadioGroupDestroy(WidgetT *w);
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
// ============================================================
// wgtRadioGroup
// widgetRadioAccelActivate
// ============================================================
WidgetT *wgtRadioGroup(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetRadioGroupE);
if (w) {
w->as.radioGroup.selectedIdx = 0;
}
return w;
}
// ============================================================
// widgetRadioGetText
// ============================================================
const char *widgetRadioGetText(const WidgetT *w) {
return w->as.radio.text;
}
// ============================================================
// widgetRadioSetText
// ============================================================
void widgetRadioSetText(WidgetT *w, const char *text) {
w->as.radio.text = text;
w->accelKey = accelParse(text);
void widgetRadioAccelActivate(WidgetT *w, WidgetT *root) {
(void)root;
widgetRadioOnMouse(w, NULL, 0, 0);
}
@ -96,12 +67,43 @@ void widgetRadioSetText(WidgetT *w, const char *text) {
// so radio buttons and checkboxes align when placed in the same
// column, and the indicator + label layout is visually consistent.
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
RadioDataT *d = (RadioDataT *)w->data;
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
textWidthAccel(font, w->as.radio.text);
textWidthAccel(font, d->text);
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
}
// ============================================================
// widgetRadioDestroy
// ============================================================
static void widgetRadioDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetRadioGetText
// ============================================================
const char *widgetRadioGetText(const WidgetT *w) {
RadioDataT *d = (RadioDataT *)w->data;
return d->text;
}
// ============================================================
// widgetRadioGroupDestroy
// ============================================================
static void widgetRadioGroupDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetRadioOnKey
// ============================================================
@ -117,11 +119,13 @@ void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// ORed with the scancode. 0x50=Down, 0x4D=Right, 0x48=Up, 0x4B=Left.
void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
RadioDataT *rd = (RadioDataT *)w->data;
if (key == ' ' || key == 0x0D) {
// Select this radio
if (w->parent && w->parent->type == WidgetRadioGroupE) {
w->parent->as.radioGroup.selectedIdx = w->as.radio.index;
if (w->parent && w->parent->type == sRadioGroupTypeId) {
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
gd->selectedIdx = rd->index;
if (w->parent->onChange) {
w->parent->onChange(w->parent);
@ -131,21 +135,23 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
wgtInvalidatePaint(w);
} else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) {
// Down or Right -- next radio in group
if (w->parent && w->parent->type == WidgetRadioGroupE) {
if (w->parent && w->parent->type == sRadioGroupTypeId) {
WidgetT *next = NULL;
for (WidgetT *s = w->nextSibling; s; s = s->nextSibling) {
if (s->type == WidgetRadioE && s->visible && s->enabled) {
if (s->type == sRadioTypeId && s->visible && s->enabled) {
next = s;
break;
}
}
if (next) {
w->focused = false;
RadioDataT *nd = (RadioDataT *)next->data;
RadioGroupDataT *gd = (RadioGroupDataT *)next->parent->data;
w->focused = false;
next->focused = true;
sFocusedWidget = next;
next->parent->as.radioGroup.selectedIdx = next->as.radio.index;
gd->selectedIdx = nd->index;
if (next->parent->onChange) {
next->parent->onChange(next->parent);
@ -156,20 +162,22 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
} else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) {
// Up or Left -- previous radio in group
if (w->parent && w->parent->type == WidgetRadioGroupE) {
if (w->parent && w->parent->type == sRadioGroupTypeId) {
WidgetT *prev = NULL;
for (WidgetT *s = w->parent->firstChild; s && s != w; s = s->nextSibling) {
if (s->type == WidgetRadioE && s->visible && s->enabled) {
if (s->type == sRadioTypeId && s->visible && s->enabled) {
prev = s;
}
}
if (prev) {
w->focused = false;
RadioDataT *pd = (RadioDataT *)prev->data;
RadioGroupDataT *gd = (RadioGroupDataT *)prev->parent->data;
w->focused = false;
prev->focused = true;
sFocusedWidget = prev;
prev->parent->as.radioGroup.selectedIdx = prev->as.radio.index;
gd->selectedIdx = pd->index;
if (prev->parent->onChange) {
prev->parent->onChange(prev->parent);
@ -192,8 +200,10 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)vy;
w->focused = true;
if (w->parent && w->parent->type == WidgetRadioGroupE) {
w->parent->as.radioGroup.selectedIdx = w->as.radio.index;
if (w->parent && w->parent->type == sRadioGroupTypeId) {
RadioDataT *rd = (RadioDataT *)w->data;
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
gd->selectedIdx = rd->index;
if (w->parent->onChange) {
w->parent->onChange(w->parent);
@ -217,9 +227,10 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
// left/right extent is computed with simple integer distance from
// the midpoint, making the diamond perfectly symmetric at any size.
void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
int32_t boxY = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;
RadioDataT *rd = (RadioDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
int32_t boxY = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;
// Draw diamond-shaped radio box
int32_t bx = w->x;
@ -254,26 +265,29 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
}
// Draw filled diamond if selected
if (w->parent && w->parent->type == WidgetRadioGroupE &&
w->parent->as.radioGroup.selectedIdx == w->as.radio.index) {
uint32_t dotFg = w->enabled ? fg : colors->windowShadow;
if (w->parent && w->parent->type == sRadioGroupTypeId) {
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
static const int32_t dotW[] = {2, 4, 6, 6, 4, 2};
if (gd->selectedIdx == rd->index) {
uint32_t dotFg = w->enabled ? fg : colors->windowShadow;
for (int32_t i = 0; i < 6; i++) {
int32_t dw = dotW[i];
drawHLine(d, ops, bx + mid - dw / 2, boxY + mid - 3 + i, dw, dotFg);
static const int32_t dotW[] = {2, 4, 6, 6, 4, 2};
for (int32_t i = 0; i < 6; i++) {
int32_t dw = dotW[i];
drawHLine(d, ops, bx + mid - dw / 2, boxY + mid - 3 + i, dw, dotFg);
}
}
}
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
int32_t labelW = textWidthAccel(font, w->as.radio.text);
int32_t labelW = textWidthAccel(font, rd->text);
if (!w->enabled) {
drawTextAccelEmbossed(d, ops, font, labelX, labelY, w->as.radio.text, colors);
drawTextAccelEmbossed(d, ops, font, labelX, labelY, rd->text, colors);
} else {
drawTextAccel(d, ops, font, labelX, labelY, w->as.radio.text, fg, bg, false);
drawTextAccel(d, ops, font, labelX, labelY, rd->text, fg, bg, false);
}
if (w->focused) {
@ -282,6 +296,17 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
}
// ============================================================
// widgetRadioSetText
// ============================================================
void widgetRadioSetText(WidgetT *w, const char *text) {
RadioDataT *d = (RadioDataT *)w->data;
d->text = text;
w->accelKey = accelParse(text);
}
// ============================================================
// DXE registration
// ============================================================
@ -295,25 +320,103 @@ static const WidgetClassT sClassRadioGroup = {
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.destroy = widgetRadioGroupDestroy,
.getText = NULL,
.setText = NULL
};
static const WidgetClassT sClassRadio = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetRadioPaint,
.paintOverlay = NULL,
.calcMinSize = widgetRadioCalcMinSize,
.layout = NULL,
.onMouse = widgetRadioOnMouse,
.onKey = widgetRadioOnKey,
.destroy = NULL,
.getText = widgetRadioGetText,
.setText = widgetRadioSetText
.flags = WCLASS_FOCUSABLE,
.paint = widgetRadioPaint,
.paintOverlay = NULL,
.calcMinSize = widgetRadioCalcMinSize,
.layout = NULL,
.onMouse = widgetRadioOnMouse,
.onKey = widgetRadioOnKey,
.onAccelActivate = widgetRadioAccelActivate,
.destroy = widgetRadioDestroy,
.getText = widgetRadioGetText,
.setText = widgetRadioSetText
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtRadio(WidgetT *parent, const char *text) {
WidgetT *w = widgetAlloc(parent, sRadioTypeId);
if (w) {
RadioDataT *d = calloc(1, sizeof(RadioDataT));
w->data = d;
d->text = text;
w->accelKey = accelParse(text);
// Auto-assign index based on position in parent
int32_t idx = 0;
for (WidgetT *c = parent->firstChild; c != w; c = c->nextSibling) {
if (c->type == sRadioTypeId) {
idx++;
}
}
d->index = idx;
}
return w;
}
WidgetT *wgtRadioGroup(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sRadioGroupTypeId);
if (w) {
RadioGroupDataT *d = calloc(1, sizeof(RadioGroupDataT));
w->data = d;
}
return w;
}
// ============================================================
// DXE registration
// ============================================================
void wgtRadioGroupSetSelected(WidgetT *w, int32_t idx) {
VALIDATE_WIDGET_VOID(w, sRadioGroupTypeId);
RadioGroupDataT *d = (RadioGroupDataT *)w->data;
d->selectedIdx = idx;
wgtInvalidatePaint(w);
}
int32_t wgtRadioGetIndex(const WidgetT *w) {
VALIDATE_WIDGET(w, sRadioTypeId, -1);
RadioDataT *d = (RadioDataT *)w->data;
return d->index;
}
static const struct {
WidgetT *(*group)(WidgetT *parent);
WidgetT *(*create)(WidgetT *parent, const char *text);
void (*groupSetSelected)(WidgetT *group, int32_t index);
int32_t (*getIndex)(const WidgetT *w);
} sApi = {
.group = wgtRadioGroup,
.create = wgtRadio,
.groupSetSelected = wgtRadioGroupSetSelected,
.getIndex = wgtRadioGetIndex
};
void wgtRegister(void) {
widgetClassTable[WidgetRadioGroupE] = &sClassRadioGroup;
widgetClassTable[WidgetRadioE] = &sClassRadio;
sRadioGroupTypeId = wgtRegisterClass(&sClassRadioGroup);
sRadioTypeId = wgtRegisterClass(&sClassRadio);
wgtRegisterApi("radio", &sApi);
}

25
widgets/widgetRadio.h Normal file
View file

@ -0,0 +1,25 @@
// widgetRadio.h -- Radio button widget API
#ifndef WIDGET_RADIO_H
#define WIDGET_RADIO_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*group)(WidgetT *parent);
WidgetT *(*create)(WidgetT *parent, const char *text);
void (*groupSetSelected)(WidgetT *w, int32_t idx);
int32_t (*getIndex)(const WidgetT *w);
} RadioApiT;
static inline const RadioApiT *dvxRadioApi(void) {
static const RadioApiT *sApi;
if (!sApi) { sApi = (const RadioApiT *)wgtGetApi("radio"); }
return sApi;
}
#define wgtRadioGroup(parent) dvxRadioApi()->group(parent)
#define wgtRadio(parent, text) dvxRadioApi()->create(parent, text)
#define wgtRadioGroupSetSelected(w, idx) dvxRadioApi()->groupSetSelected(w, idx)
#define wgtRadioGetIndex(w) dvxRadioApi()->getIndex(w)
#endif // WIDGET_RADIO_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetScrollPane.c -- ScrollPane container widget
//
// A clipping container that allows its children (laid out as a
@ -27,7 +28,14 @@
// This is a minor duplication tradeoff for allowing different scrollbar
// widths in different contexts.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
static int32_t sTypeId = -1;
typedef struct {
int32_t scrollPosV;
int32_t scrollPosH;
} ScrollPaneDataT;
#define SP_BORDER 2
#define SP_SB_W 14
@ -41,6 +49,7 @@
static void drawSPHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalW, int32_t visibleW);
static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalH, int32_t visibleH);
static void spCalcNeeds(WidgetT *w, const BitmapFontT *font, int32_t *contentMinW, int32_t *contentMinH, int32_t *innerW, int32_t *innerH, bool *needVSb, bool *needHSb);
static void widgetScrollPaneDestroy(WidgetT *w);
// ============================================================
@ -52,6 +61,7 @@ static void drawSPHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
return;
}
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
uint32_t fg = colors->contentFg;
// Trough
@ -90,7 +100,7 @@ static void drawSPHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
if (trackLen > 0 && totalW > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalW, visibleW, w->as.scrollPane.scrollPosH, &thumbPos, &thumbSize);
widgetScrollbarThumb(trackLen, totalW, visibleW, sp->scrollPosH, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX + SP_SB_W + thumbPos, sbY, thumbSize, SP_SB_W, &btnBevel);
}
}
@ -105,6 +115,7 @@ static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
return;
}
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
uint32_t fg = colors->contentFg;
// Trough
@ -143,7 +154,7 @@ static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
if (trackLen > 0 && totalH > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, totalH, visibleH, w->as.scrollPane.scrollPosV, &thumbPos, &thumbSize);
widgetScrollbarThumb(trackLen, totalH, visibleH, sp->scrollPosV, &thumbPos, &thumbSize);
drawBevel(d, ops, sbX, sbY + SP_SB_W + thumbPos, SP_SB_W, thumbSize, &btnBevel);
}
}
@ -240,6 +251,16 @@ void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font) {
}
// ============================================================
// widgetScrollPaneDestroy
// ============================================================
static void widgetScrollPaneDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetScrollPaneLayout
// ============================================================
@ -257,6 +278,8 @@ void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// proportional to its weight/totalWeight ratio. This allows stretch
// children inside the scrollable area.
void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
int32_t contentMinW;
int32_t contentMinH;
int32_t innerW;
@ -278,16 +301,16 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
maxScrollH = 0;
}
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
// Layout children as a vertical box at virtual size
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
int32_t virtualW = DVX_MAX(innerW, contentMinW);
int32_t virtualH = DVX_MAX(innerH, contentMinH);
int32_t baseX = w->x + SP_BORDER - w->as.scrollPane.scrollPosH;
int32_t baseY = w->y + SP_BORDER - w->as.scrollPane.scrollPosV;
int32_t baseX = w->x + SP_BORDER - sp->scrollPosH;
int32_t baseY = w->y + SP_BORDER - sp->scrollPosV;
int32_t childW = virtualW - pad * 2;
int32_t pos = baseY + pad;
@ -369,6 +392,7 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
AppContextT *ctx = wgtGetContext(w);
const BitmapFontT *font = &ctx->font;
@ -396,34 +420,34 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (key == (0x48 | 0x100)) {
// Up
w->as.scrollPane.scrollPosV -= step;
sp->scrollPosV -= step;
} else if (key == (0x50 | 0x100)) {
// Down
w->as.scrollPane.scrollPosV += step;
sp->scrollPosV += step;
} else if (key == (0x49 | 0x100)) {
// Page Up
w->as.scrollPane.scrollPosV -= innerH;
sp->scrollPosV -= innerH;
} else if (key == (0x51 | 0x100)) {
// Page Down
w->as.scrollPane.scrollPosV += innerH;
sp->scrollPosV += innerH;
} else if (key == (0x47 | 0x100)) {
// Home
w->as.scrollPane.scrollPosV = 0;
sp->scrollPosV = 0;
} else if (key == (0x4F | 0x100)) {
// End
w->as.scrollPane.scrollPosV = maxScrollV;
sp->scrollPosV = maxScrollV;
} else if (key == (0x4B | 0x100)) {
// Left
w->as.scrollPane.scrollPosH -= font->charWidth;
sp->scrollPosH -= font->charWidth;
} else if (key == (0x4D | 0x100)) {
// Right
w->as.scrollPane.scrollPosH += font->charWidth;
sp->scrollPosH += font->charWidth;
} else {
return;
}
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
wgtInvalidate(w);
}
@ -442,6 +466,7 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
// into scroll pane children since their coordinates may be outside
// the pane's visible bounds.
void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
ScrollPaneDataT *sp = (ScrollPaneDataT *)hit->data;
AppContextT *ctx = (AppContextT *)root->userData;
const BitmapFontT *font = &ctx->font;
@ -466,8 +491,8 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
maxScrollH = 0;
}
hit->as.scrollPane.scrollPosV = clampInt(hit->as.scrollPane.scrollPosV, 0, maxScrollV);
hit->as.scrollPane.scrollPosH = clampInt(hit->as.scrollPane.scrollPosH, 0, maxScrollH);
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
// Check vertical scrollbar
if (needVSb) {
@ -485,20 +510,20 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
}
if (relY < SP_SB_W) {
hit->as.scrollPane.scrollPosV -= font->charHeight;
sp->scrollPosV -= font->charHeight;
} else if (relY >= sbH - SP_SB_W) {
hit->as.scrollPane.scrollPosV += font->charHeight;
sp->scrollPosV += font->charHeight;
} else if (trackLen > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, contentMinH, innerH, hit->as.scrollPane.scrollPosV, &thumbPos, &thumbSize);
widgetScrollbarThumb(trackLen, contentMinH, innerH, sp->scrollPosV, &thumbPos, &thumbSize);
int32_t trackRelY = relY - SP_SB_W;
if (trackRelY < thumbPos) {
hit->as.scrollPane.scrollPosV -= pageSize;
sp->scrollPosV -= pageSize;
} else if (trackRelY >= thumbPos + thumbSize) {
hit->as.scrollPane.scrollPosV += pageSize;
sp->scrollPosV += pageSize;
} else {
sDragScrollbar = hit;
sDragScrollbarOrient = 0;
@ -507,7 +532,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
}
}
hit->as.scrollPane.scrollPosV = clampInt(hit->as.scrollPane.scrollPosV, 0, maxScrollV);
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
wgtInvalidate(hit);
return;
}
@ -529,20 +554,20 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
}
if (relX < SP_SB_W) {
hit->as.scrollPane.scrollPosH -= font->charWidth;
sp->scrollPosH -= font->charWidth;
} else if (relX >= sbW - SP_SB_W) {
hit->as.scrollPane.scrollPosH += font->charWidth;
sp->scrollPosH += font->charWidth;
} else if (trackLen > 0) {
int32_t thumbPos;
int32_t thumbSize;
widgetScrollbarThumb(trackLen, contentMinW, innerW, hit->as.scrollPane.scrollPosH, &thumbPos, &thumbSize);
widgetScrollbarThumb(trackLen, contentMinW, innerW, sp->scrollPosH, &thumbPos, &thumbSize);
int32_t trackRelX = relX - SP_SB_W;
if (trackRelX < thumbPos) {
hit->as.scrollPane.scrollPosH -= pageSize;
sp->scrollPosH -= pageSize;
} else if (trackRelX >= thumbPos + thumbSize) {
hit->as.scrollPane.scrollPosH += pageSize;
sp->scrollPosH += pageSize;
} else {
sDragScrollbar = hit;
sDragScrollbarOrient = 1;
@ -551,7 +576,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
}
}
hit->as.scrollPane.scrollPosH = clampInt(hit->as.scrollPane.scrollPosH, 0, maxScrollH);
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
wgtInvalidate(hit);
return;
}
@ -609,6 +634,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
// clip rect so they're always fully visible. The dead corner (when
// both scrollbars are present) is filled with windowFace color.
void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
int32_t contentMinW;
@ -632,8 +658,8 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
maxScrollH = 0;
}
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
// Sunken border
BevelStyleT bevel = BEVEL_SUNKEN(colors, bg, SP_BORDER);
@ -681,18 +707,38 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
// ============================================================
// wgtScrollPane
// DXE registration
// ============================================================
// Default weight=100 so the scroll pane stretches to fill available
// space in its parent container. Without this, a scroll pane in a
// vertical box would collapse to its minimal size.
static const WidgetClassT sClassScrollPane = {
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE | WCLASS_SCROLL_CONTAINER | WCLASS_RELAYOUT_ON_SCROLL,
.paint = widgetScrollPanePaint,
.paintOverlay = NULL,
.calcMinSize = widgetScrollPaneCalcMinSize,
.layout = widgetScrollPaneLayout,
.onMouse = widgetScrollPaneOnMouse,
.onKey = widgetScrollPaneOnKey,
.destroy = widgetScrollPaneDestroy,
.getText = NULL,
.setText = NULL
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtScrollPane(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetScrollPaneE);
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
w->as.scrollPane.scrollPosV = 0;
w->as.scrollPane.scrollPosH = 0;
ScrollPaneDataT *sp = (ScrollPaneDataT *)calloc(1, sizeof(ScrollPaneDataT));
if (sp) {
w->data = sp;
}
w->weight = 100;
}
@ -704,20 +750,14 @@ WidgetT *wgtScrollPane(WidgetT *parent) {
// DXE registration
// ============================================================
static const WidgetClassT sClassScrollPane = {
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
.paint = widgetScrollPanePaint,
.paintOverlay = NULL,
.calcMinSize = widgetScrollPaneCalcMinSize,
.layout = widgetScrollPaneLayout,
.onMouse = widgetScrollPaneOnMouse,
.onKey = widgetScrollPaneOnKey,
.destroy = NULL,
.getText = NULL,
.setText = NULL
static const struct {
WidgetT *(*create)(WidgetT *parent);
} sApi = {
.create = wgtScrollPane
};
void wgtRegister(void) {
widgetClassTable[WidgetScrollPaneE] = &sClassScrollPane;
sTypeId = wgtRegisterClass(&sClassScrollPane);
wgtRegisterApi("scrollpane", &sApi);
}

View file

@ -0,0 +1,19 @@
// widgetScrollPane.h -- ScrollPane widget API
#ifndef WIDGET_SCROLLPANE_H
#define WIDGET_SCROLLPANE_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent);
} ScrollPaneApiT;
static inline const ScrollPaneApiT *dvxScrollPaneApi(void) {
static const ScrollPaneApiT *sApi;
if (!sApi) { sApi = (const ScrollPaneApiT *)wgtGetApi("scrollpane"); }
return sApi;
}
#define wgtScrollPane(parent) dvxScrollPaneApi()->create(parent)
#endif // WIDGET_SCROLLPANE_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetSeparator.c -- Separator widget (horizontal and vertical)
//
// A purely decorative widget that draws a 2px etched line (shadow +
@ -17,37 +18,22 @@
//
// No mouse/key handlers -- purely decorative, non-focusable.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#define SEPARATOR_THICKNESS 2
static int32_t sTypeId = -1;
typedef struct {
bool vertical;
} SeparatorDataT;
// ============================================================
// wgtHSeparator
// Prototypes
// ============================================================
WidgetT *wgtHSeparator(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetSeparatorE);
if (w) {
w->as.separator.vertical = false;
}
return w;
}
// ============================================================
// wgtVSeparator
// ============================================================
WidgetT *wgtVSeparator(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetSeparatorE);
if (w) {
w->as.separator.vertical = true;
}
return w;
}
static void widgetSeparatorDestroy(WidgetT *w);
// ============================================================
@ -56,8 +42,9 @@ WidgetT *wgtVSeparator(WidgetT *parent) {
void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
SeparatorDataT *d = (SeparatorDataT *)w->data;
if (w->as.separator.vertical) {
if (d->vertical) {
w->calcMinW = SEPARATOR_THICKNESS;
w->calcMinH = 0;
} else {
@ -67,14 +54,25 @@ void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font) {
}
// ============================================================
// widgetSeparatorDestroy
// ============================================================
static void widgetSeparatorDestroy(WidgetT *w) {
free(w->data);
w->data = NULL;
}
// ============================================================
// widgetSeparatorPaint
// ============================================================
void widgetSeparatorPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
SeparatorDataT *sd = (SeparatorDataT *)w->data;
if (w->as.separator.vertical) {
if (sd->vertical) {
int32_t cx = w->x + w->w / 2;
drawVLine(d, ops, cx, w->y, w->h, colors->windowShadow);
drawVLine(d, ops, cx + 1, w->y, w->h, colors->windowHighlight);
@ -99,11 +97,55 @@ static const WidgetClassT sClassSeparator = {
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.destroy = widgetSeparatorDestroy,
.getText = NULL,
.setText = NULL
};
void wgtRegister(void) {
widgetClassTable[WidgetSeparatorE] = &sClassSeparator;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtHSeparator(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
SeparatorDataT *d = calloc(1, sizeof(SeparatorDataT));
w->data = d;
}
return w;
}
WidgetT *wgtVSeparator(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
SeparatorDataT *d = calloc(1, sizeof(SeparatorDataT));
w->data = d;
d->vertical = true;
}
return w;
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*hSeparator)(WidgetT *parent);
WidgetT *(*vSeparator)(WidgetT *parent);
} sApi = {
.hSeparator = wgtHSeparator,
.vSeparator = wgtVSeparator
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSeparator);
wgtRegisterApi("separator", &sApi);
}

21
widgets/widgetSeparator.h Normal file
View file

@ -0,0 +1,21 @@
// widgetSeparator.h -- Separator widget API
#ifndef WIDGET_SEPARATOR_H
#define WIDGET_SEPARATOR_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*hSeparator)(WidgetT *parent);
WidgetT *(*vSeparator)(WidgetT *parent);
} SeparatorApiT;
static inline const SeparatorApiT *dvxSeparatorApi(void) {
static const SeparatorApiT *sApi;
if (!sApi) { sApi = (const SeparatorApiT *)wgtGetApi("separator"); }
return sApi;
}
#define wgtHSeparator(parent) dvxSeparatorApi()->hSeparator(parent)
#define wgtVSeparator(parent) dvxSeparatorApi()->vSeparator(parent)
#endif // WIDGET_SEPARATOR_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetSlider.c -- Slider (trackbar) widget
//
// A continuous-value selector with a draggable thumb on a groove track.
@ -20,58 +21,19 @@
// Home/End jump to min/max. This gives ~100 keyboard steps regardless
// of the actual value range.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#define SLIDER_TRACK_H 4
#define SLIDER_THUMB_W 11
// ============================================================
// wgtSlider
// ============================================================
static int32_t sTypeId = -1;
// Default weight=100 so the slider stretches in its parent layout.
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal) {
WidgetT *w = widgetAlloc(parent, WidgetSliderE);
if (w) {
w->as.slider.value = minVal;
w->as.slider.minValue = minVal;
w->as.slider.maxValue = maxVal;
w->as.slider.vertical = false;
w->weight = 100;
}
return w;
}
// ============================================================
// wgtSliderGetValue
// ============================================================
int32_t wgtSliderGetValue(const WidgetT *w) {
VALIDATE_WIDGET(w, WidgetSliderE, 0);
return w->as.slider.value;
}
// ============================================================
// wgtSliderSetValue
// ============================================================
void wgtSliderSetValue(WidgetT *w, int32_t value) {
VALIDATE_WIDGET_VOID(w, WidgetSliderE);
if (value < w->as.slider.minValue) {
value = w->as.slider.minValue;
}
if (value > w->as.slider.maxValue) {
value = w->as.slider.maxValue;
}
w->as.slider.value = value;
wgtInvalidatePaint(w);
}
typedef struct {
int32_t value;
int32_t minValue;
int32_t maxValue;
bool vertical;
} SliderDataT;
// ============================================================
@ -83,8 +45,9 @@ void wgtSliderSetValue(WidgetT *w, int32_t value) {
// margin so the groove has visual breathing room around the thumb.
void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font) {
(void)font;
SliderDataT *d = (SliderDataT *)w->data;
if (w->as.slider.vertical) {
if (d->vertical) {
w->calcMinW = SLIDER_THUMB_W + 4;
w->calcMinH = SLIDER_THUMB_W * 5;
} else {
@ -100,8 +63,9 @@ void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font) {
void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
(void)mod;
SliderDataT *d = (SliderDataT *)w->data;
int32_t range = w->as.slider.maxValue - w->as.slider.minValue;
int32_t range = d->maxValue - d->minValue;
int32_t pageStep = range / 10;
if (pageStep < 1) {
@ -110,41 +74,41 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Arrow keys: step by 1. Page Up/Down: step by 10% of range.
// Home/End: jump to min/max.
if (w->as.slider.vertical) {
if (d->vertical) {
if (key == (0x48 | 0x100)) {
w->as.slider.value -= 1;
d->value -= 1;
} else if (key == (0x50 | 0x100)) {
w->as.slider.value += 1;
d->value += 1;
} else if (key == (0x49 | 0x100)) {
w->as.slider.value -= pageStep;
d->value -= pageStep;
} else if (key == (0x51 | 0x100)) {
w->as.slider.value += pageStep;
d->value += pageStep;
} else if (key == (0x47 | 0x100)) {
w->as.slider.value = w->as.slider.minValue;
d->value = d->minValue;
} else if (key == (0x4F | 0x100)) {
w->as.slider.value = w->as.slider.maxValue;
d->value = d->maxValue;
} else {
return;
}
} else {
if (key == (0x4B | 0x100) || key == (0x48 | 0x100)) {
w->as.slider.value -= 1;
d->value -= 1;
} else if (key == (0x4D | 0x100) || key == (0x50 | 0x100)) {
w->as.slider.value += 1;
d->value += 1;
} else if (key == (0x49 | 0x100)) {
w->as.slider.value -= pageStep;
d->value -= pageStep;
} else if (key == (0x51 | 0x100)) {
w->as.slider.value += pageStep;
d->value += pageStep;
} else if (key == (0x47 | 0x100)) {
w->as.slider.value = w->as.slider.minValue;
d->value = d->minValue;
} else if (key == (0x4F | 0x100)) {
w->as.slider.value = w->as.slider.maxValue;
d->value = d->maxValue;
} else {
return;
}
}
w->as.slider.value = clampInt(w->as.slider.value, w->as.slider.minValue, w->as.slider.maxValue);
d->value = clampInt(d->value, d->minValue, d->maxValue);
if (w->onChange) {
w->onChange(w);
@ -170,7 +134,8 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
(void)root;
hit->focused = true;
int32_t range = hit->as.slider.maxValue - hit->as.slider.minValue;
SliderDataT *d = (SliderDataT *)hit->data;
int32_t range = d->maxValue - d->minValue;
if (range <= 0) {
return;
@ -180,9 +145,9 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
int32_t thumbPos;
int32_t mousePos;
if (hit->as.slider.vertical) {
if (d->vertical) {
thumbRange = hit->h - SLIDER_THUMB_W;
thumbPos = ((hit->as.slider.value - hit->as.slider.minValue) * thumbRange) / range;
thumbPos = ((d->value - d->minValue) * thumbRange) / range;
mousePos = vy - hit->y;
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
@ -191,12 +156,12 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
sDragOffset = mousePos - thumbPos;
} else {
// Click on track -- jump to position
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
int32_t newVal = d->minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
if (newVal > hit->as.slider.maxValue) { newVal = hit->as.slider.maxValue; }
if (newVal < d->minValue) { newVal = d->minValue; }
if (newVal > d->maxValue) { newVal = d->maxValue; }
hit->as.slider.value = newVal;
d->value = newVal;
if (hit->onChange) {
hit->onChange(hit);
@ -204,7 +169,7 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
}
} else {
thumbRange = hit->w - SLIDER_THUMB_W;
thumbPos = ((hit->as.slider.value - hit->as.slider.minValue) * thumbRange) / range;
thumbPos = ((d->value - d->minValue) * thumbRange) / range;
mousePos = vx - hit->x;
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
@ -213,12 +178,12 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
sDragOffset = mousePos - thumbPos;
} else {
// Click on track -- jump to position
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
int32_t newVal = d->minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
if (newVal > hit->as.slider.maxValue) { newVal = hit->as.slider.maxValue; }
if (newVal < d->minValue) { newVal = d->minValue; }
if (newVal > d->maxValue) { newVal = d->maxValue; }
hit->as.slider.value = newVal;
d->value = newVal;
if (hit->onChange) {
hit->onChange(hit);
@ -232,27 +197,20 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// widgetSliderPaint
// ============================================================
// Paint: groove track centered in the cross-axis, thumb at value
// position. The thumb position formula is:
// thumbPos = ((value - minValue) * thumbRange) / range
// where thumbRange = widgetSize - SLIDER_THUMB_W.
// This maps value linearly to pixel position using integer math only.
// The groove is thin (SLIDER_TRACK_H = 4px) with a reversed bevel
// for the recessed look, while the thumb is the full widget height
// with a raised bevel for the 3D grab handle appearance.
void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
(void)font;
SliderDataT *sd = (SliderDataT *)w->data;
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t tickFg = w->enabled ? fg : colors->windowShadow;
uint32_t thumbFg = w->enabled ? colors->buttonFace : colors->scrollbarTrough;
int32_t range = w->as.slider.maxValue - w->as.slider.minValue;
int32_t range = sd->maxValue - sd->minValue;
if (range <= 0) {
range = 1;
}
if (w->as.slider.vertical) {
if (sd->vertical) {
// Track groove
int32_t trackX = w->x + (w->w - SLIDER_TRACK_H) / 2;
BevelStyleT groove;
@ -264,7 +222,7 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
// Thumb
int32_t thumbRange = w->h - SLIDER_THUMB_W;
int32_t thumbY = w->y + ((w->as.slider.value - w->as.slider.minValue) * thumbRange) / range;
int32_t thumbY = w->y + ((sd->value - sd->minValue) * thumbRange) / range;
BevelStyleT thumb;
thumb.highlight = colors->windowHighlight;
@ -287,7 +245,7 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
// Thumb
int32_t thumbRange = w->w - SLIDER_THUMB_W;
int32_t thumbX = w->x + ((w->as.slider.value - w->as.slider.minValue) * thumbRange) / range;
int32_t thumbX = w->x + ((sd->value - sd->minValue) * thumbRange) / range;
BevelStyleT thumb;
thumb.highlight = colors->windowHighlight;
@ -306,24 +264,136 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
}
// ============================================================
// widgetSliderScrollDragUpdate
// ============================================================
//
// Called by the event loop during slider thumb drag. Computes the
// new value from mouse position and the stored drag offset.
static void widgetSliderScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) {
(void)orient;
SliderDataT *d = (SliderDataT *)w->data;
int32_t range = d->maxValue - d->minValue;
if (range <= 0) {
return;
}
int32_t newVal;
if (d->vertical) {
int32_t thumbRange = w->h - SLIDER_THUMB_W;
int32_t relY = mouseY - w->y - dragOff;
newVal = d->minValue + (relY * range) / thumbRange;
} else {
int32_t thumbRange = w->w - SLIDER_THUMB_W;
int32_t relX = mouseX - w->x - dragOff;
newVal = d->minValue + (relX * range) / thumbRange;
}
newVal = clampInt(newVal, d->minValue, d->maxValue);
if (newVal != d->value) {
d->value = newVal;
if (w->onChange) {
w->onChange(w);
}
}
}
// ============================================================
// widgetSliderDestroy
// ============================================================
static void widgetSliderDestroy(WidgetT *w) {
free(w->data);
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassSlider = {
.flags = WCLASS_FOCUSABLE,
.paint = widgetSliderPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSliderCalcMinSize,
.layout = NULL,
.onMouse = widgetSliderOnMouse,
.onKey = widgetSliderOnKey,
.destroy = NULL,
.getText = NULL,
.setText = NULL
.flags = WCLASS_FOCUSABLE | WCLASS_SCROLLABLE,
.paint = widgetSliderPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSliderCalcMinSize,
.layout = NULL,
.onMouse = widgetSliderOnMouse,
.onKey = widgetSliderOnKey,
.destroy = widgetSliderDestroy,
.getText = NULL,
.setText = NULL,
.scrollDragUpdate = widgetSliderScrollDragUpdate
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
SliderDataT *d = (SliderDataT *)calloc(1, sizeof(SliderDataT));
d->value = minVal;
d->minValue = minVal;
d->maxValue = maxVal;
w->data = d;
w->weight = 100;
}
return w;
}
int32_t wgtSliderGetValue(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
SliderDataT *d = (SliderDataT *)w->data;
return d->value;
}
void wgtSliderSetValue(WidgetT *w, int32_t value) {
VALIDATE_WIDGET_VOID(w, sTypeId);
SliderDataT *d = (SliderDataT *)w->data;
if (value < d->minValue) {
value = d->minValue;
}
if (value > d->maxValue) {
value = d->maxValue;
}
d->value = value;
wgtInvalidatePaint(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal);
void (*setValue)(WidgetT *w, int32_t value);
int32_t (*getValue)(const WidgetT *w);
} sApi = {
.create = wgtSlider,
.setValue = wgtSliderSetValue,
.getValue = wgtSliderGetValue
};
void wgtRegister(void) {
widgetClassTable[WidgetSliderE] = &sClassSlider;
sTypeId = wgtRegisterClass(&sClassSlider);
wgtRegisterApi("slider", &sApi);
}

23
widgets/widgetSlider.h Normal file
View file

@ -0,0 +1,23 @@
// widgetSlider.h -- Slider widget API
#ifndef WIDGET_SLIDER_H
#define WIDGET_SLIDER_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal);
void (*setValue)(WidgetT *w, int32_t value);
int32_t (*getValue)(const WidgetT *w);
} SliderApiT;
static inline const SliderApiT *dvxSliderApi(void) {
static const SliderApiT *sApi;
if (!sApi) { sApi = (const SliderApiT *)wgtGetApi("slider"); }
return sApi;
}
#define wgtSlider(parent, minVal, maxVal) dvxSliderApi()->create(parent, minVal, maxVal)
#define wgtSliderSetValue(w, value) dvxSliderApi()->setValue(w, value)
#define wgtSliderGetValue(w) dvxSliderApi()->getValue(w)
#endif // WIDGET_SLIDER_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetSpacer.c -- Spacer widget (invisible stretching element)
//
// A zero-sized invisible widget with weight=100, used purely for
@ -12,22 +13,9 @@
// no mouse, no keyboard, no state. The entire widget is effectively
// just a weight value attached to a position in the sibling list.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
// ============================================================
// wgtSpacer
// ============================================================
WidgetT *wgtSpacer(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetSpacerE);
if (w) {
w->weight = 100; // spacers stretch by default
}
return w;
}
static int32_t sTypeId = -1;
// ============================================================
@ -59,6 +47,34 @@ static const WidgetClassT sClassSpacer = {
.setText = NULL
};
void wgtRegister(void) {
widgetClassTable[WidgetSpacerE] = &sClassSpacer;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtSpacer(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
w->weight = 100;
}
return w;
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent);
} sApi = {
.create = wgtSpacer
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSpacer);
wgtRegisterApi("spacer", &sApi);
}

19
widgets/widgetSpacer.h Normal file
View file

@ -0,0 +1,19 @@
// widgetSpacer.h -- Spacer widget API
#ifndef WIDGET_SPACER_H
#define WIDGET_SPACER_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent);
} SpacerApiT;
static inline const SpacerApiT *dvxSpacerApi(void) {
static const SpacerApiT *sApi;
if (!sApi) { sApi = (const SpacerApiT *)wgtGetApi("spacer"); }
return sApi;
}
#define wgtSpacer(parent) dvxSpacerApi()->create(parent)
#endif // WIDGET_SPACER_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetSpinner.c -- Spinner (numeric up/down) widget
//
// A hybrid widget combining a single-line text editor with up/down
@ -29,11 +30,31 @@
// look like they're part of the border chrome. The up/down buttons
// split the widget height evenly.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#include "../texthelp/textHelp.h"
static int32_t sTypeId = -1;
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int32_t value;
int32_t minValue;
int32_t maxValue;
int32_t step;
char buf[16];
int32_t len;
int32_t cursorPos;
int32_t scrollOff;
int32_t selStart;
int32_t selEnd;
char undoBuf[16];
int32_t undoLen;
int32_t undoCursor;
bool editing;
} SpinnerDataT;
#define SPINNER_BTN_W 14
#define SPINNER_BORDER 2
#define SPINNER_PAD 3
@ -43,26 +64,26 @@
// Prototypes
// ============================================================
static void spinnerClampAndFormat(WidgetT *w);
static void spinnerCommitEdit(WidgetT *w);
static void spinnerFormat(WidgetT *w);
static void spinnerStartEdit(WidgetT *w);
static void spinnerClampAndFormat(SpinnerDataT *d);
static void spinnerCommitEdit(SpinnerDataT *d);
static void spinnerFormat(SpinnerDataT *d);
static void spinnerStartEdit(SpinnerDataT *d);
// ============================================================
// spinnerClampAndFormat
// ============================================================
static void spinnerClampAndFormat(WidgetT *w) {
if (w->as.spinner.value < w->as.spinner.minValue) {
w->as.spinner.value = w->as.spinner.minValue;
static void spinnerClampAndFormat(SpinnerDataT *d) {
if (d->value < d->minValue) {
d->value = d->minValue;
}
if (w->as.spinner.value > w->as.spinner.maxValue) {
w->as.spinner.value = w->as.spinner.maxValue;
if (d->value > d->maxValue) {
d->value = d->maxValue;
}
spinnerFormat(w);
spinnerFormat(d);
}
@ -70,17 +91,17 @@ static void spinnerClampAndFormat(WidgetT *w) {
// spinnerCommitEdit
// ============================================================
static void spinnerCommitEdit(WidgetT *w) {
if (!w->as.spinner.editing) {
static void spinnerCommitEdit(SpinnerDataT *d) {
if (!d->editing) {
return;
}
w->as.spinner.editing = false;
w->as.spinner.buf[w->as.spinner.len] = '\0';
d->editing = false;
d->buf[d->len] = '\0';
int32_t val = (int32_t)strtol(w->as.spinner.buf, NULL, 10);
w->as.spinner.value = val;
spinnerClampAndFormat(w);
int32_t val = (int32_t)strtol(d->buf, NULL, 10);
d->value = val;
spinnerClampAndFormat(d);
}
@ -92,12 +113,12 @@ static void spinnerCommitEdit(WidgetT *w) {
// This is called after any value change to synchronize the text buffer
// with the numeric value. The cursor-at-end position matches user
// expectation after arrow-key increment/decrement.
static void spinnerFormat(WidgetT *w) {
w->as.spinner.len = snprintf(w->as.spinner.buf, sizeof(w->as.spinner.buf), "%d", (int)w->as.spinner.value);
w->as.spinner.cursorPos = w->as.spinner.len;
w->as.spinner.scrollOff = 0;
w->as.spinner.selStart = -1;
w->as.spinner.selEnd = -1;
static void spinnerFormat(SpinnerDataT *d) {
d->len = snprintf(d->buf, sizeof(d->buf), "%d", (int)d->value);
d->cursorPos = d->len;
d->scrollOff = 0;
d->selStart = -1;
d->selEnd = -1;
}
@ -109,14 +130,14 @@ static void spinnerFormat(WidgetT *w) {
// revert to the pre-edit formatted value. The snapshot is only taken
// on the transition to editing, not on every keystroke, so repeated
// typing within one edit session can be undone all at once.
static void spinnerStartEdit(WidgetT *w) {
if (!w->as.spinner.editing) {
w->as.spinner.editing = true;
static void spinnerStartEdit(SpinnerDataT *d) {
if (!d->editing) {
d->editing = true;
// Snapshot for undo
memcpy(w->as.spinner.undoBuf, w->as.spinner.buf, sizeof(w->as.spinner.buf));
w->as.spinner.undoLen = w->as.spinner.len;
w->as.spinner.undoCursor = w->as.spinner.cursorPos;
memcpy(d->undoBuf, d->buf, sizeof(d->buf));
d->undoLen = d->len;
d->undoCursor = d->cursorPos;
}
}
@ -136,7 +157,8 @@ void widgetSpinnerCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// ============================================================
const char *widgetSpinnerGetText(const WidgetT *w) {
return w->as.spinner.buf;
const SpinnerDataT *d = (const SpinnerDataT *)w->data;
return d->buf;
}
@ -153,13 +175,14 @@ const char *widgetSpinnerGetText(const WidgetT *w) {
// Page Up/Down use step*10 for coarser adjustment, matching the
// convention used by Windows spin controls.
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
int32_t step = w->as.spinner.step;
SpinnerDataT *d = (SpinnerDataT *)w->data;
int32_t step = d->step;
// Up arrow -- increment
if (key == (0x48 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value += step;
spinnerClampAndFormat(w);
spinnerCommitEdit(d);
d->value += step;
spinnerClampAndFormat(d);
if (w->onChange) {
w->onChange(w);
@ -171,9 +194,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Down arrow -- decrement
if (key == (0x50 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value -= step;
spinnerClampAndFormat(w);
spinnerCommitEdit(d);
d->value -= step;
spinnerClampAndFormat(d);
if (w->onChange) {
w->onChange(w);
@ -185,9 +208,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Page Up -- increment by step * 10
if (key == (0x49 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value += step * 10;
spinnerClampAndFormat(w);
spinnerCommitEdit(d);
d->value += step * 10;
spinnerClampAndFormat(d);
if (w->onChange) {
w->onChange(w);
@ -199,9 +222,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Page Down -- decrement by step * 10
if (key == (0x51 | 0x100)) {
spinnerCommitEdit(w);
w->as.spinner.value -= step * 10;
spinnerClampAndFormat(w);
spinnerCommitEdit(d);
d->value -= step * 10;
spinnerClampAndFormat(d);
if (w->onChange) {
w->onChange(w);
@ -213,8 +236,8 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Enter -- commit edit
if (key == '\r' || key == '\n') {
if (w->as.spinner.editing) {
spinnerCommitEdit(w);
if (d->editing) {
spinnerCommitEdit(d);
if (w->onChange) {
w->onChange(w);
@ -228,9 +251,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
// Escape -- cancel edit, revert to current value
if (key == 27) {
if (w->as.spinner.editing) {
w->as.spinner.editing = false;
spinnerFormat(w);
if (d->editing) {
d->editing = false;
spinnerFormat(d);
wgtInvalidatePaint(w);
}
@ -247,33 +270,33 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
}
// Minus only at position 0 (and only if min is negative)
if (isMinus && (w->as.spinner.cursorPos != 0 || w->as.spinner.minValue >= 0)) {
if (isMinus && (d->cursorPos != 0 || d->minValue >= 0)) {
return;
}
// Enter edit mode on first text-modifying key
if (isDigit || isMinus || key == 8 || key == 127 || key == (0x53 | 0x100)) {
spinnerStartEdit(w);
spinnerStartEdit(d);
}
// Delegate to shared text editing logic (handles cursor movement,
// selection, cut/copy/paste, undo/redo, backspace, delete, etc.)
widgetTextEditOnKey(w, key, mod,
w->as.spinner.buf, (int32_t)sizeof(w->as.spinner.buf),
&w->as.spinner.len, &w->as.spinner.cursorPos,
&w->as.spinner.scrollOff,
&w->as.spinner.selStart, &w->as.spinner.selEnd,
w->as.spinner.undoBuf, &w->as.spinner.undoLen,
&w->as.spinner.undoCursor);
d->buf, (int32_t)sizeof(d->buf),
&d->len, &d->cursorPos,
&d->scrollOff,
&d->selStart, &d->selEnd,
d->undoBuf, &d->undoLen,
&d->undoCursor);
// Validate buffer after paste -- reject non-numeric content.
// Allow optional leading minus and digits only.
bool valid = true;
for (int32_t i = 0; i < w->as.spinner.len; i++) {
char c = w->as.spinner.buf[i];
for (int32_t i = 0; i < d->len; i++) {
char c = d->buf[i];
if (c == '-' && i == 0 && w->as.spinner.minValue < 0) {
if (c == '-' && i == 0 && d->minValue < 0) {
continue;
}
@ -285,11 +308,11 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
if (!valid) {
// Revert to the undo buffer (pre-paste state)
memcpy(w->as.spinner.buf, w->as.spinner.undoBuf, sizeof(w->as.spinner.buf));
w->as.spinner.len = w->as.spinner.undoLen;
w->as.spinner.cursorPos = w->as.spinner.undoCursor;
w->as.spinner.selStart = 0;
w->as.spinner.selEnd = 0;
memcpy(d->buf, d->undoBuf, sizeof(d->buf));
d->len = d->undoLen;
d->cursorPos = d->undoCursor;
d->selStart = 0;
d->selEnd = 0;
}
wgtInvalidatePaint(w);
@ -310,21 +333,22 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
// make sense for numbers), entering edit mode to allow replacement.
void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
hit->focused = true;
SpinnerDataT *d = (SpinnerDataT *)hit->data;
int32_t btnX = hit->x + hit->w - SPINNER_BORDER - SPINNER_BTN_W;
int32_t midY = hit->y + hit->h / 2;
if (vx >= btnX) {
// Click on button area
spinnerCommitEdit(hit);
spinnerCommitEdit(d);
if (vy < midY) {
hit->as.spinner.value += hit->as.spinner.step;
d->value += d->step;
} else {
hit->as.spinner.value -= hit->as.spinner.step;
d->value -= d->step;
}
spinnerClampAndFormat(hit);
spinnerClampAndFormat(d);
if (hit->onChange) {
hit->onChange(hit);
@ -332,8 +356,8 @@ void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
} else {
// Click on text area
AppContextT *ctx = (AppContextT *)root->userData;
widgetTextEditMouseClick(hit, vx, vy, hit->x + SPINNER_BORDER + SPINNER_PAD, &ctx->font, hit->as.spinner.buf, hit->as.spinner.len, hit->as.spinner.scrollOff, &hit->as.spinner.cursorPos, &hit->as.spinner.selStart, &hit->as.spinner.selEnd, false, false);
spinnerStartEdit(hit);
widgetTextEditMouseClick(hit, vx, vy, hit->x + SPINNER_BORDER + SPINNER_PAD, &ctx->font, d->buf, d->len, d->scrollOff, &d->cursorPos, &d->selStart, &d->selEnd, false, false);
spinnerStartEdit(d);
}
wgtInvalidatePaint(hit);
@ -355,7 +379,8 @@ void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
// button area into the widget's right border so they visually merge
// with the outer bevel -- this is why btnW is btnW + SPINNER_BORDER
// in the drawBevel calls.
void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
void widgetSpinnerPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
SpinnerDataT *d = (SpinnerDataT *)w->data;
uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->contentFg) : colors->windowShadow;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
@ -368,7 +393,7 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
bevel.shadow = colors->windowHighlight;
bevel.face = bg;
bevel.width = SPINNER_BORDER;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
// Text area
int32_t textX = w->x + SPINNER_BORDER + SPINNER_PAD;
@ -381,14 +406,14 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
}
// Scroll to keep cursor visible
if (w->as.spinner.cursorPos < w->as.spinner.scrollOff) {
w->as.spinner.scrollOff = w->as.spinner.cursorPos;
} else if (w->as.spinner.cursorPos > w->as.spinner.scrollOff + maxChars) {
w->as.spinner.scrollOff = w->as.spinner.cursorPos - maxChars;
if (d->cursorPos < d->scrollOff) {
d->scrollOff = d->cursorPos;
} else if (d->cursorPos > d->scrollOff + maxChars) {
d->scrollOff = d->cursorPos - maxChars;
}
int32_t off = w->as.spinner.scrollOff;
int32_t len = w->as.spinner.len - off;
int32_t off = d->scrollOff;
int32_t len = d->len - off;
if (len > maxChars) {
len = maxChars;
@ -398,14 +423,14 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
len = 0;
}
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, &w->as.spinner.buf[off], len, off, w->as.spinner.cursorPos, w->as.spinner.selStart, w->as.spinner.selEnd, fg, bg, w->focused, w->x + SPINNER_BORDER, btnX - SPINNER_PAD);
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, &d->buf[off], len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w->focused, w->x + SPINNER_BORDER, btnX - SPINNER_PAD);
// Up button (top half)
int32_t btnTopH = w->h / 2;
int32_t btnBotH = w->h - btnTopH;
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
drawBevel(d, ops, btnX, w->y, btnW + SPINNER_BORDER, btnTopH, &btnBevel);
drawBevel(disp, ops, btnX, w->y, btnW + SPINNER_BORDER, btnTopH, &btnBevel);
// Up arrow triangle
{
@ -413,12 +438,12 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
int32_t cy = w->y + btnTopH / 2;
for (int32_t i = 0; i < 3; i++) {
drawHLine(d, ops, cx - i, cy - 1 + i, 1 + i * 2, fg);
drawHLine(disp, ops, cx - i, cy - 1 + i, 1 + i * 2, fg);
}
}
// Down button (bottom half)
drawBevel(d, ops, btnX, w->y + btnTopH, btnW + SPINNER_BORDER, btnBotH, &btnBevel);
drawBevel(disp, ops, btnX, w->y + btnTopH, btnW + SPINNER_BORDER, btnBotH, &btnBevel);
// Down arrow triangle
{
@ -426,13 +451,13 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
int32_t cy = w->y + btnTopH + btnBotH / 2;
for (int32_t i = 0; i < 3; i++) {
drawHLine(d, ops, cx - i, cy + 1 - i, 1 + i * 2, fg);
drawHLine(disp, ops, cx - i, cy + 1 - i, 1 + i * 2, fg);
}
}
// Focus rect around entire widget
if (w->focused) {
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
drawFocusRect(disp, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
}
}
@ -442,82 +467,19 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
// ============================================================
void widgetSpinnerSetText(WidgetT *w, const char *text) {
SpinnerDataT *d = (SpinnerDataT *)w->data;
int32_t val = (int32_t)strtol(text, NULL, 10);
w->as.spinner.value = val;
spinnerClampAndFormat(w);
d->value = val;
spinnerClampAndFormat(d);
}
// ============================================================
// wgtSpinner
// widgetSpinnerDestroy
// ============================================================
WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step) {
WidgetT *w = widgetAlloc(parent, WidgetSpinnerE);
if (w) {
w->as.spinner.minValue = minVal;
w->as.spinner.maxValue = maxVal;
w->as.spinner.step = step > 0 ? step : 1;
w->as.spinner.value = minVal;
w->as.spinner.editing = false;
w->as.spinner.selStart = -1;
w->as.spinner.selEnd = -1;
w->as.spinner.undoLen = 0;
w->as.spinner.undoCursor = 0;
spinnerFormat(w);
}
return w;
}
// ============================================================
// wgtSpinnerGetValue
// ============================================================
int32_t wgtSpinnerGetValue(const WidgetT *w) {
VALIDATE_WIDGET(w, WidgetSpinnerE, 0);
return w->as.spinner.value;
}
// ============================================================
// wgtSpinnerSetRange
// ============================================================
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
w->as.spinner.minValue = minVal;
w->as.spinner.maxValue = maxVal;
spinnerClampAndFormat(w);
wgtInvalidate(w);
}
// ============================================================
// wgtSpinnerSetStep
// ============================================================
void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
w->as.spinner.step = step > 0 ? step : 1;
}
// ============================================================
// wgtSpinnerSetValue
// ============================================================
void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
w->as.spinner.value = value;
spinnerClampAndFormat(w);
wgtInvalidate(w);
void widgetSpinnerDestroy(WidgetT *w) {
free(w->data);
}
@ -526,19 +488,105 @@ void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
// ============================================================
static const WidgetClassT sClassSpinner = {
.flags = WCLASS_FOCUSABLE,
.flags = WCLASS_FOCUSABLE | WCLASS_SCROLLABLE,
.paint = widgetSpinnerPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSpinnerCalcMinSize,
.layout = NULL,
.onMouse = widgetSpinnerOnMouse,
.onKey = widgetSpinnerOnKey,
.destroy = NULL,
.destroy = widgetSpinnerDestroy,
.getText = widgetSpinnerGetText,
.setText = widgetSpinnerSetText
};
void wgtRegister(void) {
widgetClassTable[WidgetSpinnerE] = &sClassSpinner;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
SpinnerDataT *d = (SpinnerDataT *)calloc(1, sizeof(SpinnerDataT));
if (!d) {
return w;
}
w->data = d;
d->minValue = minVal;
d->maxValue = maxVal;
d->step = step > 0 ? step : 1;
d->value = minVal;
d->selStart = -1;
d->selEnd = -1;
spinnerFormat(d);
}
return w;
}
int32_t wgtSpinnerGetValue(const WidgetT *w) {
VALIDATE_WIDGET(w, sTypeId, 0);
const SpinnerDataT *d = (const SpinnerDataT *)w->data;
return d->value;
}
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
VALIDATE_WIDGET_VOID(w, sTypeId);
SpinnerDataT *d = (SpinnerDataT *)w->data;
d->minValue = minVal;
d->maxValue = maxVal;
spinnerClampAndFormat(d);
wgtInvalidate(w);
}
void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
VALIDATE_WIDGET_VOID(w, sTypeId);
SpinnerDataT *d = (SpinnerDataT *)w->data;
d->step = step > 0 ? step : 1;
}
void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
VALIDATE_WIDGET_VOID(w, sTypeId);
SpinnerDataT *d = (SpinnerDataT *)w->data;
d->value = value;
spinnerClampAndFormat(d);
wgtInvalidate(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step);
void (*setValue)(WidgetT *w, int32_t value);
int32_t (*getValue)(const WidgetT *w);
void (*setRange)(WidgetT *w, int32_t minVal, int32_t maxVal);
void (*setStep)(WidgetT *w, int32_t step);
} sApi = {
.create = wgtSpinner,
.setValue = wgtSpinnerSetValue,
.getValue = wgtSpinnerGetValue,
.setRange = wgtSpinnerSetRange,
.setStep = wgtSpinnerSetStep
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSpinner);
wgtRegisterApi("spinner", &sApi);
}

27
widgets/widgetSpinner.h Normal file
View file

@ -0,0 +1,27 @@
// widgetSpinner.h -- Spinner widget API
#ifndef WIDGET_SPINNER_H
#define WIDGET_SPINNER_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step);
void (*setValue)(WidgetT *w, int32_t value);
int32_t (*getValue)(const WidgetT *w);
void (*setRange)(WidgetT *w, int32_t minVal, int32_t maxVal);
void (*setStep)(WidgetT *w, int32_t step);
} SpinnerApiT;
static inline const SpinnerApiT *dvxSpinnerApi(void) {
static const SpinnerApiT *sApi;
if (!sApi) { sApi = (const SpinnerApiT *)wgtGetApi("spinner"); }
return sApi;
}
#define wgtSpinner(parent, minVal, maxVal, step) dvxSpinnerApi()->create(parent, minVal, maxVal, step)
#define wgtSpinnerSetValue(w, value) dvxSpinnerApi()->setValue(w, value)
#define wgtSpinnerGetValue(w) dvxSpinnerApi()->getValue(w)
#define wgtSpinnerSetRange(w, minVal, maxVal) dvxSpinnerApi()->setRange(w, minVal, maxVal)
#define wgtSpinnerSetStep(w, step) dvxSpinnerApi()->setStep(w, step)
#endif // WIDGET_SPINNER_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetSplitter.c -- Splitter (draggable divider between two panes)
//
// A container that divides its area between exactly two child widgets
@ -27,7 +28,17 @@
// nothing. The clamp also ensures the divider can't be dragged past
// where the second pane would violate its minimum.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#define SPLITTER_BAR_W 5
#define SPLITTER_MIN_PANE 20
static int32_t sTypeId = -1;
typedef struct {
int32_t dividerPos;
bool vertical;
} SplitterDataT;
// ============================================================
@ -36,6 +47,8 @@
static WidgetT *spFirstChild(WidgetT *w);
static WidgetT *spSecondChild(WidgetT *w);
static void widgetSplitterDestroy(WidgetT *w);
static void widgetSplitterScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
// ============================================================
@ -83,10 +96,11 @@ static WidgetT *spSecondChild(WidgetT *w) {
// ============================================================
void widgetSplitterClampPos(WidgetT *w, int32_t *pos) {
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
bool vert = w->as.splitter.vertical;
int32_t totalSize = vert ? w->w : w->h;
SplitterDataT *d = (SplitterDataT *)w->data;
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
bool vert = d->vertical;
int32_t totalSize = vert ? w->w : w->h;
int32_t minFirst = c1 ? (vert ? c1->calcMinW : c1->calcMinH) : SPLITTER_MIN_PANE;
int32_t minSecond = c2 ? (vert ? c2->calcMinW : c2->calcMinH) : SPLITTER_MIN_PANE;
@ -126,7 +140,9 @@ void widgetSplitterCalcMinSize(WidgetT *w, const BitmapFontT *font) {
int32_t m2w = c2 ? c2->calcMinW : 0;
int32_t m2h = c2 ? c2->calcMinH : 0;
if (w->as.splitter.vertical) {
SplitterDataT *d = (SplitterDataT *)w->data;
if (d->vertical) {
w->calcMinW = m1w + m2w + SPLITTER_BAR_W;
w->calcMinH = DVX_MAX(m1h, m2h);
} else {
@ -147,14 +163,15 @@ void widgetSplitterCalcMinSize(WidgetT *w, const BitmapFontT *font) {
// user hasn't dragged yet (dividerPos=0 from construction gets
// clamped to minFirst).
void widgetSplitterLayout(WidgetT *w, const BitmapFontT *font) {
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
int32_t pos = w->as.splitter.dividerPos;
SplitterDataT *d = (SplitterDataT *)w->data;
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
int32_t pos = d->dividerPos;
widgetSplitterClampPos(w, &pos);
w->as.splitter.dividerPos = pos;
d->dividerPos = pos;
if (w->as.splitter.vertical) {
if (d->vertical) {
// Left pane
if (c1) {
c1->x = w->x;
@ -215,12 +232,13 @@ void widgetSplitterLayout(WidgetT *w, const BitmapFontT *font) {
// generic hit-test would find the splitter but not recurse into its
// clipped children).
void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
int32_t pos = hit->as.splitter.dividerPos;
SplitterDataT *d = (SplitterDataT *)hit->data;
int32_t pos = d->dividerPos;
// Check if click is on the divider bar
bool onDivider;
if (hit->as.splitter.vertical) {
if (d->vertical) {
int32_t barX = hit->x + pos;
onDivider = (vx >= barX && vx < barX + SPLITTER_BAR_W);
} else {
@ -232,7 +250,7 @@ void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
// Start dragging
sDragSplitter = hit;
if (hit->as.splitter.vertical) {
if (d->vertical) {
sDragSplitStart = vx - hit->x - pos;
} else {
sDragSplitStart = vy - hit->y - pos;
@ -282,46 +300,47 @@ void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
// a tiny raised bump. 11 bumps spaced 3px apart center vertically
// (or horizontally) on the bar. This is purely decorative but
// provides the expected visual affordance for "draggable".
void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
int32_t pos = w->as.splitter.dividerPos;
void widgetSplitterPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
SplitterDataT *d = (SplitterDataT *)w->data;
int32_t pos = d->dividerPos;
// Paint first child with clip rect
WidgetT *c1 = spFirstChild(w);
WidgetT *c2 = spSecondChild(w);
int32_t oldClipX = d->clipX;
int32_t oldClipY = d->clipY;
int32_t oldClipW = d->clipW;
int32_t oldClipH = d->clipH;
int32_t oldClipX = disp->clipX;
int32_t oldClipY = disp->clipY;
int32_t oldClipW = disp->clipW;
int32_t oldClipH = disp->clipH;
if (c1) {
if (w->as.splitter.vertical) {
setClipRect(d, w->x, w->y, pos, w->h);
if (d->vertical) {
setClipRect(disp, w->x, w->y, pos, w->h);
} else {
setClipRect(d, w->x, w->y, w->w, pos);
setClipRect(disp, w->x, w->y, w->w, pos);
}
widgetPaintOne(c1, d, ops, font, colors);
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
widgetPaintOne(c1, disp, ops, font, colors);
setClipRect(disp, oldClipX, oldClipY, oldClipW, oldClipH);
}
if (c2) {
if (w->as.splitter.vertical) {
setClipRect(d, w->x + pos + SPLITTER_BAR_W, w->y, w->w - pos - SPLITTER_BAR_W, w->h);
if (d->vertical) {
setClipRect(disp, w->x + pos + SPLITTER_BAR_W, w->y, w->w - pos - SPLITTER_BAR_W, w->h);
} else {
setClipRect(d, w->x, w->y + pos + SPLITTER_BAR_W, w->w, w->h - pos - SPLITTER_BAR_W);
setClipRect(disp, w->x, w->y + pos + SPLITTER_BAR_W, w->w, w->h - pos - SPLITTER_BAR_W);
}
widgetPaintOne(c2, d, ops, font, colors);
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
widgetPaintOne(c2, disp, ops, font, colors);
setClipRect(disp, oldClipX, oldClipY, oldClipW, oldClipH);
}
// Draw divider bar -- raised bevel
BevelStyleT bevel = BEVEL_RAISED(colors, 1);
if (w->as.splitter.vertical) {
if (d->vertical) {
int32_t barX = w->x + pos;
drawBevel(d, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
drawBevel(disp, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
// Gripper -- row of embossed 2x2 bumps centered vertically
int32_t gx = barX + 1;
@ -330,12 +349,12 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
for (int32_t i = -count; i <= count; i++) {
int32_t dy = midY + i * 3;
drawHLine(d, ops, gx, dy, 2, colors->windowHighlight);
drawHLine(d, ops, gx + 1, dy + 1, 2, colors->windowShadow);
drawHLine(disp, ops, gx, dy, 2, colors->windowHighlight);
drawHLine(disp, ops, gx + 1, dy + 1, 2, colors->windowShadow);
}
} else {
int32_t barY = w->y + pos;
drawBevel(d, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
drawBevel(disp, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
// Gripper -- row of embossed 2x2 bumps centered horizontally
int32_t gy = barY + 1;
@ -344,48 +363,82 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
for (int32_t i = -count; i <= count; i++) {
int32_t dx = midX + i * 3;
drawHLine(d, ops, dx, gy, 1, colors->windowHighlight);
drawHLine(d, ops, dx + 1, gy, 1, colors->windowShadow);
drawHLine(d, ops, dx, gy + 1, 1, colors->windowHighlight);
drawHLine(d, ops, dx + 1, gy + 1, 1, colors->windowShadow);
drawHLine(disp, ops, dx, gy, 1, colors->windowHighlight);
drawHLine(disp, ops, dx + 1, gy, 1, colors->windowShadow);
drawHLine(disp, ops, dx, gy + 1, 1, colors->windowHighlight);
drawHLine(disp, ops, dx + 1, gy + 1, 1, colors->windowShadow);
}
}
}
// ============================================================
// wgtSplitter
// widgetSplitterGetCursorShape
// ============================================================
WidgetT *wgtSplitter(WidgetT *parent, bool vertical) {
WidgetT *w = widgetAlloc(parent, WidgetSplitterE);
int32_t widgetSplitterGetCursorShape(const WidgetT *w, int32_t vx, int32_t vy) {
SplitterDataT *d = (SplitterDataT *)w->data;
if (w) {
w->as.splitter.vertical = vertical;
w->as.splitter.dividerPos = 0;
w->weight = 100;
// During active drag, always show resize cursor
if (sDragSplitter == w) {
return d->vertical ? CURSOR_RESIZE_H : CURSOR_RESIZE_V;
}
return w;
int32_t pos = d->dividerPos;
if (d->vertical) {
int32_t barX = w->x + pos;
if (vx >= barX && vx < barX + SPLITTER_BAR_W) {
return CURSOR_RESIZE_H;
}
} else {
int32_t barY = w->y + pos;
if (vy >= barY && vy < barY + SPLITTER_BAR_W) {
return CURSOR_RESIZE_V;
}
}
return 0;
}
// ============================================================
// wgtSplitterGetPos
// widgetSplitterDestroy
// ============================================================
int32_t wgtSplitterGetPos(const WidgetT *w) {
return w->as.splitter.dividerPos;
static void widgetSplitterDestroy(WidgetT *w) {
free(w->data);
}
// ============================================================
// wgtSplitterSetPos
// widgetSplitterScrollDragUpdate
// ============================================================
void wgtSplitterSetPos(WidgetT *w, int32_t pos) {
w->as.splitter.dividerPos = pos;
wgtInvalidate(w);
static void widgetSplitterScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) {
(void)orient;
SplitterDataT *d = (SplitterDataT *)w->data;
int32_t pos;
if (d->vertical) {
pos = mouseX - w->x - dragOff;
} else {
pos = mouseY - w->y - dragOff;
}
widgetSplitterClampPos(w, &pos);
if (pos != d->dividerPos) {
d->dividerPos = pos;
if (w->onChange) {
w->onChange(w);
}
wgtInvalidate(w);
}
}
@ -394,20 +447,70 @@ void wgtSplitterSetPos(WidgetT *w, int32_t pos) {
// ============================================================
static const WidgetClassT sClassSplitter = {
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
.paint = widgetSplitterPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSplitterCalcMinSize,
.layout = widgetSplitterLayout,
.onMouse = widgetSplitterOnMouse,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
.paint = widgetSplitterPaint,
.paintOverlay = NULL,
.calcMinSize = widgetSplitterCalcMinSize,
.layout = widgetSplitterLayout,
.onMouse = widgetSplitterOnMouse,
.onKey = NULL,
.destroy = widgetSplitterDestroy,
.getText = NULL,
.setText = NULL,
.getCursorShape = widgetSplitterGetCursorShape,
.scrollDragUpdate = widgetSplitterScrollDragUpdate
};
void wgtRegister(void) {
widgetClassTable[WidgetSplitterE] = &sClassSplitter;
sSplitterClampPosFn = widgetSplitterClampPos;
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtSplitter(WidgetT *parent, bool vertical) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
SplitterDataT *d = (SplitterDataT *)calloc(1, sizeof(SplitterDataT));
d->vertical = vertical;
d->dividerPos = 0;
w->data = d;
w->weight = 100;
}
return w;
}
int32_t wgtSplitterGetPos(const WidgetT *w) {
SplitterDataT *d = (SplitterDataT *)w->data;
return d->dividerPos;
}
void wgtSplitterSetPos(WidgetT *w, int32_t pos) {
SplitterDataT *d = (SplitterDataT *)w->data;
d->dividerPos = pos;
wgtInvalidate(w);
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent, bool vertical);
void (*setPos)(WidgetT *w, int32_t pos);
int32_t (*getPos)(const WidgetT *w);
} sApi = {
.create = wgtSplitter,
.setPos = wgtSplitterSetPos,
.getPos = wgtSplitterGetPos
};
void wgtRegister(void) {
sTypeId = wgtRegisterClass(&sClassSplitter);
wgtRegisterApi("splitter", &sApi);
}

23
widgets/widgetSplitter.h Normal file
View file

@ -0,0 +1,23 @@
// widgetSplitter.h -- Splitter widget API
#ifndef WIDGET_SPLITTER_H
#define WIDGET_SPLITTER_H
#include "../core/dvxWidget.h"
typedef struct {
WidgetT *(*create)(WidgetT *parent, bool vertical);
void (*setPos)(WidgetT *w, int32_t pos);
int32_t (*getPos)(const WidgetT *w);
} SplitterApiT;
static inline const SplitterApiT *dvxSplitterApi(void) {
static const SplitterApiT *sApi;
if (!sApi) { sApi = (const SplitterApiT *)wgtGetApi("splitter"); }
return sApi;
}
#define wgtSplitter(parent, vertical) dvxSplitterApi()->create(parent, vertical)
#define wgtSplitterSetPos(w, pos) dvxSplitterApi()->setPos(w, pos)
#define wgtSplitterGetPos(w) dvxSplitterApi()->getPos(w)
#endif // WIDGET_SPLITTER_H

View file

@ -1,3 +1,4 @@
#define DVX_WIDGET_IMPL
// widgetStatusBar.c -- StatusBar widget
//
// A horizontal container that draws a sunken border around each visible
@ -16,25 +17,12 @@
// No mouse/key handlers -- the status bar is purely display. Children
// that are interactive (e.g., a clickable label) handle their own events.
#include "widgetInternal.h"
#include "dvxWidgetPlugin.h"
#define TOOLBAR_PAD 2
#define TOOLBAR_GAP 2
// ============================================================
// wgtStatusBar
// ============================================================
// Tight 2px padding and spacing keeps the status bar compact, using
// minimal vertical space at the bottom of a window.
WidgetT *wgtStatusBar(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, WidgetStatusBarE);
if (w) {
w->padding = wgtPixels(2);
w->spacing = wgtPixels(2);
}
return w;
}
static int32_t sTypeId = -1;
// ============================================================
@ -63,24 +51,68 @@ void widgetStatusBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
}
// ============================================================
// widgetStatusBarGetLayoutMetrics
// ============================================================
void widgetStatusBarGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW) {
(void)w;
(void)font;
*pad = TOOLBAR_PAD;
*gap = TOOLBAR_GAP;
*extraTop = 0;
*borderW = 0;
}
// ============================================================
// DXE registration
// ============================================================
static const WidgetClassT sClassStatusBar = {
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
.paint = widgetStatusBarPaint,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
.paint = widgetStatusBarPaint,
.paintOverlay = NULL,
.calcMinSize = NULL,
.layout = NULL,
.onMouse = NULL,
.onKey = NULL,
.destroy = NULL,
.getText = NULL,
.setText = NULL,
.getLayoutMetrics = widgetStatusBarGetLayoutMetrics
};
// ============================================================
// Widget creation functions
// ============================================================
WidgetT *wgtStatusBar(WidgetT *parent) {
WidgetT *w = widgetAlloc(parent, sTypeId);
if (w) {
w->padding = wgtPixels(2);
w->spacing = wgtPixels(2);
}
return w;
}
// ============================================================
// DXE registration
// ============================================================
static const struct {
WidgetT *(*create)(WidgetT *parent);
} sApi = {
.create = wgtStatusBar
};
void wgtRegister(void) {
widgetClassTable[WidgetStatusBarE] = &sClassStatusBar;
sTypeId = wgtRegisterClass(&sClassStatusBar);
wgtRegisterApi("statusbar", &sApi);
}

Some files were not shown because too many files have changed in this diff Show more