diff --git a/Makefile b/Makefile index d78783d..87e511f 100644 --- a/Makefile +++ b/Makefile @@ -1,28 +1,37 @@ # DVX GUI -- Top-level Makefile # -# Builds the full DVX stack: library, task switcher, shell, and apps. +# Builds the full DVX stack: core library, task switcher, +# bootstrap loader, widgets, shell, and apps. -.PHONY: all clean dvx tasks dvxshell apps +.PHONY: all clean core tasks loader widgets shell apps -all: dvx tasks dvxshell apps +all: core tasks loader widgets shell apps -dvx: - $(MAKE) -C dvx +core: + $(MAKE) -C core tasks: $(MAKE) -C tasks -dvxshell: dvx tasks - $(MAKE) -C dvxshell +loader: core tasks + $(MAKE) -C loader -apps: dvx tasks dvxshell +widgets: core tasks + $(MAKE) -C widgets + +shell: core tasks + $(MAKE) -C shell + +apps: core tasks shell $(MAKE) -C apps clean: - $(MAKE) -C dvx clean + $(MAKE) -C core clean $(MAKE) -C tasks clean - $(MAKE) -C dvxshell clean + $(MAKE) -C loader clean + $(MAKE) -C widgets clean + $(MAKE) -C shell clean $(MAKE) -C apps clean - -rmdir obj/dvx/widgets obj/dvx/platform obj/dvx/thirdparty obj/dvx obj/tasks obj/dvxshell obj/apps obj 2>/dev/null - -rm -rf bin/config - -rmdir bin/apps/cpanel bin/apps/imgview bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin lib 2>/dev/null + -rmdir obj 2>/dev/null + -rm -rf bin/config bin/widgets bin/libs + -rmdir bin/apps/cpanel bin/apps/imgview bin/apps/progman bin/apps/notepad bin/apps/clock bin/apps/dvxdemo bin/apps bin 2>/dev/null diff --git a/apps/Makefile b/apps/Makefile index 8ab719f..7947061 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -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../dvx -I../tasks -I../dvxshell +CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../tasks -I../tasks/thirdparty -I../shell OBJDIR = ../obj/apps BINDIR = ../bin/apps @@ -84,12 +84,12 @@ $(BINDIR)/dvxdemo: mkdir -p $(BINDIR)/dvxdemo # Dependencies -$(OBJDIR)/imgview.o: imgview/imgview.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h -$(OBJDIR)/cpanel.o: cpanel/cpanel.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxPrefs.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/platform/dvxPlatform.h ../dvxshell/shellApp.h -$(OBJDIR)/progman.o: progman/progman.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h ../dvxshell/shellInfo.h -$(OBJDIR)/notepad.o: notepad/notepad.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvxshell/shellApp.h -$(OBJDIR)/clock.o: clock/clock.c ../dvx/dvxApp.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h ../tasks/taskswitch.h -$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/dvxVideo.h ../dvxshell/shellApp.h +$(OBJDIR)/imgview.o: imgview/imgview.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../core/dvxVideo.h ../shell/shellApp.h +$(OBJDIR)/cpanel.o: cpanel/cpanel.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxPrefs.h ../core/dvxWidget.h ../core/dvxWm.h ../core/platform/dvxPlatform.h ../shell/shellApp.h +$(OBJDIR)/progman.o: progman/progman.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../shell/shellApp.h ../shell/shellInfo.h +$(OBJDIR)/notepad.o: notepad/notepad.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../shell/shellApp.h +$(OBJDIR)/clock.o: clock/clock.c ../core/dvxApp.h ../core/dvxWidget.h ../core/dvxDraw.h ../core/dvxVideo.h ../shell/shellApp.h ../tasks/taskswitch.h +$(OBJDIR)/dvxdemo.o: dvxdemo/dvxdemo.c ../core/dvxApp.h ../core/dvxDialog.h ../core/dvxWidget.h ../core/dvxWm.h ../core/dvxVideo.h ../shell/shellApp.h clean: rm -f $(OBJDIR)/cpanel.o $(OBJDIR)/imgview.o $(OBJDIR)/progman.o $(OBJDIR)/notepad.o $(OBJDIR)/clock.o $(OBJDIR)/dvxdemo.o diff --git a/apps/cpanel/cpanel.c b/apps/cpanel/cpanel.c index adb41d4..c340ee9 100644 --- a/apps/cpanel/cpanel.c +++ b/apps/cpanel/cpanel.c @@ -14,9 +14,9 @@ #include "dvxPrefs.h" #include "dvxWidget.h" #include "dvxWm.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include "shellApp.h" -#include "thirdparty/stb_ds.h" +#include "stb_ds.h" #include #include diff --git a/apps/imgview/imgview.c b/apps/imgview/imgview.c index b493d9f..390a718 100644 --- a/apps/imgview/imgview.c +++ b/apps/imgview/imgview.c @@ -9,7 +9,7 @@ #include "dvxWidget.h" #include "dvxWm.h" #include "shellApp.h" -#include "thirdparty/stb_image.h" +#include "stb_image.h" #include #include diff --git a/apps/notepad/notepad.c b/apps/notepad/notepad.c index 78fd380..a789b36 100644 --- a/apps/notepad/notepad.c +++ b/apps/notepad/notepad.c @@ -14,7 +14,7 @@ #include "dvxDialog.h" #include "dvxWidget.h" #include "dvxWm.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include "shellApp.h" #include diff --git a/apps/progman/progman.c b/apps/progman/progman.c index 48f345f..9aca9c0 100644 --- a/apps/progman/progman.c +++ b/apps/progman/progman.c @@ -23,7 +23,7 @@ #include "dvxPrefs.h" #include "dvxWidget.h" #include "dvxWm.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include "shellApp.h" #include "shellTaskMgr.h" #include "shellInfo.h" diff --git a/dvx.ini b/config/dvx.ini similarity index 100% rename from dvx.ini rename to config/dvx.ini diff --git a/config/dvxshell.dep b/config/dvxshell.dep new file mode 100644 index 0000000..a0d5537 --- /dev/null +++ b/config/dvxshell.dep @@ -0,0 +1,14 @@ +libtasks +libdvx +box +button +checkbox +dropdown +label +listbox +listview +radio +separator +spacer +statbar +textinpt diff --git a/config/libdvx.dep b/config/libdvx.dep new file mode 100644 index 0000000..becc3d3 --- /dev/null +++ b/config/libdvx.dep @@ -0,0 +1 @@ +libtasks diff --git a/themes/cde.thm b/config/themes/cde.thm similarity index 100% rename from themes/cde.thm rename to config/themes/cde.thm diff --git a/themes/geos.thm b/config/themes/geos.thm similarity index 100% rename from themes/geos.thm rename to config/themes/geos.thm diff --git a/themes/win31.thm b/config/themes/win31.thm similarity index 100% rename from themes/win31.thm rename to config/themes/win31.thm diff --git a/wpaper/blueglow.jpg b/config/wpaper/blueglow.jpg similarity index 100% rename from wpaper/blueglow.jpg rename to config/wpaper/blueglow.jpg diff --git a/wpaper/swoop.jpg b/config/wpaper/swoop.jpg similarity index 100% rename from wpaper/swoop.jpg rename to config/wpaper/swoop.jpg diff --git a/wpaper/triangle.jpg b/config/wpaper/triangle.jpg similarity index 100% rename from wpaper/triangle.jpg rename to config/wpaper/triangle.jpg diff --git a/core/Makefile b/core/Makefile new file mode 100644 index 0000000..ecb9853 --- /dev/null +++ b/core/Makefile @@ -0,0 +1,74 @@ +# DVX Core Library Makefile for DJGPP cross-compilation +# +# Builds libdvx.lib -- core GUI infrastructure (draw, compositor, +# window manager, event dispatch, layout engine, widget infrastructure). +# Zero widget implementations -- those are in ../widgets/ as .wgt modules. + +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 + +OBJDIR = ../obj/core +LIBSDIR = ../bin/libs + +# Core sources +SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c \ + dvxApp.c dvxDialog.c dvxPrefs.c \ + widgetClass.c widgetCore.c widgetScrollbar.c \ + widgetLayout.c widgetEvent.c widgetOps.c + +OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) +TARGET = $(LIBSDIR)/libdvx.lib + +# libdvx.lib export prefixes +DVX_EXPORTS = -E _dvx -E _wgt -E _wm -E _prefs -E _rect -E _draw -E _pack -E _text \ + -E _setClip -E _resetClip -E _stbi_ -E _stbi_write -E _dirtyList \ + -E _widget \ + -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 + +.PHONY: all clean + +all: $(TARGET) $(LIBSDIR)/libdvx.dep + +$(LIBSDIR)/libdvx.dep: ../config/libdvx.dep | $(LIBSDIR) + sed 's/$$/\r/' $< > $@ + +$(TARGET): $(OBJS) | $(LIBSDIR) + $(DXE3GEN) -o $@ $(DVX_EXPORTS) -U $(OBJS) + +$(OBJDIR)/%.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(LIBSDIR): + mkdir -p $(LIBSDIR) + +# Dependencies +CORE_HDRS = dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h dvxWidget.h platform/dvxPlatform.h +$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h +$(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)/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)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h + +WIDGET_DEPS = widgetInternal.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) +$(OBJDIR)/widgetLayout.o: widgetLayout.c $(WIDGET_DEPS) +$(OBJDIR)/widgetEvent.o: widgetEvent.c $(WIDGET_DEPS) +$(OBJDIR)/widgetOps.o: widgetOps.c $(WIDGET_DEPS) + +clean: + rm -f $(OBJS) $(TARGET) $(LIBSDIR)/libdvx.dep diff --git a/dvx/README.md b/core/README.md similarity index 100% rename from dvx/README.md rename to core/README.md diff --git a/dvx/dvxApp.c b/core/dvxApp.c similarity index 99% rename from dvx/dvxApp.c rename to core/dvxApp.c index a16832d..83f2c68 100644 --- a/dvx/dvxApp.c +++ b/core/dvxApp.c @@ -31,11 +31,11 @@ #include "dvxApp.h" #include "dvxDialog.h" #include "dvxWidget.h" -#include "widgets/widgetInternal.h" +#include "widgetInternal.h" #include "dvxFont.h" #include "dvxCursor.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include "thirdparty/stb_ds.h" #include @@ -726,14 +726,18 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) { case WidgetCheckboxE: if (sFocusedWidget) { sFocusedWidget->focused = false; } - widgetCheckboxOnMouse(target, win->widgetRoot, 0, 0); + 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; } - widgetRadioOnMouse(target, win->widgetRoot, 0, 0); + if (target->wclass && target->wclass->onMouse) { + target->wclass->onMouse(target, win->widgetRoot, 0, 0); + } sFocusedWidget = target; wgtInvalidate(target); return true; @@ -4628,7 +4632,7 @@ static void updateCursorShape(AppContextT *ctx) { WidgetT *hit = widgetHitTest(win->widgetRoot, vx, vy); - if (hit && hit->type == WidgetListViewE && widgetListViewColBorderHit(hit, vx, vy)) { + if (hit && hit->type == WidgetListViewE && sListViewColBorderHitFn && sListViewColBorderHitFn(hit, vx, vy)) { newCursor = CURSOR_RESIZE_H; } diff --git a/dvx/dvxApp.h b/core/dvxApp.h similarity index 100% rename from dvx/dvxApp.h rename to core/dvxApp.h diff --git a/dvx/dvxComp.c b/core/dvxComp.c similarity index 99% rename from dvx/dvxComp.c rename to core/dvxComp.c index 32c09c6..617b9da 100644 --- a/dvx/dvxComp.c +++ b/core/dvxComp.c @@ -18,7 +18,7 @@ // exactly once per pixel per frame. #include "dvxComp.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include diff --git a/dvx/dvxComp.h b/core/dvxComp.h similarity index 100% rename from dvx/dvxComp.h rename to core/dvxComp.h diff --git a/dvx/dvxCursor.h b/core/dvxCursor.h similarity index 100% rename from dvx/dvxCursor.h rename to core/dvxCursor.h diff --git a/dvx/dvxDialog.c b/core/dvxDialog.c similarity index 99% rename from dvx/dvxDialog.c rename to core/dvxDialog.c index 2f285d8..9dee1dc 100644 --- a/dvx/dvxDialog.c +++ b/core/dvxDialog.c @@ -21,9 +21,9 @@ // context pointers through every widget callback. #include "dvxDialog.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include "dvxWidget.h" -#include "widgets/widgetInternal.h" +#include "widgetInternal.h" #include #include diff --git a/dvx/dvxDialog.h b/core/dvxDialog.h similarity index 100% rename from dvx/dvxDialog.h rename to core/dvxDialog.h diff --git a/dvx/dvxDraw.c b/core/dvxDraw.c similarity index 99% rename from dvx/dvxDraw.c rename to core/dvxDraw.c index e7d159a..6d44bf8 100644 --- a/dvx/dvxDraw.c +++ b/core/dvxDraw.c @@ -50,7 +50,7 @@ // unlikely, helping the branch predictor on Pentium and later. #include "dvxDraw.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include diff --git a/dvx/dvxDraw.h b/core/dvxDraw.h similarity index 100% rename from dvx/dvxDraw.h rename to core/dvxDraw.h diff --git a/dvx/dvxFont.h b/core/dvxFont.h similarity index 100% rename from dvx/dvxFont.h rename to core/dvxFont.h diff --git a/dvx/dvxIcon.c b/core/dvxIcon.c similarity index 100% rename from dvx/dvxIcon.c rename to core/dvxIcon.c diff --git a/dvx/dvxImageWrite.c b/core/dvxImageWrite.c similarity index 100% rename from dvx/dvxImageWrite.c rename to core/dvxImageWrite.c diff --git a/dvx/dvxPalette.h b/core/dvxPalette.h similarity index 100% rename from dvx/dvxPalette.h rename to core/dvxPalette.h diff --git a/dvx/dvxPrefs.c b/core/dvxPrefs.c similarity index 100% rename from dvx/dvxPrefs.c rename to core/dvxPrefs.c diff --git a/dvx/dvxPrefs.h b/core/dvxPrefs.h similarity index 100% rename from dvx/dvxPrefs.h rename to core/dvxPrefs.h diff --git a/dvx/dvxTypes.h b/core/dvxTypes.h similarity index 100% rename from dvx/dvxTypes.h rename to core/dvxTypes.h diff --git a/dvx/dvxVideo.c b/core/dvxVideo.c similarity index 99% rename from dvx/dvxVideo.c rename to core/dvxVideo.c index ffe2eb8..c2d65d8 100644 --- a/dvx/dvxVideo.c +++ b/core/dvxVideo.c @@ -42,7 +42,7 @@ // shuffling. #include "dvxVideo.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include "dvxPalette.h" #include diff --git a/dvx/dvxVideo.h b/core/dvxVideo.h similarity index 100% rename from dvx/dvxVideo.h rename to core/dvxVideo.h diff --git a/dvx/dvxWidget.h b/core/dvxWidget.h similarity index 93% rename from dvx/dvxWidget.h rename to core/dvxWidget.h index b97904d..512e2e3 100644 --- a/dvx/dvxWidget.h +++ b/core/dvxWidget.h @@ -67,10 +67,10 @@ struct WidgetClassT; // Widget type enum // ============================================================ // -// Used as the index into widgetClassTable[] (in widgetInternal.h) to -// look up the vtable for each widget type. Adding a new widget type -// requires adding an enum value here, a corresponding union member in -// WidgetT, and a WidgetClassT entry in widgetClassTable[]. +// 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, @@ -104,9 +104,15 @@ typedef enum { WidgetSpinnerE, WidgetScrollPaneE, WidgetSplitterE, - WidgetTimerE + 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 + // ============================================================ // ListView types // ============================================================ @@ -272,6 +278,41 @@ typedef struct { void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir); } ListViewDataT; +// ============================================================ +// Widget class vtable +// ============================================================ +// +// Each widget type has a WidgetClassT that defines its behavior. +// Built-in classes are static const; dynamically registered classes +// (from DXE plugins) are stored in the mutable region of +// widgetClassTable[]. +// +// Flags encode static properties checked by the framework: +// FOCUSABLE -- can receive keyboard focus (Tab navigation) +// BOX_CONTAINER -- uses the generic VBox/HBox layout algorithm +// HORIZ_CONTAINER -- lays out children horizontally (vs. vertical) +// 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 + +typedef struct WidgetClassT { + uint32_t flags; + 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); + void (*calcMinSize)(struct WidgetT *w, const BitmapFontT *font); + void (*layout)(struct WidgetT *w, const BitmapFontT *font); + 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 (*destroy)(struct WidgetT *w); + const char *(*getText)(const struct WidgetT *w); + void (*setText)(struct WidgetT *w, const char *text); +} WidgetClassT; + // ============================================================ // Widget structure // ============================================================ @@ -348,6 +389,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) 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); @@ -987,6 +1029,16 @@ void wgtSetTooltip(WidgetT *w, const char *text); // Draw borders around layout containers in ugly colors void wgtSetDebugLayout(struct AppContextT *ctx, bool enabled); +// ============================================================ +// Dynamic widget registration +// ============================================================ + +// 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. +// 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); + // ============================================================ // Layout (called internally; available for manual trigger) // ============================================================ diff --git a/dvx/dvxWm.c b/core/dvxWm.c similarity index 100% rename from dvx/dvxWm.c rename to core/dvxWm.c diff --git a/dvx/dvxWm.h b/core/dvxWm.h similarity index 100% rename from dvx/dvxWm.h rename to core/dvxWm.h diff --git a/dvx/platform/dvxPlatform.h b/core/platform/dvxPlatform.h similarity index 95% rename from dvx/platform/dvxPlatform.h rename to core/platform/dvxPlatform.h index 1bd0dda..bac77ab 100644 --- a/dvx/platform/dvxPlatform.h +++ b/core/platform/dvxPlatform.h @@ -22,7 +22,7 @@ #ifndef DVX_PLATFORM_H #define DVX_PLATFORM_H -#include "../dvxTypes.h" +#include "dvxTypes.h" // ============================================================ // Keyboard event @@ -231,4 +231,14 @@ const char *platformLineEnding(void); // On DOS this removes '\r' from CR+LF pairs. Returns the new length. int32_t platformStripLineEndings(char *buf, int32_t len); +// ============================================================ +// DXE module support +// ============================================================ + +// Register platform and C runtime symbols with the dynamic module +// loader so that DXE modules can resolve them at load time. On DOS +// this calls dlregsym() with the full DJGPP libc/libm/libgcc/platform +// export table. On platforms without DXE (Linux/SDL), this is a no-op. +void platformRegisterDxeExports(void); + #endif // DVX_PLATFORM_H diff --git a/dvx/platform/dvxPlatformDos.c b/core/platform/dvxPlatformDos.c similarity index 89% rename from dvx/platform/dvxPlatformDos.c rename to core/platform/dvxPlatformDos.c index 669264f..9373803 100644 --- a/dvx/platform/dvxPlatformDos.c +++ b/core/platform/dvxPlatformDos.c @@ -27,17 +27,24 @@ // of installing a real-mode callback for mouse events. #include "dvxPlatform.h" -#include "../dvxPalette.h" +#include "dvxPalette.h" #include #include +#include +#include #include +#include +#include #include #include #include #include #include +#include +#include #include +#include #include // DJGPP-specific headers -- this is the ONLY file that includes these @@ -1917,3 +1924,276 @@ static int32_t setVesaMode(uint16_t mode) { return 0; } + + +// ============================================================ +// DXE3 export table +// ============================================================ +// +// DJGPP's DXE3 modules have no implicit access to the host's symbol +// table. Every function or variable a DXE needs must be explicitly +// listed here and registered via dlregsym() before any dlopen(). +// +// Three categories: +// 1. Platform functions (all platform*() from this file) +// 2. C runtime (libc, libm, libgcc, DJGPP internals) +// 3. DXE3 runtime (dlopen/dlsym/dlclose/dlerror/dlregsym) + +// DJGPP stdio internals +extern FILE __dj_stdin; +extern FILE __dj_stdout; +extern FILE __dj_stderr; + +// libgcc 64-bit integer math helpers +extern long long __divdi3(long long, long long); +extern long long __moddi3(long long, long long); +extern long long __muldi3(long long, long long); +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; +extern void __dj_assert(const char *, const char *, int); +extern unsigned short __dj_ctype_flags[]; +extern unsigned char __dj_ctype_tolower[]; + +// GCC emulated thread-local storage +extern void *__emutls_get_address(void *); + +DXE_EXPORT_TABLE(sDxeExportTable) + + // --- platform --- + DXE_EXPORT(platformAltScanToChar) + DXE_EXPORT(platformChdir) + DXE_EXPORT(platformFlushRect) + DXE_EXPORT(platformGetMemoryInfo) + DXE_EXPORT(platformGetSystemInfo) + DXE_EXPORT(platformInit) + DXE_EXPORT(platformKeyboardGetModifiers) + DXE_EXPORT(platformKeyboardRead) + DXE_EXPORT(platformLineEnding) + DXE_EXPORT(platformMkdirRecursive) + DXE_EXPORT(platformMouseInit) + DXE_EXPORT(platformMousePoll) + DXE_EXPORT(platformMouseSetAccel) + DXE_EXPORT(platformMouseWarp) + DXE_EXPORT(platformMouseWheelInit) + DXE_EXPORT(platformMouseWheelPoll) + DXE_EXPORT(platformPathDirEnd) + DXE_EXPORT(platformRegisterDxeExports) + DXE_EXPORT(platformSpanCopy8) + DXE_EXPORT(platformSpanCopy16) + DXE_EXPORT(platformSpanCopy32) + DXE_EXPORT(platformSpanFill8) + DXE_EXPORT(platformSpanFill16) + DXE_EXPORT(platformSpanFill32) + DXE_EXPORT(platformStripLineEndings) + DXE_EXPORT(platformValidateFilename) + DXE_EXPORT(platformVideoEnumModes) + DXE_EXPORT(platformVideoFreeBuffers) + DXE_EXPORT(platformVideoInit) + DXE_EXPORT(platformVideoSetPalette) + DXE_EXPORT(platformVideoShutdown) + DXE_EXPORT(platformYield) + + // --- memory --- + DXE_EXPORT(calloc) + DXE_EXPORT(free) + DXE_EXPORT(malloc) + DXE_EXPORT(realloc) + + // --- string / memory ops --- + DXE_EXPORT(memchr) + DXE_EXPORT(memcmp) + DXE_EXPORT(memcpy) + DXE_EXPORT(memmove) + DXE_EXPORT(memset) + DXE_EXPORT(strcasecmp) + DXE_EXPORT(strcat) + DXE_EXPORT(strchr) + DXE_EXPORT(strcmp) + DXE_EXPORT(strcpy) + DXE_EXPORT(strcspn) + DXE_EXPORT(strdup) + DXE_EXPORT(strerror) + DXE_EXPORT(stricmp) + DXE_EXPORT(strlen) + DXE_EXPORT(strncasecmp) + DXE_EXPORT(strncat) + DXE_EXPORT(strncmp) + DXE_EXPORT(strncpy) + DXE_EXPORT(strpbrk) + DXE_EXPORT(strrchr) + DXE_EXPORT(strspn) + DXE_EXPORT(strstr) + DXE_EXPORT(strtok) + + // --- ctype --- + DXE_EXPORT(isalnum) + DXE_EXPORT(isalpha) + DXE_EXPORT(isdigit) + DXE_EXPORT(islower) + DXE_EXPORT(isprint) + DXE_EXPORT(ispunct) + DXE_EXPORT(isspace) + DXE_EXPORT(isupper) + DXE_EXPORT(isxdigit) + DXE_EXPORT(tolower) + DXE_EXPORT(toupper) + + // --- conversion --- + DXE_EXPORT(abs) + DXE_EXPORT(atof) + DXE_EXPORT(atoi) + DXE_EXPORT(atol) + DXE_EXPORT(labs) + DXE_EXPORT(strtod) + DXE_EXPORT(strtol) + DXE_EXPORT(strtoul) + + // --- formatted I/O --- + DXE_EXPORT(fprintf) + DXE_EXPORT(fputs) + DXE_EXPORT(fscanf) + DXE_EXPORT(printf) + DXE_EXPORT(puts) + DXE_EXPORT(snprintf) + DXE_EXPORT(sprintf) + DXE_EXPORT(sscanf) + DXE_EXPORT(vfprintf) + DXE_EXPORT(vprintf) + DXE_EXPORT(vsnprintf) + DXE_EXPORT(vsprintf) + + // --- character I/O --- + DXE_EXPORT(fgetc) + DXE_EXPORT(fgets) + DXE_EXPORT(fputc) + DXE_EXPORT(getc) + DXE_EXPORT(putc) + DXE_EXPORT(putchar) + DXE_EXPORT(ungetc) + + // --- file I/O --- + DXE_EXPORT(fclose) + DXE_EXPORT(feof) + DXE_EXPORT(ferror) + DXE_EXPORT(fflush) + DXE_EXPORT(fopen) + DXE_EXPORT(fread) + DXE_EXPORT(freopen) + DXE_EXPORT(fseek) + DXE_EXPORT(ftell) + DXE_EXPORT(fwrite) + DXE_EXPORT(remove) + DXE_EXPORT(rename) + DXE_EXPORT(rewind) + DXE_EXPORT(tmpfile) + DXE_EXPORT(tmpnam) + + // --- directory --- + DXE_EXPORT(closedir) + DXE_EXPORT(mkdir) + DXE_EXPORT(opendir) + DXE_EXPORT(readdir) + DXE_EXPORT(rmdir) + + // --- filesystem --- + DXE_EXPORT(access) + DXE_EXPORT(chdir) + DXE_EXPORT(getcwd) + DXE_EXPORT(realpath) + DXE_EXPORT(stat) + DXE_EXPORT(unlink) + + // --- time --- + DXE_EXPORT(clock) + DXE_EXPORT(difftime) + DXE_EXPORT(gmtime) + DXE_EXPORT(localtime) + DXE_EXPORT(mktime) + DXE_EXPORT(strftime) + DXE_EXPORT(time) + + // --- process / environment --- + DXE_EXPORT(abort) + DXE_EXPORT(atexit) + DXE_EXPORT(exit) + DXE_EXPORT(getenv) + DXE_EXPORT(system) + + // --- sorting / searching --- + DXE_EXPORT(bsearch) + DXE_EXPORT(qsort) + + // --- random --- + DXE_EXPORT(rand) + DXE_EXPORT(srand) + + // --- setjmp / signal --- + DXE_EXPORT(longjmp) + DXE_EXPORT(setjmp) + DXE_EXPORT(signal) + + // --- libm --- + DXE_EXPORT(acos) + DXE_EXPORT(asin) + DXE_EXPORT(atan) + DXE_EXPORT(atan2) + DXE_EXPORT(ceil) + DXE_EXPORT(cos) + DXE_EXPORT(exp) + DXE_EXPORT(fabs) + DXE_EXPORT(floor) + DXE_EXPORT(fmod) + DXE_EXPORT(frexp) + DXE_EXPORT(ldexp) + DXE_EXPORT(log) + DXE_EXPORT(log10) + DXE_EXPORT(modf) + DXE_EXPORT(pow) + DXE_EXPORT(sin) + DXE_EXPORT(sqrt) + DXE_EXPORT(tan) + + // --- errno --- + DXE_EXPORT(errno) + + // --- libgcc 64-bit integer math --- + DXE_EXPORT(__divdi3) + DXE_EXPORT(__moddi3) + DXE_EXPORT(__muldi3) + DXE_EXPORT(__udivdi3) + DXE_EXPORT(__udivmoddi4) + DXE_EXPORT(__umoddi3) + + // --- DJGPP internals --- + DXE_EXPORT(__dj_stdin) + DXE_EXPORT(__dj_stdout) + DXE_EXPORT(__dj_stderr) + DXE_EXPORT(__dj_assert) + DXE_EXPORT(__dj_ctype_flags) + DXE_EXPORT(__dj_ctype_tolower) + DXE_EXPORT(__djgpp_exception_state_ptr) + + // --- GCC internals --- + DXE_EXPORT(__emutls_get_address) + + // --- DXE3 runtime --- + DXE_EXPORT(dlopen) + DXE_EXPORT(dlsym) + DXE_EXPORT(dlclose) + DXE_EXPORT(dlerror) + DXE_EXPORT(dlregsym) + +DXE_EXPORT_END + + +// ============================================================ +// platformRegisterDxeExports +// ============================================================ + +void platformRegisterDxeExports(void) { + dlregsym(sDxeExportTable); +} diff --git a/dvx/thirdparty/stb_image.h b/core/thirdparty/stb_image.h similarity index 100% rename from dvx/thirdparty/stb_image.h rename to core/thirdparty/stb_image.h diff --git a/dvx/thirdparty/stb_image_write.h b/core/thirdparty/stb_image_write.h similarity index 100% rename from dvx/thirdparty/stb_image_write.h rename to core/thirdparty/stb_image_write.h diff --git a/core/widgetClass.c b/core/widgetClass.c new file mode 100644 index 0000000..e923857 --- /dev/null +++ b/core/widgetClass.c @@ -0,0 +1,49 @@ +// widgetClass.c -- Widget class table 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. +// +// This file contains only the table and the dynamic registration API. +// All class definitions live in their respective widget .c files. + +#include "widgetInternal.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). + +const WidgetClassT *widgetClassTable[WGT_MAX_TYPES]; + + +// ============================================================ +// wgtRegisterClass -- register a dynamic widget type at runtime +// ============================================================ +// +// 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. + +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; +} diff --git a/core/widgetCore.c b/core/widgetCore.c new file mode 100644 index 0000000..57a8a53 --- /dev/null +++ b/core/widgetCore.c @@ -0,0 +1,1805 @@ +// widgetCore.c -- Core widget infrastructure (alloc, tree ops, helpers) +// +// This file provides the foundation for the widget tree: allocation, +// parent-child linking, focus management, hit testing, and shared +// utility functions used across multiple widget types. +// +// Widgets form a tree using intrusive linked lists (firstChild/lastChild/ +// nextSibling pointers inside each WidgetT). This is a singly-linked +// child list with a tail pointer for O(1) append. The tree is owned +// by its root, which is attached to a WindowT. Destroying the root +// recursively destroys all descendants. +// +// Memory allocation is plain malloc/free rather than an arena or pool. +// The widget count per window is typically small (tens to low hundreds), +// so the allocation overhead is negligible on target hardware. An arena +// approach was considered but rejected because widgets can be individually +// created and destroyed at runtime (dialog dynamics, tree item insertion), +// which doesn't map cleanly to an arena pattern. + +#include "widgetInternal.h" + +#include +#include + +// ============================================================ +// Global state for drag and popup tracking +// ============================================================ +// +// These module-level pointers track ongoing UI interactions that span +// multiple mouse events (drags, popups, button presses). They are global +// rather than per-window because the DOS GUI is single-threaded and only +// one interaction can be active at a time. +// +// Each pointer is set when an interaction begins (e.g. mouse-down on a +// slider) and cleared when it ends (mouse-up). The event dispatcher in +// widgetEvent.c checks these before normal hit testing -- active drags +// take priority over everything else. +// +// All of these must be NULLed when the pointed-to widget is destroyed, +// otherwise dangling pointers would cause crashes. widgetDestroyChildren() +// and wgtDestroy() handle this cleanup. + +clock_t sDblClickTicks = 0; // set from ctx->dblClickTicks during first paint +bool sDebugLayout = false; +WidgetT *sFocusedWidget = NULL; // currently focused widget (O(1) access, avoids tree walk) +WidgetT *sOpenPopup = NULL; // dropdown/combobox with open popup list +WidgetT *sPressedButton = NULL; // button being held down (tracks mouse in/out) +WidgetT *sDragSlider = NULL; // slider being dragged +WidgetT *sDrawingCanvas = NULL; // canvas receiving paint strokes +WidgetT *sDragTextSelect = NULL; // text widget in drag-select mode +int32_t sDragOffset = 0; // pixel offset from drag start to thumb center +WidgetT *sResizeListView = NULL; // ListView undergoing column resize +int32_t sResizeCol = -1; // which column is being resized +int32_t sResizeStartX = 0; // mouse X at resize start +int32_t sResizeOrigW = 0; // column width at resize start +bool sResizeDragging = false; // true once mouse moves during column resize +WidgetT *sDragSplitter = NULL; // splitter being dragged +int32_t sDragSplitStart = 0; // mouse offset from splitter edge at drag start +WidgetT *sDragReorder = NULL; // list/tree widget in drag-reorder mode +WidgetT *sDragScrollbar = NULL; // widget whose scrollbar thumb is being dragged +int32_t sDragScrollbarOff = 0; // mouse offset within thumb at drag start +int32_t sDragScrollbarOrient = 0; // 0=vertical, 1=horizontal +bool (*sListViewColBorderHitFn)(const WidgetT *w, int32_t vx, int32_t vy) = NULL; +void (*sSplitterClampPosFn)(WidgetT *w, int32_t *pos) = NULL; +WidgetT *(*sTreeViewNextVisibleFn)(WidgetT *item, WidgetT *treeView) = NULL; + + +// ============================================================ +// widgetAddChild +// ============================================================ +// +// Appends a child to the end of the parent's child list. O(1) +// thanks to the lastChild tail pointer. The child list is singly- +// linked (nextSibling), which saves 4 bytes per widget vs doubly- +// linked and is sufficient because child removal is infrequent. + +void widgetAddChild(WidgetT *parent, WidgetT *child) { + child->parent = parent; + child->nextSibling = NULL; + + if (parent->lastChild) { + parent->lastChild->nextSibling = child; + parent->lastChild = child; + } else { + parent->firstChild = child; + parent->lastChild = child; + } +} + + +// ============================================================ +// widgetAlloc +// ============================================================ +// +// Allocates and zero-initializes a new widget, links it to its +// class vtable via widgetClassTable[], and optionally adds it as +// a child of the given parent. +// +// The memset to 0 is intentional -- it establishes sane defaults +// for all fields: NULL pointers, zero coordinates, no focus, +// no accel key, etc. Only visible and enabled default to true. +// +// The window pointer is inherited from the parent so that any +// widget in the tree can find its owning window without walking +// to the root. + +WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type) { + if (type < 0 || type >= WGT_MAX_TYPES || !widgetClassTable[type]) { + return NULL; + } + + WidgetT *w = (WidgetT *)malloc(sizeof(WidgetT)); + + if (!w) { + return NULL; + } + + memset(w, 0, sizeof(*w)); + w->type = type; + w->wclass = widgetClassTable[type]; + w->visible = true; + w->enabled = true; + + if (parent) { + w->window = parent->window; + widgetAddChild(parent, w); + } + + return w; +} + + +// ============================================================ +// widgetClearFocus +// ============================================================ + +void widgetClearFocus(WidgetT *root) { + if (!root) { + return; + } + + root->focused = false; + + for (WidgetT *c = root->firstChild; c; c = c->nextSibling) { + widgetClearFocus(c); + } +} + + +// ============================================================ +// widgetCountVisibleChildren +// ============================================================ + +int32_t widgetCountVisibleChildren(const WidgetT *w) { + int32_t count = 0; + + for (const WidgetT *c = w->firstChild; c; c = c->nextSibling) { + if (c->visible) { + count++; + } + } + + return count; +} + + +// ============================================================ +// widgetDestroyChildren +// ============================================================ +// +// Recursively destroys all descendants of a widget. Processes +// children depth-first (destroy grandchildren before the child +// itself) so that per-widget destroy callbacks see a consistent +// tree state. +// +// Critically, this function clears all global state pointers that +// reference destroyed widgets. Without this, any pending drag or +// focus state would become a dangling pointer. Each global is +// checked individually rather than cleared unconditionally to +// avoid disrupting unrelated ongoing interactions. + +void widgetDestroyChildren(WidgetT *w) { + WidgetT *child = w->firstChild; + + while (child) { + WidgetT *next = child->nextSibling; + widgetDestroyChildren(child); + + if (child->wclass && child->wclass->destroy) { + child->wclass->destroy(child); + } + + // Clear static references if they point to destroyed widgets + if (sFocusedWidget == child) { + sFocusedWidget = NULL; + } + + if (sOpenPopup == child) { + sOpenPopup = NULL; + } + + if (sPressedButton == child) { + sPressedButton = NULL; + } + + if (sDragSlider == child) { + sDragSlider = NULL; + } + + if (sDrawingCanvas == child) { + sDrawingCanvas = NULL; + } + + if (sResizeListView == child) { + sResizeListView = NULL; + sResizeCol = -1; + sResizeDragging = false; + } + + if (sDragScrollbar == child) { + sDragScrollbar = NULL; + } + + free(child); + child = next; + } + + w->firstChild = NULL; + w->lastChild = NULL; +} + + +// ============================================================ +// widgetDropdownPopupRect +// ============================================================ +// +// Calculates the screen rectangle for a dropdown/combobox popup list. +// Shared between Dropdown and ComboBox since they have identical +// popup positioning logic. +// +// The popup tries to open below the widget first. If there isn't +// enough room (popup would extend past the content area bottom), +// it flips to open above instead. This ensures the popup is always +// visible, even for dropdowns near the bottom of a window. +// +// Popup height is capped at DROPDOWN_MAX_VISIBLE items to prevent +// huge popups from dominating the screen. + +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->type == WidgetDropdownE) { + itemCount = w->as.dropdown.itemCount; + } else if (w->type == WidgetComboBoxE) { + itemCount = w->as.comboBox.itemCount; + } + + 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; // 2px border each side + + // Try below first, then above if no room + if (w->y + w->h + *popH <= contentH) { + *popY = w->y + w->h; + } else { + *popY = w->y - *popH; + + if (*popY < 0) { + *popY = 0; + } + } +} + + +// ============================================================ +// 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); + } +} + + +// ============================================================ +// widgetFindByAccel +// ============================================================ +// +// Finds a widget with the given Alt+key accelerator. Recurses the +// tree depth-first, respecting visibility and enabled state. +// +// Special case for TabPage widgets: even if the tab page itself is +// not visible (inactive tab), its accelKey is still checked. This +// allows Alt+key to switch to a different tab. However, children +// of invisible tab pages are NOT searched -- their accelerators +// should not be active when the tab is hidden. + +WidgetT *widgetFindByAccel(WidgetT *root, char key) { + if (!root || !root->enabled) { + return NULL; + } + + // Invisible tab pages: match the page itself (for tab switching) + // but don't recurse into children (their accels shouldn't be active) + if (!root->visible) { + if (root->type == WidgetTabPageE && root->accelKey == key) { + return root; + } + + return NULL; + } + + if (root->accelKey == key) { + return root; + } + + for (WidgetT *c = root->firstChild; c; c = c->nextSibling) { + WidgetT *found = widgetFindByAccel(c, key); + + if (found) { + return found; + } + } + + return NULL; +} + + +// ============================================================ +// widgetFindNextFocusable +// ============================================================ +// +// Implements Tab-order navigation: finds the next focusable widget +// after 'after' in depth-first tree order. The two-pass approach +// (search from 'after' to end, then wrap to start) ensures circular +// tabbing -- Tab on the last focusable widget wraps to the first. +// +// The pastAfter flag tracks whether we've passed the 'after' widget +// during traversal. Once past it, the next focusable widget is the +// answer. This avoids collecting all focusable widgets into an array +// just to find the next one -- the common case returns quickly. + +static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) { + if (!w->visible || !w->enabled) { + return NULL; + } + + if (after == NULL) { + *pastAfter = true; + } + + if (w == after) { + *pastAfter = true; + } else if (*pastAfter && widgetIsFocusable(w->type)) { + return w; + } + + for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { + WidgetT *found = findNextFocusableImpl(c, after, pastAfter); + + if (found) { + return found; + } + } + + return NULL; +} + +WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) { + bool pastAfter = false; + WidgetT *found = findNextFocusableImpl(root, after, &pastAfter); + + if (found) { + return found; + } + + // Wrap around -- search from the beginning + pastAfter = true; + return findNextFocusableImpl(root, NULL, &pastAfter); +} + + +// ============================================================ +// widgetFindPrevFocusable +// ============================================================ +// +// Shift+Tab navigation: finds the previous focusable widget. +// Unlike findNextFocusable which can short-circuit during traversal, +// finding the PREVIOUS widget requires knowing the full order. +// So this collects all focusable widgets into an array, finds the +// target's index, and returns index-1 (with wraparound). +// +// The explicit stack-based DFS (rather than recursion) is used here +// because we need to push children in reverse order to get the same +// left-to-right depth-first ordering as the recursive version. +// Fixed-size arrays (128 widgets, 64 stack depth) are adequate for +// any reasonable dialog layout and avoid dynamic allocation. + +WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) { + WidgetT *list[128]; + int32_t count = 0; + + // Collect all focusable widgets via depth-first traversal + WidgetT *stack[64]; + int32_t top = 0; + stack[top++] = root; + + while (top > 0) { + WidgetT *w = stack[--top]; + + if (!w->visible || !w->enabled) { + continue; + } + + if (widgetIsFocusable(w->type) && count < 128) { + list[count++] = w; + } + + // Push children in reverse order so first child is processed first + WidgetT *children[64]; + int32_t childCount = 0; + + for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { + if (childCount < 64) { + children[childCount++] = c; + } + } + + for (int32_t i = childCount - 1; i >= 0; i--) { + if (top < 64) { + stack[top++] = children[i]; + } + } + } + + if (count == 0) { + return NULL; + } + + // Find 'before' in the list + int32_t idx = -1; + + for (int32_t i = 0; i < count; i++) { + if (list[i] == before) { + idx = i; + break; + } + } + + if (idx <= 0) { + return list[count - 1]; // Wrap to last + } + + return list[idx - 1]; +} + + +// ============================================================ +// widgetFrameBorderWidth +// ============================================================ + +int32_t widgetFrameBorderWidth(const WidgetT *w) { + if (w->type != WidgetFrameE) { + return 0; + } + + if (w->as.frame.style == FrameFlatE) { + return FRAME_FLAT_BORDER; + } + + return FRAME_BEVEL_BORDER; +} + + +// ============================================================ +// widgetHitTest +// ============================================================ +// +// Recursive hit testing: finds the deepest (most specific) widget +// under the given coordinates. Returns the widget itself if no +// child is hit, or NULL if the point is outside this widget. +// +// Children are iterated front-to-back (first to last in the linked +// list), but the LAST match wins. This gives later siblings higher +// Z-order, which matches the painting order (later children paint +// on top of earlier ones). This is important for overlapping widgets, +// though in practice the layout engine rarely produces overlap. +// +// Widgets with WCLASS_NO_HIT_RECURSE stop the recursion -- the parent +// widget handles all mouse events for its children. This is used by +// TreeView, ScrollPane, ListView, and Splitter, which need to manage +// their own internal regions (scrollbars, column headers, tree +// expand buttons) that don't correspond to child widgets. + +WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) { + if (!w->visible) { + return NULL; + } + + if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) { + return NULL; + } + + // Widgets with WCLASS_NO_HIT_RECURSE manage their own children + if (w->wclass && (w->wclass->flags & WCLASS_NO_HIT_RECURSE)) { + return w; + } + + // Check children -- take the last match (topmost in Z-order) + WidgetT *hit = NULL; + + for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { + WidgetT *childHit = widgetHitTest(c, x, y); + + if (childHit) { + hit = childHit; + } + } + + return hit ? hit : w; +} + + +// ============================================================ +// widgetIsFocusable +// ============================================================ + +bool widgetIsFocusable(WidgetTypeE type) { + if (type < 0 || type >= WGT_MAX_TYPES || !widgetClassTable[type]) { + return false; + } + + return (widgetClassTable[type]->flags & WCLASS_FOCUSABLE) != 0; +} + + +// ============================================================ +// widgetIsBoxContainer +// ============================================================ +// +// Returns true for widget types that use the generic box layout. + +bool widgetIsBoxContainer(WidgetTypeE type) { + if (type < 0 || type >= WGT_MAX_TYPES || !widgetClassTable[type]) { + return false; + } + + return (widgetClassTable[type]->flags & WCLASS_BOX_CONTAINER) != 0; +} + + +// ============================================================ +// widgetIsHorizContainer +// ============================================================ +// +// Returns true for container types that lay out children horizontally. + +bool widgetIsHorizContainer(WidgetTypeE type) { + if (type < 0 || type >= WGT_MAX_TYPES || !widgetClassTable[type]) { + return false; + } + + return (widgetClassTable[type]->flags & WCLASS_HORIZ_CONTAINER) != 0; +} + + +// ============================================================ +// 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); + } +} + + +// ============================================================ +// widgetScrollbarThumb +// ============================================================ +// +// Calculates thumb position and size for a scrollbar track. +// Used by both the WM-level scrollbars and widget-internal scrollbars +// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior. +// +// The thumb size is proportional to visibleSize/totalSize -- a larger +// visible area means a larger thumb, giving visual feedback about how +// much content is scrollable. SB_MIN_THUMB prevents the thumb from +// becoming too small to grab with a mouse. + +void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize) { + *thumbSize = (trackLen * visibleSize) / totalSize; + + if (*thumbSize < SB_MIN_THUMB) { + *thumbSize = SB_MIN_THUMB; + } + + if (*thumbSize > trackLen) { + *thumbSize = trackLen; + } + + int32_t maxScroll = totalSize - visibleSize; + + if (maxScroll > 0) { + *thumbPos = ((trackLen - *thumbSize) * scrollPos) / maxScroll; + } else { + *thumbPos = 0; + } +} + + +// ============================================================ +// widgetRemoveChild +// ============================================================ +// +// Unlinks a child from its parent's child list. O(n) in the number +// of children because the singly-linked list requires walking to +// find the predecessor. This is acceptable because child removal +// is infrequent (widget destruction, tree item reordering). + +void widgetRemoveChild(WidgetT *parent, WidgetT *child) { + WidgetT *prev = NULL; + + for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) { + if (c == child) { + if (prev) { + prev->nextSibling = c->nextSibling; + } else { + parent->firstChild = c->nextSibling; + } + + if (parent->lastChild == child) { + parent->lastChild = prev; + } + + child->nextSibling = NULL; + child->parent = NULL; + return; + } + + prev = c; + } +} + + +// ============================================================ +// Shared text editing infrastructure +// ============================================================ +// +// The following functions provide clipboard, multi-click detection, +// word boundary logic, cross-widget selection clearing, and the +// single-line text editing engine. They are shared across multiple +// widget DXEs (TextInput, TextArea, ComboBox, Spinner, AnsiTerm) +// and live here in the core library so all DXEs can link to them. + +#define CLIPBOARD_MAX 4096 + +// Shared clipboard -- process-wide, not per-widget. +static char sClipboard[CLIPBOARD_MAX]; +static int32_t sClipboardLen = 0; + +// Multi-click state +static clock_t sLastClickTime = 0; +static int32_t sLastClickX = -1; +static int32_t sLastClickY = -1; +static int32_t sClickCount = 0; + +// 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; + +// TextArea line helpers (static copies for widgetTextDragUpdate) +static int32_t textAreaCountLines(const char *buf, int32_t len); +static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col); +static int32_t textAreaGetLineCount(WidgetT *w); +static int32_t textAreaGetMaxLineLen(WidgetT *w); +static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row); +static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row); +static int32_t textAreaMaxLineLen(const char *buf, int32_t len); + +// Shared undo/selection helpers +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); + + +static bool clearSelectionOnWidget(WidgetT *w) { + if (w->type == WidgetTextInputE) { + if (w->as.textInput.selStart != w->as.textInput.selEnd) { + w->as.textInput.selStart = -1; + w->as.textInput.selEnd = -1; + return true; + } + + w->as.textInput.selStart = -1; + w->as.textInput.selEnd = -1; + } else if (w->type == WidgetTextAreaE) { + if (w->as.textArea.selAnchor != w->as.textArea.selCursor) { + w->as.textArea.selAnchor = -1; + w->as.textArea.selCursor = -1; + return true; + } + + w->as.textArea.selAnchor = -1; + w->as.textArea.selCursor = -1; + } else if (w->type == WidgetComboBoxE) { + if (w->as.comboBox.selStart != w->as.comboBox.selEnd) { + w->as.comboBox.selStart = -1; + w->as.comboBox.selEnd = -1; + return true; + } + + w->as.comboBox.selStart = -1; + w->as.comboBox.selEnd = -1; + } else if (w->type == WidgetAnsiTermE) { + if (w->as.ansiTerm->selStartLine >= 0 && + (w->as.ansiTerm->selStartLine != w->as.ansiTerm->selEndLine || + w->as.ansiTerm->selStartCol != w->as.ansiTerm->selEndCol)) { + w->as.ansiTerm->dirtyRows = 0xFFFFFFFF; + w->as.ansiTerm->selStartLine = -1; + w->as.ansiTerm->selStartCol = -1; + w->as.ansiTerm->selEndLine = -1; + w->as.ansiTerm->selEndCol = -1; + w->as.ansiTerm->selecting = false; + return true; + } + + w->as.ansiTerm->selStartLine = -1; + w->as.ansiTerm->selStartCol = -1; + w->as.ansiTerm->selEndLine = -1; + w->as.ansiTerm->selEndCol = -1; + w->as.ansiTerm->selecting = false; + } + + return false; +} + + +// 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); + } +} + + +void clipboardCopy(const char *text, int32_t len) { + if (!text || len <= 0) { + return; + } + + if (len > CLIPBOARD_MAX - 1) { + len = CLIPBOARD_MAX - 1; + } + + memcpy(sClipboard, text, len); + sClipboard[len] = '\0'; + sClipboardLen = len; +} + + +const char *clipboardGet(int32_t *outLen) { + if (outLen) { + *outLen = sClipboardLen; + } + + return sClipboard; +} + + +bool isWordChar(char c) { + return isalnum((unsigned char)c) || c == '_'; +} + + +int32_t multiClickDetect(int32_t vx, int32_t vy) { + clock_t now = clock(); + + if ((now - sLastClickTime) < sDblClickTicks && + abs(vx - sLastClickX) < 4 && abs(vy - sLastClickY) < 4) { + sClickCount++; + } else { + sClickCount = 1; + } + + sLastClickTime = now; + sLastClickX = vx; + sLastClickY = vy; + + return sClickCount; +} + + +// ============================================================ +// TextArea line helpers (static copies for widgetTextDragUpdate) +// ============================================================ + +static int32_t textAreaCountLines(const char *buf, int32_t len) { + int32_t lines = 1; + + for (int32_t i = 0; i < len; i++) { + if (buf[i] == '\n') { + lines++; + } + } + + return lines; +} + + +static int32_t textAreaGetLineCount(WidgetT *w) { + if (w->as.textArea.cachedLines < 0) { + w->as.textArea.cachedLines = textAreaCountLines(w->as.textArea.buf, w->as.textArea.len); + } + + return w->as.textArea.cachedLines; +} + + +static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row) { + (void)len; + int32_t off = 0; + + for (int32_t r = 0; r < row; r++) { + while (off < len && buf[off] != '\n') { + off++; + } + + if (off < len) { + off++; + } + } + + return off; +} + + +static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row) { + int32_t start = textAreaLineStart(buf, len, row); + int32_t end = start; + + while (end < len && buf[end] != '\n') { + end++; + } + + return end - start; +} + + +static int32_t textAreaMaxLineLen(const char *buf, int32_t len) { + int32_t maxLen = 0; + int32_t curLen = 0; + + for (int32_t i = 0; i < len; i++) { + if (buf[i] == '\n') { + if (curLen > maxLen) { + maxLen = curLen; + } + curLen = 0; + } else { + curLen++; + } + } + + if (curLen > maxLen) { + maxLen = curLen; + } + + return maxLen; +} + + +static int32_t textAreaGetMaxLineLen(WidgetT *w) { + if (w->as.textArea.cachedMaxLL < 0) { + w->as.textArea.cachedMaxLL = textAreaMaxLineLen(w->as.textArea.buf, w->as.textArea.len); + } + + return w->as.textArea.cachedMaxLL; +} + + +static int32_t textAreaCursorToOff(const char *buf, int32_t len, int32_t row, int32_t col) { + int32_t start = textAreaLineStart(buf, len, row); + int32_t lineL = textAreaLineLen(buf, len, row); + int32_t clampC = col < lineL ? col : lineL; + + return start + clampC; +} + + +// ============================================================ +// Shared undo/selection helpers +// ============================================================ + +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; +} + + +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; +} + + +// ============================================================ +// 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; +} + + +// ============================================================ +// widgetTextDragUpdate -- update selection during mouse drag +// ============================================================ +// +// Called by the event loop on mouse-move while sDragTextSelect is set. +// Extends the selection from the anchor to the current mouse position. +// Handles auto-scroll: when the mouse is past the widget edges, the +// scroll offset is nudged by one unit per event, creating a smooth +// scroll-while-dragging effect. +void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { + AppContextT *ctx = (AppContextT *)root->userData; + const BitmapFontT *font = &ctx->font; + + if (w->type == WidgetTextInputE) { + int32_t leftEdge = w->x + TEXT_INPUT_PAD; + int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth; + widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.textInput.len, &w->as.textInput.cursorPos, &w->as.textInput.scrollOff, &w->as.textInput.selEnd); + } else if (w->type == WidgetTextAreaE) { + int32_t innerX = w->x + 2 + 2; // TEXTAREA_BORDER + TEXTAREA_PAD + int32_t innerY = w->y + 2; // TEXTAREA_BORDER + int32_t innerW = w->w - 2 * 2 - 2 * 2 - 14; // borders, pads, scrollbar + int32_t visCols = innerW / font->charWidth; + int32_t maxLL = textAreaGetMaxLineLen(w); + bool needHSb = (maxLL > visCols); + int32_t innerH = w->h - 2 * 2 - (needHSb ? 14 : 0); + int32_t visRows = innerH / font->charHeight; + int32_t totalLines = textAreaGetLineCount(w); + + if (visRows < 1) { + visRows = 1; + } + + if (visCols < 1) { + visCols = 1; + } + + // Auto-scroll vertically + if (vy < innerY && w->as.textArea.scrollRow > 0) { + w->as.textArea.scrollRow--; + } else if (vy >= innerY + visRows * font->charHeight && w->as.textArea.scrollRow + visRows < totalLines) { + w->as.textArea.scrollRow++; + } + + // Auto-scroll horizontally + int32_t rightEdge = innerX + visCols * font->charWidth; + + if (vx < innerX && w->as.textArea.scrollCol > 0) { + w->as.textArea.scrollCol--; + } else if (vx >= rightEdge && w->as.textArea.scrollCol < maxLL - visCols) { + w->as.textArea.scrollCol++; + } + + int32_t relX = vx - innerX; + int32_t relY = vy - innerY; + + int32_t clickRow = w->as.textArea.scrollRow + relY / font->charHeight; + int32_t clickCol = w->as.textArea.scrollCol + relX / font->charWidth; + + if (clickRow < 0) { + clickRow = 0; + } + + if (clickRow >= totalLines) { + clickRow = totalLines - 1; + } + + if (clickCol < 0) { + clickCol = 0; + } + + int32_t lineL = textAreaLineLen(w->as.textArea.buf, w->as.textArea.len, clickRow); + + if (clickCol > lineL) { + clickCol = lineL; + } + + w->as.textArea.cursorRow = clickRow; + w->as.textArea.cursorCol = clickCol; + w->as.textArea.desiredCol = clickCol; + w->as.textArea.selCursor = textAreaCursorToOff(w->as.textArea.buf, w->as.textArea.len, clickRow, clickCol); + } else if (w->type == WidgetComboBoxE) { + int32_t leftEdge = w->x + TEXT_INPUT_PAD; + int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2 - 16) / font->charWidth; + widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.comboBox.len, &w->as.comboBox.cursorPos, &w->as.comboBox.scrollOff, &w->as.comboBox.selEnd); + } else if (w->type == WidgetAnsiTermE) { + int32_t baseX = w->x + 2; // ANSI_BORDER + int32_t baseY = w->y + 2; + int32_t cols = w->as.ansiTerm->cols; + int32_t rows = w->as.ansiTerm->rows; + + int32_t clickRow = (vy - baseY) / font->charHeight; + int32_t clickCol = (vx - baseX) / font->charWidth; + + if (clickRow < 0) { + clickRow = 0; + } + + if (clickRow >= rows) { + clickRow = rows - 1; + } + + if (clickCol < 0) { + clickCol = 0; + } + + if (clickCol >= cols) { + clickCol = cols; + } + + w->as.ansiTerm->selEndLine = w->as.ansiTerm->scrollPos + clickRow; + w->as.ansiTerm->selEndCol = clickCol; + w->as.ansiTerm->dirtyRows = 0xFFFFFFFF; + } +} + + +// ============================================================ +// 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) { + if (sClipboardLen > 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 < sClipboardLen && paste < canFit; i++) { + if (sClipboard[i] != '\n' && sClipboard[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 < sClipboardLen && j < paste; i++) { + if (sClipboard[i] != '\n' && sClipboard[i] != '\r') { + buf[pos + j] = sClipboard[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[CLIPBOARD_MAX]; + 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->type == WidgetComboBoxE) { + fieldW -= DROPDOWN_BTN_WIDTH; + } + + 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); + } + } +} + + +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; +} diff --git a/dvx/widgets/widgetEvent.c b/core/widgetEvent.c similarity index 98% rename from dvx/widgets/widgetEvent.c rename to core/widgetEvent.c index 4678213..6f15e9f 100644 --- a/dvx/widgets/widgetEvent.c +++ b/core/widgetEvent.c @@ -258,7 +258,9 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) { // Handle canvas drawing (mouse move while pressed) if (sDrawingCanvas && (buttons & MOUSE_LEFT)) { - widgetCanvasOnMouse(sDrawingCanvas, root, x, y); + if (sDrawingCanvas->wclass && sDrawingCanvas->wclass->onMouse) { + sDrawingCanvas->wclass->onMouse(sDrawingCanvas, root, x, y); + } wgtInvalidatePaint(root); return; } @@ -387,7 +389,9 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) { pos = y - sDragSplitter->y - sDragSplitStart; } - widgetSplitterClampPos(sDragSplitter, &pos); + if (sSplitterClampPosFn) { + sSplitterClampPosFn(sDragSplitter, &pos); + } if (pos != sDragSplitter->as.splitter.dividerPos) { sDragSplitter->as.splitter.dividerPos = pos; @@ -1088,7 +1092,7 @@ void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) { WidgetT *target = NULL; bool after = false; - for (WidgetT *v = first; v; v = widgetTreeViewNextVisible(v, w)) { + for (WidgetT *v = first; v; v = sTreeViewNextVisibleFn ? sTreeViewNextVisibleFn(v, w) : NULL) { int32_t itemBot = curY + font->charHeight; int32_t mid = curY + font->charHeight / 2; @@ -1101,7 +1105,7 @@ void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) { curY = itemBot; // Check if mouse is between this item and next - WidgetT *next = widgetTreeViewNextVisible(v, w); + WidgetT *next = sTreeViewNextVisibleFn ? sTreeViewNextVisibleFn(v, w) : NULL; if (!next || y < itemBot) { target = v; diff --git a/dvx/widgets/widgetInternal.h b/core/widgetInternal.h similarity index 91% rename from dvx/widgets/widgetInternal.h rename to core/widgetInternal.h index 9a64a85..465ee7a 100644 --- a/dvx/widgets/widgetInternal.h +++ b/core/widgetInternal.h @@ -14,61 +14,28 @@ #ifndef WIDGET_INTERNAL_H #define WIDGET_INTERNAL_H -#include "../dvxWidget.h" -#include "../dvxApp.h" -#include "../dvxDraw.h" -#include "../dvxWm.h" -#include "../dvxVideo.h" +#include "dvxWidget.h" +#include "dvxApp.h" +#include "dvxDraw.h" +#include "dvxWm.h" +#include "dvxVideo.h" #include #include #include // ============================================================ -// Widget class vtable +// Widget class table // ============================================================ // -// Each widget type has a WidgetClassT entry in widgetClassTable[], indexed -// by WidgetTypeE. This is a C implementation of virtual dispatch: instead -// of a switch(type) in every operation, the code calls w->wclass->paint() -// etc. The vtable pointer is set once at widget creation and never changes. +// WidgetClassT and WCLASS_* flags are now in dvxWidget.h (public API) +// so that DXE plugins can register new widget types. // -// Flags encode static properties that the framework needs without calling -// into the vtable: -// FOCUSABLE -- can receive keyboard focus (Tab navigation) -// BOX_CONTAINER -- uses the generic VBox/HBox layout algorithm -// HORIZ_CONTAINER -- lays out children horizontally (vs. default vertical) -// PAINTS_CHILDREN -- the widget's paint function handles child rendering -// (e.g. TabControl only paints the active tab page) -// NO_HIT_RECURSE -- hit testing stops at this widget and doesn't recurse -// into children (e.g. ListBox handles its own items) -// -// NULL function pointers are valid and mean "no-op" for that operation. -// paintOverlay is used by widgets that need to draw outside their bounds -// (dropdown popup lists), rendered in a separate pass after all widgets. - -#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 - -typedef struct WidgetClassT { - uint32_t flags; - void (*paint)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); - void (*paintOverlay)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors); - void (*calcMinSize)(WidgetT *w, const BitmapFontT *font); - void (*layout)(WidgetT *w, const BitmapFontT *font); - void (*onMouse)(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy); - void (*onKey)(WidgetT *w, int32_t key, int32_t mod); - void (*destroy)(WidgetT *w); - const char *(*getText)(const WidgetT *w); - void (*setText)(WidgetT *w, const char *text); -} WidgetClassT; - -// Global vtable array -- one entry per WidgetTypeE value. Defined in -// widgetCore.c. Must stay in sync with the WidgetTypeE enum order. -extern const WidgetClassT *widgetClassTable[]; +// 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 @@ -194,6 +161,15 @@ 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 diff --git a/dvx/widgets/widgetLayout.c b/core/widgetLayout.c similarity index 100% rename from dvx/widgets/widgetLayout.c rename to core/widgetLayout.c diff --git a/dvx/widgets/widgetOps.c b/core/widgetOps.c similarity index 100% rename from dvx/widgets/widgetOps.c rename to core/widgetOps.c diff --git a/dvx/widgets/widgetScrollbar.c b/core/widgetScrollbar.c similarity index 100% rename from dvx/widgets/widgetScrollbar.c rename to core/widgetScrollbar.c diff --git a/dvx/Makefile b/dvx/Makefile deleted file mode 100644 index 156d3fa..0000000 --- a/dvx/Makefile +++ /dev/null @@ -1,140 +0,0 @@ -# DVX GUI Library Makefile for DJGPP cross-compilation - -DJGPP_PREFIX = $(HOME)/djgpp/djgpp -CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc -DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib -AR = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar -RANLIB = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ranlib -CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../tasks - -OBJDIR = ../obj/dvx -WOBJDIR = ../obj/dvx/widgets -POBJDIR = ../obj/dvx/platform -LIBDIR = ../lib - -SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c dvxDialog.c dvxPrefs.c - -PSRCS = platform/dvxPlatformDos.c - - -WSRCS = widgets/widgetAnsiTerm.c \ - widgets/widgetClass.c \ - widgets/widgetCore.c \ - widgets/widgetScrollbar.c \ - widgets/widgetLayout.c \ - widgets/widgetEvent.c \ - widgets/widgetOps.c \ - widgets/widgetBox.c \ - widgets/widgetButton.c \ - widgets/widgetCheckbox.c \ - widgets/widgetComboBox.c \ - widgets/widgetDropdown.c \ - widgets/widgetCanvas.c \ - widgets/widgetImage.c \ - widgets/widgetImageButton.c \ - widgets/widgetLabel.c \ - widgets/widgetListBox.c \ - widgets/widgetListView.c \ - widgets/widgetProgressBar.c \ - widgets/widgetRadio.c \ - widgets/widgetScrollPane.c \ - widgets/widgetSeparator.c \ - widgets/widgetSplitter.c \ - widgets/widgetSlider.c \ - widgets/widgetSpacer.c \ - widgets/widgetSpinner.c \ - widgets/widgetStatusBar.c \ - widgets/widgetTabControl.c \ - widgets/widgetTextInput.c \ - widgets/widgetTimer.c \ - widgets/widgetToolbar.c \ - widgets/widgetTreeView.c - -OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) -POBJS = $(patsubst platform/%.c,$(POBJDIR)/%.o,$(PSRCS)) -WOBJS = $(patsubst widgets/%.c,$(WOBJDIR)/%.o,$(WSRCS)) -TARGET = $(LIBDIR)/libdvx.a - -.PHONY: all clean - -all: $(TARGET) - -$(TARGET): $(OBJS) $(POBJS) $(WOBJS) | $(LIBDIR) - $(AR) rcs $@ $(OBJS) $(POBJS) $(WOBJS) - $(RANLIB) $@ - -$(OBJDIR)/%.o: %.c | $(OBJDIR) - $(CC) $(CFLAGS) -c -o $@ $< - -$(POBJDIR)/%.o: platform/%.c | $(POBJDIR) - $(CC) $(CFLAGS) -c -o $@ $< - -$(WOBJDIR)/%.o: widgets/%.c | $(WOBJDIR) - $(CC) $(CFLAGS) -c -o $@ $< - -$(OBJDIR): - mkdir -p $(OBJDIR) - -$(POBJDIR): - mkdir -p $(POBJDIR) - -$(WOBJDIR): - mkdir -p $(WOBJDIR) - -$(LIBDIR): - mkdir -p $(LIBDIR) - -# Dependencies -$(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes.h dvxPalette.h -$(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)/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 widgets/widgetInternal.h dvxTypes.h dvxDraw.h - -$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h - -# Platform file dependencies -$(POBJDIR)/dvxPlatformDos.o: platform/dvxPlatformDos.c platform/dvxPlatform.h dvxTypes.h dvxPalette.h - -# Thirdparty file dependencies - -# Widget file dependencies -WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h -$(WOBJDIR)/widgetClass.o: widgets/widgetClass.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetAnsiTerm.o: widgets/widgetAnsiTerm.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetCore.o: widgets/widgetCore.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetLayout.o: widgets/widgetLayout.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetEvent.o: widgets/widgetEvent.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetOps.o: widgets/widgetOps.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetBox.o: widgets/widgetBox.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetCanvas.o: widgets/widgetCanvas.c $(WIDGET_DEPS) thirdparty/stb_image.h thirdparty/stb_image_write.h -$(WOBJDIR)/widgetButton.o: widgets/widgetButton.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetCheckbox.o: widgets/widgetCheckbox.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetComboBox.o: widgets/widgetComboBox.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetDropdown.o: widgets/widgetDropdown.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetImage.o: widgets/widgetImage.c $(WIDGET_DEPS) thirdparty/stb_image.h -$(WOBJDIR)/widgetImageButton.o: widgets/widgetImageButton.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetLabel.o: widgets/widgetLabel.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetListBox.o: widgets/widgetListBox.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetListView.o: widgets/widgetListView.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetProgressBar.o: widgets/widgetProgressBar.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetRadio.o: widgets/widgetRadio.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetScrollPane.o: widgets/widgetScrollPane.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetSeparator.o: widgets/widgetSeparator.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetSplitter.o: widgets/widgetSplitter.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetSlider.o: widgets/widgetSlider.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetSpacer.o: widgets/widgetSpacer.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetSpinner.o: widgets/widgetSpinner.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetStatusBar.o: widgets/widgetStatusBar.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetTabControl.o: widgets/widgetTabControl.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetTextInput.o: widgets/widgetTextInput.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetTimer.o: widgets/widgetTimer.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetToolbar.o: widgets/widgetToolbar.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetScrollbar.o: widgets/widgetScrollbar.c $(WIDGET_DEPS) -$(WOBJDIR)/widgetTreeView.o: widgets/widgetTreeView.c $(WIDGET_DEPS) - -clean: - rm -f $(OBJS) $(POBJS) $(WOBJS) $(TARGET) diff --git a/dvx/widgets/widgetClass.c b/dvx/widgets/widgetClass.c deleted file mode 100644 index 28cb682..0000000 --- a/dvx/widgets/widgetClass.c +++ /dev/null @@ -1,536 +0,0 @@ -// widgetClass.c -- Widget class vtable definitions -// -// This file implements a C vtable pattern for the widget type system. -// Each widget type has a static const WidgetClassT struct that defines -// its behavior -- paint, layout, mouse handling, keyboard handling, etc. -// A master table (widgetClassTable[]) maps WidgetTypeE enum values to -// their class definitions, enabling O(1) dispatch. -// -// Why a vtable approach instead of switch statements: -// 1. Adding a new widget type is purely additive -- define a new class -// struct and add one entry to the table. No existing switch -// statements need modification, reducing the risk of forgetting -// a case. -// 2. NULL function pointers indicate "no behavior" naturally. A box -// container has no paint function because the framework paints -// its children directly. A label has no onKey handler because -// it's not interactive. This is cleaner than empty case blocks. -// 3. The flags field combines multiple boolean properties into a -// single bitmask, avoiding per-type if-chains in hot paths -// like hit testing and layout. -// -// Class flags: -// WCLASS_FOCUSABLE -- widget can receive keyboard focus (Tab order) -// WCLASS_BOX_CONTAINER -- uses the generic box layout engine (VBox/HBox) -// WCLASS_HORIZ_CONTAINER -- lays out children horizontally (HBox variant) -// WCLASS_PAINTS_CHILDREN -- widget handles its own child painting (TabControl, -// TreeView, ScrollPane, Splitter). The default paint -// walker skips children for these widgets. -// WCLASS_NO_HIT_RECURSE -- hit testing stops at this widget instead of -// recursing into children. Used by widgets that -// manage their own internal click regions (TreeView, -// ScrollPane, ListView, Splitter). - -#include "widgetInternal.h" - -// ============================================================ -// Per-type class definitions -// ============================================================ -// -// Containers (VBox, HBox, RadioGroup, TabPage, StatusBar, Toolbar) -// typically have NULL for most function pointers because their -// behavior is handled generically by the box layout engine and -// the default child-walking paint logic. -// -// Leaf widgets (Button, Label, TextInput, etc.) override paint, -// calcMinSize, and usually onMouse/onKey for interactivity. - -static const WidgetClassT sClassVBox = { - .flags = WCLASS_BOX_CONTAINER, - .paint = NULL, - .paintOverlay = NULL, - .calcMinSize = NULL, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassHBox = { - .flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER, - .paint = NULL, - .paintOverlay = NULL, - .calcMinSize = NULL, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassLabel = { - .flags = 0, - .paint = widgetLabelPaint, - .paintOverlay = NULL, - .calcMinSize = widgetLabelCalcMinSize, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = widgetLabelGetText, - .setText = widgetLabelSetText -}; - -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 -}; - -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 -}; - -static const WidgetClassT sClassRadioGroup = { - .flags = WCLASS_BOX_CONTAINER, - .paint = NULL, - .paintOverlay = NULL, - .calcMinSize = NULL, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .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 -}; - -// TextInput and TextArea have destroy functions because they dynamically -// allocate their text buffer and undo buffer. Most other widgets store -// all data inline in the WidgetT union and need no cleanup. -static const WidgetClassT sClassTextInput = { - .flags = WCLASS_FOCUSABLE, - .paint = widgetTextInputPaint, - .paintOverlay = NULL, - .calcMinSize = widgetTextInputCalcMinSize, - .layout = NULL, - .onMouse = widgetTextInputOnMouse, - .onKey = widgetTextInputOnKey, - .destroy = widgetTextInputDestroy, - .getText = widgetTextInputGetText, - .setText = widgetTextInputSetText -}; - -static const WidgetClassT sClassTextArea = { - .flags = WCLASS_FOCUSABLE, - .paint = widgetTextAreaPaint, - .paintOverlay = NULL, - .calcMinSize = widgetTextAreaCalcMinSize, - .layout = NULL, - .onMouse = widgetTextAreaOnMouse, - .onKey = widgetTextAreaOnKey, - .destroy = widgetTextAreaDestroy, - .getText = widgetTextAreaGetText, - .setText = widgetTextAreaSetText -}; - -static const WidgetClassT sClassListBox = { - .flags = WCLASS_FOCUSABLE, - .paint = widgetListBoxPaint, - .paintOverlay = NULL, - .calcMinSize = widgetListBoxCalcMinSize, - .layout = NULL, - .onMouse = widgetListBoxOnMouse, - .onKey = widgetListBoxOnKey, - .destroy = widgetListBoxDestroy, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassSpacer = { - .flags = 0, - .paint = NULL, - .paintOverlay = NULL, - .calcMinSize = widgetSpacerCalcMinSize, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassSeparator = { - .flags = 0, - .paint = widgetSeparatorPaint, - .paintOverlay = NULL, - .calcMinSize = widgetSeparatorCalcMinSize, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -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 -}; - -// Dropdown and ComboBox use paintOverlay to draw their popup lists -// on top of the entire widget tree. This is rendered in a separate -// pass after the main paint, so the popup can overlap sibling widgets. -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 -}; - -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 -}; - -static const WidgetClassT sClassProgressBar = { - .flags = 0, - .paint = widgetProgressBarPaint, - .paintOverlay = NULL, - .calcMinSize = widgetProgressBarCalcMinSize, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -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 -}; - -// TabControl has both PAINTS_CHILDREN and a custom layout function -// because it needs to show only the active tab page's children and -// position them inside the tab content area (below the tab strip). -static const WidgetClassT sClassTabControl = { - .flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN, - .paint = widgetTabControlPaint, - .paintOverlay = NULL, - .calcMinSize = widgetTabControlCalcMinSize, - .layout = widgetTabControlLayout, - .onMouse = widgetTabControlOnMouse, - .onKey = widgetTabControlOnKey, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassTabPage = { - .flags = WCLASS_BOX_CONTAINER, - .paint = NULL, - .paintOverlay = NULL, - .calcMinSize = NULL, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -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 -}; - -static const WidgetClassT sClassToolbar = { - .flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER, - .paint = widgetToolbarPaint, - .paintOverlay = NULL, - .calcMinSize = NULL, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -// TreeView uses all three special flags: -// PAINTS_CHILDREN -- it renders tree items itself (with indentation, -// expand/collapse buttons, and selection highlighting) -// NO_HIT_RECURSE -- mouse clicks go to the TreeView widget, which -// figures out which tree item was clicked based on scroll position -// and item Y coordinates, rather than letting the hit tester -// recurse into child TreeItem widgets -static const WidgetClassT sClassTreeView = { - .flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE, - .paint = widgetTreeViewPaint, - .paintOverlay = NULL, - .calcMinSize = widgetTreeViewCalcMinSize, - .layout = widgetTreeViewLayout, - .onMouse = widgetTreeViewOnMouse, - .onKey = widgetTreeViewOnKey, - .destroy = NULL, - .getText = NULL, - .setText = NULL -}; - -// TreeItem has no paint/mouse/key handlers -- it's a data-only node. -// The TreeView parent widget handles all rendering and interaction. -// TreeItem exists as a WidgetT so it can participate in the tree -// structure (parent/child/sibling links) for hierarchical data. -static const WidgetClassT sClassTreeItem = { - .flags = 0, - .paint = NULL, - .paintOverlay = NULL, - .calcMinSize = NULL, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = NULL, - .getText = widgetTreeItemGetText, - .setText = widgetTreeItemSetText -}; - -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 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 WidgetClassT sClassCanvas = { - .flags = 0, - .paint = widgetCanvasPaint, - .paintOverlay = NULL, - .calcMinSize = widgetCanvasCalcMinSize, - .layout = NULL, - .onMouse = widgetCanvasOnMouse, - .onKey = NULL, - .destroy = widgetCanvasDestroy, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassListView = { - .flags = WCLASS_FOCUSABLE | WCLASS_NO_HIT_RECURSE, - .paint = widgetListViewPaint, - .paintOverlay = NULL, - .calcMinSize = widgetListViewCalcMinSize, - .layout = NULL, - .onMouse = widgetListViewOnMouse, - .onKey = widgetListViewOnKey, - .destroy = widgetListViewDestroy, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassAnsiTerm = { - .flags = WCLASS_FOCUSABLE, - .paint = widgetAnsiTermPaint, - .paintOverlay = NULL, - .calcMinSize = widgetAnsiTermCalcMinSize, - .layout = NULL, - .onMouse = widgetAnsiTermOnMouse, - .onKey = widgetAnsiTermOnKey, - .destroy = widgetAnsiTermDestroy, - .getText = NULL, - .setText = NULL -}; - -// ScrollPane, Splitter: PAINTS_CHILDREN because they clip/position -// children in a custom way; NO_HIT_RECURSE because they manage their -// own scrollbar/divider hit regions. -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 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 -}; - -static const WidgetClassT sClassTimer = { - .flags = 0, - .paint = NULL, - .paintOverlay = NULL, - .calcMinSize = widgetTimerCalcMinSize, - .layout = NULL, - .onMouse = NULL, - .onKey = NULL, - .destroy = widgetTimerDestroy, - .getText = NULL, - .setText = NULL -}; - -static const WidgetClassT sClassSpinner = { - .flags = WCLASS_FOCUSABLE, - .paint = widgetSpinnerPaint, - .paintOverlay = NULL, - .calcMinSize = widgetSpinnerCalcMinSize, - .layout = NULL, - .onMouse = widgetSpinnerOnMouse, - .onKey = widgetSpinnerOnKey, - .destroy = NULL, - .getText = widgetSpinnerGetText, - .setText = widgetSpinnerSetText -}; - -// ============================================================ -// Class table -- indexed by WidgetTypeE -// ============================================================ -// -// This array is the central dispatch table for the widget system. -// Indexed by the WidgetTypeE enum, it provides O(1) lookup of any -// widget type's class definition. Every WidgetT stores a pointer -// to its class (w->wclass) set at allocation time, so per-widget -// dispatch doesn't even need to index this table -- it's a direct -// pointer dereference through the vtable. -// -// Using C99 designated initializers ensures the array slots match -// the enum values even if the enum is reordered. If a new enum -// value is added without a table entry, it will be NULL, which -// callers check for before dispatching. - -const WidgetClassT *widgetClassTable[] = { - [WidgetVBoxE] = &sClassVBox, - [WidgetHBoxE] = &sClassHBox, - [WidgetLabelE] = &sClassLabel, - [WidgetButtonE] = &sClassButton, - [WidgetCheckboxE] = &sClassCheckbox, - [WidgetRadioGroupE] = &sClassRadioGroup, - [WidgetRadioE] = &sClassRadio, - [WidgetTextInputE] = &sClassTextInput, - [WidgetTextAreaE] = &sClassTextArea, - [WidgetListBoxE] = &sClassListBox, - [WidgetSpacerE] = &sClassSpacer, - [WidgetSeparatorE] = &sClassSeparator, - [WidgetFrameE] = &sClassFrame, - [WidgetDropdownE] = &sClassDropdown, - [WidgetComboBoxE] = &sClassComboBox, - [WidgetProgressBarE] = &sClassProgressBar, - [WidgetSliderE] = &sClassSlider, - [WidgetTabControlE] = &sClassTabControl, - [WidgetTabPageE] = &sClassTabPage, - [WidgetStatusBarE] = &sClassStatusBar, - [WidgetToolbarE] = &sClassToolbar, - [WidgetTreeViewE] = &sClassTreeView, - [WidgetTreeItemE] = &sClassTreeItem, - [WidgetImageE] = &sClassImage, - [WidgetImageButtonE] = &sClassImageButton, - [WidgetCanvasE] = &sClassCanvas, - [WidgetAnsiTermE] = &sClassAnsiTerm, - [WidgetListViewE] = &sClassListView, - [WidgetSpinnerE] = &sClassSpinner, - [WidgetScrollPaneE] = &sClassScrollPane, - [WidgetSplitterE] = &sClassSplitter, - [WidgetTimerE] = &sClassTimer -}; diff --git a/dvx/widgets/widgetCore.c b/dvx/widgets/widgetCore.c deleted file mode 100644 index b2cb818..0000000 --- a/dvx/widgets/widgetCore.c +++ /dev/null @@ -1,746 +0,0 @@ -// widgetCore.c -- Core widget infrastructure (alloc, tree ops, helpers) -// -// This file provides the foundation for the widget tree: allocation, -// parent-child linking, focus management, hit testing, and shared -// utility functions used across multiple widget types. -// -// Widgets form a tree using intrusive linked lists (firstChild/lastChild/ -// nextSibling pointers inside each WidgetT). This is a singly-linked -// child list with a tail pointer for O(1) append. The tree is owned -// by its root, which is attached to a WindowT. Destroying the root -// recursively destroys all descendants. -// -// Memory allocation is plain malloc/free rather than an arena or pool. -// The widget count per window is typically small (tens to low hundreds), -// so the allocation overhead is negligible on target hardware. An arena -// approach was considered but rejected because widgets can be individually -// created and destroyed at runtime (dialog dynamics, tree item insertion), -// which doesn't map cleanly to an arena pattern. - -#include "widgetInternal.h" - -// ============================================================ -// Global state for drag and popup tracking -// ============================================================ -// -// These module-level pointers track ongoing UI interactions that span -// multiple mouse events (drags, popups, button presses). They are global -// rather than per-window because the DOS GUI is single-threaded and only -// one interaction can be active at a time. -// -// Each pointer is set when an interaction begins (e.g. mouse-down on a -// slider) and cleared when it ends (mouse-up). The event dispatcher in -// widgetEvent.c checks these before normal hit testing -- active drags -// take priority over everything else. -// -// All of these must be NULLed when the pointed-to widget is destroyed, -// otherwise dangling pointers would cause crashes. widgetDestroyChildren() -// and wgtDestroy() handle this cleanup. - -clock_t sDblClickTicks = 0; // set from ctx->dblClickTicks during first paint -bool sDebugLayout = false; -WidgetT *sFocusedWidget = NULL; // currently focused widget (O(1) access, avoids tree walk) -WidgetT *sOpenPopup = NULL; // dropdown/combobox with open popup list -WidgetT *sPressedButton = NULL; // button being held down (tracks mouse in/out) -WidgetT *sDragSlider = NULL; // slider being dragged -WidgetT *sDrawingCanvas = NULL; // canvas receiving paint strokes -WidgetT *sDragTextSelect = NULL; // text widget in drag-select mode -int32_t sDragOffset = 0; // pixel offset from drag start to thumb center -WidgetT *sResizeListView = NULL; // ListView undergoing column resize -int32_t sResizeCol = -1; // which column is being resized -int32_t sResizeStartX = 0; // mouse X at resize start -int32_t sResizeOrigW = 0; // column width at resize start -bool sResizeDragging = false; // true once mouse moves during column resize -WidgetT *sDragSplitter = NULL; // splitter being dragged -int32_t sDragSplitStart = 0; // mouse offset from splitter edge at drag start -WidgetT *sDragReorder = NULL; // list/tree widget in drag-reorder mode -WidgetT *sDragScrollbar = NULL; // widget whose scrollbar thumb is being dragged -int32_t sDragScrollbarOff = 0; // mouse offset within thumb at drag start -int32_t sDragScrollbarOrient = 0; // 0=vertical, 1=horizontal - - -// ============================================================ -// widgetAddChild -// ============================================================ -// -// Appends a child to the end of the parent's child list. O(1) -// thanks to the lastChild tail pointer. The child list is singly- -// linked (nextSibling), which saves 4 bytes per widget vs doubly- -// linked and is sufficient because child removal is infrequent. - -void widgetAddChild(WidgetT *parent, WidgetT *child) { - child->parent = parent; - child->nextSibling = NULL; - - if (parent->lastChild) { - parent->lastChild->nextSibling = child; - parent->lastChild = child; - } else { - parent->firstChild = child; - parent->lastChild = child; - } -} - - -// ============================================================ -// widgetAlloc -// ============================================================ -// -// Allocates and zero-initializes a new widget, links it to its -// class vtable via widgetClassTable[], and optionally adds it as -// a child of the given parent. -// -// The memset to 0 is intentional -- it establishes sane defaults -// for all fields: NULL pointers, zero coordinates, no focus, -// no accel key, etc. Only visible and enabled default to true. -// -// The window pointer is inherited from the parent so that any -// widget in the tree can find its owning window without walking -// to the root. - -WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type) { - WidgetT *w = (WidgetT *)malloc(sizeof(WidgetT)); - - if (!w) { - return NULL; - } - - memset(w, 0, sizeof(*w)); - w->type = type; - w->wclass = widgetClassTable[type]; - w->visible = true; - w->enabled = true; - - if (parent) { - w->window = parent->window; - widgetAddChild(parent, w); - } - - return w; -} - - -// ============================================================ -// widgetClearFocus -// ============================================================ - -void widgetClearFocus(WidgetT *root) { - if (!root) { - return; - } - - root->focused = false; - - for (WidgetT *c = root->firstChild; c; c = c->nextSibling) { - widgetClearFocus(c); - } -} - - -// ============================================================ -// widgetCountVisibleChildren -// ============================================================ - -int32_t widgetCountVisibleChildren(const WidgetT *w) { - int32_t count = 0; - - for (const WidgetT *c = w->firstChild; c; c = c->nextSibling) { - if (c->visible) { - count++; - } - } - - return count; -} - - -// ============================================================ -// widgetDestroyChildren -// ============================================================ -// -// Recursively destroys all descendants of a widget. Processes -// children depth-first (destroy grandchildren before the child -// itself) so that per-widget destroy callbacks see a consistent -// tree state. -// -// Critically, this function clears all global state pointers that -// reference destroyed widgets. Without this, any pending drag or -// focus state would become a dangling pointer. Each global is -// checked individually rather than cleared unconditionally to -// avoid disrupting unrelated ongoing interactions. - -void widgetDestroyChildren(WidgetT *w) { - WidgetT *child = w->firstChild; - - while (child) { - WidgetT *next = child->nextSibling; - widgetDestroyChildren(child); - - if (child->wclass && child->wclass->destroy) { - child->wclass->destroy(child); - } - - // Clear static references if they point to destroyed widgets - if (sFocusedWidget == child) { - sFocusedWidget = NULL; - } - - if (sOpenPopup == child) { - sOpenPopup = NULL; - } - - if (sPressedButton == child) { - sPressedButton = NULL; - } - - if (sDragSlider == child) { - sDragSlider = NULL; - } - - if (sDrawingCanvas == child) { - sDrawingCanvas = NULL; - } - - if (sResizeListView == child) { - sResizeListView = NULL; - sResizeCol = -1; - sResizeDragging = false; - } - - if (sDragScrollbar == child) { - sDragScrollbar = NULL; - } - - free(child); - child = next; - } - - w->firstChild = NULL; - w->lastChild = NULL; -} - - -// ============================================================ -// widgetDropdownPopupRect -// ============================================================ -// -// Calculates the screen rectangle for a dropdown/combobox popup list. -// Shared between Dropdown and ComboBox since they have identical -// popup positioning logic. -// -// The popup tries to open below the widget first. If there isn't -// enough room (popup would extend past the content area bottom), -// it flips to open above instead. This ensures the popup is always -// visible, even for dropdowns near the bottom of a window. -// -// Popup height is capped at DROPDOWN_MAX_VISIBLE items to prevent -// huge popups from dominating the screen. - -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->type == WidgetDropdownE) { - itemCount = w->as.dropdown.itemCount; - } else if (w->type == WidgetComboBoxE) { - itemCount = w->as.comboBox.itemCount; - } - - 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; // 2px border each side - - // Try below first, then above if no room - if (w->y + w->h + *popH <= contentH) { - *popY = w->y + w->h; - } else { - *popY = w->y - *popH; - - if (*popY < 0) { - *popY = 0; - } - } -} - - -// ============================================================ -// 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); - } -} - - -// ============================================================ -// widgetFindByAccel -// ============================================================ -// -// Finds a widget with the given Alt+key accelerator. Recurses the -// tree depth-first, respecting visibility and enabled state. -// -// Special case for TabPage widgets: even if the tab page itself is -// not visible (inactive tab), its accelKey is still checked. This -// allows Alt+key to switch to a different tab. However, children -// of invisible tab pages are NOT searched -- their accelerators -// should not be active when the tab is hidden. - -WidgetT *widgetFindByAccel(WidgetT *root, char key) { - if (!root || !root->enabled) { - return NULL; - } - - // Invisible tab pages: match the page itself (for tab switching) - // but don't recurse into children (their accels shouldn't be active) - if (!root->visible) { - if (root->type == WidgetTabPageE && root->accelKey == key) { - return root; - } - - return NULL; - } - - if (root->accelKey == key) { - return root; - } - - for (WidgetT *c = root->firstChild; c; c = c->nextSibling) { - WidgetT *found = widgetFindByAccel(c, key); - - if (found) { - return found; - } - } - - return NULL; -} - - -// ============================================================ -// widgetFindNextFocusable -// ============================================================ -// -// Implements Tab-order navigation: finds the next focusable widget -// after 'after' in depth-first tree order. The two-pass approach -// (search from 'after' to end, then wrap to start) ensures circular -// tabbing -- Tab on the last focusable widget wraps to the first. -// -// The pastAfter flag tracks whether we've passed the 'after' widget -// during traversal. Once past it, the next focusable widget is the -// answer. This avoids collecting all focusable widgets into an array -// just to find the next one -- the common case returns quickly. - -static WidgetT *findNextFocusableImpl(WidgetT *w, WidgetT *after, bool *pastAfter) { - if (!w->visible || !w->enabled) { - return NULL; - } - - if (after == NULL) { - *pastAfter = true; - } - - if (w == after) { - *pastAfter = true; - } else if (*pastAfter && widgetIsFocusable(w->type)) { - return w; - } - - for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { - WidgetT *found = findNextFocusableImpl(c, after, pastAfter); - - if (found) { - return found; - } - } - - return NULL; -} - -WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after) { - bool pastAfter = false; - WidgetT *found = findNextFocusableImpl(root, after, &pastAfter); - - if (found) { - return found; - } - - // Wrap around -- search from the beginning - pastAfter = true; - return findNextFocusableImpl(root, NULL, &pastAfter); -} - - -// ============================================================ -// widgetFindPrevFocusable -// ============================================================ -// -// Shift+Tab navigation: finds the previous focusable widget. -// Unlike findNextFocusable which can short-circuit during traversal, -// finding the PREVIOUS widget requires knowing the full order. -// So this collects all focusable widgets into an array, finds the -// target's index, and returns index-1 (with wraparound). -// -// The explicit stack-based DFS (rather than recursion) is used here -// because we need to push children in reverse order to get the same -// left-to-right depth-first ordering as the recursive version. -// Fixed-size arrays (128 widgets, 64 stack depth) are adequate for -// any reasonable dialog layout and avoid dynamic allocation. - -WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before) { - WidgetT *list[128]; - int32_t count = 0; - - // Collect all focusable widgets via depth-first traversal - WidgetT *stack[64]; - int32_t top = 0; - stack[top++] = root; - - while (top > 0) { - WidgetT *w = stack[--top]; - - if (!w->visible || !w->enabled) { - continue; - } - - if (widgetIsFocusable(w->type) && count < 128) { - list[count++] = w; - } - - // Push children in reverse order so first child is processed first - WidgetT *children[64]; - int32_t childCount = 0; - - for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { - if (childCount < 64) { - children[childCount++] = c; - } - } - - for (int32_t i = childCount - 1; i >= 0; i--) { - if (top < 64) { - stack[top++] = children[i]; - } - } - } - - if (count == 0) { - return NULL; - } - - // Find 'before' in the list - int32_t idx = -1; - - for (int32_t i = 0; i < count; i++) { - if (list[i] == before) { - idx = i; - break; - } - } - - if (idx <= 0) { - return list[count - 1]; // Wrap to last - } - - return list[idx - 1]; -} - - -// ============================================================ -// widgetFrameBorderWidth -// ============================================================ - -int32_t widgetFrameBorderWidth(const WidgetT *w) { - if (w->type != WidgetFrameE) { - return 0; - } - - if (w->as.frame.style == FrameFlatE) { - return FRAME_FLAT_BORDER; - } - - return FRAME_BEVEL_BORDER; -} - - -// ============================================================ -// widgetHitTest -// ============================================================ -// -// Recursive hit testing: finds the deepest (most specific) widget -// under the given coordinates. Returns the widget itself if no -// child is hit, or NULL if the point is outside this widget. -// -// Children are iterated front-to-back (first to last in the linked -// list), but the LAST match wins. This gives later siblings higher -// Z-order, which matches the painting order (later children paint -// on top of earlier ones). This is important for overlapping widgets, -// though in practice the layout engine rarely produces overlap. -// -// Widgets with WCLASS_NO_HIT_RECURSE stop the recursion -- the parent -// widget handles all mouse events for its children. This is used by -// TreeView, ScrollPane, ListView, and Splitter, which need to manage -// their own internal regions (scrollbars, column headers, tree -// expand buttons) that don't correspond to child widgets. - -WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) { - if (!w->visible) { - return NULL; - } - - if (x < w->x || x >= w->x + w->w || y < w->y || y >= w->y + w->h) { - return NULL; - } - - // Widgets with WCLASS_NO_HIT_RECURSE manage their own children - if (w->wclass && (w->wclass->flags & WCLASS_NO_HIT_RECURSE)) { - return w; - } - - // Check children -- take the last match (topmost in Z-order) - WidgetT *hit = NULL; - - for (WidgetT *c = w->firstChild; c; c = c->nextSibling) { - WidgetT *childHit = widgetHitTest(c, x, y); - - if (childHit) { - hit = childHit; - } - } - - return hit ? hit : w; -} - - -// ============================================================ -// widgetIsFocusable -// ============================================================ - -bool widgetIsFocusable(WidgetTypeE type) { - return (widgetClassTable[type]->flags & WCLASS_FOCUSABLE) != 0; -} - - -// ============================================================ -// widgetIsBoxContainer -// ============================================================ -// -// Returns true for widget types that use the generic box layout. - -bool widgetIsBoxContainer(WidgetTypeE type) { - return (widgetClassTable[type]->flags & WCLASS_BOX_CONTAINER) != 0; -} - - -// ============================================================ -// widgetIsHorizContainer -// ============================================================ -// -// Returns true for container types that lay out children horizontally. - -bool widgetIsHorizContainer(WidgetTypeE type) { - return (widgetClassTable[type]->flags & WCLASS_HORIZ_CONTAINER) != 0; -} - - -// ============================================================ -// 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); - } -} - - -// ============================================================ -// widgetScrollbarThumb -// ============================================================ -// -// Calculates thumb position and size for a scrollbar track. -// Used by both the WM-level scrollbars and widget-internal scrollbars -// (ListBox, TreeView, etc.) to maintain consistent scrollbar behavior. -// -// The thumb size is proportional to visibleSize/totalSize -- a larger -// visible area means a larger thumb, giving visual feedback about how -// much content is scrollable. SB_MIN_THUMB prevents the thumb from -// becoming too small to grab with a mouse. - -void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize) { - *thumbSize = (trackLen * visibleSize) / totalSize; - - if (*thumbSize < SB_MIN_THUMB) { - *thumbSize = SB_MIN_THUMB; - } - - if (*thumbSize > trackLen) { - *thumbSize = trackLen; - } - - int32_t maxScroll = totalSize - visibleSize; - - if (maxScroll > 0) { - *thumbPos = ((trackLen - *thumbSize) * scrollPos) / maxScroll; - } else { - *thumbPos = 0; - } -} - - -// ============================================================ -// widgetRemoveChild -// ============================================================ -// -// Unlinks a child from its parent's child list. O(n) in the number -// of children because the singly-linked list requires walking to -// find the predecessor. This is acceptable because child removal -// is infrequent (widget destruction, tree item reordering). - -void widgetRemoveChild(WidgetT *parent, WidgetT *child) { - WidgetT *prev = NULL; - - for (WidgetT *c = parent->firstChild; c; c = c->nextSibling) { - if (c == child) { - if (prev) { - prev->nextSibling = c->nextSibling; - } else { - parent->firstChild = c->nextSibling; - } - - if (parent->lastChild == child) { - parent->lastChild = prev; - } - - child->nextSibling = NULL; - child->parent = NULL; - return; - } - - prev = c; - } -} diff --git a/dvxshell/Makefile b/dvxshell/Makefile deleted file mode 100644 index 5860725..0000000 --- a/dvxshell/Makefile +++ /dev/null @@ -1,74 +0,0 @@ -# DVX Shell Makefile for DJGPP cross-compilation - -DJGPP_PREFIX = $(HOME)/djgpp/djgpp -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../dvx -I../tasks -LDFLAGS = -L../lib -ldvx -ltasks -lm - -OBJDIR = ../obj/dvxshell -BINDIR = ../bin -CONFIGDIR = ../bin/config -THEMEDIR = ../bin/config/themes -WPAPERDIR = ../bin/config/wpaper -LIBDIR = ../lib - -SRCS = shellMain.c shellApp.c shellExport.c shellInfo.c shellTaskMgr.c -OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) -TARGET = $(BINDIR)/dvx.exe - -.PHONY: all clean libs - -THEMES = $(THEMEDIR)/geos.thm $(THEMEDIR)/win31.thm $(THEMEDIR)/cde.thm -WPAPERS = $(WPAPERDIR)/blueglow.jpg $(WPAPERDIR)/swoop.jpg $(WPAPERDIR)/triangle.jpg - -all: libs $(TARGET) $(CONFIGDIR)/dvx.ini $(THEMES) $(WPAPERS) - -libs: - $(MAKE) -C ../dvx - $(MAKE) -C ../tasks - -$(TARGET): $(OBJS) $(LIBDIR)/libdvx.a $(LIBDIR)/libtasks.a | $(BINDIR) - $(CC) $(CFLAGS) -o $@ $(OBJS) $(LDFLAGS) -Wl,-Map=$(BINDIR)/dvx.map - $(EXE2COFF) $@ - cat $(CWSDSTUB) $(BINDIR)/dvx > $@ - rm -f $(BINDIR)/dvx - -$(CONFIGDIR)/dvx.ini: ../dvx.ini | $(CONFIGDIR) - sed 's/$$/\r/' $< > $@ - -$(OBJDIR)/%.o: %.c | $(OBJDIR) - $(CC) $(CFLAGS) -c -o $@ $< - -$(OBJDIR): - mkdir -p $(OBJDIR) - -$(BINDIR): - mkdir -p $(BINDIR) - -$(CONFIGDIR): - mkdir -p $(CONFIGDIR) - -$(THEMEDIR): - mkdir -p $(THEMEDIR) - -$(THEMEDIR)/%.thm: ../themes/%.thm | $(THEMEDIR) - sed 's/$$/\r/' $< > $@ - -$(WPAPERDIR): - mkdir -p $(WPAPERDIR) - -$(WPAPERDIR)/%.jpg: ../wpaper/%.jpg | $(WPAPERDIR) - cp $< $@ - -# Dependencies -$(OBJDIR)/shellMain.o: shellMain.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h -$(OBJDIR)/shellApp.o: shellApp.c shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../tasks/taskswitch.h -$(OBJDIR)/shellExport.o: shellExport.c shellApp.h shellInfo.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxDraw.h ../dvx/dvxVideo.h ../dvx/dvxWm.h ../tasks/taskswitch.h -$(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h ../dvx/dvxApp.h ../dvx/platform/dvxPlatform.h -$(OBJDIR)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h ../dvx/dvxApp.h ../dvx/dvxDialog.h ../dvx/dvxWidget.h ../dvx/dvxWm.h ../dvx/platform/dvxPlatform.h -clean: - rm -f $(OBJS) $(TARGET) $(BINDIR)/dvx.map $(BINDIR)/dvx.log - rm -rf $(WPAPERDIR) $(THEMEDIR) $(CONFIGDIR) diff --git a/dvxshell/shellExport.c b/dvxshell/shellExport.c deleted file mode 100644 index fb1e123..0000000 --- a/dvxshell/shellExport.c +++ /dev/null @@ -1,733 +0,0 @@ -// shellExport.c -- DXE export table and wrapper functions for DVX Shell -// -// Exports all dvx*/wgt*/ts* symbols that DXE apps need. A few functions -// are wrapped for resource tracking (window ownership via appId). -// -// DXE3 is DJGPP's dynamic linking mechanism. Unlike ELF shared libraries, -// DXE modules have no implicit access to the host's symbol table. Every -// function or variable the DXE needs must be explicitly listed in an -// export table registered via dlregsym() BEFORE any dlopen() call. If a -// symbol is missing, dlopen() returns NULL with a "symbol not found" error. -// -// This file is essentially the ABI contract between the shell and apps. -// Three categories of exports: -// -// 1. Wrapped functions: dvxCreateWindow, dvxCreateWindowCentered, -// dvxDestroyWindow. These are intercepted to stamp win->appId for -// resource ownership tracking. The DXE sees them under their original -// names -- the app code calls dvxCreateWindow() normally and gets our -// wrapper transparently. -// -// 2. Direct exports: all other dvx/wgt/wm/ts functions. These are safe -// to call without shell-side interception. -// -// 3. libc functions: DXE modules are statically linked against DJGPP's -// libc, but DJGPP's DXE3 loader requires explicit re-export of any -// libc symbols the module references. Without these entries, the DXE -// would fail to load with unresolved symbol errors. This is a DXE3 -// design limitation -- there's no automatic fallback to the host's libc. - -#include "shellApp.h" -#include "shellInfo.h" -#include "shellTaskMgr.h" -#include "dvxApp.h" -#include "dvxDialog.h" -#include "dvxWidget.h" -#include "dvxDraw.h" -#include "dvxPrefs.h" -#include "platform/dvxPlatform.h" -#include "dvxVideo.h" -#include "dvxWm.h" -#include "taskswitch.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// stb headers (no IMPLEMENTATION -- symbols are in libdvx.a / libtasks.a) -#include "thirdparty/stb_image.h" -#include "thirdparty/stb_image_write.h" -#include "thirdparty/stb_ds.h" - -// DJGPP stdio internals -- the stdin/stdout/stderr macros dereference -// these pointers. Without exporting them, any DXE that uses printf -// or fprintf gets an unresolved symbol. -extern FILE __dj_stdin; -extern FILE __dj_stdout; -extern FILE __dj_stderr; - -// libgcc 64-bit integer math helpers (no header declares these) -extern long long __divdi3(long long, long long); -extern long long __moddi3(long long, long long); -extern long long __muldi3(long long, long long); -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); - -// ============================================================ -// Prototypes -// ============================================================ - -static void shellRegisterExports(void); -static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable); -static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable); -static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win); - -// ============================================================ -// Wrapper: dvxCreateWindow -- stamps win->appId -// ============================================================ - -// The wrapper calls the real dvxCreateWindow, 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. The app never -// sees the difference -- the wrapper has the same signature and is -// exported under the same name as the original function. -static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) { - WindowT *win = dvxCreateWindow(ctx, title, x, y, w, h, resizable); - - if (win) { - win->appId = sCurrentAppId; - } - - return win; -} - - -// ============================================================ -// Wrapper: dvxCreateWindowCentered -- stamps win->appId -// ============================================================ - -static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable) { - WindowT *win = dvxCreateWindowCentered(ctx, title, w, h, resizable); - - if (win) { - win->appId = sCurrentAppId; - } - - return win; -} - - -// ============================================================ -// Wrapper: dvxDestroyWindow -- checks for last-window reap -// ============================================================ - -// Beyond just destroying the window, this wrapper implements the lifecycle -// rule for callback-only apps: when their last window closes, they're done. -// Main-loop apps manage their own lifetime (their task returns from -// appMain), so this check only applies to callback-only apps. -// The appId is captured before destruction because the window struct is -// freed by dvxDestroyWindow. -static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win) { - int32_t appId = win->appId; - - dvxDestroyWindow(ctx, win); - - // If this was a callback-only app's last window, mark for reaping - if (appId > 0) { - ShellAppT *app = shellGetApp(appId); - - if (app && !app->hasMainLoop && app->state == AppStateRunningE) { - // Check if app still has any windows - bool hasWindows = false; - - for (int32_t i = 0; i < ctx->stack.count; i++) { - if (ctx->stack.windows[i]->appId == appId) { - hasWindows = true; - break; - } - } - - if (!hasWindows) { - app->state = AppStateTerminatingE; - } - } - } -} - - -// ============================================================ -// Export table -// ============================================================ - -// DXE_EXPORT_TABLE generates a DXE symbol table array. DXE_EXPORT(fn) -// expands to { "_fn", (void *)fn } -- the underscore prefix matches COFF -// symbol naming. For wrapped functions we use raw entries with explicit -// names so the DXE sees "_dvxCreateWindow" but gets our wrapper's address. - -DXE_EXPORT_TABLE(shellExportTable) - // Wrapped functions (exported under original names, but pointing to - // our wrappers that add resource tracking) - { "_dvxCreateWindow", (void *)shellWrapCreateWindow }, - { "_dvxDestroyWindow", (void *)shellWrapDestroyWindow }, - - // dvxPlatform.h -- platform abstraction - DXE_EXPORT(platformLineEnding) - DXE_EXPORT(platformChdir) - DXE_EXPORT(platformGetMemoryInfo) - DXE_EXPORT(platformMkdirRecursive) - DXE_EXPORT(platformMouseSetAccel) - DXE_EXPORT(platformMouseWarp) - DXE_EXPORT(platformPathDirEnd) - DXE_EXPORT(platformStripLineEndings) - DXE_EXPORT(platformValidateFilename) - DXE_EXPORT(platformVideoEnumModes) - - // dvxPrefs.h -- preferences - DXE_EXPORT(prefsGetBool) - DXE_EXPORT(prefsGetInt) - DXE_EXPORT(prefsGetString) - DXE_EXPORT(prefsLoad) - DXE_EXPORT(prefsRemove) - DXE_EXPORT(prefsSave) - DXE_EXPORT(prefsSaveAs) - DXE_EXPORT(prefsSetBool) - DXE_EXPORT(prefsSetInt) - DXE_EXPORT(prefsSetString) - - // dvxApp.h -- direct exports - DXE_EXPORT(dvxApplyColorScheme) - DXE_EXPORT(dvxChangeVideoMode) - DXE_EXPORT(dvxColorLabel) - DXE_EXPORT(dvxColorName) - DXE_EXPORT(dvxGetColor) - DXE_EXPORT(dvxInit) - DXE_EXPORT(dvxLoadTheme) - DXE_EXPORT(dvxResetColorScheme) - DXE_EXPORT(dvxSaveTheme) - DXE_EXPORT(dvxSetColor) - DXE_EXPORT(dvxSetMouseConfig) - DXE_EXPORT(dvxSetWallpaper) - DXE_EXPORT(dvxSetWallpaperMode) - DXE_EXPORT(dvxShutdown) - DXE_EXPORT(dvxUpdate) - { "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered }, - DXE_EXPORT(dvxFitWindow) - DXE_EXPORT(dvxInvalidateRect) - DXE_EXPORT(dvxInvalidateWindow) - DXE_EXPORT(dvxMinimizeWindow) - DXE_EXPORT(dvxMaximizeWindow) - DXE_EXPORT(dvxQuit) - DXE_EXPORT(dvxSetTitle) - DXE_EXPORT(dvxGetFont) - DXE_EXPORT(dvxGetColors) - DXE_EXPORT(dvxGetDisplay) - DXE_EXPORT(dvxGetVideoModes) - DXE_EXPORT(dvxGetBlitOps) - DXE_EXPORT(dvxSetWindowIcon) - DXE_EXPORT(dvxLoadImage) - DXE_EXPORT(dvxFreeImage) - DXE_EXPORT(dvxSaveImage) - DXE_EXPORT(dvxScreenshot) - DXE_EXPORT(dvxWindowScreenshot) - DXE_EXPORT(dvxCreateAccelTable) - DXE_EXPORT(dvxFreeAccelTable) - DXE_EXPORT(dvxAddAccel) - DXE_EXPORT(dvxCascadeWindows) - DXE_EXPORT(dvxTileWindows) - DXE_EXPORT(dvxTileWindowsH) - DXE_EXPORT(dvxTileWindowsV) - DXE_EXPORT(dvxClipboardCopy) - DXE_EXPORT(dvxClipboardGet) - - // dvxDialog.h - DXE_EXPORT(dvxMessageBox) - DXE_EXPORT(dvxFileDialog) - - // dvxDraw.h - DXE_EXPORT(rectFill) - DXE_EXPORT(rectCopy) - DXE_EXPORT(drawBevel) - DXE_EXPORT(drawChar) - DXE_EXPORT(drawText) - DXE_EXPORT(drawTextN) - DXE_EXPORT(textWidth) - DXE_EXPORT(drawTextAccel) - DXE_EXPORT(textWidthAccel) - DXE_EXPORT(drawFocusRect) - DXE_EXPORT(drawHLine) - DXE_EXPORT(drawVLine) - - // dvxVideo.h - DXE_EXPORT(packColor) - DXE_EXPORT(resetClipRect) - DXE_EXPORT(setClipRect) - - // dvxWm.h - DXE_EXPORT(wmAddMenuBar) - DXE_EXPORT(wmAddMenu) - DXE_EXPORT(wmAddMenuItem) - DXE_EXPORT(wmAddMenuCheckItem) - DXE_EXPORT(wmAddMenuRadioItem) - DXE_EXPORT(wmAddMenuSeparator) - DXE_EXPORT(wmAddSubMenu) - DXE_EXPORT(wmAddVScrollbar) - DXE_EXPORT(wmAddHScrollbar) - DXE_EXPORT(wmMinimizedIconPos) - DXE_EXPORT(wmMinimizedIconRect) - DXE_EXPORT(wmSetTitle) - DXE_EXPORT(wmSetIcon) - DXE_EXPORT(wmCreateMenu) - DXE_EXPORT(wmFreeMenu) - DXE_EXPORT(wmUpdateContentRect) - DXE_EXPORT(wmReallocContentBuf) - - // dvxWidget.h -- window integration - DXE_EXPORT(wgtInitWindow) - - // dvxWidget.h -- containers - DXE_EXPORT(wgtVBox) - DXE_EXPORT(wgtHBox) - DXE_EXPORT(wgtFrame) - - // dvxWidget.h -- basic widgets - DXE_EXPORT(wgtLabel) - DXE_EXPORT(wgtLabelSetAlign) - DXE_EXPORT(wgtButton) - DXE_EXPORT(wgtCheckbox) - DXE_EXPORT(wgtTextInput) - DXE_EXPORT(wgtPasswordInput) - DXE_EXPORT(wgtMaskedInput) - - // dvxWidget.h -- radio buttons - DXE_EXPORT(wgtRadioGroup) - DXE_EXPORT(wgtRadio) - - // dvxWidget.h -- spacing - DXE_EXPORT(wgtSpacer) - DXE_EXPORT(wgtHSeparator) - DXE_EXPORT(wgtVSeparator) - - // dvxWidget.h -- complex widgets - DXE_EXPORT(wgtListBox) - DXE_EXPORT(wgtTextArea) - - // dvxWidget.h -- dropdown/combo - DXE_EXPORT(wgtDropdown) - DXE_EXPORT(wgtDropdownSetItems) - DXE_EXPORT(wgtDropdownGetSelected) - DXE_EXPORT(wgtDropdownSetSelected) - DXE_EXPORT(wgtComboBox) - DXE_EXPORT(wgtComboBoxSetItems) - DXE_EXPORT(wgtComboBoxGetSelected) - DXE_EXPORT(wgtComboBoxSetSelected) - - // dvxWidget.h -- progress bar - DXE_EXPORT(wgtProgressBar) - DXE_EXPORT(wgtProgressBarV) - DXE_EXPORT(wgtProgressBarSetValue) - DXE_EXPORT(wgtProgressBarGetValue) - - // dvxWidget.h -- slider - DXE_EXPORT(wgtSlider) - DXE_EXPORT(wgtSliderSetValue) - DXE_EXPORT(wgtSliderGetValue) - - // dvxWidget.h -- spinner - DXE_EXPORT(wgtSpinner) - DXE_EXPORT(wgtSpinnerSetValue) - DXE_EXPORT(wgtSpinnerGetValue) - DXE_EXPORT(wgtSpinnerSetRange) - DXE_EXPORT(wgtSpinnerSetStep) - - // dvxWidget.h -- tab control - DXE_EXPORT(wgtTabControl) - DXE_EXPORT(wgtTabPage) - DXE_EXPORT(wgtTabControlSetActive) - DXE_EXPORT(wgtTabControlGetActive) - - // dvxWidget.h -- status bar / toolbar - DXE_EXPORT(wgtStatusBar) - DXE_EXPORT(wgtToolbar) - - // dvxWidget.h -- tree view - DXE_EXPORT(wgtTreeView) - DXE_EXPORT(wgtTreeViewGetSelected) - DXE_EXPORT(wgtTreeViewSetSelected) - DXE_EXPORT(wgtTreeViewSetMultiSelect) - DXE_EXPORT(wgtTreeViewSetReorderable) - DXE_EXPORT(wgtTreeItem) - DXE_EXPORT(wgtTreeItemSetExpanded) - DXE_EXPORT(wgtTreeItemIsExpanded) - DXE_EXPORT(wgtTreeItemIsSelected) - DXE_EXPORT(wgtTreeItemSetSelected) - - // dvxWidget.h -- timer - DXE_EXPORT(wgtTimer) - DXE_EXPORT(wgtTimerIsRunning) - DXE_EXPORT(wgtTimerSetInterval) - DXE_EXPORT(wgtTimerStart) - DXE_EXPORT(wgtTimerStop) - - // dvxWidget.h -- list view - DXE_EXPORT(wgtListView) - DXE_EXPORT(wgtListViewSetColumns) - DXE_EXPORT(wgtListViewSetData) - DXE_EXPORT(wgtListViewGetSelected) - DXE_EXPORT(wgtListViewSetSelected) - DXE_EXPORT(wgtListViewSetSort) - DXE_EXPORT(wgtListViewSetHeaderClickCallback) - DXE_EXPORT(wgtListViewSetMultiSelect) - DXE_EXPORT(wgtListViewIsItemSelected) - DXE_EXPORT(wgtListViewSetItemSelected) - DXE_EXPORT(wgtListViewSelectAll) - DXE_EXPORT(wgtListViewClearSelection) - DXE_EXPORT(wgtListViewSetReorderable) - - // dvxWidget.h -- scroll pane / splitter - DXE_EXPORT(wgtScrollPane) - DXE_EXPORT(wgtSplitter) - DXE_EXPORT(wgtSplitterSetPos) - DXE_EXPORT(wgtSplitterGetPos) - - // dvxWidget.h -- image / image button - DXE_EXPORT(wgtImageButton) - DXE_EXPORT(wgtImageButtonFromFile) - DXE_EXPORT(wgtImageButtonSetData) - DXE_EXPORT(wgtImage) - DXE_EXPORT(wgtImageFromFile) - DXE_EXPORT(wgtImageSetData) - - // dvxWidget.h -- canvas - DXE_EXPORT(wgtCanvas) - DXE_EXPORT(wgtCanvasClear) - DXE_EXPORT(wgtCanvasSetMouseCallback) - DXE_EXPORT(wgtCanvasSetPenColor) - DXE_EXPORT(wgtCanvasSetPenSize) - DXE_EXPORT(wgtCanvasSave) - DXE_EXPORT(wgtCanvasLoad) - DXE_EXPORT(wgtCanvasDrawLine) - DXE_EXPORT(wgtCanvasDrawRect) - DXE_EXPORT(wgtCanvasFillRect) - DXE_EXPORT(wgtCanvasFillCircle) - DXE_EXPORT(wgtCanvasSetPixel) - DXE_EXPORT(wgtCanvasGetPixel) - - // dvxWidget.h -- ANSI terminal - DXE_EXPORT(wgtAnsiTerm) - DXE_EXPORT(wgtAnsiTermWrite) - DXE_EXPORT(wgtAnsiTermClear) - DXE_EXPORT(wgtAnsiTermSetComm) - DXE_EXPORT(wgtAnsiTermSetScrollback) - DXE_EXPORT(wgtAnsiTermPoll) - DXE_EXPORT(wgtAnsiTermRepaint) - - // dvxWidget.h -- operations - DXE_EXPORT(wgtInvalidate) - DXE_EXPORT(wgtInvalidatePaint) - DXE_EXPORT(wgtSetDebugLayout) - DXE_EXPORT(wgtSetText) - DXE_EXPORT(wgtSetTooltip) - DXE_EXPORT(wgtGetText) - DXE_EXPORT(wgtGetFocused) - DXE_EXPORT(wgtSetEnabled) - DXE_EXPORT(wgtSetFocused) - DXE_EXPORT(wgtSetReadOnly) - DXE_EXPORT(wgtSetVisible) - DXE_EXPORT(wgtGetContext) - DXE_EXPORT(wgtSetName) - DXE_EXPORT(wgtFind) - DXE_EXPORT(wgtDestroy) - - // dvxWidget.h -- list box ops - DXE_EXPORT(wgtListBoxSetItems) - DXE_EXPORT(wgtListBoxGetSelected) - DXE_EXPORT(wgtListBoxSetSelected) - DXE_EXPORT(wgtListBoxSetMultiSelect) - DXE_EXPORT(wgtListBoxIsItemSelected) - DXE_EXPORT(wgtListBoxSetItemSelected) - DXE_EXPORT(wgtListBoxSelectAll) - DXE_EXPORT(wgtListBoxClearSelection) - DXE_EXPORT(wgtListBoxSetReorderable) - - // dvxWidget.h -- layout - DXE_EXPORT(wgtResolveSize) - DXE_EXPORT(wgtLayout) - DXE_EXPORT(wgtPaint) - - // taskswitch.h -- only yield and query functions are exported. - // tsCreate/tsKill/etc. are NOT exported because apps should not - // manipulate the task system directly -- the shell manages task - // lifecycle through shellLoadApp/shellForceKillApp. - DXE_EXPORT(tsActiveCount) - DXE_EXPORT(tsCreate) - DXE_EXPORT(tsCurrentId) - DXE_EXPORT(tsGetName) - DXE_EXPORT(tsGetPriority) - DXE_EXPORT(tsGetState) - DXE_EXPORT(tsKill) - DXE_EXPORT(tsPause) - DXE_EXPORT(tsResume) - DXE_EXPORT(tsSetPriority) - DXE_EXPORT(tsYield) - - // dvxWm.h -- direct window management - DXE_EXPORT(wmRaiseWindow) - DXE_EXPORT(wmSetFocus) - DXE_EXPORT(wmRestoreMinimized) - - // Shell API - DXE_EXPORT(shellConfigPath) - DXE_EXPORT(shellEnsureConfigDir) - DXE_EXPORT(shellGetApp) - DXE_EXPORT(shellLoadApp) - DXE_EXPORT(shellLog) - DXE_EXPORT(shellTaskMgrOpen) - DXE_EXPORT(shellForceKillApp) - DXE_EXPORT(shellRunningAppCount) - DXE_EXPORT(shellRegisterDesktopUpdate) - DXE_EXPORT(shellUnregisterDesktopUpdate) - DXE_EXPORT(shellGetSystemInfo) - - // ================================================================ - // libc / libm exports. DXE3 modules are relocatable objects, not - // fully linked executables. Every C library function a DXE calls - // must appear here so the loader can resolve it at dlopen time. - // This is intentionally comprehensive to avoid "unresolved symbol" - // surprises when apps use standard functions. - // ================================================================ - - // --- memory --- - DXE_EXPORT(calloc) - DXE_EXPORT(free) - DXE_EXPORT(malloc) - DXE_EXPORT(realloc) - - // --- string / memory ops --- - DXE_EXPORT(memchr) - DXE_EXPORT(memcmp) - DXE_EXPORT(memcpy) - DXE_EXPORT(memmove) - DXE_EXPORT(memset) - DXE_EXPORT(strcasecmp) - DXE_EXPORT(strcat) - DXE_EXPORT(strchr) - DXE_EXPORT(strcmp) - DXE_EXPORT(strcpy) - DXE_EXPORT(strcspn) - DXE_EXPORT(strdup) - DXE_EXPORT(strerror) - DXE_EXPORT(strlen) - DXE_EXPORT(strncasecmp) - DXE_EXPORT(strncat) - DXE_EXPORT(strncmp) - DXE_EXPORT(strncpy) - DXE_EXPORT(strpbrk) - DXE_EXPORT(strrchr) - DXE_EXPORT(strspn) - DXE_EXPORT(strstr) - DXE_EXPORT(strtok) - - // --- ctype --- - DXE_EXPORT(isalnum) - DXE_EXPORT(isalpha) - DXE_EXPORT(isdigit) - DXE_EXPORT(islower) - DXE_EXPORT(isprint) - DXE_EXPORT(ispunct) - DXE_EXPORT(isspace) - DXE_EXPORT(isupper) - DXE_EXPORT(isxdigit) - DXE_EXPORT(tolower) - DXE_EXPORT(toupper) - - // --- conversion --- - DXE_EXPORT(abs) - DXE_EXPORT(atof) - DXE_EXPORT(atoi) - DXE_EXPORT(atol) - DXE_EXPORT(labs) - DXE_EXPORT(strtod) - DXE_EXPORT(strtol) - DXE_EXPORT(strtoul) - - // --- formatted I/O --- - DXE_EXPORT(fprintf) - DXE_EXPORT(fputs) - DXE_EXPORT(fscanf) - DXE_EXPORT(printf) - DXE_EXPORT(puts) - DXE_EXPORT(snprintf) - DXE_EXPORT(sprintf) - DXE_EXPORT(sscanf) - DXE_EXPORT(vfprintf) - DXE_EXPORT(vprintf) - DXE_EXPORT(vsnprintf) - DXE_EXPORT(vsprintf) - - // --- character I/O --- - DXE_EXPORT(fgetc) - DXE_EXPORT(fgets) - DXE_EXPORT(fputc) - DXE_EXPORT(getc) - DXE_EXPORT(putc) - DXE_EXPORT(putchar) - DXE_EXPORT(ungetc) - - // --- file I/O --- - DXE_EXPORT(fclose) - DXE_EXPORT(feof) - DXE_EXPORT(ferror) - DXE_EXPORT(fflush) - DXE_EXPORT(fopen) - DXE_EXPORT(fread) - DXE_EXPORT(freopen) - DXE_EXPORT(fseek) - DXE_EXPORT(ftell) - DXE_EXPORT(fwrite) - DXE_EXPORT(remove) - DXE_EXPORT(rename) - DXE_EXPORT(rewind) - DXE_EXPORT(tmpfile) - DXE_EXPORT(tmpnam) - - // --- directory --- - DXE_EXPORT(closedir) - DXE_EXPORT(mkdir) - DXE_EXPORT(opendir) - DXE_EXPORT(readdir) - DXE_EXPORT(rmdir) - - // --- filesystem --- - DXE_EXPORT(access) - DXE_EXPORT(chdir) - DXE_EXPORT(getcwd) - DXE_EXPORT(stat) - DXE_EXPORT(unlink) - - // --- time --- - DXE_EXPORT(clock) - DXE_EXPORT(difftime) - DXE_EXPORT(gmtime) - DXE_EXPORT(localtime) - DXE_EXPORT(mktime) - DXE_EXPORT(strftime) - DXE_EXPORT(time) - - // --- process / environment --- - DXE_EXPORT(abort) - DXE_EXPORT(atexit) - DXE_EXPORT(exit) - DXE_EXPORT(getenv) - DXE_EXPORT(system) - - // --- sorting / searching --- - DXE_EXPORT(bsearch) - DXE_EXPORT(qsort) - - // --- random --- - DXE_EXPORT(rand) - DXE_EXPORT(srand) - - // --- setjmp / signal --- - DXE_EXPORT(longjmp) - DXE_EXPORT(setjmp) - DXE_EXPORT(signal) - - // --- libm --- - DXE_EXPORT(acos) - DXE_EXPORT(asin) - DXE_EXPORT(atan) - DXE_EXPORT(atan2) - DXE_EXPORT(ceil) - DXE_EXPORT(cos) - DXE_EXPORT(exp) - DXE_EXPORT(fabs) - DXE_EXPORT(floor) - DXE_EXPORT(fmod) - DXE_EXPORT(frexp) - DXE_EXPORT(ldexp) - DXE_EXPORT(log) - DXE_EXPORT(log10) - DXE_EXPORT(modf) - DXE_EXPORT(pow) - DXE_EXPORT(sin) - DXE_EXPORT(sqrt) - DXE_EXPORT(tan) - - // --- errno --- - DXE_EXPORT(errno) - - // --- libgcc 64-bit integer math helpers --- - // GCC emits calls to these for int64_t division/modulo on 32-bit targets. - // Without them, any DXE using 64-bit arithmetic gets unresolved symbols. - DXE_EXPORT(__divdi3) - DXE_EXPORT(__moddi3) - DXE_EXPORT(__muldi3) - DXE_EXPORT(__udivdi3) - DXE_EXPORT(__udivmoddi4) - DXE_EXPORT(__umoddi3) - - // --- DJGPP stdio internals --- - // The stdin/stdout/stderr macros in DJGPP expand to pointers to - // these FILE structs. Without them, any DXE that does printf() - // or fprintf(stderr, ...) gets an unresolved symbol at load time. - DXE_EXPORT(__dj_stdin) - DXE_EXPORT(__dj_stdout) - DXE_EXPORT(__dj_stderr) - - // --- stb_ds (dynamic arrays / hashmaps) --- - // Internal functions called by the arrput/arrfree/hm* macros. - // Implementation lives in libtasks.a. - DXE_EXPORT(stbds_arrfreef) - DXE_EXPORT(stbds_arrgrowf) - DXE_EXPORT(stbds_hash_bytes) - DXE_EXPORT(stbds_hash_string) - DXE_EXPORT(stbds_hmdel_key) - 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_rand_seed) - DXE_EXPORT(stbds_shmode_func) - DXE_EXPORT(stbds_stralloc) - DXE_EXPORT(stbds_strreset) - - // --- stb_image (image loading) --- - DXE_EXPORT(stbi_failure_reason) - DXE_EXPORT(stbi_image_free) - DXE_EXPORT(stbi_info) - DXE_EXPORT(stbi_info_from_memory) - DXE_EXPORT(stbi_load) - DXE_EXPORT(stbi_load_from_memory) - DXE_EXPORT(stbi_set_flip_vertically_on_load) - - // --- stb_image_write --- - DXE_EXPORT(stbi_write_bmp) - DXE_EXPORT(stbi_write_png) - DXE_EXPORT(stbi_write_jpg) - DXE_EXPORT(stbi_write_tga) -DXE_EXPORT_END - - -// ============================================================ -// shellRegisterExports -// ============================================================ - -// dlregsym registers our export table with DJGPP's DXE3 runtime. -// Must be called once before any dlopen -- subsequent dlopen calls -// will search this table to resolve DXE symbol references. -static void shellRegisterExports(void) { - dlregsym(shellExportTable); -} - - -// ============================================================ -// Public init function -// ============================================================ - -void shellExportInit(void) { - shellRegisterExports(); -} diff --git a/loader/Makefile b/loader/Makefile new file mode 100644 index 0000000..9ce538d --- /dev/null +++ b/loader/Makefile @@ -0,0 +1,54 @@ +# DVX Loader Makefile for DJGPP cross-compilation +# +# Builds the bootstrap loader (dvx.exe) that loads DXE modules. +# Links dvxPlatformDos.c directly -- the platform layer provides +# the DXE export table via platformRegisterDxeExports(). + +DJGPP_PREFIX = $(HOME)/djgpp/djgpp +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 +LDFLAGS = -lm + +OBJDIR = ../obj/loader +POBJDIR = ../obj/loader/platform +BINDIR = ../bin + +SRCS = loaderMain.c +OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) +POBJS = $(POBJDIR)/dvxPlatformDos.o +TARGET = $(BINDIR)/dvx.exe + +.PHONY: all clean + +all: $(TARGET) + +$(TARGET): $(OBJS) $(POBJS) | $(BINDIR) + $(CC) $(CFLAGS) -o $@ $(OBJS) $(POBJS) $(LDFLAGS) -Wl,-Map=$(BINDIR)/dvx.map + $(EXE2COFF) $@ + cat $(CWSDSTUB) $(BINDIR)/dvx > $@ + rm -f $(BINDIR)/dvx + +$(OBJDIR)/%.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(POBJDIR)/dvxPlatformDos.o: ../core/platform/dvxPlatformDos.c | $(POBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(POBJDIR): + mkdir -p $(POBJDIR) + +$(BINDIR): + mkdir -p $(BINDIR) + +# Dependencies +$(OBJDIR)/loaderMain.o: loaderMain.c ../core/platform/dvxPlatform.h ../core/dvxTypes.h +$(POBJDIR)/dvxPlatformDos.o: ../core/platform/dvxPlatformDos.c ../core/platform/dvxPlatform.h ../core/dvxTypes.h ../core/dvxPalette.h + +clean: + rm -f $(OBJS) $(POBJS) $(TARGET) $(BINDIR)/dvx.map diff --git a/loader/loaderMain.c b/loader/loaderMain.c new file mode 100644 index 0000000..b63a8d3 --- /dev/null +++ b/loader/loaderMain.c @@ -0,0 +1,394 @@ +// loaderMain.c -- DVX bootstrap loader entry point +// +// Loads all DXE modules from two directories: +// libs/ *.lib -- core libraries (libtasks, libdvx, dvxshell) +// widgets/ *.wgt -- widget type plugins (box, button, listview, etc.) +// +// Each module may have a .dep file (same base name, .dep extension) +// listing base names of modules that must be loaded before it. +// The loader resolves the dependency graph and loads in topological +// order. After loading, any module that exports wgtRegister() has +// it called. Finally, the loader finds and calls shellMain(). + +#include "dvxPlatform.h" + +#include +#include +#include +#include +#include +#include +#include + +#define STB_DS_IMPLEMENTATION +#include "stb_ds.h" + +// ============================================================ +// Constants +// ============================================================ + +#define LIBS_DIR "LIBS" +#define WIDGET_DIR "WIDGETS" + +// ============================================================ +// Module entry for dependency resolution +// ============================================================ + +typedef struct { + char path[260]; + char baseName[16]; + char **deps; + bool loaded; + void *handle; +} ModuleT; + +// ============================================================ +// Prototypes +// ============================================================ + +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 **loadAllModules(void); +static void loadInOrder(ModuleT *mods); +static void readDeps(ModuleT *mod); +static void scanDir(const char *dirPath, const char *ext, ModuleT **mods); + +// ============================================================ +// extractBaseName -- strip directory and extension, lowercase +// ============================================================ + +static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize) { + // Find last directory separator + const char *start = path; + const char *p = path; + + while (*p) { + if (*p == '/' || *p == '\\') { + start = p + 1; + } + + p++; + } + + // Copy up to the extension + int32_t extLen = strlen(ext); + int32_t len = strlen(start); + + if (len > extLen && strcasecmp(start + len - extLen, ext) == 0) { + len -= extLen; + } + + if (len >= outSize) { + len = outSize - 1; + } + + for (int32_t i = 0; i < len; i++) { + out[i] = tolower((unsigned char)start[i]); + } + + out[len] = '\0'; +} + + +// ============================================================ +// readDeps -- parse .dep file for a module +// ============================================================ +// +// The .dep file has the same path as the module but with a .dep +// extension. Each line is a dependency base name. Empty lines +// and lines starting with # are ignored. + +static void readDeps(ModuleT *mod) { + // Build dep file path: replace extension with .dep + char depPath[260]; + strncpy(depPath, mod->path, sizeof(depPath) - 1); + depPath[sizeof(depPath) - 1] = '\0'; + + char *dot = strrchr(depPath, '.'); + + if (!dot) { + return; + } + + strcpy(dot, ".dep"); + + FILE *f = fopen(depPath, "r"); + + if (!f) { + return; + } + + char line[64]; + + while (fgets(line, sizeof(line), f)) { + // Strip \r and \n + int32_t len = strlen(line); + + while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r')) { + line[--len] = '\0'; + } + + // Skip empty lines and comments + if (len == 0 || line[0] == '#') { + continue; + } + + // Lowercase the dep name for case-insensitive matching + for (int32_t i = 0; i < len; i++) { + line[i] = tolower((unsigned char)line[i]); + } + + arrput(mod->deps, strdup(line)); + } + + fclose(f); +} + + +// ============================================================ +// scanDir -- recursively find modules with a given extension +// ============================================================ + +static void scanDir(const char *dirPath, const char *ext, ModuleT **mods) { + DIR *dir = opendir(dirPath); + + if (!dir) { + return; + } + + struct dirent *ent; + + while ((ent = readdir(dir)) != NULL) { + const char *name = ent->d_name; + + // Skip . and .. + if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { + continue; + } + + char path[260]; + snprintf(path, sizeof(path), "%s/%s", dirPath, name); + + // Check for matching extension + int32_t nameLen = strlen(name); + int32_t extLen = strlen(ext); + + if (nameLen > extLen && strcasecmp(name + nameLen - extLen, ext) == 0) { + ModuleT mod; + memset(&mod, 0, sizeof(mod)); + strncpy(mod.path, path, sizeof(mod.path) - 1); + extractBaseName(path, ext, mod.baseName, sizeof(mod.baseName)); + arrput(*mods, mod); + continue; + } + + // Recurse into subdirectories + struct stat st; + + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) { + scanDir(path, ext, mods); + } + } + + closedir(dir); +} + + +// ============================================================ +// allDepsLoaded -- check if a module's dependencies are satisfied +// ============================================================ +// +// A dep is satisfied if either: +// 1. No module with that base name exists (external, assumed OK) +// 2. The module with that base name is already loaded + +static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods) { + for (int32_t d = 0; d < arrlen(mod->deps); d++) { + for (int32_t j = 0; j < arrlen(mods); j++) { + if (strcasecmp(mods[j].baseName, mod->deps[d]) == 0) { + if (!mods[j].loaded) { + return false; + } + + break; + } + } + } + + return true; +} + + +// ============================================================ +// loadInOrder -- topological sort and load +// ============================================================ +// +// Repeatedly scans the module list for entries whose dependencies +// are all satisfied, loads them, and marks them done. Stops when +// all modules are loaded or no progress can be made (circular dep). + +static void loadInOrder(ModuleT *mods) { + typedef void (*RegFnT)(void); + int32_t total = arrlen(mods); + int32_t loaded = 0; + bool progress; + + do { + progress = false; + + for (int32_t i = 0; i < total; i++) { + if (mods[i].loaded) { + continue; + } + + if (!allDepsLoaded(&mods[i], mods)) { + continue; + } + + mods[i].handle = dlopen(mods[i].path, RTLD_GLOBAL); + + if (mods[i].handle) { + RegFnT regFn = (RegFnT)dlsym(mods[i].handle, "_wgtRegister"); + + if (regFn) { + regFn(); + } + } + + mods[i].loaded = true; + loaded++; + progress = true; + } + } while (progress && loaded < total); + + if (loaded < total) { + fprintf(stderr, "Module loader: %d of %d modules could not be loaded (circular deps or missing deps)\n", total - loaded, total); + + for (int32_t i = 0; i < total; i++) { + if (!mods[i].loaded) { + fprintf(stderr, " %s\n", mods[i].path); + } + } + } +} + + +// ============================================================ +// loadAllModules -- scan libs/ and widgets/, load in dep order +// ============================================================ +// +// Returns a stb_ds dynamic array of dlopen handles. + +static void **loadAllModules(void) { + ModuleT *mods = NULL; + + // Discover all modules + scanDir(LIBS_DIR, ".lib", &mods); + scanDir(WIDGET_DIR, ".wgt", &mods); + + // Read dependency files + for (int32_t i = 0; i < arrlen(mods); i++) { + readDeps(&mods[i]); + } + + // Load in dependency order + loadInOrder(mods); + + // Collect handles + void **handles = NULL; + + for (int32_t i = 0; i < arrlen(mods); i++) { + if (mods[i].handle) { + arrput(handles, mods[i].handle); + } + } + + // Free dep arrays + 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]); + } + + arrfree(mods[i].deps); + } + + arrfree(mods); + return handles; +} + + +// ============================================================ +// findSymbol -- search loaded handles for a symbol +// ============================================================ + +static void *findSymbol(void **handles, const char *symbol) { + for (int32_t i = 0; i < arrlen(handles); i++) { + void *sym = dlsym(handles[i], symbol); + + if (sym) { + return sym; + } + } + + return NULL; +} + + +// ============================================================ +// main +// ============================================================ + +int main(int argc, char *argv[]) { + // Change to the directory containing the executable so relative + // paths (LIBS/, WIDGETS/, APPS/, CONFIG/) resolve correctly. + char exeDir[260]; + strncpy(exeDir, argv[0], sizeof(exeDir) - 1); + exeDir[sizeof(exeDir) - 1] = '\0'; + + char *sep = platformPathDirEnd(exeDir); + + if (sep) { + *sep = '\0'; + platformChdir(exeDir); + } + + // Register platform + libc/libm/runtime symbols for DXE resolution + platformRegisterDxeExports(); + + // Load all modules from libs/ and widgets/ in dependency order. + // Each module may have a .dep file specifying load-before deps. + // Widget modules that export wgtRegister() get it called. + void **handles = loadAllModules(); + + if (!handles || arrlen(handles) == 0) { + fprintf(stderr, "No modules loaded from %s/ or %s/\n", LIBS_DIR, WIDGET_DIR); + arrfree(handles); + return 1; + } + + // Find and call shellMain from whichever module exports it + typedef int (*ShellMainFnT)(int, char **); + ShellMainFnT shellMain = (ShellMainFnT)findSymbol(handles, "_shellMain"); + + if (!shellMain) { + fprintf(stderr, "No module exports shellMain\n"); + + for (int32_t i = arrlen(handles) - 1; i >= 0; i--) { + dlclose(handles[i]); + } + + arrfree(handles); + return 1; + } + + int result = shellMain(argc, argv); + + // Clean up in reverse load order + for (int32_t i = arrlen(handles) - 1; i >= 0; i--) { + dlclose(handles[i]); + } + + arrfree(handles); + return result; +} diff --git a/mkcd.sh b/mkcd.sh index 24af418..9123329 100755 --- a/mkcd.sh +++ b/mkcd.sh @@ -20,11 +20,20 @@ ISO_PATH="$ISO_DIR/dvx.iso" echo "Building DVX..." make -C "$SCRIPT_DIR" all -# Verify build output exists -if [ ! -f "$SCRIPT_DIR/bin/dvx.exe" ]; then - echo "ERROR: bin/dvx.exe not found -- build failed?" - exit 1 -fi +# Verify core build output exists +for f in dvx.exe libs/libtasks.lib libs/libdvx.lib libs/dvxshell.lib; do + if [ ! -f "$SCRIPT_DIR/bin/$f" ]; then + echo "ERROR: bin/$f not found -- build failed?" + exit 1 + fi +done + +# Verify widget DXEs exist +WGT_COUNT=0 +for f in "$SCRIPT_DIR"/bin/widgets/*.wgt; do + [ -f "$f" ] && WGT_COUNT=$((WGT_COUNT + 1)) +done +echo "$WGT_COUNT widget modules found in bin/widgets/." # Create the ISO image # -iso-level 1: strict 8.3 filenames (DOS compatibility) diff --git a/shell/Makefile b/shell/Makefile new file mode 100644 index 0000000..ba4dfe9 --- /dev/null +++ b/shell/Makefile @@ -0,0 +1,69 @@ +# DVX Shell Makefile for DJGPP cross-compilation +# +# Builds dvxshell.lib -- the shell module loaded by the DVX loader. + +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 + +OBJDIR = ../obj/shell +LIBSDIR = ../bin/libs +CONFIGDIR = ../bin/config +THEMEDIR = ../bin/config/themes +WPAPERDIR = ../bin/config/wpaper + +SRCS = shellMain.c shellApp.c shellExport.c shellInfo.c shellTaskMgr.c +OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) +TARGET = $(LIBSDIR)/dvxshell.lib + +.PHONY: all clean + +THEMES = $(THEMEDIR)/geos.thm $(THEMEDIR)/win31.thm $(THEMEDIR)/cde.thm +WPAPERS = $(WPAPERDIR)/blueglow.jpg $(WPAPERDIR)/swoop.jpg $(WPAPERDIR)/triangle.jpg + +all: $(TARGET) $(LIBSDIR)/dvxshell.dep $(CONFIGDIR)/dvx.ini $(THEMES) $(WPAPERS) + +$(LIBSDIR)/dvxshell.dep: ../config/dvxshell.dep | $(LIBSDIR) + sed 's/$$/\r/' $< > $@ + +$(TARGET): $(OBJS) | $(LIBSDIR) + $(DXE3GEN) -o $@ -E _shell -U $(OBJS) + +$(CONFIGDIR)/dvx.ini: ../config/dvx.ini | $(CONFIGDIR) + sed 's/$$/\r/' $< > $@ + +$(OBJDIR)/%.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(LIBSDIR): + mkdir -p $(LIBSDIR) + +$(CONFIGDIR): + mkdir -p $(CONFIGDIR) + +$(THEMEDIR): + mkdir -p $(THEMEDIR) + +$(THEMEDIR)/%.thm: ../config/themes/%.thm | $(THEMEDIR) + sed 's/$$/\r/' $< > $@ + +$(WPAPERDIR): + mkdir -p $(WPAPERDIR) + +$(WPAPERDIR)/%.jpg: ../config/wpaper/%.jpg | $(WPAPERDIR) + cp $< $@ + +# Dependencies +$(OBJDIR)/shellMain.o: shellMain.c shellApp.h +$(OBJDIR)/shellApp.o: shellApp.c shellApp.h +$(OBJDIR)/shellExport.o: shellExport.c shellApp.h +$(OBJDIR)/shellInfo.o: shellInfo.c shellInfo.h shellApp.h +$(OBJDIR)/shellTaskMgr.o: shellTaskMgr.c shellTaskMgr.h shellApp.h + +clean: + rm -f $(OBJS) $(TARGET) $(LIBSDIR)/dvxshell.dep + rm -rf $(WPAPERDIR) $(THEMEDIR) $(CONFIGDIR) diff --git a/dvxshell/README.md b/shell/README.md similarity index 100% rename from dvxshell/README.md rename to shell/README.md diff --git a/dvxshell/shellApp.c b/shell/shellApp.c similarity index 99% rename from dvxshell/shellApp.c rename to shell/shellApp.c index 1b2a411..3638dfb 100644 --- a/dvxshell/shellApp.c +++ b/shell/shellApp.c @@ -5,7 +5,7 @@ #include "shellApp.h" #include "dvxDialog.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include #include diff --git a/dvxshell/shellApp.h b/shell/shellApp.h similarity index 100% rename from dvxshell/shellApp.h rename to shell/shellApp.h diff --git a/shell/shellExport.c b/shell/shellExport.c new file mode 100644 index 0000000..0b80909 --- /dev/null +++ b/shell/shellExport.c @@ -0,0 +1,126 @@ +// 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. +// +// 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 +// 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. + +#include "shellApp.h" +#include "dvxApp.h" +#include "dvxWm.h" + +#include + +// ============================================================ +// Prototypes +// ============================================================ + +static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable); +static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable); +static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win); + +// ============================================================ +// Wrapper: dvxCreateWindow -- stamps win->appId +// ============================================================ + +// The wrapper calls the real dvxCreateWindow (resolved from libdvx.dxe +// 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. +static WindowT *shellWrapCreateWindow(AppContextT *ctx, const char *title, int32_t x, int32_t y, int32_t w, int32_t h, bool resizable) { + WindowT *win = dvxCreateWindow(ctx, title, x, y, w, h, resizable); + + if (win) { + win->appId = sCurrentAppId; + } + + return win; +} + + +// ============================================================ +// Wrapper: dvxCreateWindowCentered -- stamps win->appId +// ============================================================ + +static WindowT *shellWrapCreateWindowCentered(AppContextT *ctx, const char *title, int32_t w, int32_t h, bool resizable) { + WindowT *win = dvxCreateWindowCentered(ctx, title, w, h, resizable); + + if (win) { + win->appId = sCurrentAppId; + } + + return win; +} + + +// ============================================================ +// Wrapper: dvxDestroyWindow -- checks for last-window reap +// ============================================================ + +// Beyond just destroying the window, this wrapper implements the lifecycle +// rule for callback-only apps: when their last window closes, they're done. +// Main-loop apps manage their own lifetime (their task returns from +// appMain), so this check only applies to callback-only apps. +// The appId is captured before destruction because the window struct is +// freed by dvxDestroyWindow. +static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win) { + int32_t appId = win->appId; + + dvxDestroyWindow(ctx, win); + + // If this was a callback-only app's last window, mark for reaping + if (appId > 0) { + ShellAppT *app = shellGetApp(appId); + + if (app && !app->hasMainLoop && app->state == AppStateRunningE) { + // Check if app still has any windows + bool hasWindows = false; + + for (int32_t i = 0; i < ctx->stack.count; i++) { + if (ctx->stack.windows[i]->appId == appId) { + hasWindows = true; + break; + } + } + + if (!hasWindows) { + app->state = AppStateTerminatingE; + } + } + } +} + + +// ============================================================ +// 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. +void shellExportInit(void) { + dlregsym(sWrapperTable); +} diff --git a/dvxshell/shellInfo.c b/shell/shellInfo.c similarity index 98% rename from dvxshell/shellInfo.c rename to shell/shellInfo.c index 260a5fb..92a1bbb 100644 --- a/dvxshell/shellInfo.c +++ b/shell/shellInfo.c @@ -7,7 +7,7 @@ #include "shellInfo.h" #include "shellApp.h" -#include "platform/dvxPlatform.h" +#include "dvxPlatform.h" #include diff --git a/dvxshell/shellInfo.h b/shell/shellInfo.h similarity index 100% rename from dvxshell/shellInfo.h rename to shell/shellInfo.h diff --git a/dvxshell/shellMain.c b/shell/shellMain.c similarity index 97% rename from dvxshell/shellMain.c rename to shell/shellMain.c index 581cc7c..8d2a113 100644 --- a/dvxshell/shellMain.c +++ b/shell/shellMain.c @@ -29,8 +29,8 @@ #include "shellTaskMgr.h" #include "dvxDialog.h" #include "dvxPrefs.h" -#include "platform/dvxPlatform.h" -#include "thirdparty/stb_ds.h" +#include "dvxPlatform.h" +#include "stb_ds.h" #include #include @@ -261,25 +261,12 @@ void shellUnregisterDesktopUpdate(void (*updateFn)(void)) { // ============================================================ -// main +// shellMain -- entry point called by the DVX loader // ============================================================ -int main(int argc, char *argv[]) { +int shellMain(int argc, char *argv[]) { (void)argc; - - // Change to the directory containing the executable so that relative - // paths (CONFIG/, APPS/, etc.) resolve correctly regardless of where - // the user launched from. - char exeDir[260]; - strncpy(exeDir, argv[0], sizeof(exeDir) - 1); - exeDir[sizeof(exeDir) - 1] = '\0'; - - char *sep = platformPathDirEnd(exeDir); - - if (sep) { - *sep = '\0'; - platformChdir(exeDir); - } + (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). diff --git a/dvxshell/shellTaskMgr.c b/shell/shellTaskMgr.c similarity index 99% rename from dvxshell/shellTaskMgr.c rename to shell/shellTaskMgr.c index 2c2afb0..c0e0fa3 100644 --- a/dvxshell/shellTaskMgr.c +++ b/shell/shellTaskMgr.c @@ -9,8 +9,8 @@ #include "dvxDialog.h" #include "dvxWidget.h" #include "dvxWm.h" -#include "platform/dvxPlatform.h" -#include "thirdparty/stb_ds.h" +#include "dvxPlatform.h" +#include "stb_ds.h" #include #include diff --git a/dvxshell/shellTaskMgr.h b/shell/shellTaskMgr.h similarity index 100% rename from dvxshell/shellTaskMgr.h rename to shell/shellTaskMgr.h diff --git a/tasks/Makefile b/tasks/Makefile index c5a1f38..1bf0706 100644 --- a/tasks/Makefile +++ b/tasks/Makefile @@ -1,41 +1,25 @@ # Cooperative Task Switching Library Makefile for DJGPP cross-compilation +# +# Builds libtasks.lib -- the task switching module loaded by the DVX loader. DJGPP_PREFIX = $(HOME)/djgpp/djgpp -DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc -AR = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ar -RANLIB = LD_LIBRARY_PATH=$(DJGPP_LIBPATH) $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-ranlib -EXE2COFF = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff -CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE +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 -OBJDIR = ../obj/tasks -LIBDIR = ../lib -BINDIR = ../bin +OBJDIR = ../obj/tasks +LIBSDIR = ../bin/libs SRCS = taskswitch.c OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) -TARGET = $(LIBDIR)/libtasks.a - -DEMO_SRCS = demo.c -DEMO_OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(DEMO_SRCS)) -DEMO_TARGET = $(BINDIR)/tsdemo.exe +TARGET = $(LIBSDIR)/libtasks.lib .PHONY: all clean all: $(TARGET) -demo: $(DEMO_TARGET) - -$(TARGET): $(OBJS) | $(LIBDIR) - $(AR) rcs $@ $(OBJS) - $(RANLIB) $@ - -$(DEMO_TARGET): $(DEMO_OBJS) $(TARGET) | $(BINDIR) - $(CC) $(CFLAGS) -o $@ $(DEMO_OBJS) -L$(LIBDIR) -ltasks - $(EXE2COFF) $@ - cat $(CWSDSTUB) $(BINDIR)/tsdemo > $@ - rm -f $(BINDIR)/tsdemo +$(TARGET): $(OBJS) | $(LIBSDIR) + $(DXE3GEN) -o $@ -E _ts -E _stbds_ -U $(OBJS) $(OBJDIR)/%.o: %.c | $(OBJDIR) $(CC) $(CFLAGS) -c -o $@ $< @@ -43,15 +27,11 @@ $(OBJDIR)/%.o: %.c | $(OBJDIR) $(OBJDIR): mkdir -p $(OBJDIR) -$(LIBDIR): - mkdir -p $(LIBDIR) - -$(BINDIR): - mkdir -p $(BINDIR) +$(LIBSDIR): + mkdir -p $(LIBSDIR) # Dependencies $(OBJDIR)/taskswitch.o: taskswitch.c taskswitch.h thirdparty/stb_ds.h -$(OBJDIR)/demo.o: demo.c taskswitch.h clean: - rm -f $(OBJS) $(DEMO_OBJS) $(TARGET) $(DEMO_TARGET) + rm -f $(OBJS) $(TARGET) diff --git a/widgets/Makefile b/widgets/Makefile new file mode 100644 index 0000000..73c8cb6 --- /dev/null +++ b/widgets/Makefile @@ -0,0 +1,147 @@ +# DVX Widget Modules Makefile for DJGPP cross-compilation +# +# Builds individual .wgt modules from widget source files. +# Each .wgt is a DXE loaded by the loader at startup. + +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 + +OBJDIR = ../obj/widgets +WGTDIR = ../bin/widgets + +SRCS = widgetAnsiTerm.c \ + widgetBox.c \ + widgetButton.c \ + widgetCanvas.c \ + widgetCheckbox.c \ + widgetComboBox.c \ + widgetDropdown.c \ + widgetImage.c \ + widgetImageButton.c \ + widgetLabel.c \ + widgetListBox.c \ + widgetListView.c \ + widgetProgressBar.c \ + widgetRadio.c \ + widgetScrollPane.c \ + widgetSeparator.c \ + widgetSlider.c \ + widgetSpacer.c \ + widgetSpinner.c \ + widgetSplitter.c \ + widgetStatusBar.c \ + widgetTabControl.c \ + widgetTextInput.c \ + widgetTimer.c \ + widgetToolbar.c \ + widgetTreeView.c + +OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS)) + +WGT_MODS = $(WGTDIR)/box.wgt \ + $(WGTDIR)/button.wgt \ + $(WGTDIR)/canvas.wgt \ + $(WGTDIR)/checkbox.wgt \ + $(WGTDIR)/combobox.wgt \ + $(WGTDIR)/dropdown.wgt \ + $(WGTDIR)/imgbtn.wgt \ + $(WGTDIR)/image.wgt \ + $(WGTDIR)/label.wgt \ + $(WGTDIR)/listbox.wgt \ + $(WGTDIR)/listview.wgt \ + $(WGTDIR)/progress.wgt \ + $(WGTDIR)/radio.wgt \ + $(WGTDIR)/scrlpane.wgt \ + $(WGTDIR)/separatr.wgt \ + $(WGTDIR)/slider.wgt \ + $(WGTDIR)/spacer.wgt \ + $(WGTDIR)/spinner.wgt \ + $(WGTDIR)/splitter.wgt \ + $(WGTDIR)/statbar.wgt \ + $(WGTDIR)/tabctrl.wgt \ + $(WGTDIR)/terminal.wgt \ + $(WGTDIR)/textinpt.wgt \ + $(WGTDIR)/timer.wgt \ + $(WGTDIR)/toolbar.wgt \ + $(WGTDIR)/treeview.wgt + +.PHONY: all clean + +all: $(WGT_MODS) + +# Generic widget module rule: -E _wgt exports all public API + wgtRegister +$(WGTDIR)/%.wgt: | $(WGTDIR) + $(DXE3GEN) -o $@ -E _wgt -U $< + +# Map .wgt name to .o file +$(WGTDIR)/box.wgt: $(OBJDIR)/widgetBox.o +$(WGTDIR)/button.wgt: $(OBJDIR)/widgetButton.o +$(WGTDIR)/canvas.wgt: $(OBJDIR)/widgetCanvas.o +$(WGTDIR)/checkbox.wgt: $(OBJDIR)/widgetCheckbox.o +$(WGTDIR)/combobox.wgt: $(OBJDIR)/widgetComboBox.o +$(WGTDIR)/dropdown.wgt: $(OBJDIR)/widgetDropdown.o +$(WGTDIR)/imgbtn.wgt: $(OBJDIR)/widgetImageButton.o +$(WGTDIR)/image.wgt: $(OBJDIR)/widgetImage.o +$(WGTDIR)/label.wgt: $(OBJDIR)/widgetLabel.o +$(WGTDIR)/listbox.wgt: $(OBJDIR)/widgetListBox.o +$(WGTDIR)/listview.wgt: $(OBJDIR)/widgetListView.o +$(WGTDIR)/progress.wgt: $(OBJDIR)/widgetProgressBar.o +$(WGTDIR)/radio.wgt: $(OBJDIR)/widgetRadio.o +$(WGTDIR)/scrlpane.wgt: $(OBJDIR)/widgetScrollPane.o +$(WGTDIR)/separatr.wgt: $(OBJDIR)/widgetSeparator.o +$(WGTDIR)/slider.wgt: $(OBJDIR)/widgetSlider.o +$(WGTDIR)/spacer.wgt: $(OBJDIR)/widgetSpacer.o +$(WGTDIR)/spinner.wgt: $(OBJDIR)/widgetSpinner.o +$(WGTDIR)/splitter.wgt: $(OBJDIR)/widgetSplitter.o +$(WGTDIR)/statbar.wgt: $(OBJDIR)/widgetStatusBar.o +$(WGTDIR)/tabctrl.wgt: $(OBJDIR)/widgetTabControl.o +$(WGTDIR)/terminal.wgt: $(OBJDIR)/widgetAnsiTerm.o +$(WGTDIR)/textinpt.wgt: $(OBJDIR)/widgetTextInput.o +$(WGTDIR)/timer.wgt: $(OBJDIR)/widgetTimer.o +$(WGTDIR)/toolbar.wgt: $(OBJDIR)/widgetToolbar.o +$(WGTDIR)/treeview.wgt: $(OBJDIR)/widgetTreeView.o + +# Compile +$(OBJDIR)/%.o: %.c | $(OBJDIR) + $(CC) $(CFLAGS) -c -o $@ $< + +$(OBJDIR): + mkdir -p $(OBJDIR) + +$(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 +$(OBJDIR)/widgetAnsiTerm.o: widgetAnsiTerm.c $(WIDGET_DEPS) +$(OBJDIR)/widgetBox.o: widgetBox.c $(WIDGET_DEPS) +$(OBJDIR)/widgetButton.o: widgetButton.c $(WIDGET_DEPS) +$(OBJDIR)/widgetCanvas.o: widgetCanvas.c $(WIDGET_DEPS) ../core/thirdparty/stb_image.h ../core/thirdparty/stb_image_write.h +$(OBJDIR)/widgetCheckbox.o: widgetCheckbox.c $(WIDGET_DEPS) +$(OBJDIR)/widgetComboBox.o: widgetComboBox.c $(WIDGET_DEPS) +$(OBJDIR)/widgetDropdown.o: widgetDropdown.c $(WIDGET_DEPS) +$(OBJDIR)/widgetImage.o: widgetImage.c $(WIDGET_DEPS) ../core/thirdparty/stb_image.h +$(OBJDIR)/widgetImageButton.o: widgetImageButton.c $(WIDGET_DEPS) +$(OBJDIR)/widgetLabel.o: widgetLabel.c $(WIDGET_DEPS) +$(OBJDIR)/widgetListBox.o: widgetListBox.c $(WIDGET_DEPS) +$(OBJDIR)/widgetListView.o: widgetListView.c $(WIDGET_DEPS) +$(OBJDIR)/widgetProgressBar.o: widgetProgressBar.c $(WIDGET_DEPS) +$(OBJDIR)/widgetRadio.o: widgetRadio.c $(WIDGET_DEPS) +$(OBJDIR)/widgetScrollPane.o: widgetScrollPane.c $(WIDGET_DEPS) +$(OBJDIR)/widgetSeparator.o: widgetSeparator.c $(WIDGET_DEPS) +$(OBJDIR)/widgetSlider.o: widgetSlider.c $(WIDGET_DEPS) +$(OBJDIR)/widgetSpacer.o: widgetSpacer.c $(WIDGET_DEPS) +$(OBJDIR)/widgetSpinner.o: widgetSpinner.c $(WIDGET_DEPS) +$(OBJDIR)/widgetSplitter.o: widgetSplitter.c $(WIDGET_DEPS) +$(OBJDIR)/widgetStatusBar.o: widgetStatusBar.c $(WIDGET_DEPS) +$(OBJDIR)/widgetTabControl.o: widgetTabControl.c $(WIDGET_DEPS) +$(OBJDIR)/widgetTextInput.o: widgetTextInput.c $(WIDGET_DEPS) +$(OBJDIR)/widgetTimer.o: widgetTimer.c $(WIDGET_DEPS) +$(OBJDIR)/widgetToolbar.o: widgetToolbar.c $(WIDGET_DEPS) +$(OBJDIR)/widgetTreeView.o: widgetTreeView.c $(WIDGET_DEPS) + +clean: + rm -f $(OBJS) $(WGT_MODS) + -rmdir $(WGTDIR) 2>/dev/null diff --git a/dvx/widgets/widgetAnsiTerm.c b/widgets/widgetAnsiTerm.c similarity index 99% rename from dvx/widgets/widgetAnsiTerm.c rename to widgets/widgetAnsiTerm.c index 044c762..a4fac25 100644 --- a/dvx/widgets/widgetAnsiTerm.c +++ b/widgets/widgetAnsiTerm.c @@ -2141,3 +2141,26 @@ void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, colors->contentFg); } } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassAnsiTerm = { + .flags = WCLASS_FOCUSABLE, + .paint = widgetAnsiTermPaint, + .paintOverlay = NULL, + .calcMinSize = widgetAnsiTermCalcMinSize, + .layout = NULL, + .onMouse = widgetAnsiTermOnMouse, + .onKey = widgetAnsiTermOnKey, + .destroy = widgetAnsiTermDestroy, + .getText = NULL, + .setText = NULL +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetAnsiTermE] = &sClassAnsiTerm; +} diff --git a/dvx/widgets/widgetBox.c b/widgets/widgetBox.c similarity index 80% rename from dvx/widgets/widgetBox.c rename to widgets/widgetBox.c index f6caecb..b375c6f 100644 --- a/dvx/widgets/widgetBox.c +++ b/widgets/widgetBox.c @@ -131,3 +131,54 @@ WidgetT *wgtHBox(WidgetT *parent) { WidgetT *wgtVBox(WidgetT *parent) { return widgetAlloc(parent, WidgetVBoxE); } + + +// ============================================================ +// DXE registration +// ============================================================ + + +static const WidgetClassT sClassVBox = { + .flags = WCLASS_BOX_CONTAINER, + .paint = NULL, + .paintOverlay = NULL, + .calcMinSize = NULL, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + +static const WidgetClassT sClassHBox = { + .flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER, + .paint = NULL, + .paintOverlay = NULL, + .calcMinSize = NULL, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + +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 +}; + +void wgtRegister(void) { + widgetClassTable[WidgetVBoxE] = &sClassVBox; + widgetClassTable[WidgetHBoxE] = &sClassHBox; + widgetClassTable[WidgetFrameE] = &sClassFrame; +} diff --git a/dvx/widgets/widgetButton.c b/widgets/widgetButton.c similarity index 89% rename from dvx/widgets/widgetButton.c rename to widgets/widgetButton.c index 6ea624b..bd86417 100644 --- a/dvx/widgets/widgetButton.c +++ b/widgets/widgetButton.c @@ -141,3 +141,26 @@ void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma 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); } } + + +// ============================================================ +// 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 +}; + +void wgtRegister(void) { + widgetClassTable[WidgetButtonE] = &sClassButton; +} diff --git a/dvx/widgets/widgetCanvas.c b/widgets/widgetCanvas.c similarity index 97% rename from dvx/widgets/widgetCanvas.c rename to widgets/widgetCanvas.c index 3741e50..76d3b55 100644 --- a/dvx/widgets/widgetCanvas.c +++ b/widgets/widgetCanvas.c @@ -724,3 +724,26 @@ void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma w->as.canvas.data, w->as.canvas.canvasPitch, 0, 0, imgW, imgH); } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassCanvas = { + .flags = 0, + .paint = widgetCanvasPaint, + .paintOverlay = NULL, + .calcMinSize = widgetCanvasCalcMinSize, + .layout = NULL, + .onMouse = widgetCanvasOnMouse, + .onKey = NULL, + .destroy = widgetCanvasDestroy, + .getText = NULL, + .setText = NULL +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetCanvasE] = &sClassCanvas; +} diff --git a/dvx/widgets/widgetCheckbox.c b/widgets/widgetCheckbox.c similarity index 88% rename from dvx/widgets/widgetCheckbox.c rename to widgets/widgetCheckbox.c index a7f35be..6f45360 100644 --- a/dvx/widgets/widgetCheckbox.c +++ b/widgets/widgetCheckbox.c @@ -150,3 +150,26 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg); } } + + +// ============================================================ +// 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 +}; + +void wgtRegister(void) { + widgetClassTable[WidgetCheckboxE] = &sClassCheckbox; +} diff --git a/dvx/widgets/widgetComboBox.c b/widgets/widgetComboBox.c similarity index 95% rename from dvx/widgets/widgetComboBox.c rename to widgets/widgetComboBox.c index 3fbb5c5..9bf638d 100644 --- a/dvx/widgets/widgetComboBox.c +++ b/widgets/widgetComboBox.c @@ -370,3 +370,26 @@ void widgetComboBoxSetText(WidgetT *w, const char *text) { w->as.comboBox.selEnd = -1; } } + + +// ============================================================ +// DXE registration +// ============================================================ + +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 +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetComboBoxE] = &sClassComboBox; +} diff --git a/dvx/widgets/widgetDropdown.c b/widgets/widgetDropdown.c similarity index 93% rename from dvx/widgets/widgetDropdown.c rename to widgets/widgetDropdown.c index 5401b85..01d8ed3 100644 --- a/dvx/widgets/widgetDropdown.c +++ b/widgets/widgetDropdown.c @@ -274,3 +274,26 @@ void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons 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); } + + +// ============================================================ +// DXE registration +// ============================================================ + + +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 +}; + +void wgtRegister(void) { + widgetClassTable[WidgetDropdownE] = &sClassDropdown; +} diff --git a/dvx/widgets/widgetImage.c b/widgets/widgetImage.c similarity index 89% rename from dvx/widgets/widgetImage.c rename to widgets/widgetImage.c index 7edcd83..0f2d9b5 100644 --- a/dvx/widgets/widgetImage.c +++ b/widgets/widgetImage.c @@ -160,3 +160,26 @@ void widgetImagePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap 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 +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetImageE] = &sClassImage; +} diff --git a/dvx/widgets/widgetImageButton.c b/widgets/widgetImageButton.c similarity index 89% rename from dvx/widgets/widgetImageButton.c rename to widgets/widgetImageButton.c index 710fb14..ef38b98 100644 --- a/dvx/widgets/widgetImageButton.c +++ b/widgets/widgetImageButton.c @@ -179,3 +179,26 @@ void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const 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 +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetImageButtonE] = &sClassImageButton; +} diff --git a/dvx/widgets/widgetLabel.c b/widgets/widgetLabel.c similarity index 85% rename from dvx/widgets/widgetLabel.c rename to widgets/widgetLabel.c index 93f4e3f..16fc910 100644 --- a/dvx/widgets/widgetLabel.c +++ b/widgets/widgetLabel.c @@ -99,3 +99,26 @@ void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) { w->as.label.textAlign = align; } } + + +// ============================================================ +// DXE registration +// ============================================================ + + +static const WidgetClassT sClassLabel = { + .flags = 0, + .paint = widgetLabelPaint, + .paintOverlay = NULL, + .calcMinSize = widgetLabelCalcMinSize, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = widgetLabelGetText, + .setText = widgetLabelSetText +}; + +void wgtRegister(void) { + widgetClassTable[WidgetLabelE] = &sClassLabel; +} diff --git a/dvx/widgets/widgetListBox.c b/widgets/widgetListBox.c similarity index 97% rename from dvx/widgets/widgetListBox.c rename to widgets/widgetListBox.c index 29f0cad..5e71b61 100644 --- a/dvx/widgets/widgetListBox.c +++ b/widgets/widgetListBox.c @@ -636,3 +636,26 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg); } } + + +// ============================================================ +// DXE registration +// ============================================================ + + +static const WidgetClassT sClassListBox = { + .flags = WCLASS_FOCUSABLE, + .paint = widgetListBoxPaint, + .paintOverlay = NULL, + .calcMinSize = widgetListBoxCalcMinSize, + .layout = NULL, + .onMouse = widgetListBoxOnMouse, + .onKey = widgetListBoxOnKey, + .destroy = widgetListBoxDestroy, + .getText = NULL, + .setText = NULL +}; + +void wgtRegister(void) { + widgetClassTable[WidgetListBoxE] = &sClassListBox; +} diff --git a/dvx/widgets/widgetListView.c b/widgets/widgetListView.c similarity index 98% rename from dvx/widgets/widgetListView.c rename to widgets/widgetListView.c index 25b61e7..eccb22f 100644 --- a/dvx/widgets/widgetListView.c +++ b/widgets/widgetListView.c @@ -1380,3 +1380,27 @@ void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg); } } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassListView = { + .flags = WCLASS_FOCUSABLE | WCLASS_NO_HIT_RECURSE, + .paint = widgetListViewPaint, + .paintOverlay = NULL, + .calcMinSize = widgetListViewCalcMinSize, + .layout = NULL, + .onMouse = widgetListViewOnMouse, + .onKey = widgetListViewOnKey, + .destroy = widgetListViewDestroy, + .getText = NULL, + .setText = NULL +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetListViewE] = &sClassListView; + sListViewColBorderHitFn = widgetListViewColBorderHit; +} diff --git a/dvx/widgets/widgetProgressBar.c b/widgets/widgetProgressBar.c similarity index 89% rename from dvx/widgets/widgetProgressBar.c rename to widgets/widgetProgressBar.c index a291321..683a87d 100644 --- a/dvx/widgets/widgetProgressBar.c +++ b/widgets/widgetProgressBar.c @@ -159,3 +159,26 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const } } } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassProgressBar = { + .flags = 0, + .paint = widgetProgressBarPaint, + .paintOverlay = NULL, + .calcMinSize = widgetProgressBarCalcMinSize, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetProgressBarE] = &sClassProgressBar; +} diff --git a/dvx/widgets/widgetRadio.c b/widgets/widgetRadio.c similarity index 90% rename from dvx/widgets/widgetRadio.c rename to widgets/widgetRadio.c index 31fc937..47bfc28 100644 --- a/dvx/widgets/widgetRadio.c +++ b/widgets/widgetRadio.c @@ -280,3 +280,40 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap drawFocusRect(d, ops, labelX - 1, labelY - 1, labelW + 2, font->charHeight + 2, fg); } } + + +// ============================================================ +// DXE registration +// ============================================================ + + +static const WidgetClassT sClassRadioGroup = { + .flags = WCLASS_BOX_CONTAINER, + .paint = NULL, + .paintOverlay = NULL, + .calcMinSize = NULL, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .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 +}; + +void wgtRegister(void) { + widgetClassTable[WidgetRadioGroupE] = &sClassRadioGroup; + widgetClassTable[WidgetRadioE] = &sClassRadio; +} diff --git a/dvx/widgets/widgetScrollPane.c b/widgets/widgetScrollPane.c similarity index 97% rename from dvx/widgets/widgetScrollPane.c rename to widgets/widgetScrollPane.c index 1784d14..d6cc71c 100644 --- a/dvx/widgets/widgetScrollPane.c +++ b/widgets/widgetScrollPane.c @@ -698,3 +698,26 @@ WidgetT *wgtScrollPane(WidgetT *parent) { return w; } + + +// ============================================================ +// 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 +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetScrollPaneE] = &sClassScrollPane; +} diff --git a/dvx/widgets/widgetSeparator.c b/widgets/widgetSeparator.c similarity index 82% rename from dvx/widgets/widgetSeparator.c rename to widgets/widgetSeparator.c index 3eca9ab..46f3f5b 100644 --- a/dvx/widgets/widgetSeparator.c +++ b/widgets/widgetSeparator.c @@ -84,3 +84,26 @@ void widgetSeparatorPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi drawHLine(d, ops, w->x, cy + 1, w->w, colors->windowHighlight); } } + + +// ============================================================ +// DXE registration +// ============================================================ + + +static const WidgetClassT sClassSeparator = { + .flags = 0, + .paint = widgetSeparatorPaint, + .paintOverlay = NULL, + .calcMinSize = widgetSeparatorCalcMinSize, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + +void wgtRegister(void) { + widgetClassTable[WidgetSeparatorE] = &sClassSeparator; +} diff --git a/dvx/widgets/widgetSlider.c b/widgets/widgetSlider.c similarity index 94% rename from dvx/widgets/widgetSlider.c rename to widgets/widgetSlider.c index 48b447d..36b9c9f 100644 --- a/dvx/widgets/widgetSlider.c +++ b/widgets/widgetSlider.c @@ -304,3 +304,26 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma drawFocusRect(d, ops, w->x, w->y, w->w, w->h, fg); } } + + +// ============================================================ +// 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 +}; + +void wgtRegister(void) { + widgetClassTable[WidgetSliderE] = &sClassSlider; +} diff --git a/dvx/widgets/widgetSpacer.c b/widgets/widgetSpacer.c similarity index 71% rename from dvx/widgets/widgetSpacer.c rename to widgets/widgetSpacer.c index 9ebeb82..39d5b52 100644 --- a/dvx/widgets/widgetSpacer.c +++ b/widgets/widgetSpacer.c @@ -39,3 +39,26 @@ void widgetSpacerCalcMinSize(WidgetT *w, const BitmapFontT *font) { w->calcMinW = 0; w->calcMinH = 0; } + + +// ============================================================ +// DXE registration +// ============================================================ + + +static const WidgetClassT sClassSpacer = { + .flags = 0, + .paint = NULL, + .paintOverlay = NULL, + .calcMinSize = widgetSpacerCalcMinSize, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + +void wgtRegister(void) { + widgetClassTable[WidgetSpacerE] = &sClassSpacer; +} diff --git a/dvx/widgets/widgetSpinner.c b/widgets/widgetSpinner.c similarity index 96% rename from dvx/widgets/widgetSpinner.c rename to widgets/widgetSpinner.c index 892b4d2..b534302 100644 --- a/dvx/widgets/widgetSpinner.c +++ b/widgets/widgetSpinner.c @@ -519,3 +519,26 @@ void wgtSpinnerSetValue(WidgetT *w, int32_t value) { spinnerClampAndFormat(w); wgtInvalidate(w); } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassSpinner = { + .flags = WCLASS_FOCUSABLE, + .paint = widgetSpinnerPaint, + .paintOverlay = NULL, + .calcMinSize = widgetSpinnerCalcMinSize, + .layout = NULL, + .onMouse = widgetSpinnerOnMouse, + .onKey = widgetSpinnerOnKey, + .destroy = NULL, + .getText = widgetSpinnerGetText, + .setText = widgetSpinnerSetText +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetSpinnerE] = &sClassSpinner; +} diff --git a/dvx/widgets/widgetSplitter.c b/widgets/widgetSplitter.c similarity index 94% rename from dvx/widgets/widgetSplitter.c rename to widgets/widgetSplitter.c index 9d323cc..1e8e22d 100644 --- a/dvx/widgets/widgetSplitter.c +++ b/widgets/widgetSplitter.c @@ -387,3 +387,27 @@ void wgtSplitterSetPos(WidgetT *w, int32_t pos) { w->as.splitter.dividerPos = pos; wgtInvalidate(w); } + + +// ============================================================ +// DXE registration +// ============================================================ + +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 +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetSplitterE] = &sClassSplitter; + sSplitterClampPosFn = widgetSplitterClampPos; +} diff --git a/dvx/widgets/widgetStatusBar.c b/widgets/widgetStatusBar.c similarity index 79% rename from dvx/widgets/widgetStatusBar.c rename to widgets/widgetStatusBar.c index ede409c..7dbf308 100644 --- a/dvx/widgets/widgetStatusBar.c +++ b/widgets/widgetStatusBar.c @@ -61,3 +61,26 @@ void widgetStatusBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi drawBevel(d, ops, c->x - 1, c->y - 1, c->w + 2, c->h + 2, &bevel); } } + + +// ============================================================ +// 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 +}; + +void wgtRegister(void) { + widgetClassTable[WidgetStatusBarE] = &sClassStatusBar; +} diff --git a/dvx/widgets/widgetTabControl.c b/widgets/widgetTabControl.c similarity index 94% rename from dvx/widgets/widgetTabControl.c rename to widgets/widgetTabControl.c index 90c540e..162aa86 100644 --- a/dvx/widgets/widgetTabControl.c +++ b/widgets/widgetTabControl.c @@ -558,3 +558,40 @@ void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B tabIdx++; } } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassTabControl = { + .flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN, + .paint = widgetTabControlPaint, + .paintOverlay = NULL, + .calcMinSize = widgetTabControlCalcMinSize, + .layout = widgetTabControlLayout, + .onMouse = widgetTabControlOnMouse, + .onKey = widgetTabControlOnKey, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + +static const WidgetClassT sClassTabPage = { + .flags = WCLASS_BOX_CONTAINER, + .paint = NULL, + .paintOverlay = NULL, + .calcMinSize = NULL, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetTabControlE] = &sClassTabControl; + widgetClassTable[WidgetTabPageE] = &sClassTabPage; +} diff --git a/dvx/widgets/widgetTextInput.c b/widgets/widgetTextInput.c similarity index 70% rename from dvx/widgets/widgetTextInput.c rename to widgets/widgetTextInput.c index 277b364..c1a2f27 100644 --- a/dvx/widgets/widgetTextInput.c +++ b/widgets/widgetTextInput.c @@ -1,27 +1,22 @@ // widgetTextInput.c -- TextInput and TextArea widgets // -// This file implements three text editing widgets plus shared -// infrastructure: +// This file implements two text editing widgets: // // 1. TextInput -- single-line text field with scroll, selection, // undo, password masking, and masked input (e.g., phone/SSN). // 2. TextArea -- multi-line text editor with row/col cursor, // dual-axis scrolling, and full selection/clipboard support. -// 3. Shared infrastructure -- clipboard, multi-click detection, -// word boundary logic, cross-widget selection clearing, and -// the single-line text editing engine (widgetTextEditOnKey). +// +// Shared infrastructure (clipboard, multi-click detection, word +// boundary logic, cross-widget selection clearing, and the +// single-line editing engine widgetTextEditOnKey) lives in +// widgetCore.c so it can be linked from any widget DXE. // // All text editing is done in-place in fixed-size char buffers // allocated at widget creation. No dynamic resizing -- this keeps // memory management simple and predictable on DOS where heap // fragmentation is a real concern. // -// The single-line editing engine (widgetTextEditOnKey) is factored -// out as a separate function that takes buffer/cursor/selection -// pointers as parameters, so it can be shared between TextInput, -// Spinner, and ComboBox without code duplication. Each widget -// passes its own state fields. -// // Undo is single-level swap: before each mutation, the current // buffer is copied to undoBuf. Ctrl+Z swaps current<->undo, so // a second Ctrl+Z is "redo". This is simpler than a multi-level @@ -70,7 +65,6 @@ #define TEXTAREA_MIN_ROWS 4 #define TEXTAREA_MIN_COLS 20 #define CLIPBOARD_MAX 4096 -#define DBLCLICK_TICKS (CLOCKS_PER_SEC / 2) // Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c) #define CURSOR_BLINK_MS 250 @@ -94,18 +88,10 @@ static int32_t textAreaLineLen(const char *buf, int32_t len, int32_t row); static int32_t textAreaLineStart(const char *buf, int32_t len, int32_t row); static int32_t textAreaMaxLineLen(const char *buf, int32_t len); static void textAreaOffToRowCol(const char *buf, int32_t off, int32_t *row, int32_t *col); -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); -// ============================================================ -// Shared clipboard -// ============================================================ - -static char sClipboard[CLIPBOARD_MAX]; -static int32_t sClipboardLen = 0; - // ============================================================ // Cursor blink state // ============================================================ @@ -116,30 +102,6 @@ bool sCursorBlinkOn = true; static clock_t sCursorBlinkTime = 0; -void clipboardCopy(const char *text, int32_t len) { - if (!text || len <= 0) { - return; - } - - if (len > CLIPBOARD_MAX - 1) { - len = CLIPBOARD_MAX - 1; - } - - memcpy(sClipboard, text, len); - sClipboard[len] = '\0'; - sClipboardLen = len; -} - - -const char *clipboardGet(int32_t *outLen) { - if (outLen) { - *outLen = sClipboardLen; - } - - return sClipboard; -} - - // ============================================================ // Cursor blink // ============================================================ @@ -160,317 +122,6 @@ void wgtUpdateCursorBlink(void) { } -// ============================================================ -// Shared single-line text editing: mouse click -// ============================================================ -// -// Computes cursor position from pixel coordinates, handles multi-click -// (double = word select, triple = select all), and optionally starts -// drag-select. Used by TextInput, ComboBox, and Spinner mouse handlers -// so click-to-cursor behavior is consistent across all text widgets. -// -// wordSelect: if true, double-click selects the word under the cursor; -// if false, double-click selects all (used by Spinner). -// dragSelect: if true, single-click registers the widget for drag-select -// tracking (TextInput/ComboBox); false for Spinner. - -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; -} - - -// ============================================================ -// Shared single-line text editing: drag update -// ============================================================ -// -// Called during mouse drag to extend the selection. Auto-scrolls -// when the mouse moves past the visible text edges. Used by -// widgetTextDragUpdate for TextInput and ComboBox. - -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; -} - - -// ============================================================ -// Shared single-line text editing: paint -// ============================================================ -// -// Renders a single line of text with optional selection highlighting -// and a blinking cursor. Draws up to 3 runs (before/during/after -// selection) to avoid overdraw. Used by TextInput, ComboBox, and -// Spinner paint functions so selection rendering is identical. -// -// buf points to the already-scrolled display text (buf + scrollOff), -// and visLen is the number of visible characters. For password mode, -// the caller passes a bullet-filled display buffer. - -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); - } - } -} - - -// ============================================================ -// Multi-click tracking -// ============================================================ - -static clock_t sLastClickTime = 0; -static int32_t sLastClickX = -1; -static int32_t sLastClickY = -1; -static int32_t sClickCount = 0; - -int32_t multiClickDetect(int32_t vx, int32_t vy) { - clock_t now = clock(); - - if ((now - sLastClickTime) < sDblClickTicks && - abs(vx - sLastClickX) < 4 && abs(vy - sLastClickY) < 4) { - sClickCount++; - } else { - sClickCount = 1; - } - - sLastClickTime = now; - sLastClickX = vx; - sLastClickY = vy; - - return sClickCount; -} - - -bool isWordChar(char c) { - return isalnum((unsigned char)c) || c == '_'; -} - - -int32_t wordStart(const char *buf, int32_t pos) { - while (pos > 0 && isWordChar(buf[pos - 1])) { - pos--; - } - - return pos; -} - - -int32_t wordEnd(const char *buf, int32_t len, int32_t pos) { - while (pos < len && isWordChar(buf[pos])) { - pos++; - } - - return pos; -} - - -// ============================================================ -// Clear selection on all text widgets except 'except' -// ============================================================ - -// 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. -// This is critical for performance on 486 -- a brute-force walk -// across all widgets in all windows on every focus change would be -// prohibitive. -static WidgetT *sLastSelectedWidget = NULL; - - -static bool clearSelectionOnWidget(WidgetT *w) { - if (w->type == WidgetTextInputE) { - if (w->as.textInput.selStart != w->as.textInput.selEnd) { - w->as.textInput.selStart = -1; - w->as.textInput.selEnd = -1; - return true; - } - - w->as.textInput.selStart = -1; - w->as.textInput.selEnd = -1; - } else if (w->type == WidgetTextAreaE) { - if (w->as.textArea.selAnchor != w->as.textArea.selCursor) { - w->as.textArea.selAnchor = -1; - w->as.textArea.selCursor = -1; - return true; - } - - w->as.textArea.selAnchor = -1; - w->as.textArea.selCursor = -1; - } else if (w->type == WidgetComboBoxE) { - if (w->as.comboBox.selStart != w->as.comboBox.selEnd) { - w->as.comboBox.selStart = -1; - w->as.comboBox.selEnd = -1; - return true; - } - - w->as.comboBox.selStart = -1; - w->as.comboBox.selEnd = -1; - } else if (w->type == WidgetAnsiTermE) { - if (w->as.ansiTerm->selStartLine >= 0 && - (w->as.ansiTerm->selStartLine != w->as.ansiTerm->selEndLine || - w->as.ansiTerm->selStartCol != w->as.ansiTerm->selEndCol)) { - w->as.ansiTerm->dirtyRows = 0xFFFFFFFF; - w->as.ansiTerm->selStartLine = -1; - w->as.ansiTerm->selStartCol = -1; - w->as.ansiTerm->selEndLine = -1; - w->as.ansiTerm->selEndCol = -1; - w->as.ansiTerm->selecting = false; - return true; - } - - w->as.ansiTerm->selStartLine = -1; - w->as.ansiTerm->selStartCol = -1; - w->as.ansiTerm->selEndLine = -1; - w->as.ansiTerm->selEndCol = -1; - w->as.ansiTerm->selecting = false; - } - - return false; -} - - -// 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); - } -} // ============================================================ @@ -490,36 +141,6 @@ static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoB } -// ============================================================ -// Shared 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; -} - - // ============================================================ // wordBoundaryLeft // ============================================================ @@ -1326,7 +947,10 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { // Ctrl+V -- paste if (key == 22) { - if (sClipboardLen > 0) { + int32_t clipLen = 0; + const char *clip = clipboardGet(&clipLen); + + if (clipLen > 0) { textEditSaveUndo(buf, *pLen, CUR_OFF(), w->as.textArea.undoBuf, &w->as.textArea.undoLen, &w->as.textArea.undoCursor, bufSize); if (HAS_SEL()) { @@ -1341,11 +965,11 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) { int32_t off = CUR_OFF(); int32_t canFit = bufSize - 1 - *pLen; - int32_t paste = sClipboardLen < canFit ? sClipboardLen : canFit; + int32_t paste = clipLen < canFit ? clipLen : canFit; if (paste > 0) { memmove(buf + off + paste, buf + off, *pLen - off + 1); - memcpy(buf + off, sClipboard, paste); + memcpy(buf + off, clip, paste); *pLen += paste; textAreaOffToRowCol(buf, off + paste, pRow, pCol); w->as.textArea.desiredCol = *pCol; @@ -1977,126 +1601,6 @@ void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { } -// ============================================================ -// widgetTextDragUpdate -- update selection during mouse drag -// ============================================================ - -// Called by the event loop on mouse-move while sDragTextSelect is set. -// Extends the selection from the anchor to the current mouse position. -// Handles auto-scroll: when the mouse is past the widget edges, the -// scroll offset is nudged by one unit per event, creating a smooth -// scroll-while-dragging effect. This function handles TextInput, -// TextArea, ComboBox, and AnsiTerm -- all widgets that support -// drag-selection. The type switch is slightly ugly but avoids -// needing a virtual method for what is essentially the same operation -// with different field names. -void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) { - AppContextT *ctx = (AppContextT *)root->userData; - const BitmapFontT *font = &ctx->font; - - if (w->type == WidgetTextInputE) { - int32_t leftEdge = w->x + TEXT_INPUT_PAD; - int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth; - widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.textInput.len, &w->as.textInput.cursorPos, &w->as.textInput.scrollOff, &w->as.textInput.selEnd); - } else if (w->type == WidgetTextAreaE) { - int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD; - int32_t innerY = w->y + TEXTAREA_BORDER; - int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W; - int32_t visCols = innerW / font->charWidth; - int32_t maxLL = textAreaGetMaxLineLen(w); - bool needHSb = (maxLL > visCols); - int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0); - int32_t visRows = innerH / font->charHeight; - int32_t totalLines = textAreaGetLineCount(w); - - if (visRows < 1) { - visRows = 1; - } - - if (visCols < 1) { - visCols = 1; - } - - // Auto-scroll vertically - if (vy < innerY && w->as.textArea.scrollRow > 0) { - w->as.textArea.scrollRow--; - } else if (vy >= innerY + visRows * font->charHeight && w->as.textArea.scrollRow + visRows < totalLines) { - w->as.textArea.scrollRow++; - } - - // Auto-scroll horizontally - int32_t rightEdge = innerX + visCols * font->charWidth; - - if (vx < innerX && w->as.textArea.scrollCol > 0) { - w->as.textArea.scrollCol--; - } else if (vx >= rightEdge && w->as.textArea.scrollCol < maxLL - visCols) { - w->as.textArea.scrollCol++; - } - - int32_t relX = vx - innerX; - int32_t relY = vy - innerY; - - int32_t clickRow = w->as.textArea.scrollRow + relY / font->charHeight; - int32_t clickCol = w->as.textArea.scrollCol + relX / font->charWidth; - - if (clickRow < 0) { - clickRow = 0; - } - - if (clickRow >= totalLines) { - clickRow = totalLines - 1; - } - - if (clickCol < 0) { - clickCol = 0; - } - - int32_t lineL = textAreaLineLen(w->as.textArea.buf, w->as.textArea.len, clickRow); - - if (clickCol > lineL) { - clickCol = lineL; - } - - w->as.textArea.cursorRow = clickRow; - w->as.textArea.cursorCol = clickCol; - w->as.textArea.desiredCol = clickCol; - w->as.textArea.selCursor = textAreaCursorToOff(w->as.textArea.buf, w->as.textArea.len, clickRow, clickCol); - } else if (w->type == WidgetComboBoxE) { - int32_t leftEdge = w->x + TEXT_INPUT_PAD; - int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2 - 16) / font->charWidth; - widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.comboBox.len, &w->as.comboBox.cursorPos, &w->as.comboBox.scrollOff, &w->as.comboBox.selEnd); - } else if (w->type == WidgetAnsiTermE) { - int32_t baseX = w->x + 2; // ANSI_BORDER - int32_t baseY = w->y + 2; - int32_t cols = w->as.ansiTerm->cols; - int32_t rows = w->as.ansiTerm->rows; - - int32_t clickRow = (vy - baseY) / font->charHeight; - int32_t clickCol = (vx - baseX) / font->charWidth; - - if (clickRow < 0) { - clickRow = 0; - } - - if (clickRow >= rows) { - clickRow = rows - 1; - } - - if (clickCol < 0) { - clickCol = 0; - } - - if (clickCol >= cols) { - clickCol = cols; - } - - w->as.ansiTerm->selEndLine = w->as.ansiTerm->scrollPos + clickRow; - w->as.ansiTerm->selEndCol = clickCol; - w->as.ansiTerm->dirtyRows = 0xFFFFFFFF; - } -} - - // ============================================================ // widgetTextAreaPaint // ============================================================ @@ -2559,411 +2063,37 @@ void widgetTextInputSetText(WidgetT *w, const char *text) { // ============================================================ -// widgetTextEditOnKey -- shared single-line text editing logic +// DXE registration // ============================================================ -// This is the core single-line text editing engine, parameterized by -// pointer to allow reuse across TextInput, Spinner, and ComboBox. -// All buffer manipulation (insert, delete, cursor movement, selection, -// clipboard, undo) is handled here. The caller passes pointers to -// its own state fields so this function can modify them directly. -// -// Key mapping follows DOS conventions for extended keys (scancode | -// 0x100): 0x4B=Left, 0x4D=Right, 0x47=Home, 0x4F=End, 0x53=Delete, -// 0x73=Ctrl+Left, 0x74=Ctrl+Right. Control characters: 1=Ctrl+A, -// 3=Ctrl+C, 22=Ctrl+V, 24=Ctrl+X, 26=Ctrl+Z. -// -// The "goto adjustScroll" pattern consolidates the scroll-offset -// adjustment and repaint that most key handlers need, reducing -// code duplication. The scroll offset keeps the cursor visible -// within the visible character range. -// -// Single-line paste strips newlines from clipboard content, which -// is important when pasting from TextArea to TextInput. -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) { - if (sClipboardLen > 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 < sClipboardLen && paste < canFit; i++) { - if (sClipboard[i] != '\n' && sClipboard[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 < sClipboardLen && j < paste; i++) { - if (sClipboard[i] != '\n' && sClipboard[i] != '\r') { - buf[pos + j] = sClipboard[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[CLIPBOARD_MAX]; - 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->type == WidgetComboBoxE) { - fieldW -= DROPDOWN_BTN_WIDTH; - } - - 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); +static const WidgetClassT sClassTextInput = { + .flags = WCLASS_FOCUSABLE, + .paint = widgetTextInputPaint, + .paintOverlay = NULL, + .calcMinSize = widgetTextInputCalcMinSize, + .layout = NULL, + .onMouse = widgetTextInputOnMouse, + .onKey = widgetTextInputOnKey, + .destroy = widgetTextInputDestroy, + .getText = widgetTextInputGetText, + .setText = widgetTextInputSetText +}; + +static const WidgetClassT sClassTextArea = { + .flags = WCLASS_FOCUSABLE, + .paint = widgetTextAreaPaint, + .paintOverlay = NULL, + .calcMinSize = widgetTextAreaCalcMinSize, + .layout = NULL, + .onMouse = widgetTextAreaOnMouse, + .onKey = widgetTextAreaOnKey, + .destroy = widgetTextAreaDestroy, + .getText = widgetTextAreaGetText, + .setText = widgetTextAreaSetText +}; + +void wgtRegister(void) { + widgetClassTable[WidgetTextInputE] = &sClassTextInput; + widgetClassTable[WidgetTextAreaE] = &sClassTextArea; } diff --git a/dvx/widgets/widgetTimer.c b/widgets/widgetTimer.c similarity index 89% rename from dvx/widgets/widgetTimer.c rename to widgets/widgetTimer.c index b8aa6d0..e27f23a 100644 --- a/dvx/widgets/widgetTimer.c +++ b/widgets/widgetTimer.c @@ -12,7 +12,7 @@ #include -#include "thirdparty/stb_ds.h" +#include "stb_ds.h" // Active timer list -- avoids walking the widget tree per frame @@ -182,3 +182,26 @@ void wgtUpdateTimers(void) { } } } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassTimer = { + .flags = 0, + .paint = NULL, + .paintOverlay = NULL, + .calcMinSize = widgetTimerCalcMinSize, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = widgetTimerDestroy, + .getText = NULL, + .setText = NULL +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetTimerE] = &sClassTimer; +} diff --git a/dvx/widgets/widgetToolbar.c b/widgets/widgetToolbar.c similarity index 76% rename from dvx/widgets/widgetToolbar.c rename to widgets/widgetToolbar.c index 2a6fbff..3e3c73e 100644 --- a/dvx/widgets/widgetToolbar.c +++ b/widgets/widgetToolbar.c @@ -52,3 +52,26 @@ void widgetToolbarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm bevel.width = 1; drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel); } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassToolbar = { + .flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER, + .paint = widgetToolbarPaint, + .paintOverlay = NULL, + .calcMinSize = NULL, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetToolbarE] = &sClassToolbar; +} diff --git a/dvx/widgets/widgetTreeView.c b/widgets/widgetTreeView.c similarity index 97% rename from dvx/widgets/widgetTreeView.c rename to widgets/widgetTreeView.c index 0b929be..285be73 100644 --- a/dvx/widgets/widgetTreeView.c +++ b/widgets/widgetTreeView.c @@ -1386,3 +1386,41 @@ void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg); } } + + +// ============================================================ +// DXE registration +// ============================================================ + +static const WidgetClassT sClassTreeView = { + .flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE, + .paint = widgetTreeViewPaint, + .paintOverlay = NULL, + .calcMinSize = widgetTreeViewCalcMinSize, + .layout = widgetTreeViewLayout, + .onMouse = widgetTreeViewOnMouse, + .onKey = widgetTreeViewOnKey, + .destroy = NULL, + .getText = NULL, + .setText = NULL +}; + +static const WidgetClassT sClassTreeItem = { + .flags = 0, + .paint = NULL, + .paintOverlay = NULL, + .calcMinSize = NULL, + .layout = NULL, + .onMouse = NULL, + .onKey = NULL, + .destroy = NULL, + .getText = widgetTreeItemGetText, + .setText = widgetTreeItemSetText +}; + + +void wgtRegister(void) { + widgetClassTable[WidgetTreeViewE] = &sClassTreeView; + widgetClassTable[WidgetTreeItemE] = &sClassTreeItem; + sTreeViewNextVisibleFn = widgetTreeViewNextVisible; +}