MAJOR refactoring. Everything is dynamically loaded now. All new bugs to squash!
This commit is contained in:
parent
3b2d87845e
commit
9ee73c8806
111 changed files with 8400 additions and 9686 deletions
16
Makefile
16
Makefile
|
|
@ -1,11 +1,11 @@
|
|||
# DVX GUI -- Top-level Makefile
|
||||
#
|
||||
# Builds the full DVX stack: core library, task switcher,
|
||||
# bootstrap loader, widgets, shell, and apps.
|
||||
# bootstrap loader, text help library, widgets, shell, and apps.
|
||||
|
||||
.PHONY: all clean core tasks loader widgets shell apps
|
||||
.PHONY: all clean core tasks loader texthelp listhelp widgets shell apps
|
||||
|
||||
all: core tasks loader widgets shell apps
|
||||
all: core tasks loader texthelp listhelp widgets shell apps
|
||||
|
||||
core:
|
||||
$(MAKE) -C core
|
||||
|
|
@ -16,7 +16,13 @@ tasks:
|
|||
loader: core tasks
|
||||
$(MAKE) -C loader
|
||||
|
||||
widgets: core tasks
|
||||
texthelp: core tasks
|
||||
$(MAKE) -C texthelp
|
||||
|
||||
listhelp: core tasks
|
||||
$(MAKE) -C listhelp
|
||||
|
||||
widgets: core tasks texthelp listhelp
|
||||
$(MAKE) -C widgets
|
||||
|
||||
shell: core tasks
|
||||
|
|
@ -29,6 +35,8 @@ clean:
|
|||
$(MAKE) -C core clean
|
||||
$(MAKE) -C tasks clean
|
||||
$(MAKE) -C loader clean
|
||||
$(MAKE) -C texthelp clean
|
||||
$(MAKE) -C listhelp clean
|
||||
$(MAKE) -C widgets clean
|
||||
$(MAKE) -C shell clean
|
||||
$(MAKE) -C apps clean
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
|||
DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../tasks -I../tasks/thirdparty -I../shell
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../core/thirdparty -I../widgets -I../tasks -I../core/thirdparty -I../shell
|
||||
|
||||
OBJDIR = ../obj/apps
|
||||
BINDIR = ../bin/apps
|
||||
|
|
|
|||
|
|
@ -13,6 +13,15 @@
|
|||
#include "dvxDialog.h"
|
||||
#include "dvxPrefs.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetCanvas.h"
|
||||
#include "widgetDropdown.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetListBox.h"
|
||||
#include "widgetSlider.h"
|
||||
#include "widgetSpacer.h"
|
||||
#include "widgetTabControl.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "shellApp.h"
|
||||
|
|
|
|||
|
|
@ -17,6 +17,30 @@
|
|||
#include "dvxApp.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetAnsiTerm.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetCanvas.h"
|
||||
#include "widgetCheckbox.h"
|
||||
#include "widgetComboBox.h"
|
||||
#include "widgetDropdown.h"
|
||||
#include "widgetImage.h"
|
||||
#include "widgetImageButton.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetListBox.h"
|
||||
#include "widgetProgressBar.h"
|
||||
#include "widgetRadio.h"
|
||||
#include "widgetScrollPane.h"
|
||||
#include "widgetSeparator.h"
|
||||
#include "widgetSlider.h"
|
||||
#include "widgetSpacer.h"
|
||||
#include "widgetSpinner.h"
|
||||
#include "widgetSplitter.h"
|
||||
#include "widgetStatusBar.h"
|
||||
#include "widgetTabControl.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "widgetToolbar.h"
|
||||
#include "widgetTreeView.h"
|
||||
#include "dvxWm.h"
|
||||
#include "shellApp.h"
|
||||
|
||||
|
|
@ -105,11 +129,8 @@ AppDescriptorT appDescriptor = {
|
|||
// ============================================================
|
||||
|
||||
static void onCanvasDraw(WidgetT *w, int32_t cx, int32_t cy, bool drag) {
|
||||
if (drag && w->as.canvas.lastX >= 0) {
|
||||
wgtCanvasDrawLine(w, w->as.canvas.lastX, w->as.canvas.lastY, cx, cy);
|
||||
} else {
|
||||
(void)drag;
|
||||
wgtCanvasDrawLine(w, cx, cy, cx, cy);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -709,12 +730,12 @@ static void setupControlsWindow(void) {
|
|||
wgtButton(enCol, "A &button");
|
||||
|
||||
WidgetT *enChk = wgtCheckbox(enCol, "&Check me");
|
||||
enChk->as.checkbox.checked = true;
|
||||
wgtCheckboxSetChecked(enChk, true);
|
||||
|
||||
WidgetT *enRg = wgtRadioGroup(enCol);
|
||||
wgtRadio(enRg, "&Radio 1");
|
||||
WidgetT *enR2 = wgtRadio(enRg, "R&adio 2");
|
||||
enRg->as.radioGroup.selectedIdx = enR2->as.radio.index;
|
||||
wgtRadioGroupSetSelected(enRg, wgtRadioGetIndex(enR2));
|
||||
|
||||
static const char *disItems[] = {"Alpha", "Beta", "Gamma"};
|
||||
|
||||
|
|
@ -747,13 +768,13 @@ static void setupControlsWindow(void) {
|
|||
wgtSetEnabled(dBtn, false);
|
||||
|
||||
WidgetT *dChk = wgtCheckbox(disCol, "&Check me");
|
||||
dChk->as.checkbox.checked = true;
|
||||
wgtCheckboxSetChecked(dChk, true);
|
||||
wgtSetEnabled(dChk, false);
|
||||
|
||||
WidgetT *dRg = wgtRadioGroup(disCol);
|
||||
WidgetT *dR1 = wgtRadio(dRg, "&Radio 1");
|
||||
WidgetT *dR2 = wgtRadio(dRg, "R&adio 2");
|
||||
dRg->as.radioGroup.selectedIdx = dR2->as.radio.index;
|
||||
wgtRadioGroupSetSelected(dRg, wgtRadioGetIndex(dR2));
|
||||
wgtSetEnabled(dR1, false);
|
||||
wgtSetEnabled(dR2, false);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "dvxApp.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "shellApp.h"
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@
|
|||
#include "dvxDialog.h"
|
||||
#include "dvxPrefs.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetStatusBar.h"
|
||||
#include "widgetTextInput.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "shellApp.h"
|
||||
|
|
@ -195,7 +200,6 @@ static void buildPmWindow(void) {
|
|||
btn->onDblClick = onAppButtonClick;
|
||||
btn->onClick = onAppButtonClick;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Status bar at bottom; weight=100 on the label makes it fill the bar
|
||||
|
|
|
|||
2
config/combobox.dep
Normal file
2
config/combobox.dep
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
texthelp
|
||||
listhelp
|
||||
1
config/dropdown.dep
Normal file
1
config/dropdown.dep
Normal file
|
|
@ -0,0 +1 @@
|
|||
listhelp
|
||||
|
|
@ -1,14 +1,4 @@
|
|||
libtasks
|
||||
libdvx
|
||||
box
|
||||
button
|
||||
checkbox
|
||||
dropdown
|
||||
label
|
||||
listbox
|
||||
listview
|
||||
radio
|
||||
separator
|
||||
spacer
|
||||
statbar
|
||||
textinpt
|
||||
texthelp
|
||||
listhelp
|
||||
|
|
|
|||
1
config/listbox.dep
Normal file
1
config/listbox.dep
Normal file
|
|
@ -0,0 +1 @@
|
|||
listhelp
|
||||
2
config/listhelp.dep
Normal file
2
config/listhelp.dep
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
libtasks
|
||||
libdvx
|
||||
1
config/listview.dep
Normal file
1
config/listview.dep
Normal file
|
|
@ -0,0 +1 @@
|
|||
listhelp
|
||||
1
config/spinner.dep
Normal file
1
config/spinner.dep
Normal file
|
|
@ -0,0 +1 @@
|
|||
texthelp
|
||||
1
config/terminal.dep
Normal file
1
config/terminal.dep
Normal file
|
|
@ -0,0 +1 @@
|
|||
texthelp
|
||||
2
config/texthelp.dep
Normal file
2
config/texthelp.dep
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
libtasks
|
||||
libdvx
|
||||
1
config/textinpt.dep
Normal file
1
config/textinpt.dep
Normal file
|
|
@ -0,0 +1 @@
|
|||
texthelp
|
||||
1
config/treeview.dep
Normal file
1
config/treeview.dep
Normal file
|
|
@ -0,0 +1 @@
|
|||
listhelp
|
||||
|
|
@ -7,13 +7,13 @@
|
|||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -Iplatform -I../tasks
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -Iplatform -I../tasks -Ithirdparty
|
||||
|
||||
OBJDIR = ../obj/core
|
||||
LIBSDIR = ../bin/libs
|
||||
|
||||
# Core sources
|
||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c \
|
||||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxImage.c dvxImageWrite.c \
|
||||
dvxApp.c dvxDialog.c dvxPrefs.c \
|
||||
widgetClass.c widgetCore.c widgetScrollbar.c \
|
||||
widgetLayout.c widgetEvent.c widgetOps.c
|
||||
|
|
@ -28,8 +28,7 @@ DVX_EXPORTS = -E _dvx -E _wgt -E _wm -E _prefs -E _rect -E _draw -E _pack -E _te
|
|||
-E _sCursor -E _sDbl -E _sDebug -E _sClosed -E _sFocused -E _sKey \
|
||||
-E _sOpen -E _sPressed -E _sDrag -E _sDrawing -E _sResize \
|
||||
-E _sListView -E _sSplitter -E _sTreeView \
|
||||
-E _accelParse \
|
||||
-E _clearOther -E _clipboard -E _isWord -E _multiClick -E _wordStart -E _wordEnd
|
||||
-E _accelParse -E _clipboard -E _multiClick
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
|
|
@ -39,7 +38,8 @@ $(LIBSDIR)/libdvx.dep: ../config/libdvx.dep | $(LIBSDIR)
|
|||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBSDIR)
|
||||
$(DXE3GEN) -o $@ $(DVX_EXPORTS) -U $(OBJS)
|
||||
$(DXE3GEN) -o $(LIBSDIR)/libdvx.dxe $(DVX_EXPORTS) -U $(OBJS)
|
||||
mv $(LIBSDIR)/libdvx.dxe $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
|
@ -56,13 +56,13 @@ $(OBJDIR)/dvxVideo.o: dvxVideo.c dvxVideo.h platform/dvxPlatform.h dvxTypes
|
|||
$(OBJDIR)/dvxDraw.o: dvxDraw.c dvxDraw.h platform/dvxPlatform.h dvxTypes.h
|
||||
$(OBJDIR)/dvxComp.o: dvxComp.c dvxComp.h platform/dvxPlatform.h dvxTypes.h
|
||||
$(OBJDIR)/dvxWm.o: dvxWm.c dvxWm.h dvxTypes.h dvxDraw.h dvxComp.h dvxVideo.h dvxWidget.h thirdparty/stb_image.h
|
||||
$(OBJDIR)/dvxIcon.o: dvxIcon.c thirdparty/stb_image.h
|
||||
$(OBJDIR)/dvxImage.o: dvxImage.c thirdparty/stb_image.h
|
||||
$(OBJDIR)/dvxImageWrite.o: dvxImageWrite.c thirdparty/stb_image_write.h
|
||||
$(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h platform/dvxPlatform.h dvxTypes.h dvxVideo.h dvxDraw.h dvxComp.h dvxWm.h dvxFont.h dvxCursor.h
|
||||
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h widgetInternal.h dvxTypes.h dvxDraw.h
|
||||
$(OBJDIR)/dvxDialog.o: dvxDialog.c dvxDialog.h platform/dvxPlatform.h dvxApp.h dvxWidget.h dvxWidgetPlugin.h dvxTypes.h dvxDraw.h
|
||||
$(OBJDIR)/dvxPrefs.o: dvxPrefs.c dvxPrefs.h
|
||||
|
||||
WIDGET_DEPS = widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
||||
WIDGET_DEPS = dvxWidgetPlugin.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
||||
$(OBJDIR)/widgetClass.o: widgetClass.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetCore.o: widgetCore.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetScrollbar.o: widgetScrollbar.c $(WIDGET_DEPS)
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ DXE modules in `../widgets/`.
|
|||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| `widgetInternal.h` | Internal header for widget implementation files: vtable externs, shared state, helper prototypes |
|
||||
| `dvxWidgetPlugin.h` | Internal header for widget implementation files: vtable externs, shared state, helper prototypes |
|
||||
| `widgetClass.c` | Widget class table (`widgetClassTable[]`, starts empty) and `wgtRegisterClass()` dynamic registration |
|
||||
| `widgetCore.c` | Widget allocation, tree ops, hit testing, focus management, shared helpers |
|
||||
| `widgetScrollbar.c` | Widget-internal scrollbar drawing and hit testing (shared by ListBox, TreeView, etc.) |
|
||||
|
|
|
|||
259
core/dvxApp.c
259
core/dvxApp.c
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// dvx_app.c -- Layer 5: Application API for DVX GUI
|
||||
//
|
||||
// Top-level layer of the DVX windowing system. This is the only layer
|
||||
|
|
@ -31,7 +32,8 @@
|
|||
#include "dvxApp.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../widgets/widgetTimer.h"
|
||||
#include "dvxFont.h"
|
||||
#include "dvxCursor.h"
|
||||
|
||||
|
|
@ -119,8 +121,8 @@ static void openContextMenu(AppContextT *ctx, WindowT *win, MenuT *menu, int32_t
|
|||
static void openPopupAtMenu(AppContextT *ctx, WindowT *win, int32_t menuIdx);
|
||||
static void openSubMenu(AppContextT *ctx);
|
||||
static void openSysMenu(AppContextT *ctx, WindowT *win);
|
||||
static void pollAnsiTermWidgets(AppContextT *ctx);
|
||||
static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win);
|
||||
static void pollWidgets(AppContextT *ctx);
|
||||
static void pollWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win);
|
||||
static void pollKeyboard(AppContextT *ctx);
|
||||
static void pollMouse(AppContextT *ctx);
|
||||
static void refreshMinimizedIcons(AppContextT *ctx);
|
||||
|
|
@ -713,100 +715,9 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
|||
if (win->widgetRoot) {
|
||||
WidgetT *target = widgetFindByAccel(win->widgetRoot, key);
|
||||
|
||||
if (target) {
|
||||
switch (target->type) {
|
||||
case WidgetButtonE:
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
sFocusedWidget = target;
|
||||
target->focused = true;
|
||||
target->as.button.pressed = true;
|
||||
sKeyPressedBtn = target;
|
||||
wgtInvalidate(target);
|
||||
return true;
|
||||
|
||||
case WidgetCheckboxE:
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
if (target->wclass && target->wclass->onMouse) {
|
||||
target->wclass->onMouse(target, win->widgetRoot, 0, 0);
|
||||
}
|
||||
sFocusedWidget = target;
|
||||
wgtInvalidate(target);
|
||||
return true;
|
||||
|
||||
case WidgetRadioE:
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
if (target->wclass && target->wclass->onMouse) {
|
||||
target->wclass->onMouse(target, win->widgetRoot, 0, 0);
|
||||
}
|
||||
sFocusedWidget = target;
|
||||
wgtInvalidate(target);
|
||||
return true;
|
||||
|
||||
case WidgetImageButtonE:
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
sFocusedWidget = target;
|
||||
target->focused = true;
|
||||
target->as.imageButton.pressed = true;
|
||||
sKeyPressedBtn = target;
|
||||
wgtInvalidate(target);
|
||||
return true;
|
||||
|
||||
case WidgetTabPageE:
|
||||
{
|
||||
// Close any open dropdown/combobox popup
|
||||
if (sOpenPopup) {
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
sOpenPopup->as.dropdown.open = false;
|
||||
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
||||
sOpenPopup->as.comboBox.open = false;
|
||||
}
|
||||
|
||||
sOpenPopup = NULL;
|
||||
}
|
||||
|
||||
// Activate this tab in its parent TabControl
|
||||
if (target->parent && target->parent->type == WidgetTabControlE) {
|
||||
int32_t tabIdx = 0;
|
||||
|
||||
for (WidgetT *c = target->parent->firstChild; c; c = c->nextSibling) {
|
||||
if (c == target) {
|
||||
wgtTabControlSetActive(target->parent, tabIdx);
|
||||
break;
|
||||
}
|
||||
|
||||
if (c->type == WidgetTabPageE) {
|
||||
tabIdx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
case WidgetDropdownE:
|
||||
target->as.dropdown.open = true;
|
||||
target->as.dropdown.hoverIdx = target->as.dropdown.selectedIdx;
|
||||
sOpenPopup = target;
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
sFocusedWidget = target;
|
||||
target->focused = true;
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
return true;
|
||||
|
||||
case WidgetComboBoxE:
|
||||
target->as.comboBox.open = true;
|
||||
target->as.comboBox.hoverIdx = target->as.comboBox.selectedIdx;
|
||||
sOpenPopup = target;
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
sFocusedWidget = target;
|
||||
target->focused = true;
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
return true;
|
||||
|
||||
case WidgetLabelE:
|
||||
case WidgetFrameE:
|
||||
{
|
||||
// Focus the next focusable widget
|
||||
if (target && target->wclass) {
|
||||
if (target->wclass->flags & WCLASS_FOCUS_FORWARD) {
|
||||
// Label/Frame: focus-forward to next focusable
|
||||
WidgetT *next = widgetFindNextFocusable(win->widgetRoot, target);
|
||||
|
||||
if (next) {
|
||||
|
|
@ -814,35 +725,22 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
|||
sFocusedWidget = next;
|
||||
next->focused = true;
|
||||
|
||||
// Open dropdown/combobox if that's the focused target
|
||||
if (next->type == WidgetDropdownE) {
|
||||
next->as.dropdown.open = true;
|
||||
next->as.dropdown.hoverIdx = next->as.dropdown.selectedIdx;
|
||||
sOpenPopup = next;
|
||||
} else if (next->type == WidgetComboBoxE) {
|
||||
next->as.comboBox.open = true;
|
||||
next->as.comboBox.hoverIdx = next->as.comboBox.selectedIdx;
|
||||
sOpenPopup = next;
|
||||
if (next->wclass && next->wclass->onAccelActivate) {
|
||||
next->wclass->onAccelActivate(next, win->widgetRoot);
|
||||
}
|
||||
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
default:
|
||||
// For focusable widgets, just focus them
|
||||
if (widgetIsFocusable(target->type)) {
|
||||
} else if (target->wclass->onAccelActivate) {
|
||||
if (sFocusedWidget) { sFocusedWidget->focused = false; }
|
||||
sFocusedWidget = target;
|
||||
target->focused = true;
|
||||
target->wclass->onAccelActivate(target, win->widgetRoot);
|
||||
wgtInvalidate(win->widgetRoot);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -1237,20 +1135,12 @@ static void dispatchEvents(AppContextT *ctx) {
|
|||
// itself or a ScrollPane ancestor that contains it.
|
||||
WidgetT *target = NULL;
|
||||
|
||||
if (focus->type == WidgetListBoxE ||
|
||||
focus->type == WidgetListViewE ||
|
||||
focus->type == WidgetTreeViewE ||
|
||||
focus->type == WidgetTextAreaE ||
|
||||
focus->type == WidgetAnsiTermE ||
|
||||
focus->type == WidgetSliderE ||
|
||||
focus->type == WidgetSpinnerE ||
|
||||
focus->type == WidgetDropdownE ||
|
||||
focus->type == WidgetComboBoxE) {
|
||||
if (focus->wclass && (focus->wclass->flags & WCLASS_SCROLLABLE)) {
|
||||
target = focus;
|
||||
} else {
|
||||
// Walk up to find a ScrollPane ancestor
|
||||
for (WidgetT *p = focus->parent; p; p = p->parent) {
|
||||
if (p->type == WidgetScrollPaneE) {
|
||||
if (p->wclass && (p->wclass->flags & WCLASS_SCROLL_CONTAINER)) {
|
||||
target = p;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1704,7 +1594,7 @@ void dvxResetColorScheme(AppContextT *ctx) {
|
|||
// Arranges windows in the classic cascade pattern: each window is the same
|
||||
// size (2/3 of screen), offset diagonally by the title bar height so each
|
||||
// title bar remains visible. When the cascade would go off-screen, it wraps
|
||||
// back to (0,0). This matches DESQview/X and Windows 3.x cascade behavior.
|
||||
// back to (0,0). This matches the Windows 3.x cascade behavior.
|
||||
// The step size is title_height + border_width so exactly one title bar's
|
||||
// worth of the previous window peeks out above and to the left.
|
||||
|
||||
|
|
@ -2692,7 +2582,7 @@ bool dvxUpdate(AppContextT *ctx) {
|
|||
pollKeyboard(ctx);
|
||||
dispatchEvents(ctx);
|
||||
updateTooltip(ctx);
|
||||
pollAnsiTermWidgets(ctx);
|
||||
pollWidgets(ctx);
|
||||
wgtUpdateCursorBlink();
|
||||
wgtUpdateTimers();
|
||||
|
||||
|
|
@ -2715,10 +2605,8 @@ bool dvxUpdate(AppContextT *ctx) {
|
|||
// onClick. The one-frame delay ensures the pressed visual state
|
||||
// renders before the callback runs (which may open a dialog, etc.).
|
||||
if (sKeyPressedBtn) {
|
||||
if (sKeyPressedBtn->type == WidgetImageButtonE) {
|
||||
sKeyPressedBtn->as.imageButton.pressed = false;
|
||||
} else {
|
||||
sKeyPressedBtn->as.button.pressed = false;
|
||||
if (sKeyPressedBtn->wclass && sKeyPressedBtn->wclass->setPressed) {
|
||||
sKeyPressedBtn->wclass->setPressed(sKeyPressedBtn, false);
|
||||
}
|
||||
|
||||
if (sKeyPressedBtn->onClick) {
|
||||
|
|
@ -3088,7 +2976,7 @@ static void interactiveWindowScreenshot(AppContextT *ctx, WindowT *win) {
|
|||
// ============================================================
|
||||
//
|
||||
// Executes a system menu (window control menu) command. The system menu
|
||||
// is the DESQview/X equivalent of the Win3.x control-menu box -- it
|
||||
// is the DVX equivalent of the Win3.x control-menu box -- it
|
||||
// provides Restore, Move, Size, Minimize, Maximize, and Close. Keyboard
|
||||
// move/resize mode is entered by setting kbMoveResize state, which causes
|
||||
// pollKeyboard to intercept arrow keys until Enter/Esc.
|
||||
|
|
@ -3188,7 +3076,7 @@ static WindowT *findWindowById(AppContextT *ctx, int32_t id) {
|
|||
// hitPart 0: content area (forwarded to window's onMouse callback)
|
||||
// hitPart 1: title bar (begins mouse drag)
|
||||
// hitPart 2: close/sys-menu gadget (single-click opens sys menu,
|
||||
// double-click closes -- DESQview/X convention)
|
||||
// double-click closes -- DVX convention)
|
||||
// hitPart 3: resize border (begins edge/corner resize)
|
||||
// hitPart 4: menu bar (opens popup for clicked menu)
|
||||
// hitPart 5/6: vertical/horizontal scrollbar
|
||||
|
|
@ -3371,7 +3259,7 @@ static void handleMouseButton(AppContextT *ctx, int32_t mx, int32_t my, int32_t
|
|||
// by GEOS Ensemble with Motif-style 3D bevels: teal desktop, grey window
|
||||
// chrome with white highlights and dark shadows to create the raised/sunken
|
||||
// illusion. The dark charcoal active title bar distinguishes it from
|
||||
// GEOS's blue, giving DV/X its own identity.
|
||||
// GEOS's blue, giving DVX its own identity.
|
||||
|
||||
static void initColorScheme(AppContextT *ctx) {
|
||||
// Load defaults into the RGB source array, then pack all at once
|
||||
|
|
@ -3660,56 +3548,37 @@ static void openSysMenu(AppContextT *ctx, WindowT *win) {
|
|||
|
||||
|
||||
// ============================================================
|
||||
// pollAnsiTermWidgets -- poll and repaint all ANSI term widgets
|
||||
// pollWidgets -- poll widgets that need periodic updates
|
||||
// ============================================================
|
||||
//
|
||||
// ANSI terminal widgets have asynchronous data sources (PTYs, serial
|
||||
// ports) that produce output between frames. This function walks every
|
||||
// window's widget tree looking for AnsiTerm widgets, polls them for new
|
||||
// data, and if data arrived, triggers a targeted repaint of just the
|
||||
// affected rows. The fine-grained dirty rect (just the changed rows
|
||||
// rather than the whole window) is critical for terminal performance --
|
||||
// a single character echo should only flush one row to the LFB, not
|
||||
// the entire terminal viewport.
|
||||
// Widgets with WCLASS_NEEDS_POLL (e.g. ANSI terminals with asynchronous
|
||||
// data sources) need periodic polling between frames. This function walks
|
||||
// every window's widget tree and calls the poll vtable method on matching
|
||||
// widgets. The poll method handles data ingestion and targeted dirty
|
||||
// rect generation for efficient repainting.
|
||||
|
||||
static void pollAnsiTermWidgets(AppContextT *ctx) {
|
||||
static void pollWidgets(AppContextT *ctx) {
|
||||
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
||||
WindowT *win = ctx->stack.windows[i];
|
||||
|
||||
if (win->widgetRoot) {
|
||||
pollAnsiTermWidgetsWalk(ctx, win->widgetRoot, win);
|
||||
pollWidgetsWalk(ctx, win->widgetRoot, win);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// pollAnsiTermWidgetsWalk -- recursive helper
|
||||
// pollWidgetsWalk -- recursive helper
|
||||
// ============================================================
|
||||
|
||||
static void pollAnsiTermWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) {
|
||||
if (w->type == WidgetAnsiTermE) {
|
||||
wgtAnsiTermPoll(w);
|
||||
|
||||
int32_t dirtyY = 0;
|
||||
int32_t dirtyH = 0;
|
||||
|
||||
if (wgtAnsiTermRepaint(w, &dirtyY, &dirtyH) > 0) {
|
||||
win->contentDirty = true;
|
||||
|
||||
// Dirty only the affected rows (in screen coords) instead of
|
||||
// the entire window. This dramatically reduces compositor and
|
||||
// LFB flush work for cursor blink and single-row updates.
|
||||
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
||||
int32_t rectX = win->x + win->contentX;
|
||||
int32_t rectY = win->y + win->contentY + dirtyY - scrollY;
|
||||
int32_t rectW = win->contentW;
|
||||
dirtyListAdd(&ctx->dirty, rectX, rectY, rectW, dirtyH);
|
||||
}
|
||||
static void pollWidgetsWalk(AppContextT *ctx, WidgetT *w, WindowT *win) {
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_NEEDS_POLL) && w->wclass->poll) {
|
||||
w->wclass->poll(w, win);
|
||||
}
|
||||
|
||||
for (WidgetT *child = w->firstChild; child; child = child->nextSibling) {
|
||||
pollAnsiTermWidgetsWalk(ctx, child, win);
|
||||
pollWidgetsWalk(ctx, child, win);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4273,16 +4142,16 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
int32_t popY;
|
||||
int32_t popW;
|
||||
int32_t popH;
|
||||
widgetDropdownPopupRect(sOpenPopup, &ctx->font, popWin->contentH, &popX, &popY, &popW, &popH);
|
||||
if (sOpenPopup->wclass && sOpenPopup->wclass->getPopupRect) {
|
||||
sOpenPopup->wclass->getPopupRect(sOpenPopup, &ctx->font, popWin->contentH, &popX, &popY, &popW, &popH);
|
||||
}
|
||||
dirtyListAdd(&ctx->dirty, popWin->x + popWin->contentX + popX, popWin->y + popWin->contentY + popY, popW, popH);
|
||||
|
||||
WidgetT *closing = sOpenPopup;
|
||||
sOpenPopup = NULL;
|
||||
|
||||
if (closing->type == WidgetDropdownE) {
|
||||
closing->as.dropdown.open = false;
|
||||
} else if (closing->type == WidgetComboBoxE) {
|
||||
closing->as.comboBox.open = false;
|
||||
if (closing->wclass && closing->wclass->closePopup) {
|
||||
closing->wclass->closePopup(closing);
|
||||
}
|
||||
|
||||
wgtInvalidate(closing);
|
||||
|
|
@ -4323,8 +4192,8 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
WidgetT *w = fstack[--ftop];
|
||||
|
||||
if (w->focused && widgetIsFocusable(w->type)) {
|
||||
// Don't tab out of the terminal -- it swallows Tab
|
||||
if (w->type == WidgetAnsiTermE) {
|
||||
// Don't tab out of widgets that swallow Tab
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||
current = NULL;
|
||||
break;
|
||||
}
|
||||
|
|
@ -4350,7 +4219,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
|||
while (ftop > 0) {
|
||||
WidgetT *w = fstack[--ftop];
|
||||
|
||||
if (w->focused && w->type == WidgetAnsiTermE) {
|
||||
if (w->focused && w->wclass && (w->wclass->flags & WCLASS_SWALLOWS_TAB)) {
|
||||
termFocused = true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -4592,7 +4461,9 @@ static void updateCursorShape(AppContextT *ctx) {
|
|||
}
|
||||
// Active splitter drag
|
||||
else if (sDragSplitter) {
|
||||
newCursor = sDragSplitter->as.splitter.vertical ? CURSOR_RESIZE_H : CURSOR_RESIZE_V;
|
||||
if (sDragSplitter->wclass && sDragSplitter->wclass->getCursorShape) {
|
||||
newCursor = sDragSplitter->wclass->getCursorShape(sDragSplitter, 0, 0);
|
||||
}
|
||||
}
|
||||
// Not in an active drag/resize -- check what we're hovering
|
||||
else if (ctx->stack.dragWindow < 0 && ctx->stack.scrollWindow < 0) {
|
||||
|
|
@ -4632,38 +4503,12 @@ static void updateCursorShape(AppContextT *ctx) {
|
|||
|
||||
WidgetT *hit = widgetHitTest(win->widgetRoot, vx, vy);
|
||||
|
||||
if (hit && hit->type == WidgetListViewE && sListViewColBorderHitFn && sListViewColBorderHitFn(hit, vx, vy)) {
|
||||
newCursor = CURSOR_RESIZE_H;
|
||||
if (hit && hit->wclass && hit->wclass->getCursorShape) {
|
||||
int32_t shape = hit->wclass->getCursorShape(hit, vx, vy);
|
||||
|
||||
if (shape > 0) {
|
||||
newCursor = shape;
|
||||
}
|
||||
|
||||
// Walk into splitters (NO_HIT_RECURSE stops widgetHitTest at the outermost one)
|
||||
while (hit && hit->type == WidgetSplitterE) {
|
||||
int32_t pos = hit->as.splitter.dividerPos;
|
||||
bool onBar;
|
||||
|
||||
if (hit->as.splitter.vertical) {
|
||||
int32_t barX = hit->x + pos;
|
||||
onBar = (vx >= barX && vx < barX + SPLITTER_BAR_W);
|
||||
} else {
|
||||
int32_t barY = hit->y + pos;
|
||||
onBar = (vy >= barY && vy < barY + SPLITTER_BAR_W);
|
||||
}
|
||||
|
||||
if (onBar) {
|
||||
newCursor = hit->as.splitter.vertical ? CURSOR_RESIZE_H : CURSOR_RESIZE_V;
|
||||
break;
|
||||
}
|
||||
|
||||
// Not on this splitter's bar -- check children for nested splitters
|
||||
WidgetT *inner = NULL;
|
||||
|
||||
for (WidgetT *c = hit->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible && vx >= c->x && vx < c->x + c->w && vy >= c->y && vy < c->y + c->h) {
|
||||
inner = c;
|
||||
}
|
||||
}
|
||||
|
||||
hit = inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ typedef struct AppContextT {
|
|||
// buffer down to icon size.
|
||||
int32_t iconRefreshIdx; // next minimized icon to refresh (staggered)
|
||||
int32_t frameCount; // frame counter for periodic tasks
|
||||
// The idle callback allows the host application (e.g. the DV/X shell)
|
||||
// The idle callback allows the host application (e.g. the DVX shell)
|
||||
// to do background work (poll serial ports, service DXE apps) during
|
||||
// frames where no GUI events occurred, instead of just yielding the CPU.
|
||||
void (*idleCallback)(void *ctx); // called instead of yield when non-NULL
|
||||
|
|
@ -184,7 +184,7 @@ void dvxShutdown(AppContextT *ctx);
|
|||
void dvxRun(AppContextT *ctx);
|
||||
|
||||
// Process exactly one frame of the event loop. Provided for applications
|
||||
// that need to integrate the GUI into their own main loop (e.g. the DV/X
|
||||
// that need to integrate the GUI into their own main loop (e.g. the DVX
|
||||
// shell polling serial ports between frames). Returns false when the GUI
|
||||
// wants to exit.
|
||||
bool dvxUpdate(AppContextT *ctx);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,14 @@
|
|||
#include "dvxDialog.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../widgets/widgetBox.h"
|
||||
#include "../widgets/widgetButton.h"
|
||||
#include "../widgets/widgetDropdown.h"
|
||||
#include "../widgets/widgetLabel.h"
|
||||
#include "../widgets/widgetListBox.h"
|
||||
#include "../widgets/widgetSpacer.h"
|
||||
#include "../widgets/widgetTextInput.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
|
|
@ -864,7 +871,6 @@ static void fdLoadDir(void) {
|
|||
|
||||
wgtListBoxSetItems(sFd.fileList, sFd.listItems, sFd.entryCount);
|
||||
wgtListBoxSetSelected(sFd.fileList, 0);
|
||||
sFd.fileList->as.listBox.scrollPos = 0;
|
||||
|
||||
// Update path display
|
||||
wgtSetText(sFd.pathInput, sFd.curDir);
|
||||
|
|
|
|||
|
|
@ -169,16 +169,16 @@ static inline void clipRect(const DisplayT *d, int32_t *x, int32_t *y, int32_t *
|
|||
// drawBevel
|
||||
// ============================================================
|
||||
//
|
||||
// Draws a Motif/DESQview-style beveled rectangular frame. The bevel
|
||||
// Draws a Motif-style beveled rectangular frame. The bevel
|
||||
// creates the illusion of a raised or sunken 3D surface by drawing
|
||||
// lighter "highlight" edges on the top and left, and darker "shadow"
|
||||
// edges on the bottom and right. Swapping highlight and shadow gives
|
||||
// a sunken appearance (see BEVEL_RAISED/BEVEL_SUNKEN macros in
|
||||
// dvxTypes.h).
|
||||
//
|
||||
// BevelStyleT.width controls the border thickness. DV/X uses 2px
|
||||
// bevels for most window chrome (matching the original DESQview/X
|
||||
// and Motif look), 1px for inner borders and scrollbar elements.
|
||||
// BevelStyleT.width controls the border thickness. DVX uses 2px
|
||||
// bevels for most window chrome (matching the Motif look), 1px for
|
||||
// inner borders and scrollbar elements.
|
||||
//
|
||||
// The implementation has special-cased fast paths for bw==2 and bw==1
|
||||
// that emit exact spans via rectFill rather than looping. This
|
||||
|
|
|
|||
559
core/dvxFont.h
559
core/dvxFont.h
|
|
@ -1,11 +1,11 @@
|
|||
// dvx_font.h -- Embedded VGA bitmap font data (CP437) for DVX GUI
|
||||
//
|
||||
// Contains the raw glyph bitmaps for two standard VGA ROM fonts (8x14 and
|
||||
// 8x16) covering the full IBM Code Page 437 character set (256 glyphs).
|
||||
// These are byte-for-byte copies of the fonts stored in the VGA BIOS ROM.
|
||||
// Contains the raw glyph bitmaps for the 8x16 VGA ROM font covering the
|
||||
// full IBM Code Page 437 character set (256 glyphs). This is a byte-for-
|
||||
// byte copy of the font stored in the VGA BIOS ROM.
|
||||
//
|
||||
// Embedding them as static const arrays (rather than reading from the VGA
|
||||
// ROM at INT 10h) serves two purposes:
|
||||
// Embedding as a static const array (rather than reading from the VGA ROM
|
||||
// at INT 10h) serves two purposes:
|
||||
// 1. The data is available on non-DOS platforms (Linux/SDL) where no
|
||||
// VGA BIOS exists.
|
||||
// 2. On DOS, reading the VGA ROM font requires a real-mode interrupt
|
||||
|
|
@ -13,13 +13,13 @@
|
|||
// anyway. Compiling it in is simpler and more portable.
|
||||
//
|
||||
// The glyph format is 1 bit per pixel, 8 pixels wide, with MSB = leftmost
|
||||
// pixel. Each glyph occupies charHeight consecutive bytes (14 or 16).
|
||||
// The drawing code in dvxDraw.c processes one byte per scanline, testing
|
||||
// each bit to decide foreground vs. background -- the 8-pixel width means
|
||||
// no bit-shifting across byte boundaries is ever needed.
|
||||
// pixel. Each glyph occupies 16 consecutive bytes. The drawing code in
|
||||
// dvxDraw.c processes one byte per scanline, testing each bit to decide
|
||||
// foreground vs. background -- the 8-pixel width means no bit-shifting
|
||||
// across byte boundaries is ever needed.
|
||||
//
|
||||
// CP437 includes the full ASCII printable range (32-126) plus box-drawing
|
||||
// characters (176-223), which are essential for the Motif/DV/X look
|
||||
// characters (176-223), which are essential for the Motif/DVX look
|
||||
// (scrollbar arrows, window close gadget, etc.), as well as international
|
||||
// characters, math symbols, and the classic smiley/card suit characters
|
||||
// in positions 1-31.
|
||||
|
|
@ -28,528 +28,6 @@
|
|||
|
||||
#include "dvxTypes.h"
|
||||
|
||||
// ============================================================
|
||||
// 8x14 VGA ROM font -- standard IBM CP437 character set
|
||||
// 256 glyphs, 14 bytes per glyph, MSB = leftmost pixel
|
||||
// ============================================================
|
||||
|
||||
static const uint8_t font8x14[256 * 14] = {
|
||||
// Char 0: NULL (blank)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 1: Smiley (outline)
|
||||
0x00, 0x00, 0x7E, 0x81, 0xA5, 0x81, 0x81, 0xBD, 0x99, 0x81, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 2: Smiley (filled)
|
||||
0x00, 0x00, 0x7E, 0xFF, 0xDB, 0xFF, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 3: Heart
|
||||
0x00, 0x00, 0x00, 0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 4: Diamond
|
||||
0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 5: Club
|
||||
0x00, 0x00, 0x18, 0x3C, 0x3C, 0xE7, 0xE7, 0xE7, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 6: Spade
|
||||
0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 7: Bullet
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 8: Inverse bullet
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
|
||||
// Char 9: Circle (outline)
|
||||
0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 10: Inverse circle
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0xBD, 0x99, 0xC3, 0xFF, 0xFF, 0xFF, 0x00, 0x00,
|
||||
// Char 11: Male sign
|
||||
0x00, 0x00, 0x1E, 0x0E, 0x1A, 0x32, 0x78, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 12: Female sign
|
||||
0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 13: Note single
|
||||
0x00, 0x00, 0x3F, 0x33, 0x3F, 0x30, 0x30, 0x30, 0x70, 0xF0, 0xE0, 0x00, 0x00, 0x00,
|
||||
// Char 14: Note double
|
||||
0x00, 0x00, 0x7F, 0x63, 0x7F, 0x63, 0x63, 0x63, 0x67, 0xE7, 0xE6, 0xC0, 0x00, 0x00,
|
||||
// Char 15: Sun
|
||||
0x00, 0x00, 0x18, 0xDB, 0x3C, 0xE7, 0x3C, 0xDB, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 16: Right triangle
|
||||
0x00, 0x00, 0x80, 0xC0, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00,
|
||||
// Char 17: Left triangle
|
||||
0x00, 0x00, 0x02, 0x06, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00,
|
||||
// Char 18: Up-down arrow
|
||||
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 19: Double exclamation
|
||||
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 20: Paragraph
|
||||
0x00, 0x00, 0x7F, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 21: Section
|
||||
0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0x6C, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00,
|
||||
// Char 22: Bottom bar
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 23: Up-down arrow with base
|
||||
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 24: Up arrow
|
||||
0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 25: Down arrow
|
||||
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 26: Right arrow
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 27: Left arrow
|
||||
0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 28: Right angle
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 29: Left-right arrow
|
||||
0x00, 0x00, 0x00, 0x00, 0x24, 0x66, 0xFF, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 30: Up triangle
|
||||
0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7C, 0x7C, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 31: Down triangle
|
||||
0x00, 0x00, 0x00, 0xFE, 0xFE, 0x7C, 0x7C, 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// Char 32: Space
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 33: !
|
||||
0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 34: "
|
||||
0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 35: #
|
||||
0x00, 0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 36: $
|
||||
0x00, 0x18, 0x18, 0x7C, 0xC6, 0xC0, 0x7C, 0x06, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00,
|
||||
// Char 37: %
|
||||
0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18, 0x30, 0x66, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 38: &
|
||||
0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 39: '
|
||||
0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 40: (
|
||||
0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00,
|
||||
// Char 41: )
|
||||
0x00, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00,
|
||||
// Char 42: *
|
||||
0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 43: +
|
||||
0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 44: ,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00,
|
||||
// Char 45: -
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 46: .
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 47: /
|
||||
0x00, 0x00, 0x02, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 48: 0
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xCE, 0xDE, 0xF6, 0xE6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 49: 1
|
||||
0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 50: 2
|
||||
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 51: 3
|
||||
0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 52: 4
|
||||
0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00,
|
||||
// Char 53: 5
|
||||
0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xFC, 0x06, 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 54: 6
|
||||
0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xFC, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 55: 7
|
||||
0x00, 0x00, 0xFE, 0xC6, 0x06, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00,
|
||||
// Char 56: 8
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 57: 9
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0x0C, 0x78, 0x00, 0x00, 0x00,
|
||||
// Char 58: :
|
||||
0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 59: ;
|
||||
0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00,
|
||||
// Char 60: <
|
||||
0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00,
|
||||
// Char 61: =
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 62: >
|
||||
0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00,
|
||||
// Char 63: ?
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 64: @
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE, 0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 65: A
|
||||
0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 66: B
|
||||
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00,
|
||||
// Char 67: C
|
||||
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 68: D
|
||||
0x00, 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00,
|
||||
// Char 69: E
|
||||
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 70: F
|
||||
0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00,
|
||||
// Char 71: G
|
||||
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xDE, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00,
|
||||
// Char 72: H
|
||||
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 73: I
|
||||
0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 74: J
|
||||
0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00,
|
||||
// Char 75: K
|
||||
0x00, 0x00, 0xE6, 0x66, 0x6C, 0x6C, 0x78, 0x6C, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00,
|
||||
// Char 76: L
|
||||
0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 77: M
|
||||
0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 78: N
|
||||
0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 79: O
|
||||
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00,
|
||||
// Char 80: P
|
||||
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00,
|
||||
// Char 81: Q
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xDE, 0x7C, 0x0C, 0x0E, 0x00, 0x00,
|
||||
// Char 82: R
|
||||
0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00,
|
||||
// Char 83: S
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x60, 0x38, 0x0C, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 84: T
|
||||
0x00, 0x00, 0x7E, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 85: U
|
||||
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 86: V
|
||||
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00,
|
||||
// Char 87: W
|
||||
0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, 0xFE, 0x7C, 0x6C, 0x00, 0x00, 0x00,
|
||||
// Char 88: X
|
||||
0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x38, 0x38, 0x38, 0x6C, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 89: Y
|
||||
0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 90: Z
|
||||
0x00, 0x00, 0xFE, 0xC6, 0x8C, 0x18, 0x30, 0x60, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 91: [
|
||||
0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 92: backslash
|
||||
0x00, 0x00, 0x80, 0xC0, 0xE0, 0x70, 0x38, 0x1C, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00,
|
||||
// Char 93: ]
|
||||
0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 94: ^
|
||||
0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 95: _
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00,
|
||||
// Char 96: `
|
||||
0x00, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 97: a
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 98: b
|
||||
0x00, 0x00, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 99: c
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 100: d
|
||||
0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 101: e
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 102: f
|
||||
0x00, 0x00, 0x1C, 0x36, 0x32, 0x30, 0x7C, 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00,
|
||||
// Char 103: g
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00,
|
||||
// Char 104: h
|
||||
0x00, 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00,
|
||||
// Char 105: i
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 106: j
|
||||
0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00,
|
||||
// Char 107: k
|
||||
0x00, 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00,
|
||||
// Char 108: l
|
||||
0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 109: m
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xD6, 0xD6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 110: n
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00,
|
||||
// Char 111: o
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 112: p
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00,
|
||||
// Char 113: q
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00,
|
||||
// Char 114: r
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x76, 0x66, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00,
|
||||
// Char 115: s
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x70, 0x1C, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 116: t
|
||||
0x00, 0x00, 0x10, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00,
|
||||
// Char 117: u
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 118: v
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00,
|
||||
// Char 119: w
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00,
|
||||
// Char 120: x
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 121: y
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0x00,
|
||||
// Char 122: z
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCC, 0x18, 0x30, 0x66, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 123: {
|
||||
0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00,
|
||||
// Char 124: |
|
||||
0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 125: }
|
||||
0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00,
|
||||
// Char 126: ~
|
||||
0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 127: House/Delete
|
||||
0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
// Char 128: C-cedilla (upper)
|
||||
0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, 0xC2, 0x66, 0x3C, 0x0C, 0x06, 0x7C,
|
||||
// Char 129: u-diaeresis
|
||||
0x00, 0x00, 0xCC, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 130: e-acute
|
||||
0x00, 0x0C, 0x18, 0x30, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 131: a-circumflex
|
||||
0x00, 0x10, 0x38, 0x6C, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 132: a-diaeresis
|
||||
0x00, 0x00, 0xCC, 0xCC, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 133: a-grave
|
||||
0x00, 0x60, 0x30, 0x18, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 134: a-ring
|
||||
0x00, 0x38, 0x6C, 0x38, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 135: c-cedilla
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, 0xC0, 0xC6, 0x7C, 0x18, 0x0C, 0x78,
|
||||
// Char 136: e-circumflex
|
||||
0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 137: e-diaeresis
|
||||
0x00, 0x00, 0xC6, 0xC6, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 138: e-grave
|
||||
0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xFE, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 139: i-diaeresis
|
||||
0x00, 0x00, 0x66, 0x66, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 140: i-circumflex
|
||||
0x00, 0x18, 0x3C, 0x66, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 141: i-grave
|
||||
0x00, 0x60, 0x30, 0x18, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 142: A-diaeresis
|
||||
0x00, 0xC6, 0xC6, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 143: A-ring
|
||||
0x38, 0x6C, 0x38, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 144: E-acute
|
||||
0x0C, 0x18, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, 0x66, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 145: ae ligature
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0x36, 0x76, 0xDC, 0xD8, 0x6E, 0x00, 0x00, 0x00,
|
||||
// Char 146: AE ligature
|
||||
0x00, 0x00, 0x3E, 0x6C, 0xCC, 0xCC, 0xFE, 0xCC, 0xCC, 0xCC, 0xCE, 0x00, 0x00, 0x00,
|
||||
// Char 147: o-circumflex
|
||||
0x00, 0x10, 0x38, 0x6C, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 148: o-diaeresis
|
||||
0x00, 0x00, 0xC6, 0xC6, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 149: o-grave
|
||||
0x00, 0x60, 0x30, 0x18, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 150: u-circumflex
|
||||
0x00, 0x30, 0x78, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 151: u-grave
|
||||
0x00, 0x60, 0x30, 0x18, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 152: y-diaeresis
|
||||
0x00, 0x00, 0xC6, 0xC6, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0x78, 0x00,
|
||||
// Char 153: O-diaeresis
|
||||
0x00, 0xC6, 0xC6, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00,
|
||||
// Char 154: U-diaeresis
|
||||
0x00, 0xC6, 0xC6, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 155: Cent sign
|
||||
0x00, 0x18, 0x18, 0x7C, 0xC6, 0xC0, 0xC0, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 156: Pound sterling
|
||||
0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, 0x60, 0x60, 0xE6, 0xFC, 0x00, 0x00, 0x00,
|
||||
// Char 157: Yen sign
|
||||
0x00, 0x00, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 158: Peseta
|
||||
0x00, 0xF8, 0xCC, 0xCC, 0xF8, 0xC4, 0xCC, 0xDE, 0xCC, 0xCC, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 159: f-hook
|
||||
0x00, 0x0E, 0x1B, 0x18, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x18, 0xD8, 0x70, 0x00, 0x00,
|
||||
// Char 160: a-acute
|
||||
0x00, 0x18, 0x30, 0x60, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 161: i-acute
|
||||
0x00, 0x0C, 0x18, 0x30, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 162: o-acute
|
||||
0x00, 0x18, 0x30, 0x60, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 163: u-acute
|
||||
0x00, 0x18, 0x30, 0x60, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 164: n-tilde
|
||||
0x00, 0x00, 0x76, 0xDC, 0x00, 0xDC, 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00,
|
||||
// Char 165: N-tilde
|
||||
0x76, 0xDC, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 166: Feminine ordinal
|
||||
0x00, 0x00, 0x3C, 0x6C, 0x6C, 0x3E, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 167: Masculine ordinal
|
||||
0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 168: Inverted question mark
|
||||
0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x60, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00,
|
||||
// Char 169: Reversed not sign (left corner)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 170: Not sign (right corner)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 171: 1/2
|
||||
0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xCE, 0x9E, 0x06, 0x00, 0x00,
|
||||
// Char 172: 1/4
|
||||
0x00, 0xC0, 0xC0, 0xC2, 0xC6, 0xCC, 0x18, 0x30, 0x60, 0xDC, 0x86, 0x0C, 0x18, 0x00,
|
||||
// Char 173: Inverted exclamation mark
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x18, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 174: Left-pointing double angle
|
||||
0x00, 0x00, 0x00, 0x00, 0x36, 0x6C, 0xD8, 0x6C, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 175: Right-pointing double angle
|
||||
0x00, 0x00, 0x00, 0x00, 0xD8, 0x6C, 0x36, 0x6C, 0xD8, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 176: Light shade
|
||||
0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44, 0x11, 0x44,
|
||||
// Char 177: Medium shade
|
||||
0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA,
|
||||
// Char 178: Dark shade
|
||||
0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77, 0xDD, 0x77,
|
||||
// Char 179: Box light vertical
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 180: Box light vertical and left
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 181: Box vertical single and left double
|
||||
0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 182: Box double vertical and left
|
||||
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 183: Box down single and left double
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 184: Box down double and left single
|
||||
0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 185: Box double vertical and left
|
||||
0x36, 0x36, 0x36, 0x36, 0xF6, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 186: Box double vertical
|
||||
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 187: Box double down and left
|
||||
0x00, 0x00, 0x00, 0x00, 0xFE, 0x06, 0xF6, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 188: Box double up and left
|
||||
0x36, 0x36, 0x36, 0x36, 0xF6, 0x06, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 189: Box double up and right (via left)
|
||||
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 190: Box up single and left double
|
||||
0x18, 0x18, 0x18, 0x18, 0xF8, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 191: Box light down and left
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 192: Box light up and right
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 193: Box light up and horizontal
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 194: Box light down and horizontal
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 195: Box light vertical and right
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 196: Box light horizontal
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 197: Box light vertical and horizontal
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 198: Box vertical single and right double
|
||||
0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 199: Box double vertical and right
|
||||
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 200: Box double up and right
|
||||
0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 201: Box double down and right
|
||||
0x00, 0x00, 0x00, 0x00, 0x3F, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 202: Box double up and horizontal
|
||||
0x36, 0x36, 0x36, 0x36, 0xF7, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 203: Box double down and horizontal
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 204: Box double vertical and right
|
||||
0x36, 0x36, 0x36, 0x36, 0x37, 0x30, 0x37, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 205: Box double horizontal
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 206: Box double vertical and horizontal
|
||||
0x36, 0x36, 0x36, 0x36, 0xF7, 0x00, 0xF7, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 207: Box up double and horizontal single
|
||||
0x18, 0x18, 0x18, 0x18, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 208: Box double up and horizontal single (upper)
|
||||
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 209: Box down double and horizontal single
|
||||
0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 210: Box double down and horizontal single (upper)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 211: Box double up and right single
|
||||
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 212: Box up single and right double
|
||||
0x18, 0x18, 0x18, 0x18, 0x1F, 0x18, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 213: Box down single and right double
|
||||
0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 214: Box double down and right single
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 215: Box double vertical and horizontal single
|
||||
0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0xFF, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
|
||||
// Char 216: Box vertical single and horizontal double
|
||||
0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0xFF, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 217: Box light up and left
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 218: Box light down and right
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 219: Full block
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
// Char 220: Lower half block
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
// Char 221: Left half block
|
||||
0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, 0xF0,
|
||||
// Char 222: Right half block
|
||||
0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, 0x0F,
|
||||
// Char 223: Upper half block
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 224: alpha
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0xD8, 0xD8, 0xDC, 0x76, 0x00, 0x00, 0x00,
|
||||
// Char 225: beta (sharp s)
|
||||
0x00, 0x00, 0x78, 0xCC, 0xCC, 0xD8, 0xCC, 0xC6, 0xC6, 0xC6, 0xCC, 0x00, 0x00, 0x00,
|
||||
// Char 226: Gamma
|
||||
0x00, 0x00, 0xFE, 0xC6, 0xC6, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00,
|
||||
// Char 227: pi
|
||||
0x00, 0x00, 0x00, 0x00, 0xFE, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00,
|
||||
// Char 228: Sigma (upper)
|
||||
0x00, 0x00, 0xFE, 0xC6, 0x60, 0x30, 0x18, 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00,
|
||||
// Char 229: sigma (lower)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xD8, 0xD8, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00,
|
||||
// Char 230: mu
|
||||
0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xC0, 0x00,
|
||||
// Char 231: tau
|
||||
0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00,
|
||||
// Char 232: Phi
|
||||
0x00, 0x00, 0x7E, 0x18, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 233: Theta
|
||||
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00,
|
||||
// Char 234: Omega
|
||||
0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x6C, 0x6C, 0xEE, 0x00, 0x00, 0x00,
|
||||
// Char 235: delta
|
||||
0x00, 0x00, 0x1C, 0x30, 0x18, 0x0C, 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00,
|
||||
// Char 236: infinity
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xDB, 0xDB, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 237: phi (lower)
|
||||
0x00, 0x00, 0x06, 0x0C, 0x7C, 0xDE, 0xD6, 0xF6, 0x7C, 0x60, 0xC0, 0x00, 0x00, 0x00,
|
||||
// Char 238: epsilon
|
||||
0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xF8, 0xC0, 0xC0, 0x60, 0x38, 0x00, 0x00, 0x00,
|
||||
// Char 239: intersection
|
||||
0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00,
|
||||
// Char 240: triple bar (equiv)
|
||||
0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 241: plus-minus
|
||||
0x00, 0x00, 0x18, 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 242: greater-than-or-equal
|
||||
0x00, 0x00, 0x30, 0x18, 0x0C, 0x06, 0x0C, 0x18, 0x30, 0x00, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 243: less-than-or-equal
|
||||
0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30, 0x18, 0x0C, 0x00, 0x7E, 0x00, 0x00, 0x00,
|
||||
// Char 244: top of integral
|
||||
0x00, 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
|
||||
// Char 245: bottom of integral
|
||||
0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00,
|
||||
// Char 246: division sign
|
||||
0x00, 0x00, 0x18, 0x18, 0x00, 0x7E, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 247: approximately equal
|
||||
0x00, 0x00, 0x00, 0x76, 0xDC, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 248: degree
|
||||
0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 249: bullet (middle dot)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 250: small bullet
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 251: square root
|
||||
0x00, 0x0F, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0xEC, 0x6C, 0x3C, 0x1C, 0x00, 0x00, 0x00,
|
||||
// Char 252: superscript n
|
||||
0x00, 0xD8, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 253: superscript 2
|
||||
0x00, 0x70, 0xD8, 0x30, 0x60, 0xC8, 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 254: filled square
|
||||
0x00, 0x00, 0x00, 0x00, 0x7C, 0x7C, 0x7C, 0x7C, 0x7C, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
// Char 255: Non-breaking space (blank)
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// 8x16 VGA ROM font -- standard IBM CP437 character set
|
||||
// 256 glyphs, 16 bytes per glyph, MSB = leftmost pixel
|
||||
|
|
@ -1073,23 +551,8 @@ static const uint8_t font8x16[256 * 16] = {
|
|||
};
|
||||
|
||||
// ============================================================
|
||||
// Font descriptor structs
|
||||
// Font descriptor struct
|
||||
// ============================================================
|
||||
//
|
||||
// Pre-built BitmapFontT instances ready to pass to dvxInit() or use
|
||||
// directly with draw functions. The 8x14 font fits more rows on screen
|
||||
// (34 rows at 480 pixels) while 8x16 matches the standard VGA text mode
|
||||
// cell height (30 rows at 480). DVX defaults to 8x14 for maximum usable
|
||||
// content area, but 8x16 is available for applications that want the
|
||||
// classic DOS text mode look.
|
||||
|
||||
static const BitmapFontT dvxFont8x14 = {
|
||||
.charWidth = 8,
|
||||
.charHeight = 14,
|
||||
.firstChar = 0,
|
||||
.numChars = 256,
|
||||
.glyphData = font8x14,
|
||||
};
|
||||
|
||||
static const BitmapFontT dvxFont8x16 = {
|
||||
.charWidth = 8,
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ typedef struct {
|
|||
// Bevel style
|
||||
// ============================================================
|
||||
//
|
||||
// Bevels are the defining visual element of the Motif/DESQview/X aesthetic.
|
||||
// Bevels are the defining visual element of the Motif aesthetic.
|
||||
// Swapping highlight and shadow colors flips between raised and sunken
|
||||
// appearance -- the BEVEL_RAISED and BEVEL_SUNKEN macros encode this
|
||||
// convention so callers don't get the colors backwards.
|
||||
|
|
@ -271,7 +271,7 @@ typedef struct {
|
|||
// ============================================================
|
||||
//
|
||||
// These define the pixel geometry of the window frame (border, title bar,
|
||||
// menu bar). They're compile-time constants because the DV/X look demands
|
||||
// menu bar). They're compile-time constants because the DVX look demands
|
||||
// a fixed, uniform chrome thickness across all windows -- there's no
|
||||
// per-window customization of frame geometry.
|
||||
//
|
||||
|
|
@ -484,7 +484,7 @@ typedef struct {
|
|||
#define SCROLL_HORIZONTAL 1
|
||||
|
||||
// Minimized windows display as icons at the bottom of the screen,
|
||||
// similar to DESQview/X's original icon bar behavior.
|
||||
// similar to a classic desktop icon bar.
|
||||
#define ICON_SIZE 64
|
||||
#define ICON_BORDER 2
|
||||
#define ICON_TOTAL_SIZE (ICON_SIZE + ICON_BORDER * 2)
|
||||
|
|
|
|||
849
core/dvxWidget.h
849
core/dvxWidget.h
|
|
@ -8,11 +8,15 @@
|
|||
//
|
||||
// Design decisions and rationale:
|
||||
//
|
||||
// - Single WidgetT struct with a tagged union: avoids the overhead of a
|
||||
// class hierarchy and virtual dispatch (no C++ vtable indirection).
|
||||
// The wclass pointer provides vtable-style dispatch only for the few
|
||||
// operations that genuinely differ per widget type (paint, layout, events).
|
||||
// Type-specific data lives in the `as` union to keep the struct compact.
|
||||
// - Generic WidgetT with void *data: core knows nothing about individual
|
||||
// widget types. Each widget DXE allocates and manages its own private
|
||||
// data struct via w->data. The wclass vtable provides polymorphic
|
||||
// dispatch for paint, layout, events, and all type-specific operations.
|
||||
// No widget-specific enums, structs, or union members in this header.
|
||||
//
|
||||
// - Dynamic type IDs: wgtRegisterClass() returns a runtime ID at load
|
||||
// time. No compile-time enum. Adding a new widget requires zero core
|
||||
// changes -- just drop a .wgt DXE in the widgets directory.
|
||||
//
|
||||
// - Tagged size values (wgtPixels/wgtChars/wgtPercent): encoding the unit
|
||||
// in the high bits of a single int32_t avoids extra struct fields for
|
||||
|
|
@ -23,11 +27,6 @@
|
|||
// this halves the pointer overhead per widget and insertion/removal is
|
||||
// still O(n) in the worst case, which is acceptable given typical tree
|
||||
// depths of 5-10 nodes.
|
||||
//
|
||||
// - Large widget data (AnsiTermDataT, ListViewDataT) is separately
|
||||
// allocated and stored as a pointer in the union rather than inlined,
|
||||
// because these structures are hundreds of bytes and would bloat every
|
||||
// WidgetT even for simple labels and buttons.
|
||||
#ifndef DVX_WIDGET_H
|
||||
#define DVX_WIDGET_H
|
||||
|
||||
|
|
@ -63,62 +62,14 @@ struct WidgetClassT;
|
|||
#define wgtChars(v) ((int32_t)(WGT_SIZE_CHARS | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
||||
#define wgtPercent(v) ((int32_t)(WGT_SIZE_PERCENT | ((uint32_t)(v) & WGT_SIZE_VAL_MASK)))
|
||||
|
||||
// ============================================================
|
||||
// Widget type enum
|
||||
// ============================================================
|
||||
//
|
||||
// Used as the index into widgetClassTable[] to look up the vtable for
|
||||
// each widget type. Built-in types occupy slots 0..WGT_TYPE_BUILTIN_COUNT-1.
|
||||
// Dynamic types registered via wgtRegisterClass() start at
|
||||
// WGT_TYPE_DYNAMIC_BASE.
|
||||
|
||||
typedef enum {
|
||||
WidgetVBoxE,
|
||||
WidgetHBoxE,
|
||||
WidgetLabelE,
|
||||
WidgetButtonE,
|
||||
WidgetCheckboxE,
|
||||
WidgetRadioGroupE,
|
||||
WidgetRadioE,
|
||||
WidgetTextInputE,
|
||||
WidgetTextAreaE,
|
||||
WidgetListBoxE,
|
||||
WidgetSpacerE,
|
||||
WidgetSeparatorE,
|
||||
WidgetFrameE,
|
||||
WidgetDropdownE,
|
||||
WidgetComboBoxE,
|
||||
WidgetProgressBarE,
|
||||
WidgetSliderE,
|
||||
WidgetTabControlE,
|
||||
WidgetTabPageE,
|
||||
WidgetStatusBarE,
|
||||
WidgetToolbarE,
|
||||
WidgetTreeViewE,
|
||||
WidgetTreeItemE,
|
||||
WidgetImageE,
|
||||
WidgetImageButtonE,
|
||||
WidgetCanvasE,
|
||||
WidgetAnsiTermE,
|
||||
WidgetListViewE,
|
||||
WidgetSpinnerE,
|
||||
WidgetScrollPaneE,
|
||||
WidgetSplitterE,
|
||||
WidgetTimerE,
|
||||
WGT_TYPE_BUILTIN_COUNT // must be last built-in entry
|
||||
} WidgetTypeE;
|
||||
|
||||
// Dynamic widget type IDs start here. External DXEs register new
|
||||
// widget types via wgtRegisterClass() and receive IDs >= this value.
|
||||
#define WGT_TYPE_DYNAMIC_BASE 32
|
||||
#define WGT_MAX_TYPES 64
|
||||
// Widget type IDs are assigned dynamically by wgtRegisterClass() at
|
||||
// runtime. There is no compile-time enum. Each widget DXE stores
|
||||
// its assigned ID(s) in file-scope globals.
|
||||
|
||||
// ============================================================
|
||||
// ListView types
|
||||
// ListView column types (used by ListView API in widgetListView.h)
|
||||
// ============================================================
|
||||
|
||||
#define LISTVIEW_MAX_COLS 16
|
||||
|
||||
typedef enum {
|
||||
ListViewAlignLeftE,
|
||||
ListViewAlignCenterE,
|
||||
|
|
@ -152,131 +103,12 @@ typedef enum {
|
|||
} WidgetAlignE;
|
||||
|
||||
// ============================================================
|
||||
// Frame style enum
|
||||
// ============================================================
|
||||
|
||||
typedef enum {
|
||||
FrameInE, // beveled inward (sunken) -- default
|
||||
FrameOutE, // beveled outward (raised)
|
||||
FrameFlatE // solid color line
|
||||
} FrameStyleE;
|
||||
|
||||
// ============================================================
|
||||
// Text input mode enum
|
||||
// ============================================================
|
||||
|
||||
typedef enum {
|
||||
InputNormalE, // default free-form text
|
||||
InputPasswordE, // displays bullets, no copy
|
||||
InputMaskedE // format mask (e.g. "(###) ###-####")
|
||||
} InputModeE;
|
||||
|
||||
// ============================================================
|
||||
// Large widget data (separately allocated to reduce WidgetT size)
|
||||
// Widget-specific enums and types used by the public API
|
||||
// ============================================================
|
||||
//
|
||||
// AnsiTermDataT and ListViewDataT are heap-allocated and stored as
|
||||
// pointers in the WidgetT union. Inlining them would add hundreds of
|
||||
// bytes to every WidgetT instance (even labels and buttons), wasting
|
||||
// memory on the 30+ simple widgets that don't need terminal or listview
|
||||
// state. The pointer indirection adds one dereference but saves
|
||||
// significant memory across a typical widget tree.
|
||||
|
||||
// AnsiTermDataT -- full VT100/ANSI terminal emulator state.
|
||||
// Implements a subset of DEC VT100 escape sequences sufficient for BBS
|
||||
// and DOS ANSI art rendering: cursor movement, color attributes (16-color
|
||||
// with bold-as-bright), scrolling regions, and blink. The parser is a
|
||||
// simple state machine (normal -> ESC -> CSI) with parameter accumulation.
|
||||
typedef struct {
|
||||
uint8_t *cells; // character cells: (ch, attr) pairs, cols*rows*2 bytes
|
||||
int32_t cols; // columns (default 80)
|
||||
int32_t rows; // rows (default 25)
|
||||
int32_t cursorRow; // 0-based cursor row
|
||||
int32_t cursorCol; // 0-based cursor column
|
||||
bool cursorVisible;
|
||||
bool wrapMode; // auto-wrap at right margin
|
||||
bool bold; // SGR bold flag (brightens foreground)
|
||||
bool originMode; // cursor positioning relative to scroll region
|
||||
bool csiPrivate; // '?' prefix in CSI sequence
|
||||
uint8_t curAttr; // current text attribute (fg | bg<<4)
|
||||
uint8_t parseState; // 0=normal, 1=ESC, 2=CSI
|
||||
int32_t params[8]; // CSI parameter accumulator
|
||||
int32_t paramCount; // number of CSI params collected
|
||||
int32_t savedRow; // saved cursor position (SCP)
|
||||
int32_t savedCol;
|
||||
// Scrolling region (0-based, inclusive)
|
||||
int32_t scrollTop; // top row of scroll region
|
||||
int32_t scrollBot; // bottom row of scroll region
|
||||
// Scrollback -- circular buffer so old lines age out naturally without
|
||||
// memmove. Each line is cols*2 bytes (same ch/attr format as cells).
|
||||
// scrollPos tracks the view offset: when equal to scrollbackCount,
|
||||
// the user sees the live screen; when less, they're viewing history.
|
||||
uint8_t *scrollback; // circular buffer of scrollback lines
|
||||
int32_t scrollbackMax; // max lines in scrollback buffer
|
||||
int32_t scrollbackCount; // current number of lines stored
|
||||
int32_t scrollbackHead; // write position (circular index)
|
||||
int32_t scrollPos; // view position (scrollbackCount = live)
|
||||
// Blink support
|
||||
bool blinkVisible; // current blink phase (true = text visible)
|
||||
clock_t blinkTime; // timestamp of last blink toggle
|
||||
// Cursor blink
|
||||
bool cursorOn; // current cursor blink phase
|
||||
clock_t cursorTime; // timestamp of last cursor toggle
|
||||
// Dirty tracking -- a 32-bit bitmask where each bit corresponds to one
|
||||
// terminal row. Only dirty rows are repainted, which is critical because
|
||||
// the ANSI terminal can receive data every frame (at 9600+ baud) and
|
||||
// re-rendering all 25 rows of 80 columns each frame would dominate the
|
||||
// CPU budget. Limits terminal height to 32 rows, which is fine for the
|
||||
// 25-row default target.
|
||||
uint32_t dirtyRows; // bitmask of rows needing repaint
|
||||
int32_t lastCursorRow; // cursor row at last repaint
|
||||
int32_t lastCursorCol; // cursor col at last repaint
|
||||
// Pre-packed 16-color palette avoids calling packColor() (which involves
|
||||
// shift/mask arithmetic) 80*25 = 2000 times per full repaint.
|
||||
uint32_t packedPalette[16];
|
||||
bool paletteValid;
|
||||
// Selection (line indices in scrollback+screen space)
|
||||
int32_t selStartLine;
|
||||
int32_t selStartCol;
|
||||
int32_t selEndLine;
|
||||
int32_t selEndCol;
|
||||
bool selecting;
|
||||
// Communications interface -- abstracted so the terminal can connect to
|
||||
// different backends (serial port, secLink channel, local pipe) without
|
||||
// knowing the transport details. When all are NULL, the terminal is in
|
||||
// offline/disconnected mode (useful for viewing .ANS files).
|
||||
void *commCtx;
|
||||
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
|
||||
int32_t (*commWrite)(void *ctx, const uint8_t *buf, int32_t len);
|
||||
} AnsiTermDataT;
|
||||
|
||||
// ListViewDataT -- multi-column list with sortable headers and optional
|
||||
// multi-select. cellData is a flat array of strings indexed as
|
||||
// cellData[row * colCount + col]. The sortIndex is an indirection array
|
||||
// that maps displayed row numbers to data row numbers, allowing sort
|
||||
// without rearranging the actual data. resolvedColW[] caches pixel widths
|
||||
// after resolving tagged column sizes, avoiding re-resolution on every paint.
|
||||
typedef struct {
|
||||
const ListViewColT *cols;
|
||||
int32_t colCount;
|
||||
const char **cellData;
|
||||
int32_t rowCount;
|
||||
int32_t selectedIdx;
|
||||
int32_t scrollPos;
|
||||
int32_t scrollPosH;
|
||||
int32_t sortCol;
|
||||
ListViewSortE sortDir;
|
||||
int32_t resolvedColW[LISTVIEW_MAX_COLS];
|
||||
int32_t totalColW;
|
||||
int32_t *sortIndex;
|
||||
bool multiSelect;
|
||||
int32_t anchorIdx;
|
||||
uint8_t *selBits;
|
||||
bool reorderable;
|
||||
int32_t dragIdx;
|
||||
int32_t dropIdx;
|
||||
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
|
||||
} ListViewDataT;
|
||||
// These types are referenced by application code through the wgtApi
|
||||
// function table. Widget-private types (data structs) are defined
|
||||
// in their respective widget .c files.
|
||||
|
||||
// ============================================================
|
||||
// Widget class vtable
|
||||
|
|
@ -294,23 +126,79 @@ typedef struct {
|
|||
// PAINTS_CHILDREN -- widget handles child rendering itself
|
||||
// NO_HIT_RECURSE -- hit testing stops here, no child recursion
|
||||
|
||||
#define WCLASS_FOCUSABLE 0x0001
|
||||
#define WCLASS_BOX_CONTAINER 0x0002
|
||||
#define WCLASS_HORIZ_CONTAINER 0x0004
|
||||
#define WCLASS_PAINTS_CHILDREN 0x0008
|
||||
#define WCLASS_NO_HIT_RECURSE 0x0010
|
||||
// Flags encode static properties checked by the framework.
|
||||
// Widgets set these in their WidgetClassT definition.
|
||||
#define WCLASS_FOCUSABLE 0x00000001
|
||||
#define WCLASS_BOX_CONTAINER 0x00000002
|
||||
#define WCLASS_HORIZ_CONTAINER 0x00000004
|
||||
#define WCLASS_PAINTS_CHILDREN 0x00000008
|
||||
#define WCLASS_NO_HIT_RECURSE 0x00000010
|
||||
#define WCLASS_FOCUS_FORWARD 0x00000020 // accel hit forwards focus to next focusable
|
||||
#define WCLASS_HAS_POPUP 0x00000040 // has dropdown popup overlay
|
||||
#define WCLASS_SCROLLABLE 0x00000080 // accepts mouse wheel events
|
||||
#define WCLASS_SCROLL_CONTAINER 0x00000100 // scroll container (ScrollPane)
|
||||
#define WCLASS_NEEDS_POLL 0x00000200 // needs periodic polling (AnsiTerm comms)
|
||||
#define WCLASS_SWALLOWS_TAB 0x00000400 // Tab key goes to widget, not focus nav
|
||||
#define WCLASS_RELAYOUT_ON_SCROLL 0x00000800 // full relayout on scrollbar drag
|
||||
#define WCLASS_PRESS_RELEASE 0x00001000 // click = press+release (Button, ImageButton)
|
||||
#define WCLASS_ACCEL_WHEN_HIDDEN 0x00002000 // accel matching works even when invisible
|
||||
|
||||
typedef struct WidgetClassT {
|
||||
uint32_t flags;
|
||||
|
||||
// Rendering
|
||||
void (*paint)(struct WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void (*paintOverlay)(struct WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
||||
// Layout
|
||||
void (*calcMinSize)(struct WidgetT *w, const BitmapFontT *font);
|
||||
void (*layout)(struct WidgetT *w, const BitmapFontT *font);
|
||||
void (*getLayoutMetrics)(const struct WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW);
|
||||
|
||||
// Input
|
||||
void (*onMouse)(struct WidgetT *w, struct WidgetT *root, int32_t vx, int32_t vy);
|
||||
void (*onKey)(struct WidgetT *w, int32_t key, int32_t mod);
|
||||
void (*onAccelActivate)(struct WidgetT *w, struct WidgetT *root);
|
||||
|
||||
// Lifecycle
|
||||
void (*destroy)(struct WidgetT *w);
|
||||
void (*onChildChanged)(struct WidgetT *parent, struct WidgetT *child);
|
||||
|
||||
// Text
|
||||
const char *(*getText)(const struct WidgetT *w);
|
||||
void (*setText)(struct WidgetT *w, const char *text);
|
||||
|
||||
// Selection / drag-select
|
||||
bool (*clearSelection)(struct WidgetT *w);
|
||||
void (*dragSelect)(struct WidgetT *w, struct WidgetT *root, int32_t vx, int32_t vy);
|
||||
|
||||
// Popup (dropdown/combobox)
|
||||
void (*openPopup)(struct WidgetT *w);
|
||||
void (*closePopup)(struct WidgetT *w);
|
||||
int32_t (*getPopupItemCount)(const struct WidgetT *w);
|
||||
void (*getPopupRect)(const struct WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
|
||||
|
||||
// Button press state
|
||||
void (*setPressed)(struct WidgetT *w, bool pressed);
|
||||
|
||||
// Drag reorder (listbox/listview/treeview)
|
||||
void (*reorderDrop)(struct WidgetT *w);
|
||||
void (*reorderUpdate)(struct WidgetT *w, struct WidgetT *root, int32_t x, int32_t y);
|
||||
|
||||
// Cursor shape (returns cursor ID, 0 = default)
|
||||
int32_t (*getCursorShape)(const struct WidgetT *w, int32_t vx, int32_t vy);
|
||||
|
||||
// Polling (AnsiTerm comms)
|
||||
void (*poll)(struct WidgetT *w, WindowT *win);
|
||||
|
||||
// Fast incremental repaint (returns dirty row count, 0 = none)
|
||||
int32_t (*quickRepaint)(struct WidgetT *w, int32_t *outY, int32_t *outH);
|
||||
|
||||
// Text field width (for scroll calculations; 0 = use widget width)
|
||||
int32_t (*getTextFieldWidth)(const struct WidgetT *w);
|
||||
|
||||
// Scrollbar drag update (orient: 0=vert, 1=horiz)
|
||||
void (*scrollDragUpdate)(struct WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
|
||||
} WidgetClassT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -320,17 +208,13 @@ typedef struct WidgetClassT {
|
|||
#define MAX_WIDGET_NAME 32
|
||||
|
||||
typedef struct WidgetT {
|
||||
WidgetTypeE type;
|
||||
int32_t type; // assigned by wgtRegisterClass()
|
||||
// wclass points to the vtable for this widget type. Looked up once at
|
||||
// creation from widgetClassTable[type]. This avoids a switch on type
|
||||
// in every paint/layout/event dispatch -- the cost is one pointer per
|
||||
// widget, which is negligible.
|
||||
const struct WidgetClassT *wclass;
|
||||
char name[MAX_WIDGET_NAME];
|
||||
// djb2 hash of the name string, computed at wgtSetName() time.
|
||||
// wgtFind() compares hashes before strcmp, making name lookups fast
|
||||
// for the common no-match case in a tree with many unnamed widgets.
|
||||
uint32_t nameHash; // djb2 hash of name, 0 if unnamed
|
||||
|
||||
// Tree linkage
|
||||
struct WidgetT *parent;
|
||||
|
|
@ -389,7 +273,7 @@ typedef struct WidgetT {
|
|||
// Type-specific handlers (e.g. button press animation, listbox
|
||||
// selection) run first, then these universal callbacks fire.
|
||||
void *userData;
|
||||
void *customData; // DXE widget plugin private data (NULL for built-in types)
|
||||
void *data; // widget-private data (allocated by widget DXE)
|
||||
const char *tooltip; // tooltip text (NULL = none, caller owns string)
|
||||
MenuT *contextMenu; // right-click context menu (NULL = none, caller owns)
|
||||
void (*onClick)(struct WidgetT *w);
|
||||
|
|
@ -398,249 +282,6 @@ typedef struct WidgetT {
|
|||
void (*onFocus)(struct WidgetT *w);
|
||||
void (*onBlur)(struct WidgetT *w);
|
||||
|
||||
// Type-specific data -- tagged union keyed by the `type` field.
|
||||
// Only the member corresponding to `type` is valid. This is the C
|
||||
// equivalent of a discriminated union / variant type. Using a union
|
||||
// instead of separate structs per widget type keeps all widget data
|
||||
// in a single allocation, which simplifies memory management and
|
||||
// avoids pointer chasing during layout/paint traversal.
|
||||
union {
|
||||
struct {
|
||||
const char *text;
|
||||
WidgetAlignE textAlign; // AlignStartE=left, AlignCenterE, AlignEndE=right
|
||||
} label;
|
||||
|
||||
struct {
|
||||
const char *text;
|
||||
bool pressed;
|
||||
} button;
|
||||
|
||||
struct {
|
||||
const char *text;
|
||||
bool checked;
|
||||
} checkbox;
|
||||
|
||||
struct {
|
||||
int32_t selectedIdx;
|
||||
} radioGroup;
|
||||
|
||||
struct {
|
||||
const char *text;
|
||||
int32_t index;
|
||||
} radio;
|
||||
|
||||
// Text input has its own edit buffer (not a pointer to external
|
||||
// storage) so the widget fully owns its text lifecycle. The undo
|
||||
// buffer holds a single-level snapshot taken before each edit
|
||||
// operation -- Ctrl+Z restores to the snapshot. This is simpler
|
||||
// than a full undo stack but sufficient for single-line fields.
|
||||
struct {
|
||||
char *buf;
|
||||
int32_t bufSize;
|
||||
int32_t len;
|
||||
int32_t cursorPos;
|
||||
int32_t scrollOff;
|
||||
int32_t selStart; // selection anchor (-1 = none)
|
||||
int32_t selEnd; // selection end (-1 = none)
|
||||
char *undoBuf;
|
||||
int32_t undoLen;
|
||||
int32_t undoCursor;
|
||||
InputModeE inputMode;
|
||||
const char *mask; // format mask for InputMaskedE
|
||||
} textInput;
|
||||
|
||||
// Multi-line text editor. desiredCol implements the "sticky column"
|
||||
// behavior where pressing Up/Down tries to return to the column
|
||||
// the cursor was at before traversing shorter lines (standard
|
||||
// text editor UX). cachedLines/cachedMaxLL cache values that
|
||||
// require full-buffer scans, invalidated to -1 on any text change.
|
||||
struct {
|
||||
char *buf;
|
||||
int32_t bufSize;
|
||||
int32_t len;
|
||||
int32_t cursorRow;
|
||||
int32_t cursorCol;
|
||||
int32_t scrollRow;
|
||||
int32_t scrollCol;
|
||||
int32_t desiredCol; // sticky column for up/down movement
|
||||
int32_t selAnchor; // selection anchor byte offset (-1 = none)
|
||||
int32_t selCursor; // selection cursor byte offset (-1 = none)
|
||||
char *undoBuf;
|
||||
int32_t undoLen;
|
||||
int32_t undoCursor; // byte offset at time of snapshot
|
||||
int32_t cachedLines; // cached line count (-1 = dirty)
|
||||
int32_t cachedMaxLL; // cached max line length (-1 = dirty)
|
||||
} textArea;
|
||||
|
||||
struct {
|
||||
const char **items;
|
||||
int32_t itemCount;
|
||||
int32_t selectedIdx; // cursor position (always valid); also sole selection in single-select
|
||||
int32_t scrollPos;
|
||||
int32_t maxItemLen; // cached max strlen of items
|
||||
bool multiSelect;
|
||||
int32_t anchorIdx; // anchor for shift+click range selection
|
||||
uint8_t *selBits; // per-item selection flags (multi-select only)
|
||||
bool reorderable; // allow drag-reorder of items
|
||||
int32_t dragIdx; // item being dragged (-1 = none)
|
||||
int32_t dropIdx; // insertion point (-1 = none)
|
||||
} listBox;
|
||||
|
||||
struct {
|
||||
bool vertical;
|
||||
} separator;
|
||||
|
||||
struct {
|
||||
const char *title;
|
||||
FrameStyleE style; // FrameInE (default), FrameOutE, FrameFlatE
|
||||
uint32_t color; // border color for FrameFlatE (0 = use windowShadow)
|
||||
} frame;
|
||||
|
||||
struct {
|
||||
const char **items;
|
||||
int32_t itemCount;
|
||||
int32_t selectedIdx;
|
||||
bool open;
|
||||
int32_t hoverIdx;
|
||||
int32_t scrollPos;
|
||||
int32_t maxItemLen; // cached max strlen of items
|
||||
} dropdown;
|
||||
|
||||
struct {
|
||||
char *buf;
|
||||
int32_t bufSize;
|
||||
int32_t len;
|
||||
int32_t cursorPos;
|
||||
int32_t scrollOff;
|
||||
int32_t selStart; // selection anchor (-1 = none)
|
||||
int32_t selEnd; // selection end (-1 = none)
|
||||
char *undoBuf;
|
||||
int32_t undoLen;
|
||||
int32_t undoCursor;
|
||||
const char **items;
|
||||
int32_t itemCount;
|
||||
int32_t selectedIdx;
|
||||
bool open;
|
||||
int32_t hoverIdx;
|
||||
int32_t listScrollPos;
|
||||
int32_t maxItemLen; // cached max strlen of items
|
||||
} comboBox;
|
||||
|
||||
struct {
|
||||
int32_t value;
|
||||
int32_t maxValue;
|
||||
bool vertical;
|
||||
} progressBar;
|
||||
|
||||
struct {
|
||||
int32_t value;
|
||||
int32_t minValue;
|
||||
int32_t maxValue;
|
||||
bool vertical;
|
||||
} slider;
|
||||
|
||||
struct {
|
||||
int32_t activeTab;
|
||||
int32_t scrollOffset; // horizontal scroll of tab headers
|
||||
} tabControl;
|
||||
|
||||
struct {
|
||||
const char *title;
|
||||
} tabPage;
|
||||
|
||||
// TreeView uses widget children (WidgetTreeItemE) as its items,
|
||||
// unlike ListBox which uses a string array. This allows nested
|
||||
// hierarchies with expand/collapse state per item. The tree is
|
||||
// rendered by flattening visible items during paint, with TREE_INDENT
|
||||
// pixels of indentation per nesting level.
|
||||
struct {
|
||||
int32_t scrollPos;
|
||||
int32_t scrollPosH;
|
||||
struct WidgetT *selectedItem;
|
||||
struct WidgetT *anchorItem; // anchor for shift+click range selection
|
||||
bool multiSelect;
|
||||
bool reorderable; // allow drag-reorder of items
|
||||
struct WidgetT *dragItem; // item being dragged (NULL = none)
|
||||
struct WidgetT *dropTarget; // insertion target (NULL = none)
|
||||
bool dropAfter; // true = insert after target, false = before
|
||||
int32_t cachedTotalHeight; // cached result of calcTreeItemsHeight
|
||||
int32_t cachedMaxWidth; // cached result of calcTreeItemsMaxWidth
|
||||
bool dimsValid; // false = recalculate on next scrollbar check
|
||||
} treeView;
|
||||
|
||||
struct {
|
||||
const char *text;
|
||||
bool expanded;
|
||||
bool selected; // per-item flag for multi-select
|
||||
} treeItem;
|
||||
|
||||
struct {
|
||||
uint8_t *data; // pixel buffer in display format
|
||||
int32_t imgW;
|
||||
int32_t imgH;
|
||||
int32_t imgPitch;
|
||||
bool pressed;
|
||||
} image;
|
||||
|
||||
struct {
|
||||
uint8_t *data; // pixel buffer in display format
|
||||
int32_t imgW;
|
||||
int32_t imgH;
|
||||
int32_t imgPitch;
|
||||
bool pressed;
|
||||
} imageButton;
|
||||
|
||||
struct {
|
||||
uint8_t *data; // pixel buffer in display format
|
||||
int32_t canvasW;
|
||||
int32_t canvasH;
|
||||
int32_t canvasPitch;
|
||||
int32_t canvasBpp; // cached bytes per pixel (avoids pitch/w division)
|
||||
uint32_t penColor;
|
||||
int32_t penSize;
|
||||
int32_t lastX;
|
||||
int32_t lastY;
|
||||
void (*onMouse)(struct WidgetT *w, int32_t cx, int32_t cy, bool drag);
|
||||
} canvas;
|
||||
|
||||
AnsiTermDataT *ansiTerm;
|
||||
|
||||
ListViewDataT *listView;
|
||||
|
||||
struct {
|
||||
int32_t value;
|
||||
int32_t minValue;
|
||||
int32_t maxValue;
|
||||
int32_t step;
|
||||
char buf[16]; // formatted value text
|
||||
int32_t len;
|
||||
int32_t cursorPos;
|
||||
int32_t scrollOff;
|
||||
int32_t selStart; // selection anchor (-1 = none)
|
||||
int32_t selEnd; // selection end (-1 = none)
|
||||
char undoBuf[16]; // undo snapshot
|
||||
int32_t undoLen;
|
||||
int32_t undoCursor;
|
||||
bool editing; // true when user is typing
|
||||
} spinner;
|
||||
|
||||
struct {
|
||||
int32_t scrollPosV;
|
||||
int32_t scrollPosH;
|
||||
} scrollPane;
|
||||
|
||||
struct {
|
||||
int32_t dividerPos; // pixels from left/top edge
|
||||
bool vertical; // true = vertical divider (left|right panes)
|
||||
} splitter;
|
||||
|
||||
struct {
|
||||
int32_t intervalMs; // timer period in milliseconds
|
||||
bool repeat; // true = repeating, false = one-shot
|
||||
bool running; // true = active
|
||||
clock_t lastFire; // clock() value at last fire (or start)
|
||||
} timer;
|
||||
} as;
|
||||
} WidgetT;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -655,299 +296,6 @@ typedef struct WidgetT {
|
|||
// root container. The window's userData is set to the AppContextT pointer.
|
||||
WidgetT *wgtInitWindow(struct AppContextT *ctx, WindowT *win);
|
||||
|
||||
// ============================================================
|
||||
// Container creation
|
||||
// ============================================================
|
||||
//
|
||||
// VBox and HBox are the primary layout containers, analogous to CSS
|
||||
// flexbox with column/row direction. Children are laid out sequentially
|
||||
// along the main axis (vertical for VBox, horizontal for HBox) with
|
||||
// spacing between them. Extra space is distributed according to each
|
||||
// child's weight. Frame is a titled groupbox container with a bevel border.
|
||||
|
||||
WidgetT *wgtVBox(WidgetT *parent);
|
||||
WidgetT *wgtHBox(WidgetT *parent);
|
||||
WidgetT *wgtFrame(WidgetT *parent, const char *title);
|
||||
|
||||
// ============================================================
|
||||
// Basic widgets
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtLabel(WidgetT *parent, const char *text);
|
||||
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align);
|
||||
WidgetT *wgtButton(WidgetT *parent, const char *text);
|
||||
WidgetT *wgtCheckbox(WidgetT *parent, const char *text);
|
||||
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen);
|
||||
WidgetT *wgtPasswordInput(WidgetT *parent, int32_t maxLen);
|
||||
WidgetT *wgtMaskedInput(WidgetT *parent, const char *mask);
|
||||
|
||||
// ============================================================
|
||||
// Radio buttons
|
||||
// ============================================================
|
||||
//
|
||||
// Radio buttons must be children of a RadioGroup container. The group
|
||||
// tracks which child is selected (by index). Clicking a radio button
|
||||
// automatically deselects the previously selected sibling. This parent-
|
||||
// tracking approach avoids explicit "radio group ID" parameters.
|
||||
|
||||
WidgetT *wgtRadioGroup(WidgetT *parent);
|
||||
WidgetT *wgtRadio(WidgetT *parent, const char *text);
|
||||
|
||||
// ============================================================
|
||||
// Spacing and visual dividers
|
||||
// ============================================================
|
||||
//
|
||||
// Spacer is an invisible flexible widget (weight=100) that absorbs
|
||||
// extra space -- useful for pushing subsequent siblings to the end of
|
||||
// a container (like CSS flex: 1 auto). Separators are thin beveled
|
||||
// lines for visual grouping.
|
||||
|
||||
WidgetT *wgtSpacer(WidgetT *parent);
|
||||
WidgetT *wgtHSeparator(WidgetT *parent);
|
||||
WidgetT *wgtVSeparator(WidgetT *parent);
|
||||
|
||||
// ============================================================
|
||||
// Complex widgets
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtListBox(WidgetT *parent);
|
||||
WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen);
|
||||
|
||||
// ============================================================
|
||||
// Dropdown and ComboBox
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtDropdown(WidgetT *parent);
|
||||
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t wgtDropdownGetSelected(const WidgetT *w);
|
||||
void wgtDropdownSetSelected(WidgetT *w, int32_t idx);
|
||||
|
||||
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen);
|
||||
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t wgtComboBoxGetSelected(const WidgetT *w);
|
||||
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx);
|
||||
|
||||
// ============================================================
|
||||
// ProgressBar
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtProgressBar(WidgetT *parent);
|
||||
WidgetT *wgtProgressBarV(WidgetT *parent);
|
||||
void wgtProgressBarSetValue(WidgetT *w, int32_t value);
|
||||
int32_t wgtProgressBarGetValue(const WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// Slider (TrackBar)
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal);
|
||||
void wgtSliderSetValue(WidgetT *w, int32_t value);
|
||||
int32_t wgtSliderGetValue(const WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// Spinner (numeric input)
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step);
|
||||
void wgtSpinnerSetValue(WidgetT *w, int32_t value);
|
||||
int32_t wgtSpinnerGetValue(const WidgetT *w);
|
||||
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal);
|
||||
void wgtSpinnerSetStep(WidgetT *w, int32_t step);
|
||||
|
||||
// ============================================================
|
||||
// TabControl
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtTabControl(WidgetT *parent);
|
||||
WidgetT *wgtTabPage(WidgetT *parent, const char *title);
|
||||
void wgtTabControlSetActive(WidgetT *w, int32_t idx);
|
||||
int32_t wgtTabControlGetActive(const WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// StatusBar and Toolbar
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtStatusBar(WidgetT *parent);
|
||||
WidgetT *wgtToolbar(WidgetT *parent);
|
||||
|
||||
// ============================================================
|
||||
// TreeView
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtTreeView(WidgetT *parent);
|
||||
WidgetT *wgtTreeViewGetSelected(const WidgetT *w);
|
||||
void wgtTreeViewSetSelected(WidgetT *w, WidgetT *item);
|
||||
void wgtTreeViewSetMultiSelect(WidgetT *w, bool multi);
|
||||
void wgtTreeViewSetReorderable(WidgetT *w, bool reorderable);
|
||||
WidgetT *wgtTreeItem(WidgetT *parent, const char *text);
|
||||
void wgtTreeItemSetExpanded(WidgetT *w, bool expanded);
|
||||
bool wgtTreeItemIsExpanded(const WidgetT *w);
|
||||
bool wgtTreeItemIsSelected(const WidgetT *w);
|
||||
void wgtTreeItemSetSelected(WidgetT *w, bool selected);
|
||||
|
||||
// ============================================================
|
||||
// ListView (multi-column list)
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtListView(WidgetT *parent);
|
||||
void wgtListViewSetColumns(WidgetT *w, const ListViewColT *cols, int32_t count);
|
||||
void wgtListViewSetData(WidgetT *w, const char **cellData, int32_t rowCount);
|
||||
int32_t wgtListViewGetSelected(const WidgetT *w);
|
||||
void wgtListViewSetSelected(WidgetT *w, int32_t idx);
|
||||
void wgtListViewSetSort(WidgetT *w, int32_t col, ListViewSortE dir);
|
||||
void wgtListViewSetHeaderClickCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir));
|
||||
void wgtListViewSetMultiSelect(WidgetT *w, bool multi);
|
||||
bool wgtListViewIsItemSelected(const WidgetT *w, int32_t idx);
|
||||
void wgtListViewSetItemSelected(WidgetT *w, int32_t idx, bool selected);
|
||||
void wgtListViewSelectAll(WidgetT *w);
|
||||
void wgtListViewClearSelection(WidgetT *w);
|
||||
void wgtListViewSetReorderable(WidgetT *w, bool reorderable);
|
||||
|
||||
// ============================================================
|
||||
// ScrollPane
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtScrollPane(WidgetT *parent);
|
||||
|
||||
// ============================================================
|
||||
// Splitter (draggable divider between two child regions)
|
||||
// ============================================================
|
||||
|
||||
// Create a splitter. If vertical is true, children are arranged left|right;
|
||||
// if false, top|bottom. Add exactly two children.
|
||||
WidgetT *wgtSplitter(WidgetT *parent, bool vertical);
|
||||
void wgtSplitterSetPos(WidgetT *w, int32_t pos);
|
||||
int32_t wgtSplitterGetPos(const WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// Timer (invisible, callback-driven)
|
||||
// ============================================================
|
||||
|
||||
// Create a timer widget. The timer is invisible and takes no space in
|
||||
// layout. When running, it calls onChange at the specified interval.
|
||||
// intervalMs: period in milliseconds. repeat: true for repeating,
|
||||
// false for one-shot (auto-stops after first fire).
|
||||
WidgetT *wgtTimer(WidgetT *parent, int32_t intervalMs, bool repeat);
|
||||
|
||||
// Start/restart the timer. Resets the elapsed time.
|
||||
void wgtTimerStart(WidgetT *w);
|
||||
|
||||
// Stop the timer.
|
||||
void wgtTimerStop(WidgetT *w);
|
||||
|
||||
// Change the interval (takes effect on next fire).
|
||||
void wgtTimerSetInterval(WidgetT *w, int32_t intervalMs);
|
||||
|
||||
// Check if the timer is currently running.
|
||||
bool wgtTimerIsRunning(const WidgetT *w);
|
||||
|
||||
// Called once per frame by dvxUpdate. Checks all active timers.
|
||||
void wgtUpdateTimers(void);
|
||||
|
||||
// ============================================================
|
||||
// ImageButton
|
||||
// ============================================================
|
||||
|
||||
// Create an image button from raw pixel data (display format).
|
||||
// Takes ownership of the data buffer (freed on destroy).
|
||||
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
|
||||
|
||||
// Load an image button from a file (BMP, PNG, JPEG, GIF).
|
||||
// Returns NULL on load failure; falls through gracefully.
|
||||
WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path);
|
||||
|
||||
// Replace the image data. Takes ownership of the new buffer.
|
||||
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
|
||||
|
||||
// ============================================================
|
||||
// Image
|
||||
// ============================================================
|
||||
|
||||
// Create an image widget from raw pixel data (display format).
|
||||
// Takes ownership of the data buffer (freed on destroy).
|
||||
WidgetT *wgtImage(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
|
||||
|
||||
// Load an image widget from a file (BMP, PNG, JPEG, GIF).
|
||||
// Returns NULL on load failure.
|
||||
WidgetT *wgtImageFromFile(WidgetT *parent, const char *path);
|
||||
|
||||
// Replace the image data. Takes ownership of the new buffer.
|
||||
void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
|
||||
|
||||
// ============================================================
|
||||
// Canvas
|
||||
// ============================================================
|
||||
|
||||
// Create a drawable canvas widget with the given dimensions.
|
||||
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h);
|
||||
|
||||
// Clear the canvas to the specified color.
|
||||
void wgtCanvasClear(WidgetT *w, uint32_t color);
|
||||
|
||||
// Set the pen color (in display pixel format).
|
||||
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color);
|
||||
|
||||
// Set the pen size in pixels (diameter).
|
||||
void wgtCanvasSetPenSize(WidgetT *w, int32_t size);
|
||||
|
||||
// Set the mouse callback. Called on click (drag=false) and drag (drag=true)
|
||||
// with canvas-relative coordinates. If NULL (default), mouse events are ignored.
|
||||
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
|
||||
|
||||
// Save the canvas to a PNG file. Returns 0 on success, -1 on failure.
|
||||
int32_t wgtCanvasSave(WidgetT *w, const char *path);
|
||||
|
||||
// Load a PNG file onto the canvas. Returns 0 on success, -1 on failure.
|
||||
int32_t wgtCanvasLoad(WidgetT *w, const char *path);
|
||||
|
||||
// Programmatic drawing (coordinates are in canvas space)
|
||||
void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
|
||||
void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
||||
void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
||||
void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
|
||||
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color);
|
||||
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y);
|
||||
|
||||
// ============================================================
|
||||
// ANSI Terminal
|
||||
// ============================================================
|
||||
//
|
||||
// A VT100/ANSI terminal emulator widget. Supports the subset of escape
|
||||
// sequences needed for DOS BBS programs and ANSI art: cursor positioning,
|
||||
// SGR color/attribute codes, scrolling regions, and text blink. Pairs
|
||||
// with the secLink communications layer for remote serial connections
|
||||
// or can be used standalone for viewing .ANS files.
|
||||
|
||||
// Create an ANSI terminal widget (0 for cols/rows = 80x25 default)
|
||||
WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows);
|
||||
|
||||
// Write raw data through the ANSI escape sequence parser. Used for
|
||||
// loading .ANS files or feeding data from a source that isn't using
|
||||
// the comm interface.
|
||||
void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len);
|
||||
|
||||
// Clear the terminal screen and reset cursor to home
|
||||
void wgtAnsiTermClear(WidgetT *w);
|
||||
|
||||
// Set the communications interface (NULL function pointers = disconnected)
|
||||
void wgtAnsiTermSetComm(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t));
|
||||
|
||||
// Set the scrollback buffer size in lines (default 500). Clears existing scrollback.
|
||||
void wgtAnsiTermSetScrollback(WidgetT *w, int32_t maxLines);
|
||||
|
||||
// Poll the comm interface for incoming data and process it. Returns bytes processed.
|
||||
int32_t wgtAnsiTermPoll(WidgetT *w);
|
||||
|
||||
// Fast-path repaint for the terminal widget. Instead of going through the
|
||||
// full widget paint pipeline (which would repaint the entire widget), this
|
||||
// renders only the dirty rows (tracked via the dirtyRows bitmask) directly
|
||||
// into the window's content buffer. This is essential for responsive terminal
|
||||
// output -- incoming serial data can dirty a few rows per frame, and
|
||||
// repainting only those rows keeps the cost proportional to the actual
|
||||
// change rather than the full 80x25 grid. Returns the number of rows
|
||||
// repainted; outY/outH report the affected region for dirty-rect tracking.
|
||||
int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH);
|
||||
|
||||
// ============================================================
|
||||
// Operations
|
||||
// ============================================================
|
||||
|
|
@ -958,11 +306,6 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH);
|
|||
// function call.
|
||||
struct AppContextT *wgtGetContext(const WidgetT *w);
|
||||
|
||||
// Update text cursor blink state. Call once per frame from dvxUpdate.
|
||||
// Toggles the cursor visibility at 250ms intervals, matching the ANSI
|
||||
// terminal cursor rate.
|
||||
void wgtUpdateCursorBlink(void);
|
||||
|
||||
// Mark a widget as needing both re-layout (measure + position) and
|
||||
// repaint. Propagates upward to ancestors since a child's size change
|
||||
// can affect parent layout. Use this after structural changes (adding/
|
||||
|
|
@ -1000,20 +343,6 @@ WidgetT *wgtFind(WidgetT *root, const char *name);
|
|||
// Destroy a widget and all its children (removes from parent)
|
||||
void wgtDestroy(WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// List box operations
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t wgtListBoxGetSelected(const WidgetT *w);
|
||||
void wgtListBoxSetSelected(WidgetT *w, int32_t idx);
|
||||
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi);
|
||||
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx);
|
||||
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected);
|
||||
void wgtListBoxSelectAll(WidgetT *w);
|
||||
void wgtListBoxClearSelection(WidgetT *w);
|
||||
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable);
|
||||
|
||||
// ============================================================
|
||||
// Tooltip
|
||||
// ============================================================
|
||||
|
|
@ -1034,7 +363,7 @@ void wgtSetDebugLayout(struct AppContextT *ctx, bool enabled);
|
|||
// ============================================================
|
||||
|
||||
// Register a new widget class at runtime. Returns the assigned type ID
|
||||
// (>= WGT_TYPE_DYNAMIC_BASE) on success, or -1 if the table is full.
|
||||
// Appends wclass to widgetClassTable and returns the assigned type ID.
|
||||
// The WidgetClassT must remain valid for the lifetime of the process
|
||||
// (typically a static const in the registering DXE).
|
||||
int32_t wgtRegisterClass(const WidgetClassT *wclass);
|
||||
|
|
@ -1063,4 +392,20 @@ void wgtLayout(WidgetT *root, int32_t availW, int32_t availH, const BitmapFontT
|
|||
// after the main tree so they render on top of everything.
|
||||
void wgtPaint(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
||||
// ============================================================
|
||||
// Widget API registry
|
||||
// ============================================================
|
||||
//
|
||||
// Each widget DXE registers a small API struct under a name
|
||||
// during wgtRegister(). Callers retrieve it via wgtGetApi()
|
||||
// and cast to the widget-specific API type. Per-widget headers
|
||||
// (e.g. widgetButton.h) provide typed accessors and convenience
|
||||
// macros so callers don't need manual casts.
|
||||
//
|
||||
// This replaces the monolithic WidgetApiT -- adding a new widget
|
||||
// requires zero changes to dvxWidget.h.
|
||||
|
||||
void wgtRegisterApi(const char *name, const void *api);
|
||||
const void *wgtGetApi(const char *name);
|
||||
|
||||
#endif // DVX_WIDGET_H
|
||||
|
|
|
|||
213
core/dvxWidgetPlugin.h
Normal file
213
core/dvxWidgetPlugin.h
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
// dvxWidgetPlugin.h -- Plugin API for DVX widget DXE modules
|
||||
//
|
||||
// Included by widget .c files to access core infrastructure.
|
||||
// Provides: widget allocation, tree ops, class table, shared
|
||||
// interaction state, scrollbar helpers, layout helpers, and
|
||||
// common utility functions.
|
||||
//
|
||||
// Does NOT contain any per-widget prototypes. Each widget's
|
||||
// internal functions (paint, onMouse, onKey, calcMinSize, etc.)
|
||||
// are private to its own .c file and referenced only through
|
||||
// the WidgetClassT vtable.
|
||||
#ifndef DVX_WIDGET_PLUGIN_H
|
||||
#define DVX_WIDGET_PLUGIN_H
|
||||
|
||||
#include "dvxWidget.h"
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxVideo.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// ============================================================
|
||||
// Widget class table
|
||||
// ============================================================
|
||||
|
||||
// stb_ds dynamic array. Each widget DXE appends its class(es)
|
||||
// via wgtRegisterClass() during wgtRegister().
|
||||
extern const WidgetClassT **widgetClassTable;
|
||||
|
||||
// ============================================================
|
||||
// Validation macros
|
||||
// ============================================================
|
||||
|
||||
#define VALIDATE_WIDGET(w, wtype, retval) \
|
||||
do { if (!(w) || (w)->type != (wtype)) { return (retval); } } while (0)
|
||||
|
||||
#define VALIDATE_WIDGET_VOID(w, wtype) \
|
||||
do { if (!(w) || (w)->type != (wtype)) { return; } } while (0)
|
||||
|
||||
// ============================================================
|
||||
// Cursor shape IDs (mirrors dvxCursor.h defines)
|
||||
// ============================================================
|
||||
|
||||
#define CURSOR_ARROW 0
|
||||
#define CURSOR_RESIZE_H 1
|
||||
#define CURSOR_RESIZE_V 2
|
||||
#define CURSOR_RESIZE_DIAG_NWSE 3
|
||||
#define CURSOR_RESIZE_DIAG_NESW 4
|
||||
|
||||
// ============================================================
|
||||
// Keyboard modifier flags
|
||||
// ============================================================
|
||||
|
||||
#define KEY_MOD_SHIFT 0x03
|
||||
#define KEY_MOD_CTRL 0x04
|
||||
#define KEY_MOD_ALT 0x08
|
||||
|
||||
// ============================================================
|
||||
// Core layout constants
|
||||
// ============================================================
|
||||
|
||||
#define DEFAULT_SPACING 4
|
||||
#define DEFAULT_PADDING 4
|
||||
#define SB_MIN_THUMB 14
|
||||
#define WGT_SB_W 14
|
||||
|
||||
// ============================================================
|
||||
// Inline helpers
|
||||
// ============================================================
|
||||
|
||||
static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
|
||||
if (val < lo) { return lo; }
|
||||
if (val > hi) { return hi; }
|
||||
return val;
|
||||
}
|
||||
|
||||
|
||||
static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
|
||||
drawText(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
|
||||
drawText(d, ops, font, x, y, text, colors->windowShadow, 0, false);
|
||||
}
|
||||
|
||||
|
||||
static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
|
||||
drawTextAccel(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
|
||||
drawTextAccel(d, ops, font, x, y, text, colors->windowShadow, 0, false);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Shared interaction state (defined in widgetCore.c)
|
||||
// ============================================================
|
||||
|
||||
extern bool sCursorBlinkOn;
|
||||
extern clock_t sDblClickTicks;
|
||||
extern bool sDebugLayout;
|
||||
extern WidgetT *sClosedPopup;
|
||||
extern WidgetT *sFocusedWidget;
|
||||
extern WidgetT *sKeyPressedBtn;
|
||||
extern WidgetT *sOpenPopup;
|
||||
extern WidgetT *sPressedButton;
|
||||
extern WidgetT *sDragSlider;
|
||||
extern WidgetT *sDrawingCanvas;
|
||||
extern WidgetT *sDragTextSelect;
|
||||
extern int32_t sDragOffset;
|
||||
extern WidgetT *sResizeListView;
|
||||
extern WidgetT *sDragSplitter;
|
||||
extern int32_t sDragSplitStart;
|
||||
extern WidgetT *sDragReorder;
|
||||
extern WidgetT *sDragScrollbar;
|
||||
extern int32_t sDragScrollbarOff;
|
||||
extern int32_t sDragScrollbarOrient;
|
||||
|
||||
// ============================================================
|
||||
// Core widget functions (widgetCore.c)
|
||||
// ============================================================
|
||||
|
||||
// Tree manipulation
|
||||
void widgetAddChild(WidgetT *parent, WidgetT *child);
|
||||
void widgetRemoveChild(WidgetT *parent, WidgetT *child);
|
||||
void widgetDestroyChildren(WidgetT *w);
|
||||
|
||||
// Allocation
|
||||
WidgetT *widgetAlloc(WidgetT *parent, int32_t type);
|
||||
|
||||
// Focus management
|
||||
void widgetClearFocus(WidgetT *root);
|
||||
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
|
||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
|
||||
WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
||||
|
||||
// Utility queries
|
||||
int32_t widgetCountVisibleChildren(const WidgetT *w);
|
||||
int32_t widgetFrameBorderWidth(const WidgetT *w);
|
||||
bool widgetIsFocusable(int32_t type);
|
||||
bool widgetIsBoxContainer(int32_t type);
|
||||
bool widgetIsHorizContainer(int32_t type);
|
||||
int32_t multiClickDetect(int32_t vx, int32_t vy);
|
||||
|
||||
// Clipboard
|
||||
void clipboardCopy(const char *text, int32_t len);
|
||||
const char *clipboardGet(int32_t *outLen);
|
||||
int32_t clipboardMaxLen(void);
|
||||
|
||||
// Hit testing
|
||||
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
|
||||
|
||||
// Scrollbar helpers
|
||||
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize);
|
||||
|
||||
// ============================================================
|
||||
// Scrollbar drawing and hit testing (widgetScrollbar.c)
|
||||
// ============================================================
|
||||
|
||||
typedef enum {
|
||||
ScrollHitNoneE,
|
||||
ScrollHitArrowDecE,
|
||||
ScrollHitArrowIncE,
|
||||
ScrollHitPageDecE,
|
||||
ScrollHitPageIncE,
|
||||
ScrollHitThumbE
|
||||
} ScrollHitE;
|
||||
|
||||
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
|
||||
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
|
||||
// ============================================================
|
||||
// Layout functions (widgetLayout.c)
|
||||
// ============================================================
|
||||
|
||||
void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetLayoutBox(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
|
||||
|
||||
// ============================================================
|
||||
// Event dispatch (widgetEvent.c)
|
||||
// ============================================================
|
||||
|
||||
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
|
||||
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
|
||||
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
|
||||
void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
|
||||
|
||||
// ============================================================
|
||||
// Paint helpers (widgetOps.c)
|
||||
// ============================================================
|
||||
|
||||
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
||||
// Shared helper libraries:
|
||||
// texthelp/textHelp.h -- clipboard, text editing, word boundaries
|
||||
// listhelp/listHelp.h -- dropdown arrow, popup list, keyboard nav
|
||||
// Widget DXEs that use these include the headers directly.
|
||||
|
||||
// widgetTextDragUpdate stays in core (widgetCore.c)
|
||||
void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
|
||||
// ============================================================
|
||||
// Drag-reorder helpers (widgetEvent.c)
|
||||
// ============================================================
|
||||
|
||||
void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
||||
void widgetReorderDrop(WidgetT *w);
|
||||
|
||||
#endif // DVX_WIDGET_PLUGIN_H
|
||||
28
core/dvxWm.c
28
core/dvxWm.c
|
|
@ -144,9 +144,9 @@ static void computeMenuBarPositions(WindowT *win, const BitmapFontT *font) {
|
|||
// Compute title bar gadget positions. Used by both drawTitleBar()
|
||||
// and wmHitTest() to keep geometry in sync.
|
||||
//
|
||||
// Layout follows the DESQview/X + GEOS Ensemble convention:
|
||||
// Layout follows the Motif + GEOS Ensemble convention:
|
||||
// [Close] ---- Title Text ---- [Minimize] [Maximize]
|
||||
// Close is on the far left (also doubles as system menu in DV/X style),
|
||||
// Close is on the far left (also doubles as system menu),
|
||||
// minimize and maximize pack right-to-left from the far right edge.
|
||||
// Modal windows suppress the minimize gadget since they must stay visible.
|
||||
// Non-resizable windows suppress the maximize gadget.
|
||||
|
|
@ -1410,8 +1410,8 @@ void wmDrawContent(DisplayT *d, const BlitOpsT *ops, WindowT *win, const RectT *
|
|||
// 2. A live thumbnail of the window's content buffer (also scaled)
|
||||
// 3. A grey fill if neither is available
|
||||
//
|
||||
// The live thumbnail approach (option 2) is a DESQview/X homage -- DV/X
|
||||
// showed miniature window contents in its icon/task view. The thumbnail
|
||||
// The live thumbnail (option 2) shows miniature window contents in the
|
||||
// icon/task view, giving a quick preview of each minimized window. The thumbnail
|
||||
// is rendered from the existing content buffer, so no extra rendering
|
||||
// pass is needed. contentDirty tracks whether the content has changed
|
||||
// since the last icon refresh.
|
||||
|
|
@ -1441,7 +1441,14 @@ void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
|||
continue;
|
||||
}
|
||||
|
||||
// Draw beveled border
|
||||
int32_t bpp = d->format.bytesPerPixel;
|
||||
|
||||
if (win->iconData && win->iconW > 0 && win->iconH > 0) {
|
||||
// Provided icon image -- draw edge-to-edge, no bevel
|
||||
drawScaledRect(d, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE,
|
||||
win->iconData, win->iconW, win->iconH, win->iconPitch, bpp);
|
||||
} else {
|
||||
// Live thumbnail or grey fill -- draw with beveled border
|
||||
BevelStyleT bevel;
|
||||
bevel.highlight = colors->windowHighlight;
|
||||
bevel.shadow = colors->windowShadow;
|
||||
|
|
@ -1449,24 +1456,17 @@ void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
|||
bevel.width = ICON_BORDER;
|
||||
drawBevel(d, ops, ix, iy, ICON_TOTAL_SIZE, ICON_TOTAL_SIZE, &bevel);
|
||||
|
||||
// Draw icon content
|
||||
int32_t contentX = ix + ICON_BORDER;
|
||||
int32_t contentY = iy + ICON_BORDER;
|
||||
int32_t bpp = d->format.bytesPerPixel;
|
||||
|
||||
if (win->iconData && win->iconW > 0 && win->iconH > 0) {
|
||||
// Draw loaded icon image, scaled to ICON_SIZE x ICON_SIZE
|
||||
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
|
||||
win->iconData, win->iconW, win->iconH, win->iconPitch, bpp);
|
||||
} else if (win->contentBuf && win->contentW > 0 && win->contentH > 0) {
|
||||
// Draw scaled copy of window contents
|
||||
if (win->contentBuf && win->contentW > 0 && win->contentH > 0) {
|
||||
drawScaledRect(d, contentX, contentY, ICON_SIZE, ICON_SIZE,
|
||||
win->contentBuf, win->contentW, win->contentH, win->contentPitch, bpp);
|
||||
} else {
|
||||
// No content -- draw grey fill
|
||||
rectFill(d, ops, contentX, contentY, ICON_SIZE, ICON_SIZE, colors->windowFace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ void wmDrawScrollbars(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colo
|
|||
|
||||
// Draw icons for all minimized windows along the bottom of the screen.
|
||||
// Each icon shows a scaled-down preview of the window's content (or a
|
||||
// default icon) with a beveled border, similar to DESQview/X's icon bar.
|
||||
// default icon) with a beveled border, similar to a classic desktop icon bar.
|
||||
void wmDrawMinimizedIcons(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, const WindowStackT *stack, const RectT *clipTo);
|
||||
|
||||
// Determine which window and which part of that window is under the given
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
#include "dvxTypes.h"
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
// ============================================================
|
||||
// Keyboard event
|
||||
// ============================================================
|
||||
|
|
@ -47,7 +49,7 @@ void platformInit(void);
|
|||
|
||||
// Cooperative yield -- give up the CPU timeslice when the event loop has
|
||||
// nothing to do. On DOS this calls __dpmi_yield() to be friendly to
|
||||
// multitaskers (Windows 3.x, OS/2, DESQview). On Linux this calls
|
||||
// multitaskers (Windows 3.x, OS/2). On Linux this calls
|
||||
// SDL_Delay(1) to avoid busy-spinning at 100% CPU.
|
||||
void platformYield(void);
|
||||
|
||||
|
|
@ -241,4 +243,41 @@ int32_t platformStripLineEndings(char *buf, int32_t len);
|
|||
// export table. On platforms without DXE (Linux/SDL), this is a no-op.
|
||||
void platformRegisterDxeExports(void);
|
||||
|
||||
// ============================================================
|
||||
// Crash recovery
|
||||
// ============================================================
|
||||
|
||||
// Log function signature for crash diagnostics.
|
||||
typedef void (*PlatformLogFnT)(const char *fmt, ...);
|
||||
|
||||
// Install signal handlers for fatal exceptions (SIGSEGV, SIGFPE, SIGILL).
|
||||
// On crash, logs platform-specific diagnostics (register dump on DJGPP)
|
||||
// via logFn, stores the signal number in *crashSignal, then longjmps to
|
||||
// recoveryBuf. The caller is responsible for app-level diagnostics in
|
||||
// the setjmp recovery block.
|
||||
void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn);
|
||||
|
||||
// Log platform-specific crash diagnostics (signal name and register dump
|
||||
// on DJGPP). Called internally by the crash handler before longjmp;
|
||||
// also available for manual invocation if needed.
|
||||
void platformLogCrashDetail(int sig, PlatformLogFnT logFn);
|
||||
|
||||
// ============================================================
|
||||
// DXE symbol overrides
|
||||
// ============================================================
|
||||
|
||||
// A name/function-pointer pair for registering symbol overrides with
|
||||
// the dynamic module loader. On DJGPP this maps directly to the
|
||||
// dxe_symbol_table struct used by dlregsym().
|
||||
typedef struct {
|
||||
const char *name;
|
||||
void *func;
|
||||
} PlatformSymOverrideT;
|
||||
|
||||
// Register function pointer overrides for subsequently loaded modules.
|
||||
// entries is an array of {name, funcPtr} pairs terminated by {NULL, NULL}.
|
||||
// On DJGPP this wraps dlregsym(). On platforms without DXE, this is a
|
||||
// no-op (RTLD_GLOBAL or equivalent handles symbol resolution).
|
||||
void platformRegisterSymOverrides(const PlatformSymOverrideT *entries);
|
||||
|
||||
#endif // DVX_PLATFORM_H
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
#include <dpmi.h>
|
||||
#include <go32.h>
|
||||
#include <pc.h>
|
||||
#include <sys/exceptn.h>
|
||||
#include <sys/nearptr.h>
|
||||
#include <sys/farptr.h>
|
||||
|
||||
|
|
@ -71,6 +72,7 @@ static int32_t findBestMode(int32_t requestedW, int32_t requestedH, int32_t pre
|
|||
static void getModeInfo(uint16_t mode, DisplayT *d, int32_t *score, int32_t requestedW, int32_t requestedH, int32_t preferredBpp);
|
||||
static bool hasCpuid(void);
|
||||
static int32_t mapLfb(DisplayT *d, uint32_t physAddr);
|
||||
static void platformCrashHandler(int sig);
|
||||
void platformVideoEnumModes(void (*cb)(int32_t w, int32_t h, int32_t bpp, void *userData), void *userData);
|
||||
static int32_t setVesaMode(uint16_t mode);
|
||||
static void sysInfoAppend(const char *fmt, ...);
|
||||
|
|
@ -1891,7 +1893,7 @@ void platformVideoShutdown(DisplayT *d) {
|
|||
// ============================================================
|
||||
//
|
||||
// Cooperative yield to the DPMI host. In a multitasking DOS
|
||||
// environment (Windows 3.x DOS box, OS/2 VDM, or DESQview),
|
||||
// environment (Windows 3.x DOS box or OS/2 VDM),
|
||||
// this gives other tasks a chance to run. Under a single-tasking
|
||||
// DPMI server (CWSDPMI) it's essentially a no-op, but it doesn't
|
||||
// hurt. Called once per event loop iteration when idle.
|
||||
|
|
@ -1926,6 +1928,103 @@ static int32_t setVesaMode(uint16_t mode) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Crash recovery state
|
||||
// ============================================================
|
||||
|
||||
static jmp_buf *sPlatformCrashJmp = NULL;
|
||||
static volatile int *sPlatformCrashSignal = NULL;
|
||||
static PlatformLogFnT sPlatformLogFn = NULL;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformCrashHandler -- signal handler for fatal exceptions
|
||||
// ============================================================
|
||||
//
|
||||
// DJGPP uses System V signal semantics where the handler is reset to
|
||||
// SIG_DFL after each delivery, so we must re-install before doing
|
||||
// anything else. After logging platform-specific diagnostics and
|
||||
// storing the signal number, we longjmp back to the caller's recovery
|
||||
// point (typically the shell's main loop).
|
||||
|
||||
static void platformCrashHandler(int sig) {
|
||||
if (sPlatformLogFn) {
|
||||
platformLogCrashDetail(sig, sPlatformLogFn);
|
||||
}
|
||||
|
||||
signal(sig, platformCrashHandler);
|
||||
|
||||
if (sPlatformCrashSignal) {
|
||||
*sPlatformCrashSignal = sig;
|
||||
}
|
||||
|
||||
if (sPlatformCrashJmp) {
|
||||
longjmp(*sPlatformCrashJmp, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformInstallCrashHandler
|
||||
// ============================================================
|
||||
|
||||
void platformInstallCrashHandler(jmp_buf *recoveryBuf, volatile int *crashSignal, PlatformLogFnT logFn) {
|
||||
sPlatformCrashJmp = recoveryBuf;
|
||||
sPlatformCrashSignal = crashSignal;
|
||||
sPlatformLogFn = logFn;
|
||||
|
||||
signal(SIGSEGV, platformCrashHandler);
|
||||
signal(SIGFPE, platformCrashHandler);
|
||||
signal(SIGILL, platformCrashHandler);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformLogCrashDetail -- log signal name and CPU register dump
|
||||
// ============================================================
|
||||
|
||||
void platformLogCrashDetail(int sig, PlatformLogFnT logFn) {
|
||||
const char *sigName = "UNKNOWN";
|
||||
|
||||
if (sig == SIGSEGV) {
|
||||
sigName = "SIGSEGV (page fault)";
|
||||
} else if (sig == SIGFPE) {
|
||||
sigName = "SIGFPE (floating point exception)";
|
||||
} else if (sig == SIGILL) {
|
||||
sigName = "SIGILL (illegal instruction)";
|
||||
}
|
||||
|
||||
logFn("=== CRASH ===");
|
||||
logFn("Signal: %d (%s)", sig, sigName);
|
||||
|
||||
// __djgpp_exception_state_ptr captures the full CPU register state
|
||||
// at the point of the exception. This gives us the faulting EIP,
|
||||
// stack pointer, and all GPRs -- invaluable for post-mortem debugging.
|
||||
jmp_buf *estate = __djgpp_exception_state_ptr;
|
||||
|
||||
if (estate) {
|
||||
struct __jmp_buf *regs = &(*estate)[0];
|
||||
logFn("EIP: 0x%08lx CS: 0x%04x", regs->__eip, regs->__cs);
|
||||
logFn("EAX: 0x%08lx EBX: 0x%08lx ECX: 0x%08lx EDX: 0x%08lx", regs->__eax, regs->__ebx, regs->__ecx, regs->__edx);
|
||||
logFn("ESI: 0x%08lx EDI: 0x%08lx EBP: 0x%08lx ESP: 0x%08lx", regs->__esi, regs->__edi, regs->__ebp, regs->__esp);
|
||||
logFn("DS: 0x%04x ES: 0x%04x FS: 0x%04x GS: 0x%04x SS: 0x%04x", regs->__ds, regs->__es, regs->__fs, regs->__gs, regs->__ss);
|
||||
logFn("EFLAGS: 0x%08lx", regs->__eflags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// platformRegisterSymOverrides
|
||||
// ============================================================
|
||||
//
|
||||
// PlatformSymOverrideT has the same layout as dxe_symbol_table
|
||||
// ({const char *name, void *ptr}), so we cast directly.
|
||||
|
||||
void platformRegisterSymOverrides(const PlatformSymOverrideT *entries) {
|
||||
dlregsym((const dxe_symbol_table *)entries);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE3 export table
|
||||
// ============================================================
|
||||
|
|
@ -1952,8 +2051,8 @@ extern unsigned long long __udivdi3(unsigned long long, unsigned long long);
|
|||
extern unsigned long long __udivmoddi4(unsigned long long, unsigned long long, unsigned long long *);
|
||||
extern unsigned long long __umoddi3(unsigned long long, unsigned long long);
|
||||
|
||||
// DJGPP runtime internals
|
||||
extern void *__djgpp_exception_state_ptr;
|
||||
// DJGPP runtime internals (__djgpp_exception_state_ptr comes from
|
||||
// <sys/exceptn.h> included above, with proper jmp_buf* typing)
|
||||
extern void __dj_assert(const char *, const char *, int);
|
||||
extern unsigned short __dj_ctype_flags[];
|
||||
extern unsigned char __dj_ctype_tolower[];
|
||||
|
|
@ -1961,6 +2060,22 @@ extern unsigned char __dj_ctype_tolower[];
|
|||
// GCC emulated thread-local storage
|
||||
extern void *__emutls_get_address(void *);
|
||||
|
||||
// stb_ds internals (implementation compiled into dvx.exe via loaderMain.c)
|
||||
extern void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap);
|
||||
extern void stbds_arrfreef(void *a);
|
||||
extern void stbds_hmfree_func(void *a, size_t elemsize);
|
||||
extern void *stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
|
||||
extern void *stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode);
|
||||
extern void *stbds_hmput_default(void *a, size_t elemsize);
|
||||
extern void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
|
||||
extern void *stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode);
|
||||
extern void *stbds_shmode_func(size_t elemsize, int mode);
|
||||
extern size_t stbds_hash_string(char *str, size_t seed);
|
||||
extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
|
||||
extern void stbds_rand_seed(size_t seed);
|
||||
extern char *stbds_stralloc(void *a, char *str);
|
||||
extern void stbds_strreset(void *a);
|
||||
|
||||
DXE_EXPORT_TABLE(sDxeExportTable)
|
||||
|
||||
// --- platform ---
|
||||
|
|
@ -1970,9 +2085,11 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(platformGetMemoryInfo)
|
||||
DXE_EXPORT(platformGetSystemInfo)
|
||||
DXE_EXPORT(platformInit)
|
||||
DXE_EXPORT(platformInstallCrashHandler)
|
||||
DXE_EXPORT(platformKeyboardGetModifiers)
|
||||
DXE_EXPORT(platformKeyboardRead)
|
||||
DXE_EXPORT(platformLineEnding)
|
||||
DXE_EXPORT(platformLogCrashDetail)
|
||||
DXE_EXPORT(platformMkdirRecursive)
|
||||
DXE_EXPORT(platformMouseInit)
|
||||
DXE_EXPORT(platformMousePoll)
|
||||
|
|
@ -1982,6 +2099,7 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(platformMouseWheelPoll)
|
||||
DXE_EXPORT(platformPathDirEnd)
|
||||
DXE_EXPORT(platformRegisterDxeExports)
|
||||
DXE_EXPORT(platformRegisterSymOverrides)
|
||||
DXE_EXPORT(platformSpanCopy8)
|
||||
DXE_EXPORT(platformSpanCopy16)
|
||||
DXE_EXPORT(platformSpanCopy32)
|
||||
|
|
@ -2187,6 +2305,22 @@ DXE_EXPORT_TABLE(sDxeExportTable)
|
|||
DXE_EXPORT(dlerror)
|
||||
DXE_EXPORT(dlregsym)
|
||||
|
||||
// --- stb_ds (implementation lives in dvx.exe) ---
|
||||
DXE_EXPORT(stbds_arrgrowf)
|
||||
DXE_EXPORT(stbds_arrfreef)
|
||||
DXE_EXPORT(stbds_hmfree_func)
|
||||
DXE_EXPORT(stbds_hmget_key)
|
||||
DXE_EXPORT(stbds_hmget_key_ts)
|
||||
DXE_EXPORT(stbds_hmput_default)
|
||||
DXE_EXPORT(stbds_hmput_key)
|
||||
DXE_EXPORT(stbds_hmdel_key)
|
||||
DXE_EXPORT(stbds_shmode_func)
|
||||
DXE_EXPORT(stbds_hash_string)
|
||||
DXE_EXPORT(stbds_hash_bytes)
|
||||
DXE_EXPORT(stbds_rand_seed)
|
||||
DXE_EXPORT(stbds_stralloc)
|
||||
DXE_EXPORT(stbds_strreset)
|
||||
|
||||
DXE_EXPORT_END
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,49 +1,86 @@
|
|||
// widgetClass.c -- Widget class table and dynamic registration
|
||||
#define DVX_WIDGET_IMPL
|
||||
// widgetClass.c -- Widget class table, API registry, and dynamic registration
|
||||
//
|
||||
// The widgetClassTable[] is the central dispatch table for the widget
|
||||
// system. All entries start NULL and are filled at runtime by widget
|
||||
// DXE registration functions (wgtBoxRegister, wgtButtonRegister, etc.).
|
||||
// The loader loads each widget DXE and calls its register function
|
||||
// before starting the shell.
|
||||
// widgetClassTable is a stb_ds dynamic array. Widget DXEs call
|
||||
// wgtRegisterClass() during wgtRegister() to append their class
|
||||
// definition and receive a runtime type ID. No widget types are
|
||||
// known at compile time.
|
||||
//
|
||||
// This file contains only the table and the dynamic registration API.
|
||||
// All class definitions live in their respective widget .c files.
|
||||
// The API registry uses an stb_ds string hashmap for O(1) lookup.
|
||||
// Each widget DXE calls wgtRegisterApi("name", &sApi) during
|
||||
// wgtRegister(). App/core code calls wgtGetApi("name") to get
|
||||
// the API pointer, then casts and calls through it.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "stb_ds.h"
|
||||
|
||||
// ============================================================
|
||||
// Class table -- indexed by WidgetTypeE
|
||||
// ============================================================
|
||||
//
|
||||
// Every entry is NULL until the corresponding widget DXE calls its
|
||||
// registration function (e.g. wgtBoxRegister sets VBox/HBox/Frame).
|
||||
// Sized WGT_MAX_TYPES to accommodate both built-in widget types
|
||||
// (0..WGT_TYPE_BUILTIN_COUNT-1) and dynamically registered types
|
||||
// (WGT_TYPE_DYNAMIC_BASE..WGT_MAX_TYPES-1).
|
||||
#include <string.h>
|
||||
|
||||
const WidgetClassT *widgetClassTable[WGT_MAX_TYPES];
|
||||
// stb_ds dynamic array of class pointers. Grows on each
|
||||
// wgtRegisterClass() call. Index = type ID.
|
||||
const WidgetClassT **widgetClassTable = NULL;
|
||||
|
||||
// stb_ds string hashmap: key = widget name, value = API pointer
|
||||
typedef struct {
|
||||
char *key; // stb_ds string key (heap-allocated by shput)
|
||||
const void *value;
|
||||
} ApiMapEntryT;
|
||||
|
||||
static ApiMapEntryT *sApiMap = NULL;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtRegisterClass -- register a dynamic widget type at runtime
|
||||
// wgtGetApi
|
||||
// ============================================================
|
||||
//
|
||||
// External DXE plugins call this to add new widget types without
|
||||
// modifying the core library. Returns the assigned type ID
|
||||
// (>= WGT_TYPE_DYNAMIC_BASE) so the caller can use it with
|
||||
// widgetAlloc(). Returns -1 if the table is full.
|
||||
// Look up a widget API by name. O(1) via stb_ds string hashmap.
|
||||
// Returns NULL if the widget is not loaded. Callers should still
|
||||
// cache the result in a static local to skip even the hash.
|
||||
|
||||
const void *wgtGetApi(const char *name) {
|
||||
if (!name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int32_t idx = shgeti(sApiMap, name);
|
||||
|
||||
if (idx < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return sApiMap[idx].value;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtRegisterApi
|
||||
// ============================================================
|
||||
//
|
||||
// Register a widget's public API struct under a name. Called by
|
||||
// each widget DXE during wgtRegister().
|
||||
|
||||
void wgtRegisterApi(const char *name, const void *api) {
|
||||
if (!name || !api) {
|
||||
return;
|
||||
}
|
||||
|
||||
shput(sApiMap, name, api);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtRegisterClass
|
||||
// ============================================================
|
||||
//
|
||||
// Appends a class to the table and returns the assigned type ID.
|
||||
// The ID is simply the array index.
|
||||
|
||||
int32_t wgtRegisterClass(const WidgetClassT *wclass) {
|
||||
if (!wclass) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int32_t i = WGT_TYPE_DYNAMIC_BASE; i < WGT_MAX_TYPES; i++) {
|
||||
if (!widgetClassTable[i]) {
|
||||
widgetClassTable[i] = wclass;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
int32_t id = arrlen(widgetClassTable);
|
||||
arrput(widgetClassTable, wclass);
|
||||
return id;
|
||||
}
|
||||
|
|
|
|||
1338
core/widgetCore.c
1338
core/widgetCore.c
File diff suppressed because it is too large
Load diff
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetEvent.c -- Window event handlers for widget system
|
||||
//
|
||||
// This file routes window-level events (mouse, keyboard, paint, resize,
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
// still adjusts the value, because sDragSlider captures the event
|
||||
// before hit testing runs.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
// Widget whose popup was just closed by click-outside -- prevents
|
||||
// immediate re-open on the same click. Without this, clicking the
|
||||
|
|
@ -206,10 +207,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
|
||||
// Close popups from other windows
|
||||
if (sOpenPopup && sOpenPopup->window != win) {
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
sOpenPopup->as.dropdown.open = false;
|
||||
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
||||
sOpenPopup->as.comboBox.open = false;
|
||||
if (sOpenPopup->wclass && sOpenPopup->wclass->closePopup) {
|
||||
sOpenPopup->wclass->closePopup(sOpenPopup);
|
||||
}
|
||||
|
||||
sOpenPopup = NULL;
|
||||
|
|
@ -225,13 +224,13 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
if (sDragTextSelect && (buttons & MOUSE_LEFT)) {
|
||||
widgetTextDragUpdate(sDragTextSelect, root, x, y);
|
||||
|
||||
if (sDragTextSelect->type == WidgetAnsiTermE) {
|
||||
// Fast path: repaint only dirty terminal rows into the
|
||||
// content buffer, then dirty just that screen stripe.
|
||||
if (sDragTextSelect->wclass && sDragTextSelect->wclass->quickRepaint) {
|
||||
// Fast path: repaint only dirty rows into the content
|
||||
// buffer, then dirty just that screen stripe.
|
||||
int32_t dirtyY = 0;
|
||||
int32_t dirtyH = 0;
|
||||
|
||||
if (wgtAnsiTermRepaint(sDragTextSelect, &dirtyY, &dirtyH) > 0) {
|
||||
if (sDragTextSelect->wclass->quickRepaint(sDragTextSelect, &dirtyY, &dirtyH) > 0) {
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
int32_t scrollY2 = win->vScroll ? win->vScroll->value : 0;
|
||||
int32_t rectX = win->x + win->contentX;
|
||||
|
|
@ -249,8 +248,6 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
|
||||
// Handle canvas drawing release
|
||||
if (sDrawingCanvas && !(buttons & MOUSE_LEFT)) {
|
||||
sDrawingCanvas->as.canvas.lastX = -1;
|
||||
sDrawingCanvas->as.canvas.lastY = -1;
|
||||
sDrawingCanvas = NULL;
|
||||
wgtInvalidatePaint(root);
|
||||
return;
|
||||
|
|
@ -274,39 +271,10 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
|
||||
// Handle slider drag (mouse move while pressed)
|
||||
if (sDragSlider && (buttons & MOUSE_LEFT)) {
|
||||
int32_t range = sDragSlider->as.slider.maxValue - sDragSlider->as.slider.minValue;
|
||||
|
||||
if (range > 0) {
|
||||
int32_t newVal;
|
||||
|
||||
if (sDragSlider->as.slider.vertical) {
|
||||
int32_t thumbRange = sDragSlider->h - SLIDER_THUMB_W;
|
||||
int32_t relY = y - sDragSlider->y - sDragOffset;
|
||||
newVal = sDragSlider->as.slider.minValue + (relY * range) / thumbRange;
|
||||
} else {
|
||||
int32_t thumbRange = sDragSlider->w - SLIDER_THUMB_W;
|
||||
int32_t relX = x - sDragSlider->x - sDragOffset;
|
||||
newVal = sDragSlider->as.slider.minValue + (relX * range) / thumbRange;
|
||||
}
|
||||
|
||||
if (newVal < sDragSlider->as.slider.minValue) {
|
||||
newVal = sDragSlider->as.slider.minValue;
|
||||
}
|
||||
|
||||
if (newVal > sDragSlider->as.slider.maxValue) {
|
||||
newVal = sDragSlider->as.slider.maxValue;
|
||||
}
|
||||
|
||||
if (newVal != sDragSlider->as.slider.value) {
|
||||
sDragSlider->as.slider.value = newVal;
|
||||
|
||||
if (sDragSlider->onChange) {
|
||||
sDragSlider->onChange(sDragSlider);
|
||||
}
|
||||
|
||||
if (sDragSlider->wclass && sDragSlider->wclass->scrollDragUpdate) {
|
||||
sDragSlider->wclass->scrollDragUpdate(sDragSlider, 0, sDragOffset, x, y);
|
||||
wgtInvalidatePaint(root);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -314,44 +282,13 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
// Handle ListView column resize release
|
||||
if (sResizeListView && !(buttons & MOUSE_LEFT)) {
|
||||
sResizeListView = NULL;
|
||||
sResizeCol = -1;
|
||||
sResizeDragging = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle ListView column resize drag. The drag is deferred: on the
|
||||
// initial click sResizeDragging is false and no resize happens until
|
||||
// the mouse actually moves from the click point. This allows double-
|
||||
// click auto-size to work (the first click sets sResizeListView but
|
||||
// doesn't start dragging; the release clears it; the second click
|
||||
// reaches widgetListViewOnMouse where multiClickDetect returns 2).
|
||||
// Handle ListView column resize drag via vtable
|
||||
if (sResizeListView && (buttons & MOUSE_LEFT)) {
|
||||
if (!sResizeDragging) {
|
||||
if (x == sResizeStartX) {
|
||||
return;
|
||||
}
|
||||
|
||||
sResizeDragging = true;
|
||||
}
|
||||
|
||||
int32_t delta = x - sResizeStartX;
|
||||
int32_t newW = sResizeOrigW + delta;
|
||||
|
||||
if (newW < LISTVIEW_MIN_COL_W) {
|
||||
newW = LISTVIEW_MIN_COL_W;
|
||||
}
|
||||
|
||||
if (newW != sResizeListView->as.listView->resolvedColW[sResizeCol]) {
|
||||
sResizeListView->as.listView->resolvedColW[sResizeCol] = newW;
|
||||
|
||||
// Recalculate totalColW
|
||||
int32_t total = 0;
|
||||
|
||||
for (int32_t c = 0; c < sResizeListView->as.listView->colCount; c++) {
|
||||
total += sResizeListView->as.listView->resolvedColW[c];
|
||||
}
|
||||
|
||||
sResizeListView->as.listView->totalColW = total;
|
||||
if (sResizeListView->wclass && sResizeListView->wclass->scrollDragUpdate) {
|
||||
sResizeListView->wclass->scrollDragUpdate(sResizeListView, -1, 0, x, y);
|
||||
wgtInvalidatePaint(root);
|
||||
}
|
||||
|
||||
|
|
@ -379,28 +316,10 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle splitter drag
|
||||
// Handle splitter drag via vtable
|
||||
if (sDragSplitter && (buttons & MOUSE_LEFT)) {
|
||||
int32_t pos;
|
||||
|
||||
if (sDragSplitter->as.splitter.vertical) {
|
||||
pos = x - sDragSplitter->x - sDragSplitStart;
|
||||
} else {
|
||||
pos = y - sDragSplitter->y - sDragSplitStart;
|
||||
}
|
||||
|
||||
if (sSplitterClampPosFn) {
|
||||
sSplitterClampPosFn(sDragSplitter, &pos);
|
||||
}
|
||||
|
||||
if (pos != sDragSplitter->as.splitter.dividerPos) {
|
||||
sDragSplitter->as.splitter.dividerPos = pos;
|
||||
|
||||
if (sDragSplitter->onChange) {
|
||||
sDragSplitter->onChange(sDragSplitter);
|
||||
}
|
||||
|
||||
wgtInvalidate(sDragSplitter);
|
||||
if (sDragSplitter->wclass && sDragSplitter->wclass->scrollDragUpdate) {
|
||||
sDragSplitter->wclass->scrollDragUpdate(sDragSplitter, 0, sDragSplitStart, x, y);
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -417,9 +336,9 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
if (sDragScrollbar && (buttons & MOUSE_LEFT)) {
|
||||
widgetScrollbarDragUpdate(sDragScrollbar, sDragScrollbarOrient, sDragScrollbarOff, x, y);
|
||||
|
||||
// ScrollPane needs full relayout because it positions children
|
||||
// Scroll containers need full relayout because they position children
|
||||
// at absolute coordinates incorporating the scroll offset.
|
||||
if (sDragScrollbar->type == WidgetScrollPaneE) {
|
||||
if (sDragScrollbar->wclass && (sDragScrollbar->wclass->flags & WCLASS_RELAYOUT_ON_SCROLL)) {
|
||||
wgtInvalidate(sDragScrollbar);
|
||||
} else {
|
||||
wgtInvalidatePaint(root);
|
||||
|
|
@ -430,10 +349,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
|
||||
// Handle button press release
|
||||
if (sPressedButton && !(buttons & MOUSE_LEFT)) {
|
||||
if (sPressedButton->type == WidgetImageButtonE) {
|
||||
sPressedButton->as.imageButton.pressed = false;
|
||||
} else {
|
||||
sPressedButton->as.button.pressed = false;
|
||||
if (sPressedButton->wclass && sPressedButton->wclass->setPressed) {
|
||||
sPressedButton->wclass->setPressed(sPressedButton, false);
|
||||
}
|
||||
|
||||
// Fire onClick if released over the same button in the same window
|
||||
|
|
@ -460,20 +377,11 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
y >= sPressedButton->y && y < sPressedButton->y + sPressedButton->h);
|
||||
}
|
||||
|
||||
bool curPressed = (sPressedButton->type == WidgetImageButtonE)
|
||||
? sPressedButton->as.imageButton.pressed
|
||||
: sPressedButton->as.button.pressed;
|
||||
|
||||
if (curPressed != over) {
|
||||
if (sPressedButton->type == WidgetImageButtonE) {
|
||||
sPressedButton->as.imageButton.pressed = over;
|
||||
} else {
|
||||
sPressedButton->as.button.pressed = over;
|
||||
if (sPressedButton->wclass && sPressedButton->wclass->setPressed) {
|
||||
sPressedButton->wclass->setPressed(sPressedButton, over);
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(sPressedButton);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -487,47 +395,17 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
int32_t popW;
|
||||
int32_t popH;
|
||||
|
||||
widgetDropdownPopupRect(sOpenPopup, font, win->contentH, &popX, &popY, &popW, &popH);
|
||||
if (!sOpenPopup->wclass || !sOpenPopup->wclass->getPopupRect) {
|
||||
sOpenPopup = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
sOpenPopup->wclass->getPopupRect(sOpenPopup, font, win->contentH, &popX, &popY, &popW, &popH);
|
||||
|
||||
if (x >= popX && x < popX + popW && y >= popY && y < popY + popH) {
|
||||
// Click on popup item
|
||||
int32_t itemIdx = (y - popY - 2) / font->charHeight;
|
||||
int32_t scrollP = 0;
|
||||
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
scrollP = sOpenPopup->as.dropdown.scrollPos;
|
||||
} else {
|
||||
scrollP = sOpenPopup->as.comboBox.listScrollPos;
|
||||
}
|
||||
|
||||
itemIdx += scrollP;
|
||||
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
if (itemIdx >= 0 && itemIdx < sOpenPopup->as.dropdown.itemCount) {
|
||||
sOpenPopup->as.dropdown.selectedIdx = itemIdx;
|
||||
sOpenPopup->as.dropdown.open = false;
|
||||
|
||||
if (sOpenPopup->onChange) {
|
||||
sOpenPopup->onChange(sOpenPopup);
|
||||
}
|
||||
}
|
||||
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
||||
if (itemIdx >= 0 && itemIdx < sOpenPopup->as.comboBox.itemCount) {
|
||||
sOpenPopup->as.comboBox.selectedIdx = itemIdx;
|
||||
sOpenPopup->as.comboBox.open = false;
|
||||
|
||||
// Copy selected item text to buffer
|
||||
const char *itemText = sOpenPopup->as.comboBox.items[itemIdx];
|
||||
strncpy(sOpenPopup->as.comboBox.buf, itemText, sOpenPopup->as.comboBox.bufSize - 1);
|
||||
sOpenPopup->as.comboBox.buf[sOpenPopup->as.comboBox.bufSize - 1] = '\0';
|
||||
sOpenPopup->as.comboBox.len = (int32_t)strlen(sOpenPopup->as.comboBox.buf);
|
||||
sOpenPopup->as.comboBox.cursorPos = sOpenPopup->as.comboBox.len;
|
||||
sOpenPopup->as.comboBox.scrollOff = 0;
|
||||
|
||||
if (sOpenPopup->onChange) {
|
||||
sOpenPopup->onChange(sOpenPopup);
|
||||
}
|
||||
}
|
||||
// Click on popup item -- dispatch to widget's onMouse
|
||||
if (sOpenPopup->wclass && sOpenPopup->wclass->onMouse) {
|
||||
sOpenPopup->wclass->onMouse(sOpenPopup, root, x, y);
|
||||
}
|
||||
|
||||
sOpenPopup = NULL;
|
||||
|
|
@ -538,10 +416,8 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
// Click outside popup -- close it and remember which widget it was
|
||||
sClosedPopup = sOpenPopup;
|
||||
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
sOpenPopup->as.dropdown.open = false;
|
||||
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
||||
sOpenPopup->as.comboBox.open = false;
|
||||
if (sOpenPopup->wclass && sOpenPopup->wclass->closePopup) {
|
||||
sOpenPopup->wclass->closePopup(sOpenPopup);
|
||||
}
|
||||
|
||||
sOpenPopup = NULL;
|
||||
|
|
@ -587,7 +463,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
// in the release handler above. They still get onDblClick here.
|
||||
if (hit->enabled) {
|
||||
int32_t clicks = multiClickDetect(vx, vy);
|
||||
bool isBtn = (hit->type == WidgetButtonE || hit->type == WidgetImageButtonE);
|
||||
bool isBtn = (hit->wclass && (hit->wclass->flags & WCLASS_PRESS_RELEASE));
|
||||
|
||||
if (clicks >= 2 && hit->onDblClick) {
|
||||
hit->onDblClick(hit);
|
||||
|
|
@ -755,223 +631,12 @@ void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value) {
|
|||
// widgetReorderDrop -- finalize drag-reorder on mouse release
|
||||
// ============================================================
|
||||
//
|
||||
// Completes a drag-reorder operation for ListBox, ListView, or TreeView.
|
||||
// Moves the dragged item from its original position to the drop position
|
||||
// by shifting intermediate elements. This is an in-place array rotation
|
||||
// for ListBox/ListView (O(n) element moves) and a linked-list splice
|
||||
// for TreeView.
|
||||
//
|
||||
// For ListBox and ListView, the item array, selection bits, and sort
|
||||
// indices are all shifted together to maintain consistency. The selected
|
||||
// index is updated to follow the moved item.
|
||||
//
|
||||
// For TreeView, the operation is a tree node re-parenting: unlink the
|
||||
// dragged item from its old parent's child list, then insert it before
|
||||
// or after the drop target in the target's parent's child list.
|
||||
// Dispatches to the widget's reorderDrop vtable method, which handles
|
||||
// the type-specific logic for completing the drag-reorder operation.
|
||||
|
||||
void widgetReorderDrop(WidgetT *w) {
|
||||
if (w->type == WidgetListBoxE) {
|
||||
int32_t drag = w->as.listBox.dragIdx;
|
||||
int32_t drop = w->as.listBox.dropIdx;
|
||||
w->as.listBox.dragIdx = -1;
|
||||
w->as.listBox.dropIdx = -1;
|
||||
|
||||
if (drag < 0 || drop < 0 || drag == drop || drag == drop - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move item at dragIdx to before dropIdx
|
||||
const char *temp = w->as.listBox.items[drag];
|
||||
uint8_t selBit = 0;
|
||||
|
||||
if (w->as.listBox.multiSelect && w->as.listBox.selBits) {
|
||||
selBit = w->as.listBox.selBits[drag];
|
||||
}
|
||||
|
||||
if (drag < drop) {
|
||||
for (int32_t i = drag; i < drop - 1; i++) {
|
||||
w->as.listBox.items[i] = w->as.listBox.items[i + 1];
|
||||
|
||||
if (w->as.listBox.selBits) {
|
||||
w->as.listBox.selBits[i] = w->as.listBox.selBits[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
w->as.listBox.items[drop - 1] = temp;
|
||||
|
||||
if (w->as.listBox.selBits) {
|
||||
w->as.listBox.selBits[drop - 1] = selBit;
|
||||
}
|
||||
|
||||
w->as.listBox.selectedIdx = drop - 1;
|
||||
} else {
|
||||
for (int32_t i = drag; i > drop; i--) {
|
||||
w->as.listBox.items[i] = w->as.listBox.items[i - 1];
|
||||
|
||||
if (w->as.listBox.selBits) {
|
||||
w->as.listBox.selBits[i] = w->as.listBox.selBits[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
w->as.listBox.items[drop] = temp;
|
||||
|
||||
if (w->as.listBox.selBits) {
|
||||
w->as.listBox.selBits[drop] = selBit;
|
||||
}
|
||||
|
||||
w->as.listBox.selectedIdx = drop;
|
||||
}
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
} else if (w->type == WidgetListViewE) {
|
||||
int32_t drag = w->as.listView->dragIdx;
|
||||
int32_t drop = w->as.listView->dropIdx;
|
||||
int32_t colCnt = w->as.listView->colCount;
|
||||
w->as.listView->dragIdx = -1;
|
||||
w->as.listView->dropIdx = -1;
|
||||
|
||||
if (drag < 0 || drop < 0 || drag == drop || drag == drop - 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Move row at dragIdx to before dropIdx
|
||||
const char *temp[LISTVIEW_MAX_COLS];
|
||||
|
||||
for (int32_t c = 0; c < colCnt; c++) {
|
||||
temp[c] = w->as.listView->cellData[drag * colCnt + c];
|
||||
}
|
||||
|
||||
uint8_t selBit = 0;
|
||||
|
||||
if (w->as.listView->multiSelect && w->as.listView->selBits) {
|
||||
selBit = w->as.listView->selBits[drag];
|
||||
}
|
||||
|
||||
int32_t sortVal = 0;
|
||||
|
||||
if (w->as.listView->sortIndex) {
|
||||
sortVal = w->as.listView->sortIndex[drag];
|
||||
}
|
||||
|
||||
if (drag < drop) {
|
||||
for (int32_t i = drag; i < drop - 1; i++) {
|
||||
for (int32_t c = 0; c < colCnt; c++) {
|
||||
w->as.listView->cellData[i * colCnt + c] = w->as.listView->cellData[(i + 1) * colCnt + c];
|
||||
}
|
||||
|
||||
if (w->as.listView->selBits) {
|
||||
w->as.listView->selBits[i] = w->as.listView->selBits[i + 1];
|
||||
}
|
||||
|
||||
if (w->as.listView->sortIndex) {
|
||||
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
int32_t dest = drop - 1;
|
||||
|
||||
for (int32_t c = 0; c < colCnt; c++) {
|
||||
w->as.listView->cellData[dest * colCnt + c] = temp[c];
|
||||
}
|
||||
|
||||
if (w->as.listView->selBits) {
|
||||
w->as.listView->selBits[dest] = selBit;
|
||||
}
|
||||
|
||||
if (w->as.listView->sortIndex) {
|
||||
w->as.listView->sortIndex[dest] = sortVal;
|
||||
}
|
||||
|
||||
w->as.listView->selectedIdx = dest;
|
||||
} else {
|
||||
for (int32_t i = drag; i > drop; i--) {
|
||||
for (int32_t c = 0; c < colCnt; c++) {
|
||||
w->as.listView->cellData[i * colCnt + c] = w->as.listView->cellData[(i - 1) * colCnt + c];
|
||||
}
|
||||
|
||||
if (w->as.listView->selBits) {
|
||||
w->as.listView->selBits[i] = w->as.listView->selBits[i - 1];
|
||||
}
|
||||
|
||||
if (w->as.listView->sortIndex) {
|
||||
w->as.listView->sortIndex[i] = w->as.listView->sortIndex[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
for (int32_t c = 0; c < colCnt; c++) {
|
||||
w->as.listView->cellData[drop * colCnt + c] = temp[c];
|
||||
}
|
||||
|
||||
if (w->as.listView->selBits) {
|
||||
w->as.listView->selBits[drop] = selBit;
|
||||
}
|
||||
|
||||
if (w->as.listView->sortIndex) {
|
||||
w->as.listView->sortIndex[drop] = sortVal;
|
||||
}
|
||||
|
||||
w->as.listView->selectedIdx = drop;
|
||||
}
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
} else if (w->type == WidgetTreeViewE) {
|
||||
WidgetT *drag = w->as.treeView.dragItem;
|
||||
WidgetT *target = w->as.treeView.dropTarget;
|
||||
bool after = w->as.treeView.dropAfter;
|
||||
w->as.treeView.dragItem = NULL;
|
||||
w->as.treeView.dropTarget = NULL;
|
||||
|
||||
if (!drag || !target || drag == target) {
|
||||
return;
|
||||
}
|
||||
|
||||
w->as.treeView.dimsValid = false;
|
||||
|
||||
// Unlink drag from its current parent
|
||||
WidgetT *oldParent = drag->parent;
|
||||
|
||||
if (oldParent->firstChild == drag) {
|
||||
oldParent->firstChild = drag->nextSibling;
|
||||
} else {
|
||||
for (WidgetT *c = oldParent->firstChild; c; c = c->nextSibling) {
|
||||
if (c->nextSibling == drag) {
|
||||
c->nextSibling = drag->nextSibling;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drag->nextSibling = NULL;
|
||||
|
||||
// Insert drag before or after target (same parent level)
|
||||
WidgetT *newParent = target->parent;
|
||||
drag->parent = newParent;
|
||||
|
||||
if (after) {
|
||||
drag->nextSibling = target->nextSibling;
|
||||
target->nextSibling = drag;
|
||||
} else {
|
||||
if (newParent->firstChild == target) {
|
||||
drag->nextSibling = target;
|
||||
newParent->firstChild = drag;
|
||||
} else {
|
||||
for (WidgetT *c = newParent->firstChild; c; c = c->nextSibling) {
|
||||
if (c->nextSibling == target) {
|
||||
c->nextSibling = drag;
|
||||
drag->nextSibling = target;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
if (w->wclass && w->wclass->reorderDrop) {
|
||||
w->wclass->reorderDrop(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -980,141 +645,11 @@ void widgetReorderDrop(WidgetT *w) {
|
|||
// widgetReorderUpdate -- update drop position during drag
|
||||
// ============================================================
|
||||
//
|
||||
// Tracks the mouse during a drag-reorder, updating the drop indicator
|
||||
// position and auto-scrolling when the mouse is near the widget's edges.
|
||||
//
|
||||
// The drop position is computed from the mouse Y relative to item
|
||||
// boundaries: if the mouse is in the top half of an item, the drop
|
||||
// indicator goes before that item; if in the bottom half, it goes
|
||||
// after. This gives intuitive "insert between items" behavior.
|
||||
//
|
||||
// Auto-scrolling happens when the mouse is within one row-height of
|
||||
// the top or bottom edge, allowing the user to drag items to positions
|
||||
// not currently visible.
|
||||
// Dispatches to the widget's reorderUpdate vtable method, which handles
|
||||
// type-specific drop indicator positioning and auto-scrolling.
|
||||
|
||||
void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y) {
|
||||
(void)x;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
if (w->type == WidgetListBoxE) {
|
||||
int32_t innerY = w->y + LISTBOX_BORDER;
|
||||
int32_t innerH = w->h - LISTBOX_BORDER * 2;
|
||||
int32_t visibleRows = innerH / font->charHeight;
|
||||
int32_t maxScroll = w->as.listBox.itemCount - visibleRows;
|
||||
|
||||
if (maxScroll < 0) {
|
||||
maxScroll = 0;
|
||||
}
|
||||
|
||||
// Auto-scroll when dragging near edges
|
||||
if (y < innerY + font->charHeight && w->as.listBox.scrollPos > 0) {
|
||||
w->as.listBox.scrollPos--;
|
||||
} else if (y > innerY + innerH - font->charHeight && w->as.listBox.scrollPos < maxScroll) {
|
||||
w->as.listBox.scrollPos++;
|
||||
}
|
||||
|
||||
int32_t relY = y - innerY;
|
||||
int32_t row = w->as.listBox.scrollPos + relY / font->charHeight;
|
||||
int32_t halfRow = (relY % font->charHeight) >= font->charHeight / 2 ? 1 : 0;
|
||||
int32_t drop = row + halfRow;
|
||||
|
||||
if (drop < 0) {
|
||||
drop = 0;
|
||||
}
|
||||
|
||||
if (drop > w->as.listBox.itemCount) {
|
||||
drop = w->as.listBox.itemCount;
|
||||
}
|
||||
|
||||
w->as.listBox.dropIdx = drop;
|
||||
} else if (w->type == WidgetListViewE) {
|
||||
int32_t headerH = font->charHeight + 4;
|
||||
int32_t innerY = w->y + LISTVIEW_BORDER + headerH;
|
||||
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;
|
||||
int32_t visibleRows = innerH / font->charHeight;
|
||||
int32_t maxScroll = w->as.listView->rowCount - visibleRows;
|
||||
|
||||
if (maxScroll < 0) {
|
||||
maxScroll = 0;
|
||||
}
|
||||
|
||||
// Auto-scroll when dragging near edges
|
||||
if (y < innerY + font->charHeight && w->as.listView->scrollPos > 0) {
|
||||
w->as.listView->scrollPos--;
|
||||
} else if (y > innerY + innerH - font->charHeight && w->as.listView->scrollPos < maxScroll) {
|
||||
w->as.listView->scrollPos++;
|
||||
}
|
||||
|
||||
int32_t relY = y - innerY;
|
||||
int32_t row = w->as.listView->scrollPos + relY / font->charHeight;
|
||||
int32_t halfRow = (relY % font->charHeight) >= font->charHeight / 2 ? 1 : 0;
|
||||
int32_t drop = row + halfRow;
|
||||
|
||||
if (drop < 0) {
|
||||
drop = 0;
|
||||
}
|
||||
|
||||
if (drop > w->as.listView->rowCount) {
|
||||
drop = w->as.listView->rowCount;
|
||||
}
|
||||
|
||||
w->as.listView->dropIdx = drop;
|
||||
} else if (w->type == WidgetTreeViewE) {
|
||||
int32_t innerY = w->y + TREE_BORDER;
|
||||
int32_t innerH = w->h - TREE_BORDER * 2;
|
||||
|
||||
// Auto-scroll when dragging near edges (pixel-based scroll)
|
||||
if (y < innerY + font->charHeight && w->as.treeView.scrollPos > 0) {
|
||||
w->as.treeView.scrollPos -= font->charHeight;
|
||||
|
||||
if (w->as.treeView.scrollPos < 0) {
|
||||
w->as.treeView.scrollPos = 0;
|
||||
}
|
||||
} else if (y > innerY + innerH - font->charHeight) {
|
||||
w->as.treeView.scrollPos += font->charHeight;
|
||||
// Paint will clamp to actual max
|
||||
}
|
||||
|
||||
// Find which visible item the mouse is over
|
||||
int32_t curY = w->y + TREE_BORDER - w->as.treeView.scrollPos;
|
||||
|
||||
// Walk visible items to find drop target
|
||||
WidgetT *first = NULL;
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type == WidgetTreeItemE && c->visible) {
|
||||
first = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
WidgetT *target = NULL;
|
||||
bool after = false;
|
||||
|
||||
for (WidgetT *v = first; v; v = sTreeViewNextVisibleFn ? sTreeViewNextVisibleFn(v, w) : NULL) {
|
||||
int32_t itemBot = curY + font->charHeight;
|
||||
int32_t mid = curY + font->charHeight / 2;
|
||||
|
||||
if (y < mid) {
|
||||
target = v;
|
||||
after = false;
|
||||
break;
|
||||
}
|
||||
|
||||
curY = itemBot;
|
||||
|
||||
// Check if mouse is between this item and next
|
||||
WidgetT *next = sTreeViewNextVisibleFn ? sTreeViewNextVisibleFn(v, w) : NULL;
|
||||
|
||||
if (!next || y < itemBot) {
|
||||
target = v;
|
||||
after = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
w->as.treeView.dropTarget = target;
|
||||
w->as.treeView.dropAfter = after;
|
||||
if (w->wclass && w->wclass->reorderUpdate) {
|
||||
w->wclass->reorderUpdate(w, root, x, y);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,494 +0,0 @@
|
|||
// widgetInternal.h -- Shared internal header for widget implementation files
|
||||
//
|
||||
// This header is included only by widget implementation .c files (in the
|
||||
// widgets/ directory), never by application code. It exposes the vtable
|
||||
// system, shared module-level state, and internal helper functions that
|
||||
// widget implementations need but the public API should not expose.
|
||||
//
|
||||
// The widget system is split across multiple .c files by concern:
|
||||
// widgetCore.c -- allocation, tree ops, focus management, shared helpers
|
||||
// widgetLayout.c -- measure + layout algorithms for box containers
|
||||
// widgetEvent.c -- mouse/keyboard dispatch, scrollbar management
|
||||
// widgetOps.c -- paint dispatch, overlay rendering
|
||||
// widget*.c -- per-type paint, event, and layout implementations
|
||||
#ifndef WIDGET_INTERNAL_H
|
||||
#define WIDGET_INTERNAL_H
|
||||
|
||||
#include "dvxWidget.h"
|
||||
#include "dvxApp.h"
|
||||
#include "dvxDraw.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxVideo.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// ============================================================
|
||||
// Widget class table
|
||||
// ============================================================
|
||||
//
|
||||
// WidgetClassT and WCLASS_* flags are now in dvxWidget.h (public API)
|
||||
// so that DXE plugins can register new widget types.
|
||||
//
|
||||
// Global vtable array -- sized WGT_MAX_TYPES. Built-in entries
|
||||
// (0..WGT_TYPE_BUILTIN_COUNT-1) are initialized at startup. Dynamic
|
||||
// entries (WGT_TYPE_DYNAMIC_BASE..WGT_MAX_TYPES-1) are filled by
|
||||
// wgtRegisterClass(). Defined in widgetClass.c.
|
||||
extern const WidgetClassT *widgetClassTable[WGT_MAX_TYPES];
|
||||
|
||||
// ============================================================
|
||||
// Validation macros
|
||||
// ============================================================
|
||||
//
|
||||
// Defensive type-checking at the top of public widget API functions.
|
||||
// Since WidgetT is a tagged union, calling a ListBox function with a
|
||||
// Button widget would access the wrong union member and corrupt state.
|
||||
// These macros provide a consistent early-return guard pattern.
|
||||
|
||||
#define VALIDATE_WIDGET(w, wtype, retval) \
|
||||
do { if (!(w) || (w)->type != (wtype)) { return (retval); } } while (0)
|
||||
|
||||
#define VALIDATE_WIDGET_VOID(w, wtype) \
|
||||
do { if (!(w) || (w)->type != (wtype)) { return; } } while (0)
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
||||
// Modifier flags (BIOS INT 16h shift state bits). Duplicated from
|
||||
// dvxTypes.h ACCEL_xxx constants because widget code references them
|
||||
// frequently and the KEY_MOD_ prefix reads better in event handlers.
|
||||
#define KEY_MOD_SHIFT 0x03
|
||||
#define KEY_MOD_CTRL 0x04
|
||||
#define KEY_MOD_ALT 0x08
|
||||
|
||||
// Layout and geometry constants. These define the visual metrics of
|
||||
// each widget type and are tuned for readability at 640x480 with 8px
|
||||
// wide fonts. Changing these affects every instance of the widget type.
|
||||
#define DEFAULT_SPACING 4
|
||||
#define DEFAULT_PADDING 4
|
||||
#define SEPARATOR_THICKNESS 2
|
||||
#define BUTTON_PAD_H 8
|
||||
#define BUTTON_PAD_V 4
|
||||
#define BUTTON_FOCUS_INSET 3 // focus rect inset from button edge
|
||||
#define BUTTON_PRESS_OFFSET 1 // text/focus shift when button is pressed
|
||||
#define CHECKBOX_BOX_SIZE 12
|
||||
#define CHECKBOX_GAP 4
|
||||
#define FRAME_BEVEL_BORDER 2
|
||||
#define FRAME_FLAT_BORDER 1
|
||||
#define TEXT_INPUT_PAD 3
|
||||
#define DROPDOWN_BTN_WIDTH 16
|
||||
#define DROPDOWN_MAX_VISIBLE 8
|
||||
#define SLIDER_TRACK_H 4
|
||||
#define SLIDER_THUMB_W 11
|
||||
#define TAB_PAD_H 8
|
||||
#define TAB_PAD_V 4
|
||||
#define TAB_BORDER 2
|
||||
#define LISTBOX_BORDER 2
|
||||
#define LISTVIEW_BORDER 2
|
||||
#define LISTVIEW_MIN_COL_W 20 // minimum column width during resize
|
||||
#define TREE_INDENT 16
|
||||
#define TREE_EXPAND_SIZE 9
|
||||
#define TREE_ICON_GAP 4
|
||||
#define TREE_BORDER 2
|
||||
// WGT_SB_W is the widget-internal scrollbar width (slightly narrower than
|
||||
// the window-level SCROLLBAR_WIDTH) to fit within widget content areas.
|
||||
#define WGT_SB_W 14
|
||||
#define TREE_MIN_ROWS 4
|
||||
#define SB_MIN_THUMB 14
|
||||
#define SPLITTER_BAR_W 5
|
||||
#define SPLITTER_MIN_PANE 20
|
||||
#define TOOLBAR_PAD 2 // padding inside toolbar/statusbar
|
||||
#define TOOLBAR_GAP 2 // gap between toolbar/statusbar children
|
||||
|
||||
// ============================================================
|
||||
// Inline helpers
|
||||
// ============================================================
|
||||
|
||||
static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
|
||||
if (val < lo) { return lo; }
|
||||
if (val > hi) { return hi; }
|
||||
return val;
|
||||
}
|
||||
|
||||
// Classic Windows 3.1 embossed (etched) text for disabled widgets.
|
||||
// The illusion works by drawing the text twice: first offset +1,+1 in
|
||||
// the highlight color (creating a light "shadow" behind the text), then
|
||||
// at the original position in the shadow color. The result is text that
|
||||
// appears chiseled into the surface -- the universal visual indicator for
|
||||
// "greyed out" in Motif and Windows 3.x era GUIs.
|
||||
static inline void drawTextEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
|
||||
drawText(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
|
||||
drawText(d, ops, font, x, y, text, colors->windowShadow, 0, false);
|
||||
}
|
||||
|
||||
static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, const ColorSchemeT *colors) {
|
||||
drawTextAccel(d, ops, font, x + 1, y + 1, text, colors->windowHighlight, 0, false);
|
||||
drawTextAccel(d, ops, font, x, y, text, colors->windowShadow, 0, false);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Shared state (defined in widgetCore.c)
|
||||
// ============================================================
|
||||
//
|
||||
// Module-level globals for tracking active interactive operations.
|
||||
// Only one of these can be active at a time (e.g. you can't drag a
|
||||
// slider and resize a listview column simultaneously). Using globals
|
||||
// rather than per-widget state avoids bloating every widget with fields
|
||||
// that are only relevant during a single drag operation. The tradeoff
|
||||
// is that these are truly global -- but since the GUI is single-threaded
|
||||
// and only one mouse can exist, this is safe.
|
||||
|
||||
extern bool sCursorBlinkOn; // text cursor blink phase (toggled by wgtUpdateCursorBlink)
|
||||
extern clock_t sDblClickTicks; // double-click threshold (set from AppContextT.dblClickTicks)
|
||||
extern bool sDebugLayout;
|
||||
extern WidgetT *sClosedPopup; // popup that was just closed (prevents immediate reopen)
|
||||
extern WidgetT *sFocusedWidget; // currently focused widget across all windows
|
||||
extern WidgetT *sKeyPressedBtn; // button being held via keyboard (Space/Enter)
|
||||
extern WidgetT *sOpenPopup; // dropdown/combobox with open popup list
|
||||
extern WidgetT *sPressedButton; // button/imagebutton being held via mouse
|
||||
extern WidgetT *sDragSlider; // slider being dragged
|
||||
extern WidgetT *sDrawingCanvas; // canvas receiving drag events
|
||||
extern WidgetT *sDragTextSelect; // text widget with active mouse selection drag
|
||||
extern int32_t sDragOffset; // mouse offset within thumb/handle at drag start
|
||||
extern WidgetT *sResizeListView; // listview whose column is being resized
|
||||
extern int32_t sResizeCol; // which column is being resized
|
||||
extern int32_t sResizeStartX; // mouse X at start of column resize
|
||||
extern int32_t sResizeOrigW; // original column width at start of resize
|
||||
extern bool sResizeDragging; // true once mouse has moved from click point
|
||||
extern WidgetT *sDragSplitter; // splitter being dragged
|
||||
extern int32_t sDragSplitStart; // mouse position at start of splitter drag
|
||||
extern WidgetT *sDragReorder; // listbox/treeview item being drag-reordered
|
||||
extern WidgetT *sDragScrollbar; // widget whose scrollbar thumb is being dragged
|
||||
// Hook for cursor shape change over ListView column borders.
|
||||
// Set by wgtListViewRegister(); NULL if ListView DXE not loaded.
|
||||
extern bool (*sListViewColBorderHitFn)(const WidgetT *w, int32_t vx, int32_t vy);
|
||||
// Hook for splitter divider clamping during drag.
|
||||
// Set by wgtSplitterRegister(); NULL if Splitter DXE not loaded.
|
||||
extern void (*sSplitterClampPosFn)(WidgetT *w, int32_t *pos);
|
||||
// Hook for TreeView visible item iteration during drag-reorder.
|
||||
// Set by wgtTreeViewRegister(); NULL if TreeView DXE not loaded.
|
||||
extern WidgetT *(*sTreeViewNextVisibleFn)(WidgetT *item, WidgetT *treeView);
|
||||
extern int32_t sDragScrollbarOff; // mouse offset within thumb at drag start
|
||||
extern int32_t sDragScrollbarOrient; // 0=vertical, 1=horizontal
|
||||
|
||||
// ============================================================
|
||||
// Core functions (widgetCore.c)
|
||||
// ============================================================
|
||||
|
||||
// Tree manipulation
|
||||
void widgetAddChild(WidgetT *parent, WidgetT *child);
|
||||
void widgetRemoveChild(WidgetT *parent, WidgetT *child);
|
||||
void widgetDestroyChildren(WidgetT *w);
|
||||
|
||||
// Allocate a new widget, set its type and vtable, and add it as a child.
|
||||
WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type);
|
||||
|
||||
// Focus management -- Tab/Shift-Tab navigation walks the tree in creation
|
||||
// order, skipping non-focusable and hidden widgets.
|
||||
void widgetClearFocus(WidgetT *root);
|
||||
WidgetT *widgetFindNextFocusable(WidgetT *root, WidgetT *after);
|
||||
WidgetT *widgetFindPrevFocusable(WidgetT *root, WidgetT *before);
|
||||
|
||||
// Alt+key accelerator lookup -- searches the tree for a widget whose
|
||||
// accelKey matches, used for keyboard navigation of labeled controls.
|
||||
WidgetT *widgetFindByAccel(WidgetT *root, char key);
|
||||
|
||||
// Utility queries
|
||||
int32_t widgetCountVisibleChildren(const WidgetT *w);
|
||||
int32_t widgetFrameBorderWidth(const WidgetT *w);
|
||||
bool widgetIsFocusable(WidgetTypeE type);
|
||||
bool widgetIsBoxContainer(WidgetTypeE type);
|
||||
bool widgetIsHorizContainer(WidgetTypeE type);
|
||||
|
||||
// Hit testing -- find the deepest widget containing the point (x,y).
|
||||
// Respects WCLASS_NO_HIT_RECURSE to stop at list/tree widgets.
|
||||
WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y);
|
||||
|
||||
// Translate arrow/PgUp/PgDn/Home/End keys into list index changes.
|
||||
// Shared by ListBox, ListView, Dropdown, and TreeView.
|
||||
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
|
||||
|
||||
// Compute popup position for a dropdown/combobox, clamped to screen bounds.
|
||||
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
|
||||
|
||||
// Draw the small downward-pointing triangle glyph used by dropdown buttons.
|
||||
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color);
|
||||
|
||||
// Compute the maximum strlen across an array of string items.
|
||||
int32_t widgetMaxItemLen(const char **items, int32_t count);
|
||||
|
||||
// Paint a generic popup item list (used by dropdown, combobox, and popup menus).
|
||||
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
|
||||
|
||||
// Compute scrollbar thumb position and size for a given track length,
|
||||
// content size, visible size, and scroll position. Used by both widget-
|
||||
// internal scrollbars and the window-level scrollbar drawing code.
|
||||
void widgetScrollbarThumb(int32_t trackLen, int32_t totalSize, int32_t visibleSize, int32_t scrollPos, int32_t *thumbPos, int32_t *thumbSize);
|
||||
|
||||
// ============================================================
|
||||
// Shared scrollbar functions (widgetScrollbar.c)
|
||||
// ============================================================
|
||||
//
|
||||
// Widget-internal scrollbar rendering and hit testing, shared across
|
||||
// ListBox, ListView, TreeView, TextArea, ScrollPane, and AnsiTerm.
|
||||
// These operate on abstract track coordinates (position + length) so
|
||||
// the same code handles both horizontal and vertical scrollbars.
|
||||
|
||||
typedef enum {
|
||||
ScrollHitNoneE,
|
||||
ScrollHitArrowDecE,
|
||||
ScrollHitArrowIncE,
|
||||
ScrollHitPageDecE,
|
||||
ScrollHitPageIncE,
|
||||
ScrollHitThumbE
|
||||
} ScrollHitE;
|
||||
|
||||
void widgetDrawScrollbarH(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
|
||||
ScrollHitE widgetScrollbarHitTest(int32_t sbLen, int32_t relPos, int32_t totalSize, int32_t visibleSize, int32_t scrollPos);
|
||||
|
||||
// ============================================================
|
||||
// Layout functions (widgetLayout.c)
|
||||
// ============================================================
|
||||
//
|
||||
// The layout algorithm is a two-pass flexbox-like system:
|
||||
//
|
||||
// calcMinSize (bottom-up): each leaf widget reports its minimum size
|
||||
// (e.g. button = text width + padding). Containers sum their children's
|
||||
// minimums plus spacing along the main axis, and take the maximum of
|
||||
// children's minimums on the cross axis.
|
||||
//
|
||||
// layout (top-down): starting from the available space (window content
|
||||
// area), each container distributes space to children. Children that fit
|
||||
// at their minimum get their minimum. Remaining space is distributed
|
||||
// proportionally by weight to flexible children. This happens recursively
|
||||
// down the tree.
|
||||
|
||||
void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetLayoutBox(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font);
|
||||
|
||||
// ============================================================
|
||||
// Event functions (widgetEvent.c)
|
||||
// ============================================================
|
||||
//
|
||||
// These are the WindowT callback implementations installed by wgtInitWindow().
|
||||
// They bridge the window manager's callback interface to the widget tree's
|
||||
// event dispatch. Each one locates the appropriate widget (via hit-test for
|
||||
// mouse events, or the focused widget for keyboard events) and calls the
|
||||
// widget's vtable handler.
|
||||
|
||||
// Sync window-level scrollbar ranges to match the widget tree's overflow.
|
||||
void widgetManageScrollbars(WindowT *win, AppContextT *ctx);
|
||||
|
||||
void widgetOnKey(WindowT *win, int32_t key, int32_t mod);
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons);
|
||||
void widgetOnPaint(WindowT *win, RectT *dirtyArea);
|
||||
void widgetOnResize(WindowT *win, int32_t newW, int32_t newH);
|
||||
void widgetOnScroll(WindowT *win, ScrollbarOrientE orient, int32_t value);
|
||||
|
||||
// ============================================================
|
||||
// Paint/ops functions (widgetOps.c)
|
||||
// ============================================================
|
||||
|
||||
void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget paint functions
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetImagePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetListViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
bool widgetListViewColBorderHit(const WidgetT *w, int32_t vx, int32_t vy);
|
||||
void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetSeparatorPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetStatusBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetTabControlPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetToolbarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget calcMinSize functions
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetListViewCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetSplitterCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetSpinnerCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetSpacerCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTabControlCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTextAreaCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTextInputCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget layout functions (for special containers)
|
||||
// ============================================================
|
||||
|
||||
void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetSplitterLayout(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTabControlLayout(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget getText/setText functions
|
||||
// ============================================================
|
||||
|
||||
const char *widgetButtonGetText(const WidgetT *w);
|
||||
void widgetButtonSetText(WidgetT *w, const char *text);
|
||||
const char *widgetCheckboxGetText(const WidgetT *w);
|
||||
void widgetCheckboxSetText(WidgetT *w, const char *text);
|
||||
const char *widgetComboBoxGetText(const WidgetT *w);
|
||||
void widgetComboBoxSetText(WidgetT *w, const char *text);
|
||||
const char *widgetDropdownGetText(const WidgetT *w);
|
||||
const char *widgetLabelGetText(const WidgetT *w);
|
||||
void widgetLabelSetText(WidgetT *w, const char *text);
|
||||
const char *widgetRadioGetText(const WidgetT *w);
|
||||
void widgetRadioSetText(WidgetT *w, const char *text);
|
||||
const char *widgetTextAreaGetText(const WidgetT *w);
|
||||
void widgetTextAreaSetText(WidgetT *w, const char *text);
|
||||
const char *widgetTextInputGetText(const WidgetT *w);
|
||||
void widgetTextInputSetText(WidgetT *w, const char *text);
|
||||
const char *widgetTreeItemGetText(const WidgetT *w);
|
||||
void widgetTreeItemSetText(WidgetT *w, const char *text);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget destroy functions
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermDestroy(WidgetT *w);
|
||||
void widgetCanvasDestroy(WidgetT *w);
|
||||
void widgetComboBoxDestroy(WidgetT *w);
|
||||
void widgetImageButtonDestroy(WidgetT *w);
|
||||
void widgetImageDestroy(WidgetT *w);
|
||||
void widgetListBoxDestroy(WidgetT *w);
|
||||
void widgetListViewDestroy(WidgetT *w);
|
||||
void widgetTextAreaDestroy(WidgetT *w);
|
||||
void widgetTextInputDestroy(WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget mouse/key functions
|
||||
// ============================================================
|
||||
//
|
||||
// Shared text editing helpers used by TextInput, TextArea, ComboBox, and
|
||||
// Spinner. The widgetTextEditOnKey function implements the common subset
|
||||
// of single-line text editing behavior (cursor movement, selection,
|
||||
// copy/paste, undo) shared across all text-editing widgets, so changes
|
||||
// to editing behavior only need to happen in one place.
|
||||
|
||||
// Clear text selections in all widgets except the given one (ensures only
|
||||
// one text selection is active at a time across the entire GUI).
|
||||
void clearOtherSelections(WidgetT *except);
|
||||
|
||||
void clipboardCopy(const char *text, int32_t len);
|
||||
const char *clipboardGet(int32_t *outLen);
|
||||
bool isWordChar(char c);
|
||||
|
||||
// Detect double/triple clicks based on timing and position proximity.
|
||||
// Returns 1 for single, 2 for double, 3 for triple click.
|
||||
int32_t multiClickDetect(int32_t vx, int32_t vy);
|
||||
void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetAnsiTermOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetCanvasOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetListBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetListViewOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetListViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetScrollPaneOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetSplitterClampPos(WidgetT *w, int32_t *pos);
|
||||
void widgetSplitterOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetSliderOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetSpinnerOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
const char *widgetSpinnerGetText(const WidgetT *w);
|
||||
void widgetSpinnerSetText(WidgetT *w, const char *text);
|
||||
void widgetTabControlOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetTabControlOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
// Shared single-line text editing logic. Takes pointers to all the edit
|
||||
// state fields so the same code works for TextInput, ComboBox, and Spinner
|
||||
// (which store these fields in different union members). This avoids
|
||||
// duplicating the full editing logic (cursor movement, word selection,
|
||||
// clipboard, undo) three times.
|
||||
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
|
||||
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
|
||||
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
||||
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
|
||||
void widgetTextInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
||||
int32_t wordStart(const char *buf, int32_t pos);
|
||||
void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetTreeViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
|
||||
// Timer internals
|
||||
void widgetTimerCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTimerDestroy(WidgetT *w);
|
||||
|
||||
// Drag-reorder helpers -- generic drag-and-drop reordering shared by
|
||||
// ListBox, ListView, and TreeView. The update function tracks the mouse
|
||||
// position and computes the drop target, the drop function commits the
|
||||
// reorder by relinking the item in the data structure.
|
||||
void widgetReorderUpdate(WidgetT *w, WidgetT *root, int32_t x, int32_t y);
|
||||
void widgetReorderDrop(WidgetT *w);
|
||||
|
||||
// Iterate to the next visible item in a tree view (skipping collapsed
|
||||
// subtrees). Used for keyboard navigation and rendering.
|
||||
WidgetT *widgetTreeViewNextVisible(WidgetT *item, WidgetT *treeView);
|
||||
|
||||
#endif // WIDGET_INTERNAL_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetLayout.c -- Layout engine (measure + arrange)
|
||||
//
|
||||
// Implements a two-pass layout algorithm inspired by CSS flexbox:
|
||||
|
|
@ -26,7 +27,7 @@
|
|||
// unit types into a single int32_t without needing a separate struct.
|
||||
// The wgtResolveSize() function decodes the tag and converts to pixels.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -58,21 +59,12 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
|
|||
gap = DEFAULT_SPACING;
|
||||
}
|
||||
|
||||
// Frame reserves extra vertical space at the top so the frame title
|
||||
// text can sit centered on the top border line (like Windows group boxes).
|
||||
// Without this, the first child would overlap the title.
|
||||
// Widgets with getLayoutMetrics override default padding/gap/extraTop
|
||||
int32_t frameExtraTop = 0;
|
||||
|
||||
if (w->type == WidgetFrameE) {
|
||||
frameExtraTop = font->charHeight / 2;
|
||||
pad = DEFAULT_PADDING;
|
||||
}
|
||||
|
||||
// Toolbar and StatusBar use minimal padding to pack controls tightly,
|
||||
// matching the compact feel of classic Windows/Motif toolbars.
|
||||
if (w->type == WidgetToolbarE || w->type == WidgetStatusBarE) {
|
||||
pad = TOOLBAR_PAD;
|
||||
gap = TOOLBAR_GAP;
|
||||
if (w->wclass && w->wclass->getLayoutMetrics) {
|
||||
int32_t metricBorderW = 0;
|
||||
w->wclass->getLayoutMetrics(w, font, &pad, &gap, &frameExtraTop, &metricBorderW);
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
|
|
@ -110,8 +102,8 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
|
|||
w->calcMinH = mainSize + frameExtraTop;
|
||||
}
|
||||
|
||||
// Frame border
|
||||
if (w->type == WidgetFrameE) {
|
||||
// Border (Frame and similar containers with getLayoutMetrics)
|
||||
if (w->wclass && w->wclass->getLayoutMetrics) {
|
||||
int32_t fb = widgetFrameBorderWidth(w);
|
||||
w->calcMinW += fb * 2;
|
||||
w->calcMinH += fb * 2;
|
||||
|
|
@ -205,20 +197,12 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
|||
gap = DEFAULT_SPACING;
|
||||
}
|
||||
|
||||
// Frame adjustments
|
||||
// Widgets with getLayoutMetrics override default padding/gap/extraTop/border
|
||||
int32_t frameExtraTop = 0;
|
||||
int32_t fb = 0;
|
||||
|
||||
if (w->type == WidgetFrameE) {
|
||||
frameExtraTop = font->charHeight / 2;
|
||||
fb = widgetFrameBorderWidth(w);
|
||||
pad = DEFAULT_PADDING;
|
||||
}
|
||||
|
||||
// Toolbar and StatusBar use tighter padding
|
||||
if (w->type == WidgetToolbarE || w->type == WidgetStatusBarE) {
|
||||
pad = TOOLBAR_PAD;
|
||||
gap = TOOLBAR_GAP;
|
||||
if (w->wclass && w->wclass->getLayoutMetrics) {
|
||||
w->wclass->getLayoutMetrics(w, font, &pad, &gap, &frameExtraTop, &fb);
|
||||
}
|
||||
|
||||
int32_t innerX = w->x + pad + fb;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetOps.c -- Paint dispatcher and public widget operations
|
||||
//
|
||||
// This file contains two categories of functions:
|
||||
|
|
@ -10,7 +11,8 @@
|
|||
// because they share the same invalidation infrastructure (wgtInvalidate
|
||||
// and wgtInvalidatePaint).
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../widgets/widgetBox.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -153,11 +155,11 @@ void wgtDestroy(WidgetT *w) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Invalidate tree dimension cache if destroying a tree item
|
||||
if (w->type == WidgetTreeItemE && w->parent) {
|
||||
// Notify parent chain of child destruction via onChildChanged vtable
|
||||
if (w->parent) {
|
||||
for (WidgetT *p = w->parent; p; p = p->parent) {
|
||||
if (p->type == WidgetTreeViewE) {
|
||||
p->as.treeView.dimsValid = false;
|
||||
if (p->wclass && p->wclass->onChildChanged) {
|
||||
p->wclass->onChildChanged(p, w);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -200,43 +202,16 @@ void wgtDestroy(WidgetT *w) {
|
|||
|
||||
|
||||
// ============================================================
|
||||
// widgetHashName -- djb2 hash for widget name lookup
|
||||
// wgtFind
|
||||
// ============================================================
|
||||
//
|
||||
// The djb2 hash function (Dan Bernstein) is used for fast name-based
|
||||
// widget lookup. The hash is stored in each widget's nameHash field
|
||||
// so that wgtFind() can reject non-matches without calling strcmp()
|
||||
// on every node. In a tree of 100 widgets, this typically reduces
|
||||
// the search from 100 strcmp calls to 1-2.
|
||||
|
||||
static uint32_t widgetHashName(const char *s) {
|
||||
uint32_t h = 5381;
|
||||
|
||||
while (*s) {
|
||||
h = ((h << 5) + h) + (uint8_t)*s;
|
||||
s++;
|
||||
static WidgetT *wgtFindImpl(WidgetT *w, const char *name) {
|
||||
if (w->name[0] && strcmp(w->name, name) == 0) {
|
||||
return w;
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtFindByHash -- recursive search with hash fast-reject
|
||||
// ============================================================
|
||||
//
|
||||
// Depth-first search by name with hash pre-screening. The hash
|
||||
// comparison (integer ==) is much cheaper than strcmp and eliminates
|
||||
// most non-matches. A full strcmp is only done when hashes match,
|
||||
// which should be the target widget or a rare collision.
|
||||
|
||||
static WidgetT *wgtFindByHash(WidgetT *root, const char *name, uint32_t hash) {
|
||||
if (root->nameHash == hash && root->name[0] && strcmp(root->name, name) == 0) {
|
||||
return root;
|
||||
}
|
||||
|
||||
for (WidgetT *c = root->firstChild; c; c = c->nextSibling) {
|
||||
WidgetT *found = wgtFindByHash(c, name, hash);
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
WidgetT *found = wgtFindImpl(c, name);
|
||||
|
||||
if (found) {
|
||||
return found;
|
||||
|
|
@ -246,19 +221,12 @@ static WidgetT *wgtFindByHash(WidgetT *root, const char *name, uint32_t hash) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtFind
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtFind(WidgetT *root, const char *name) {
|
||||
if (!root || !name) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint32_t hash = widgetHashName(name);
|
||||
|
||||
return wgtFindByHash(root, name, hash);
|
||||
return wgtFindImpl(root, name);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -273,7 +241,6 @@ void wgtSetName(WidgetT *w, const char *name) {
|
|||
|
||||
strncpy(w->name, name, MAX_WIDGET_NAME - 1);
|
||||
w->name[MAX_WIDGET_NAME - 1] = '\0';
|
||||
w->nameHash = widgetHashName(w->name);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -339,7 +306,7 @@ const char *wgtGetText(const WidgetT *w) {
|
|||
// widget function -- any widget can retrieve it via wgtGetContext().
|
||||
|
||||
WidgetT *wgtInitWindow(AppContextT *ctx, WindowT *win) {
|
||||
WidgetT *root = widgetAlloc(NULL, WidgetVBoxE);
|
||||
WidgetT *root = dvxBoxApi() ? dvxBoxApi()->vBox(NULL) : NULL;
|
||||
|
||||
if (!root) {
|
||||
return NULL;
|
||||
|
|
@ -566,11 +533,11 @@ void wgtSetVisible(WidgetT *w, bool visible) {
|
|||
if (w) {
|
||||
w->visible = visible;
|
||||
|
||||
// Invalidate tree dimension cache if this is a tree item
|
||||
if (w->type == WidgetTreeItemE && w->parent) {
|
||||
// Notify parent chain of child visibility change via onChildChanged vtable
|
||||
if (w->parent) {
|
||||
for (WidgetT *p = w->parent; p; p = p->parent) {
|
||||
if (p->type == WidgetTreeViewE) {
|
||||
p->as.treeView.dimsValid = false;
|
||||
if (p->wclass && p->wclass->onChildChanged) {
|
||||
p->wclass->onChildChanged(p, w);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetScrollbar.c -- Shared scrollbar painting and hit-testing
|
||||
//
|
||||
// These are not widgets themselves -- they are stateless rendering and
|
||||
|
|
@ -20,17 +21,7 @@
|
|||
// If the container is too small, the scrollbar is simply not drawn
|
||||
// rather than rendering a corrupted mess.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
|
||||
// Constants duplicated from widgetTextInput.c and widgetScrollPane.c
|
||||
// for use in widgetScrollbarDragUpdate. These are file-local in their
|
||||
// source files, so we repeat the values here to avoid cross-file
|
||||
// coupling. They must stay in sync with the originals.
|
||||
#define TEXTAREA_BORDER 2
|
||||
#define TEXTAREA_PAD 2
|
||||
#define TEXTAREA_SB_W 14
|
||||
#define SP_BORDER 2
|
||||
#define SP_SB_W 14
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -150,395 +141,14 @@ void widgetDrawScrollbarV(DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *
|
|||
// ============================================================
|
||||
|
||||
// Handles ongoing scrollbar thumb drag for widget-internal scrollbars.
|
||||
// Converts the mouse pixel position into a scroll value using linear
|
||||
// interpolation, matching the WM-level wmScrollbarDrag logic.
|
||||
// orient: 0=vertical, 1=horizontal.
|
||||
// dragOff: mouse offset within thumb captured at drag start.
|
||||
//
|
||||
// Each widget type stores scroll state differently (row counts vs
|
||||
// pixel offsets, different struct fields), so this function switches
|
||||
// on widget type to extract the scrollbar geometry and update the
|
||||
// correct scroll field.
|
||||
// Dispatches to the widget's scrollDragUpdate vtable method, which
|
||||
// handles type-specific geometry extraction and scroll position update.
|
||||
void widgetScrollbarDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) {
|
||||
// Determine scrollbar geometry per widget type.
|
||||
// sbOrigin: screen coordinate of the scrollbar's top/left edge
|
||||
// sbLen: total length of the scrollbar (including arrow buttons)
|
||||
// totalSize: total content size (items, pixels, or columns)
|
||||
// visibleSize: visible portion of content
|
||||
// scrollPos: current scroll position (pointer to update)
|
||||
// maxScroll: maximum scroll value
|
||||
int32_t sbOrigin = 0;
|
||||
int32_t sbLen = 0;
|
||||
int32_t totalSize = 0;
|
||||
int32_t visibleSize = 0;
|
||||
int32_t maxScroll = 0;
|
||||
int32_t sbWidth = WGT_SB_W;
|
||||
|
||||
if (w->type == WidgetTextAreaE) {
|
||||
sbWidth = TEXTAREA_SB_W;
|
||||
|
||||
if (orient == 0) {
|
||||
// Vertical scrollbar
|
||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
||||
int32_t visCols = innerW / font->charWidth;
|
||||
int32_t maxLL = 0;
|
||||
|
||||
// Compute max line length by scanning lines
|
||||
const char *buf = w->as.textArea.buf;
|
||||
int32_t len = w->as.textArea.len;
|
||||
int32_t lineStart = 0;
|
||||
|
||||
for (int32_t i = 0; i <= len; i++) {
|
||||
if (i == len || buf[i] == '\n') {
|
||||
int32_t ll = i - lineStart;
|
||||
|
||||
if (ll > maxLL) {
|
||||
maxLL = ll;
|
||||
}
|
||||
|
||||
lineStart = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
bool needHSb = (maxLL > visCols);
|
||||
int32_t innerH = w->h - TEXTAREA_BORDER * 2 - (needHSb ? TEXTAREA_SB_W : 0);
|
||||
int32_t visRows = innerH / font->charHeight;
|
||||
|
||||
if (visRows < 1) {
|
||||
visRows = 1;
|
||||
}
|
||||
|
||||
// Count total lines
|
||||
int32_t totalLines = 1;
|
||||
|
||||
for (int32_t i = 0; i < len; i++) {
|
||||
if (buf[i] == '\n') {
|
||||
totalLines++;
|
||||
}
|
||||
}
|
||||
|
||||
sbOrigin = w->y + TEXTAREA_BORDER;
|
||||
sbLen = innerH;
|
||||
totalSize = totalLines;
|
||||
visibleSize = visRows;
|
||||
maxScroll = totalLines - visRows;
|
||||
} else {
|
||||
// Horizontal scrollbar
|
||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
int32_t innerW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_PAD * 2 - TEXTAREA_SB_W;
|
||||
int32_t visCols = innerW / font->charWidth;
|
||||
int32_t maxLL = 0;
|
||||
|
||||
const char *buf = w->as.textArea.buf;
|
||||
int32_t len = w->as.textArea.len;
|
||||
int32_t lineStart = 0;
|
||||
|
||||
for (int32_t i = 0; i <= len; i++) {
|
||||
if (i == len || buf[i] == '\n') {
|
||||
int32_t ll = i - lineStart;
|
||||
|
||||
if (ll > maxLL) {
|
||||
maxLL = ll;
|
||||
}
|
||||
|
||||
lineStart = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t hsbW = w->w - TEXTAREA_BORDER * 2 - TEXTAREA_SB_W;
|
||||
|
||||
sbOrigin = w->x + TEXTAREA_BORDER;
|
||||
sbLen = hsbW;
|
||||
totalSize = maxLL;
|
||||
visibleSize = visCols;
|
||||
maxScroll = maxLL - visCols;
|
||||
}
|
||||
} else if (w->type == WidgetListBoxE) {
|
||||
// Vertical only
|
||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
int32_t innerH = w->h - LISTBOX_BORDER * 2;
|
||||
int32_t visibleRows = innerH / font->charHeight;
|
||||
|
||||
sbOrigin = w->y + LISTBOX_BORDER;
|
||||
sbLen = innerH;
|
||||
totalSize = w->as.listBox.itemCount;
|
||||
visibleSize = visibleRows;
|
||||
maxScroll = totalSize - visibleSize;
|
||||
} else if (w->type == WidgetListViewE) {
|
||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
int32_t headerH = font->charHeight + 4;
|
||||
int32_t innerH = w->h - LISTVIEW_BORDER * 2 - headerH;
|
||||
int32_t innerW = w->w - LISTVIEW_BORDER * 2;
|
||||
int32_t visibleRows = innerH / font->charHeight;
|
||||
int32_t totalColW = w->as.listView->totalColW;
|
||||
bool needVSb = (w->as.listView->rowCount > visibleRows);
|
||||
|
||||
if (needVSb) {
|
||||
innerW -= WGT_SB_W;
|
||||
}
|
||||
|
||||
if (totalColW > innerW) {
|
||||
innerH -= WGT_SB_W;
|
||||
visibleRows = innerH / font->charHeight;
|
||||
|
||||
if (!needVSb && w->as.listView->rowCount > visibleRows) {
|
||||
innerW -= WGT_SB_W;
|
||||
}
|
||||
}
|
||||
|
||||
if (visibleRows < 1) {
|
||||
visibleRows = 1;
|
||||
}
|
||||
|
||||
if (orient == 0) {
|
||||
// Vertical
|
||||
sbOrigin = w->y + LISTVIEW_BORDER + headerH;
|
||||
sbLen = innerH;
|
||||
totalSize = w->as.listView->rowCount;
|
||||
visibleSize = visibleRows;
|
||||
maxScroll = totalSize - visibleSize;
|
||||
} else {
|
||||
// Horizontal
|
||||
sbOrigin = w->x + LISTVIEW_BORDER;
|
||||
sbLen = innerW;
|
||||
totalSize = totalColW;
|
||||
visibleSize = innerW;
|
||||
maxScroll = totalColW - innerW;
|
||||
}
|
||||
} else if (w->type == WidgetTreeViewE) {
|
||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
int32_t totalH;
|
||||
int32_t totalW;
|
||||
int32_t innerH;
|
||||
int32_t innerW;
|
||||
bool needVSb;
|
||||
|
||||
// Walk the visible tree to compute total content dimensions.
|
||||
// This duplicates treeCalcScrollbarNeeds which is file-static,
|
||||
// so we compute it inline using the same logic.
|
||||
int32_t treeH = 0;
|
||||
int32_t treeW = 0;
|
||||
|
||||
// Count visible items and max width
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type == WidgetTreeItemE && c->visible) {
|
||||
// Walk visible items
|
||||
WidgetT *item = c;
|
||||
|
||||
while (item) {
|
||||
treeH += font->charHeight;
|
||||
|
||||
// Compute depth
|
||||
int32_t depth = 0;
|
||||
WidgetT *p = item->parent;
|
||||
|
||||
while (p && p != w) {
|
||||
depth++;
|
||||
p = p->parent;
|
||||
}
|
||||
|
||||
int32_t itemW = depth * TREE_INDENT + TREE_EXPAND_SIZE + TREE_ICON_GAP;
|
||||
|
||||
if (item->as.treeItem.text) {
|
||||
itemW += (int32_t)strlen(item->as.treeItem.text) * font->charWidth;
|
||||
}
|
||||
|
||||
if (itemW > treeW) {
|
||||
treeW = itemW;
|
||||
}
|
||||
|
||||
item = widgetTreeViewNextVisible(item, w);
|
||||
}
|
||||
|
||||
break; // Only process first top-level visible chain
|
||||
}
|
||||
}
|
||||
|
||||
totalH = treeH;
|
||||
totalW = treeW;
|
||||
innerH = w->h - TREE_BORDER * 2;
|
||||
innerW = w->w - TREE_BORDER * 2;
|
||||
needVSb = (totalH > innerH);
|
||||
|
||||
if (needVSb) {
|
||||
innerW -= WGT_SB_W;
|
||||
}
|
||||
|
||||
if (totalW > innerW) {
|
||||
innerH -= WGT_SB_W;
|
||||
|
||||
if (!needVSb && totalH > innerH) {
|
||||
needVSb = true;
|
||||
innerW -= WGT_SB_W;
|
||||
}
|
||||
}
|
||||
|
||||
if (orient == 0) {
|
||||
// Vertical (pixel-based scroll)
|
||||
sbOrigin = w->y + TREE_BORDER;
|
||||
sbLen = innerH;
|
||||
totalSize = totalH;
|
||||
visibleSize = innerH;
|
||||
maxScroll = totalH - innerH;
|
||||
} else {
|
||||
// Horizontal (pixel-based scroll)
|
||||
sbOrigin = w->x + TREE_BORDER;
|
||||
sbLen = innerW;
|
||||
totalSize = totalW;
|
||||
visibleSize = innerW;
|
||||
maxScroll = totalW - innerW;
|
||||
}
|
||||
} else if (w->type == WidgetScrollPaneE) {
|
||||
sbWidth = SP_SB_W;
|
||||
|
||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
// Compute content min size (must match spCalcNeeds in widgetScrollPane.c)
|
||||
int32_t contentMinW = 0;
|
||||
int32_t contentMinH = 0;
|
||||
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
|
||||
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
|
||||
int32_t count = 0;
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->visible) {
|
||||
if (c->wclass && c->wclass->calcMinSize) {
|
||||
c->wclass->calcMinSize(c, font);
|
||||
}
|
||||
|
||||
if (c->calcMinW > contentMinW) {
|
||||
contentMinW = c->calcMinW;
|
||||
}
|
||||
|
||||
contentMinH += c->calcMinH;
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
contentMinH += gap * (count - 1);
|
||||
}
|
||||
|
||||
contentMinW += pad * 2;
|
||||
contentMinH += pad * 2;
|
||||
|
||||
int32_t innerH = w->h - SP_BORDER * 2;
|
||||
int32_t innerW = w->w - SP_BORDER * 2;
|
||||
bool needVSb = (contentMinH > innerH);
|
||||
|
||||
if (needVSb) {
|
||||
innerW -= SP_SB_W;
|
||||
}
|
||||
|
||||
if (contentMinW > innerW) {
|
||||
innerH -= SP_SB_W;
|
||||
|
||||
if (!needVSb && contentMinH > innerH) {
|
||||
needVSb = true;
|
||||
innerW -= SP_SB_W;
|
||||
}
|
||||
}
|
||||
|
||||
if (orient == 0) {
|
||||
// Vertical
|
||||
sbOrigin = w->y + SP_BORDER;
|
||||
sbLen = innerH;
|
||||
totalSize = contentMinH;
|
||||
visibleSize = innerH;
|
||||
maxScroll = contentMinH - innerH;
|
||||
} else {
|
||||
// Horizontal
|
||||
sbOrigin = w->x + SP_BORDER;
|
||||
sbLen = innerW;
|
||||
totalSize = contentMinW;
|
||||
visibleSize = innerW;
|
||||
maxScroll = contentMinW - innerW;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (maxScroll < 0) {
|
||||
maxScroll = 0;
|
||||
}
|
||||
|
||||
if (maxScroll == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute thumb geometry
|
||||
int32_t trackLen = sbLen - sbWidth * 2;
|
||||
|
||||
if (trackLen <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, totalSize, visibleSize, 0, &thumbPos, &thumbSize);
|
||||
|
||||
if (trackLen <= thumbSize) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert mouse position to scroll value
|
||||
int32_t mousePos;
|
||||
|
||||
if (orient == 0) {
|
||||
mousePos = mouseY - sbOrigin - sbWidth - dragOff;
|
||||
} else {
|
||||
mousePos = mouseX - sbOrigin - sbWidth - dragOff;
|
||||
}
|
||||
|
||||
int32_t newScroll = (mousePos * maxScroll) / (trackLen - thumbSize);
|
||||
|
||||
if (newScroll < 0) {
|
||||
newScroll = 0;
|
||||
}
|
||||
|
||||
if (newScroll > maxScroll) {
|
||||
newScroll = maxScroll;
|
||||
}
|
||||
|
||||
// Update the widget's scroll position
|
||||
if (w->type == WidgetTextAreaE) {
|
||||
if (orient == 0) {
|
||||
w->as.textArea.scrollRow = newScroll;
|
||||
} else {
|
||||
w->as.textArea.scrollCol = newScroll;
|
||||
}
|
||||
} else if (w->type == WidgetListBoxE) {
|
||||
w->as.listBox.scrollPos = newScroll;
|
||||
} else if (w->type == WidgetListViewE) {
|
||||
if (orient == 0) {
|
||||
w->as.listView->scrollPos = newScroll;
|
||||
} else {
|
||||
w->as.listView->scrollPosH = newScroll;
|
||||
}
|
||||
} else if (w->type == WidgetTreeViewE) {
|
||||
if (orient == 0) {
|
||||
w->as.treeView.scrollPos = newScroll;
|
||||
} else {
|
||||
w->as.treeView.scrollPosH = newScroll;
|
||||
}
|
||||
} else if (w->type == WidgetScrollPaneE) {
|
||||
if (orient == 0) {
|
||||
w->as.scrollPane.scrollPosV = newScroll;
|
||||
} else {
|
||||
w->as.scrollPane.scrollPosH = newScroll;
|
||||
}
|
||||
// Dispatch to the widget's scrollDragUpdate vtable method, which
|
||||
// handles type-specific geometry extraction, thumb computation,
|
||||
// and scroll position update.
|
||||
if (w->wclass && w->wclass->scrollDragUpdate) {
|
||||
w->wclass->scrollDragUpdate(w, orient, dragOff, mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
42
listhelp/Makefile
Normal file
42
listhelp/Makefile
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# List Help Library Makefile for DJGPP cross-compilation
|
||||
#
|
||||
# Builds listhelp.lib -- shared list/dropdown helper infrastructure
|
||||
# (dropdown arrow, item length, keyboard navigation, popup list painting).
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -I../core -I../core/platform -I../tasks -I../core/thirdparty
|
||||
|
||||
OBJDIR = ../obj/listhelp
|
||||
LIBSDIR = ../bin/libs
|
||||
|
||||
SRCS = listHelp.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
TARGET = $(LIBSDIR)/listhelp.lib
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET) $(LIBSDIR)/listhelp.dep
|
||||
|
||||
$(LIBSDIR)/listhelp.dep: ../config/listhelp.dep | $(LIBSDIR)
|
||||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBSDIR)
|
||||
$(DXE3GEN) -o $(LIBSDIR)/listhelp.dxe -E _widgetDraw -E _widgetDropdown -E _widgetMax -E _widgetNavigate -E _widgetPaint -U $(OBJS)
|
||||
mv $(LIBSDIR)/listhelp.dxe $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(LIBSDIR):
|
||||
mkdir -p $(LIBSDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/listHelp.o: listHelp.c listHelp.h ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET) $(LIBSDIR)/listhelp.dep
|
||||
187
listhelp/listHelp.c
Normal file
187
listhelp/listHelp.c
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// listHelp.c -- Shared list/dropdown helper functions
|
||||
//
|
||||
// Implements dropdown arrow drawing, item length scanning, keyboard
|
||||
// navigation, and popup list painting used by ListBox, Dropdown,
|
||||
// ComboBox, ListView, and TreeView widgets.
|
||||
|
||||
#include "listHelp.h"
|
||||
#include "../texthelp/textHelp.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDrawDropdownArrow
|
||||
// ============================================================
|
||||
//
|
||||
// Draws a small downward-pointing filled triangle (7, 5, 3, 1 pixels
|
||||
// wide across 4 rows) centered at the given position. Used by both
|
||||
// Dropdown and ComboBox for the drop button arrow glyph.
|
||||
|
||||
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color) {
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
drawHLine(d, ops, centerX - 3 + i, centerY + i, 7 - i * 2, color);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetMaxItemLen
|
||||
// ============================================================
|
||||
//
|
||||
// Scans an array of string items and returns the maximum strlen.
|
||||
// Shared by ListBox, Dropdown, and ComboBox to cache the widest
|
||||
// item length for calcMinSize without duplicating the loop.
|
||||
|
||||
int32_t widgetMaxItemLen(const char **items, int32_t count) {
|
||||
int32_t maxLen = 0;
|
||||
|
||||
for (int32_t i = 0; i < count; i++) {
|
||||
int32_t slen = (int32_t)strlen(items[i]);
|
||||
|
||||
if (slen > maxLen) {
|
||||
maxLen = slen;
|
||||
}
|
||||
}
|
||||
|
||||
return maxLen;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetNavigateIndex
|
||||
// ============================================================
|
||||
//
|
||||
// Shared keyboard navigation for list-like widgets (ListBox, Dropdown,
|
||||
// ListView, etc.). Encapsulates the Up/Down/Home/End/PgUp/PgDn logic
|
||||
// so each widget doesn't have to reimplement index clamping.
|
||||
//
|
||||
// Key values use the 0x100 flag to mark extended scan codes (arrow
|
||||
// keys, Home, End, etc.) -- this is the DVX convention for passing
|
||||
// scan codes through the same int32_t channel as ASCII values.
|
||||
//
|
||||
// Returns -1 for unrecognized keys so callers can check whether the
|
||||
// key was consumed.
|
||||
|
||||
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize) {
|
||||
if (key == (0x50 | 0x100)) {
|
||||
// Down arrow
|
||||
if (current < count - 1) {
|
||||
return current + 1;
|
||||
}
|
||||
|
||||
return current < 0 ? 0 : current;
|
||||
}
|
||||
|
||||
if (key == (0x48 | 0x100)) {
|
||||
// Up arrow
|
||||
if (current > 0) {
|
||||
return current - 1;
|
||||
}
|
||||
|
||||
return current < 0 ? 0 : current;
|
||||
}
|
||||
|
||||
if (key == (0x47 | 0x100)) {
|
||||
// Home
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (key == (0x4F | 0x100)) {
|
||||
// End
|
||||
return count - 1;
|
||||
}
|
||||
|
||||
if (key == (0x51 | 0x100)) {
|
||||
// Page Down
|
||||
int32_t n = current + pageSize;
|
||||
return n >= count ? count - 1 : n;
|
||||
}
|
||||
|
||||
if (key == (0x49 | 0x100)) {
|
||||
// Page Up
|
||||
int32_t n = current - pageSize;
|
||||
return n < 0 ? 0 : n;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetPaintPopupList
|
||||
// ============================================================
|
||||
//
|
||||
// Shared popup list painting for Dropdown and ComboBox.
|
||||
|
||||
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos) {
|
||||
// Draw popup border
|
||||
BevelStyleT bevel;
|
||||
bevel.highlight = colors->windowHighlight;
|
||||
bevel.shadow = colors->windowShadow;
|
||||
bevel.face = colors->contentBg;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, popX, popY, popW, popH, &bevel);
|
||||
|
||||
// Draw items
|
||||
int32_t visibleItems = popH / font->charHeight;
|
||||
int32_t textX = popX + TEXT_INPUT_PAD;
|
||||
int32_t textY = popY + 2;
|
||||
int32_t textW = popW - TEXT_INPUT_PAD * 2 - 4;
|
||||
|
||||
for (int32_t i = 0; i < visibleItems && (scrollPos + i) < itemCount; i++) {
|
||||
int32_t idx = scrollPos + i;
|
||||
int32_t iy = textY + i * font->charHeight;
|
||||
uint32_t ifg = colors->contentFg;
|
||||
uint32_t ibg = colors->contentBg;
|
||||
|
||||
if (idx == hoverIdx) {
|
||||
ifg = colors->menuHighlightFg;
|
||||
ibg = colors->menuHighlightBg;
|
||||
rectFill(d, ops, popX + 2, iy, textW + TEXT_INPUT_PAD * 2, font->charHeight, ibg);
|
||||
}
|
||||
|
||||
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownPopupRect
|
||||
// ============================================================
|
||||
//
|
||||
// Calculates the screen rectangle for a dropdown/combobox popup list.
|
||||
// Shared between Dropdown and ComboBox since they have identical
|
||||
// popup positioning logic.
|
||||
|
||||
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
|
||||
int32_t itemCount = 0;
|
||||
|
||||
if (w->wclass && w->wclass->getPopupItemCount) {
|
||||
itemCount = w->wclass->getPopupItemCount(w);
|
||||
}
|
||||
|
||||
int32_t visibleItems = itemCount;
|
||||
|
||||
if (visibleItems > DROPDOWN_MAX_VISIBLE) {
|
||||
visibleItems = DROPDOWN_MAX_VISIBLE;
|
||||
}
|
||||
|
||||
if (visibleItems < 1) {
|
||||
visibleItems = 1;
|
||||
}
|
||||
|
||||
*popX = w->x;
|
||||
*popW = w->w;
|
||||
*popH = visibleItems * font->charHeight + 4;
|
||||
|
||||
if (w->y + w->h + *popH <= contentH) {
|
||||
*popY = w->y + w->h;
|
||||
} else {
|
||||
*popY = w->y - *popH;
|
||||
|
||||
if (*popY < 0) {
|
||||
*popY = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
45
listhelp/listHelp.h
Normal file
45
listhelp/listHelp.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
// listHelp.h -- Public API for the shared list/dropdown helper library
|
||||
//
|
||||
// Declares dropdown arrow drawing, item length scanning, keyboard
|
||||
// navigation, and popup list painting shared across widget DXEs
|
||||
// (ListBox, Dropdown, ComboBox, ListView, TreeView).
|
||||
|
||||
#ifndef LIST_HELP_H
|
||||
#define LIST_HELP_H
|
||||
|
||||
#include "../core/dvxWidgetPlugin.h"
|
||||
|
||||
#define DROPDOWN_BTN_WIDTH 16
|
||||
#define DROPDOWN_MAX_VISIBLE 8
|
||||
|
||||
// ============================================================
|
||||
// Dropdown arrow glyph
|
||||
// ============================================================
|
||||
|
||||
void widgetDrawDropdownArrow(DisplayT *d, const BlitOpsT *ops, int32_t centerX, int32_t centerY, uint32_t color);
|
||||
|
||||
// ============================================================
|
||||
// Item measurement
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetMaxItemLen(const char **items, int32_t count);
|
||||
|
||||
// ============================================================
|
||||
// Keyboard navigation (Up/Down/Home/End/PgUp/PgDn)
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetNavigateIndex(int32_t key, int32_t current, int32_t count, int32_t pageSize);
|
||||
|
||||
// ============================================================
|
||||
// Popup rect calculation
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownPopupRect(WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH);
|
||||
|
||||
// ============================================================
|
||||
// Popup list painting
|
||||
// ============================================================
|
||||
|
||||
void widgetPaintPopupList(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t popX, int32_t popY, int32_t popW, int32_t popH, const char **items, int32_t itemCount, int32_t hoverIdx, int32_t scrollPos);
|
||||
|
||||
#endif // LIST_HELP_H
|
||||
|
|
@ -9,7 +9,7 @@ DJGPP_LIBPATH = $(HOME)/claude/windriver/tools/lib
|
|||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
EXE2COFF = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/exe2coff
|
||||
CWSDSTUB = $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/CWSDSTUB.EXE
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../core/thirdparty
|
||||
LDFLAGS = -lm
|
||||
|
||||
OBJDIR = ../obj/loader
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <ctype.h>
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
|
|
@ -29,6 +30,26 @@
|
|||
|
||||
#define LIBS_DIR "LIBS"
|
||||
#define WIDGET_DIR "WIDGETS"
|
||||
#define LOG_PATH "dvx.log"
|
||||
|
||||
// ============================================================
|
||||
// loaderLog -- write to dvx.log (before shell is loaded)
|
||||
// ============================================================
|
||||
|
||||
static void loaderLog(const char *fmt, ...) {
|
||||
FILE *f = fopen(LOG_PATH, "a");
|
||||
|
||||
if (!f) {
|
||||
return;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(f, fmt, ap);
|
||||
va_end(ap);
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Module entry for dependency resolution
|
||||
|
|
@ -49,8 +70,10 @@ typedef struct {
|
|||
static bool allDepsLoaded(const ModuleT *mod, const ModuleT *mods);
|
||||
static void extractBaseName(const char *path, const char *ext, char *out, int32_t outSize);
|
||||
static void *findSymbol(void **handles, const char *symbol);
|
||||
static void freeMods(ModuleT *mods);
|
||||
static void **loadAllModules(void);
|
||||
static void loadInOrder(ModuleT *mods);
|
||||
static void logAndReadDeps(ModuleT *mods);
|
||||
static void readDeps(ModuleT *mod);
|
||||
static void scanDir(const char *dirPath, const char *ext, ModuleT **mods);
|
||||
|
||||
|
|
@ -246,15 +269,26 @@ static void loadInOrder(ModuleT *mods) {
|
|||
continue;
|
||||
}
|
||||
|
||||
loaderLog("Loading: %s", mods[i].path);
|
||||
mods[i].handle = dlopen(mods[i].path, RTLD_GLOBAL);
|
||||
|
||||
if (mods[i].handle) {
|
||||
if (!mods[i].handle) {
|
||||
const char *err = dlerror();
|
||||
loaderLog(" FAILED: %s", err ? err : "(unknown)");
|
||||
mods[i].loaded = true;
|
||||
loaded++;
|
||||
progress = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
loaderLog(" OK");
|
||||
|
||||
RegFnT regFn = (RegFnT)dlsym(mods[i].handle, "_wgtRegister");
|
||||
|
||||
if (regFn) {
|
||||
loaderLog(" Calling wgtRegister()");
|
||||
regFn();
|
||||
}
|
||||
}
|
||||
|
||||
mods[i].loaded = true;
|
||||
loaded++;
|
||||
|
|
@ -280,31 +314,28 @@ static void loadInOrder(ModuleT *mods) {
|
|||
//
|
||||
// Returns a stb_ds dynamic array of dlopen handles.
|
||||
|
||||
static void **loadAllModules(void) {
|
||||
ModuleT *mods = NULL;
|
||||
static void logAndReadDeps(ModuleT *mods) {
|
||||
loaderLog("Discovered %d modules:", arrlen(mods));
|
||||
|
||||
// Discover all modules
|
||||
scanDir(LIBS_DIR, ".lib", &mods);
|
||||
scanDir(WIDGET_DIR, ".wgt", &mods);
|
||||
for (int32_t i = 0; i < arrlen(mods); i++) {
|
||||
loaderLog(" [%d] %s (base: %s)", i, mods[i].path, mods[i].baseName);
|
||||
}
|
||||
|
||||
// Read dependency files
|
||||
for (int32_t i = 0; i < arrlen(mods); i++) {
|
||||
readDeps(&mods[i]);
|
||||
}
|
||||
|
||||
// Load in dependency order
|
||||
loadInOrder(mods);
|
||||
if (arrlen(mods[i].deps) > 0) {
|
||||
loaderLog(" %s deps:", mods[i].baseName);
|
||||
|
||||
// Collect handles
|
||||
void **handles = NULL;
|
||||
|
||||
for (int32_t i = 0; i < arrlen(mods); i++) {
|
||||
if (mods[i].handle) {
|
||||
arrput(handles, mods[i].handle);
|
||||
for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
|
||||
loaderLog(" %s", mods[i].deps[d]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free dep arrays
|
||||
|
||||
static void freeMods(ModuleT *mods) {
|
||||
for (int32_t i = 0; i < arrlen(mods); i++) {
|
||||
for (int32_t d = 0; d < arrlen(mods[i].deps); d++) {
|
||||
free(mods[i].deps[d]);
|
||||
|
|
@ -314,6 +345,40 @@ static void **loadAllModules(void) {
|
|||
}
|
||||
|
||||
arrfree(mods);
|
||||
}
|
||||
|
||||
|
||||
static void **loadAllModules(void) {
|
||||
void **handles = NULL;
|
||||
|
||||
// Phase 1: load libraries in dependency order (libs before widgets)
|
||||
ModuleT *libs = NULL;
|
||||
scanDir(LIBS_DIR, ".lib", &libs);
|
||||
logAndReadDeps(libs);
|
||||
loadInOrder(libs);
|
||||
|
||||
for (int32_t i = 0; i < arrlen(libs); i++) {
|
||||
if (libs[i].handle) {
|
||||
arrput(handles, libs[i].handle);
|
||||
}
|
||||
}
|
||||
|
||||
freeMods(libs);
|
||||
|
||||
// Phase 2: load widgets in dependency order (all libs already loaded)
|
||||
ModuleT *widgets = NULL;
|
||||
scanDir(WIDGET_DIR, ".wgt", &widgets);
|
||||
logAndReadDeps(widgets);
|
||||
loadInOrder(widgets);
|
||||
|
||||
for (int32_t i = 0; i < arrlen(widgets); i++) {
|
||||
if (widgets[i].handle) {
|
||||
arrput(handles, widgets[i].handle);
|
||||
}
|
||||
}
|
||||
|
||||
freeMods(widgets);
|
||||
|
||||
return handles;
|
||||
}
|
||||
|
||||
|
|
@ -353,8 +418,18 @@ int main(int argc, char *argv[]) {
|
|||
platformChdir(exeDir);
|
||||
}
|
||||
|
||||
// Truncate log, then use append-per-write
|
||||
FILE *logInit = fopen(LOG_PATH, "w");
|
||||
|
||||
if (logInit) {
|
||||
fclose(logInit);
|
||||
}
|
||||
|
||||
loaderLog("DVX Loader starting...");
|
||||
|
||||
// Register platform + libc/libm/runtime symbols for DXE resolution
|
||||
platformRegisterDxeExports();
|
||||
loaderLog("Platform exports registered.");
|
||||
|
||||
// Load all modules from libs/ and widgets/ in dependency order.
|
||||
// Each module may have a .dep file specifying load-before deps.
|
||||
|
|
@ -372,7 +447,7 @@ int main(int argc, char *argv[]) {
|
|||
ShellMainFnT shellMain = (ShellMainFnT)findSymbol(handles, "_shellMain");
|
||||
|
||||
if (!shellMain) {
|
||||
fprintf(stderr, "No module exports shellMain\n");
|
||||
loaderLog("ERROR: No module exports shellMain");
|
||||
|
||||
for (int32_t i = arrlen(handles) - 1; i >= 0; i--) {
|
||||
dlclose(handles[i]);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../widgets -I../tasks -I../core/thirdparty
|
||||
|
||||
OBJDIR = ../obj/shell
|
||||
LIBSDIR = ../bin/libs
|
||||
|
|
@ -28,7 +28,8 @@ $(LIBSDIR)/dvxshell.dep: ../config/dvxshell.dep | $(LIBSDIR)
|
|||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBSDIR)
|
||||
$(DXE3GEN) -o $@ -E _shell -U $(OBJS)
|
||||
$(DXE3GEN) -o $(LIBSDIR)/dvxshell.dxe -E _shell -U $(OBJS)
|
||||
mv $(LIBSDIR)/dvxshell.dxe $@
|
||||
|
||||
$(CONFIGDIR)/dvx.ini: ../config/dvx.ini | $(CONFIGDIR)
|
||||
sed 's/$$/\r/' $< > $@
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include "shellApp.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "stb_ds.h"
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
|
|
@ -18,12 +19,11 @@
|
|||
// Module state
|
||||
// ============================================================
|
||||
|
||||
// Fixed-size app slot table. Using a static array (not dynamic) because:
|
||||
// 1. The number of concurrent apps is bounded by available DOS memory
|
||||
// 2. App IDs are slot indices, so a fixed array gives O(1) lookup
|
||||
// 3. No fragmentation concerns from repeated malloc/free cycles
|
||||
// Slot 0 is reserved (represents the shell itself); apps use slots 1..31.
|
||||
static ShellAppT sApps[SHELL_MAX_APPS];
|
||||
// Dynamic app slot table (stb_ds array). App IDs are array indices.
|
||||
// Slot 0 is reserved (represents the shell itself); apps use slots 1+.
|
||||
// New slots are appended as needed; freed slots are recycled.
|
||||
static ShellAppT *sApps = NULL;
|
||||
static int32_t sAppsCap = 0; // number of slots allocated (including slot 0)
|
||||
int32_t sCurrentAppId = 0;
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -52,15 +52,20 @@ void shellTerminateAllApps(AppContextT *ctx);
|
|||
|
||||
// Find the first free slot, starting at 1 (slot 0 is the shell).
|
||||
// Returns the slot index which also serves as the app's unique ID.
|
||||
// This linear scan is fine for 32 slots.
|
||||
// If no free slot exists, appends a new one.
|
||||
static int32_t allocSlot(void) {
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
||||
if (sApps[i].state == AppStateFreeE) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
// No free slot -- grow the array
|
||||
ShellAppT newSlot;
|
||||
memset(&newSlot, 0, sizeof(newSlot));
|
||||
arrput(sApps, newSlot);
|
||||
|
||||
return arrlen(sApps) - 1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -144,7 +149,7 @@ static int32_t copyFile(const char *src, const char *dst) {
|
|||
// Find an active slot with the given DXE path. Returns the slot pointer,
|
||||
// or NULL if no running app was loaded from this path.
|
||||
static ShellAppT *findLoadedPath(const char *path) {
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
||||
if (sApps[i].state != AppStateFreeE && strcmp(sApps[i].path, path) == 0) {
|
||||
return &sApps[i];
|
||||
}
|
||||
|
|
@ -194,7 +199,11 @@ static int32_t makeTempPath(const char *origPath, int32_t id, char *out, int32_t
|
|||
// ============================================================
|
||||
|
||||
void shellAppInit(void) {
|
||||
memset(sApps, 0, sizeof(sApps));
|
||||
// Seed slot 0 (reserved for the shell)
|
||||
ShellAppT slot0;
|
||||
memset(&slot0, 0, sizeof(slot0));
|
||||
arrput(sApps, slot0);
|
||||
sAppsCap = 1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -255,7 +264,7 @@ void shellForceKillApp(AppContextT *ctx, ShellAppT *app) {
|
|||
|
||||
|
||||
ShellAppT *shellGetApp(int32_t appId) {
|
||||
if (appId < 1 || appId >= SHELL_MAX_APPS) {
|
||||
if (appId < 1 || appId >= arrlen(sApps)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -508,7 +517,7 @@ void shellReapApp(AppContextT *ctx, ShellAppT *app) {
|
|||
bool shellReapApps(AppContextT *ctx) {
|
||||
bool reaped = false;
|
||||
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
||||
if (sApps[i].state == AppStateTerminatingE) {
|
||||
shellReapApp(ctx, &sApps[i]);
|
||||
reaped = true;
|
||||
|
|
@ -519,10 +528,15 @@ bool shellReapApps(AppContextT *ctx) {
|
|||
}
|
||||
|
||||
|
||||
int32_t shellAppSlotCount(void) {
|
||||
return arrlen(sApps);
|
||||
}
|
||||
|
||||
|
||||
int32_t shellRunningAppCount(void) {
|
||||
int32_t count = 0;
|
||||
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
||||
if (sApps[i].state == AppStateRunningE || sApps[i].state == AppStateLoadedE) {
|
||||
count++;
|
||||
}
|
||||
|
|
@ -533,7 +547,7 @@ int32_t shellRunningAppCount(void) {
|
|||
|
||||
|
||||
void shellTerminateAllApps(AppContextT *ctx) {
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
for (int32_t i = 1; i < arrlen(sApps); i++) {
|
||||
if (sApps[i].state != AppStateFreeE) {
|
||||
shellForceKillApp(ctx, &sApps[i]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,10 +34,6 @@
|
|||
// ============================================================
|
||||
|
||||
#define SHELL_APP_NAME_MAX 64
|
||||
// Fixed array size for app slots. A small fixed limit avoids dynamic
|
||||
// allocation complexity and matches the practical limit of a DOS desktop.
|
||||
// Slot 0 is reserved for the shell itself, so 31 app slots are usable.
|
||||
#define SHELL_MAX_APPS 32
|
||||
|
||||
// Every DXE app exports a global AppDescriptorT named "appDescriptor".
|
||||
// The shell reads it at load time to determine how to launch the app.
|
||||
|
|
@ -148,6 +144,9 @@ void shellTerminateAllApps(AppContextT *ctx);
|
|||
// Get app slot by ID (returns NULL if invalid/free)
|
||||
ShellAppT *shellGetApp(int32_t appId);
|
||||
|
||||
// Total number of app slots (for iteration bounds)
|
||||
int32_t shellAppSlotCount(void);
|
||||
|
||||
// Count running apps (not counting the shell itself)
|
||||
int32_t shellRunningAppCount(void);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
// shellExport.c -- DXE wrapper overrides for resource tracking
|
||||
//
|
||||
// Registers three wrapper functions via dlregsym that override the
|
||||
// real dvxCreateWindow, dvxCreateWindowCentered, and dvxDestroyWindow
|
||||
// for all subsequently loaded app DXEs.
|
||||
// Registers three wrapper functions that override the real
|
||||
// dvxCreateWindow, dvxCreateWindowCentered, and dvxDestroyWindow
|
||||
// for all subsequently loaded app modules.
|
||||
//
|
||||
// The key mechanic: dlregsym takes precedence over RTLD_GLOBAL exports.
|
||||
// Since libdvx.dxe (which has the real functions) was loaded before
|
||||
// shellExportInit() registers these wrappers, libdvx.dxe keeps the
|
||||
// real implementations. But any app DXE loaded afterward gets the
|
||||
// The key mechanic: symbol overrides registered via
|
||||
// platformRegisterSymOverrides take precedence over previously loaded
|
||||
// symbols. Since libdvx (which has the real functions) was loaded
|
||||
// before shellExportInit() registers these wrappers, libdvx keeps the
|
||||
// real implementations. But any app module loaded afterward gets the
|
||||
// wrappers, which add resource tracking (appId stamping, last-window
|
||||
// reaping) transparently.
|
||||
//
|
||||
// All other symbol exports (dvx*, wgt*, platform*, libc) are handled
|
||||
// by the DXE modules and the loader's dlregsym table -- they no longer
|
||||
// need to be listed here.
|
||||
// by the loaded modules and the platform layer's export table -- they
|
||||
// no longer need to be listed here.
|
||||
|
||||
#include "shellApp.h"
|
||||
#include "dvxApp.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "dvxWm.h"
|
||||
|
||||
#include <sys/dxe.h>
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
|
@ -33,7 +33,7 @@ static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win);
|
|||
// Wrapper: dvxCreateWindow -- stamps win->appId
|
||||
// ============================================================
|
||||
|
||||
// The wrapper calls the real dvxCreateWindow (resolved from libdvx.dxe
|
||||
// The wrapper calls the real dvxCreateWindow (resolved from libdvx
|
||||
// at shellcore load time), then tags the result with sCurrentAppId.
|
||||
// This is how the shell knows which app owns which window, enabling
|
||||
// per-app window cleanup on crash/termination.
|
||||
|
|
@ -101,26 +101,19 @@ static void shellWrapDestroyWindow(AppContextT *ctx, WindowT *win) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Wrapper export table
|
||||
// ============================================================
|
||||
|
||||
// Only three entries: the resource-tracking wrappers exported under
|
||||
// the original function names. All other symbols come from the
|
||||
// loaded DXE modules (via RTLD_GLOBAL) and the loader's dlregsym.
|
||||
DXE_EXPORT_TABLE(sWrapperTable)
|
||||
{ "_dvxCreateWindow", (void *)shellWrapCreateWindow },
|
||||
{ "_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered },
|
||||
{ "_dvxDestroyWindow", (void *)shellWrapDestroyWindow },
|
||||
DXE_EXPORT_END
|
||||
|
||||
|
||||
// ============================================================
|
||||
// shellExportInit
|
||||
// ============================================================
|
||||
|
||||
// Register the wrapper overrides. Must be called before any
|
||||
// shellLoadApp() -- wrappers only affect subsequently loaded DXEs.
|
||||
// shellLoadApp() -- wrappers only affect subsequently loaded modules.
|
||||
void shellExportInit(void) {
|
||||
dlregsym(sWrapperTable);
|
||||
static const PlatformSymOverrideT sOverrides[] = {
|
||||
{"_dvxCreateWindow", (void *)shellWrapCreateWindow},
|
||||
{"_dvxCreateWindowCentered", (void *)shellWrapCreateWindowCentered},
|
||||
{"_dvxDestroyWindow", (void *)shellWrapDestroyWindow},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
platformRegisterSymOverrides(sOverrides);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@
|
|||
// apps were reaped, so it can refresh its task list.
|
||||
//
|
||||
// Crash recovery uses setjmp/longjmp:
|
||||
// The shell installs signal handlers for SIGSEGV, SIGFPE, SIGILL. If a
|
||||
// crash occurs in an app task, the handler longjmps back to the setjmp
|
||||
// point in main(). This works because longjmp restores the main task's
|
||||
// stack frame regardless of which task was running. tsRecoverToMain()
|
||||
// then fixes the scheduler's bookkeeping, and the crashed app is killed.
|
||||
// The platform layer installs signal handlers for SIGSEGV, SIGFPE, SIGILL
|
||||
// via platformInstallCrashHandler(). If a crash occurs in an app task, the
|
||||
// handler logs platform-specific diagnostics (register dump on DJGPP),
|
||||
// then longjmps back to the setjmp point in main(). This works because
|
||||
// longjmp restores the main task's stack frame regardless of which task
|
||||
// was running. tsRecoverToMain() then fixes the scheduler's bookkeeping,
|
||||
// the shell logs app-specific info, and the crashed app is killed.
|
||||
// This gives the shell Windows 3.1-style fault tolerance -- one bad app
|
||||
// doesn't take down the whole system.
|
||||
|
||||
|
|
@ -34,12 +36,8 @@
|
|||
|
||||
#include <stdarg.h>
|
||||
#include <setjmp.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
// DJGPP-specific: provides __djgpp_exception_state_ptr for accessing
|
||||
// CPU register state at the point of the exception
|
||||
#include <sys/exceptn.h>
|
||||
|
||||
// ============================================================
|
||||
// Module state
|
||||
|
|
@ -61,37 +59,9 @@ static DesktopUpdateFnT *sDesktopUpdateFns = NULL;
|
|||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void crashHandler(int sig);
|
||||
static void idleYield(void *ctx);
|
||||
static void installCrashHandler(void);
|
||||
static void logCrash(int sig);
|
||||
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData);
|
||||
|
||||
// ============================================================
|
||||
// crashHandler -- catch page faults and other fatal signals
|
||||
// ============================================================
|
||||
|
||||
// Signal handler for fatal exceptions. DJGPP uses System V signal
|
||||
// semantics where the handler is reset to SIG_DFL after each delivery,
|
||||
// so we must re-install it before doing anything else.
|
||||
//
|
||||
// The longjmp is the key to crash recovery: it unwinds whatever stack
|
||||
// we're on (potentially a crashed app's task stack) and restores the
|
||||
// main task's stack frame to the setjmp point in main(). This is safe
|
||||
// because cooperative switching means the main task's stack is always
|
||||
// intact -- it was cleanly suspended at a yield point. The crashed
|
||||
// task's stack is abandoned (and later freed by tsKill).
|
||||
static void crashHandler(int sig) {
|
||||
logCrash(sig);
|
||||
|
||||
// Re-install handler (DJGPP resets to SIG_DFL after delivery)
|
||||
signal(sig, crashHandler);
|
||||
|
||||
sCrashSignal = sig;
|
||||
longjmp(sCrashJmp, 1);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// shellDesktopUpdate -- notify desktop app of state change
|
||||
// ============================================================
|
||||
|
|
@ -141,71 +111,6 @@ static void idleYield(void *ctx) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// installCrashHandler
|
||||
// ============================================================
|
||||
|
||||
static void installCrashHandler(void) {
|
||||
signal(SIGSEGV, crashHandler);
|
||||
signal(SIGFPE, crashHandler);
|
||||
signal(SIGILL, crashHandler);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// logCrash -- write exception details to the log
|
||||
// ============================================================
|
||||
|
||||
// Dump as much diagnostic info as possible before longjmp destroys the
|
||||
// crash context. This runs inside the signal handler, so only
|
||||
// async-signal-safe functions should be used -- but since we're in
|
||||
// DJGPP (single-threaded DOS), reentrancy isn't a practical concern
|
||||
// and vfprintf/fflush are safe to call here.
|
||||
static void logCrash(int sig) {
|
||||
const char *sigName = "UNKNOWN";
|
||||
|
||||
if (sig == SIGSEGV) {
|
||||
sigName = "SIGSEGV (page fault)";
|
||||
} else if (sig == SIGFPE) {
|
||||
sigName = "SIGFPE (floating point exception)";
|
||||
} else if (sig == SIGILL) {
|
||||
sigName = "SIGILL (illegal instruction)";
|
||||
}
|
||||
|
||||
shellLog("=== CRASH ===");
|
||||
shellLog("Signal: %d (%s)", sig, sigName);
|
||||
shellLog("Current app ID: %ld", (long)sCurrentAppId);
|
||||
|
||||
if (sCurrentAppId > 0) {
|
||||
ShellAppT *app = shellGetApp(sCurrentAppId);
|
||||
|
||||
if (app) {
|
||||
shellLog("App name: %s", app->name);
|
||||
shellLog("App path: %s", app->path);
|
||||
shellLog("Has main loop: %s", app->hasMainLoop ? "yes" : "no");
|
||||
shellLog("Task ID: %lu", (unsigned long)app->mainTaskId);
|
||||
}
|
||||
} else {
|
||||
shellLog("Crashed in shell (task 0)");
|
||||
}
|
||||
|
||||
// __djgpp_exception_state_ptr is a DJGPP extension that captures the
|
||||
// full CPU register state at the point of the exception. This gives
|
||||
// us the faulting EIP, stack pointer, and all GPRs -- invaluable for
|
||||
// post-mortem debugging of app crashes from the log file.
|
||||
jmp_buf *estate = __djgpp_exception_state_ptr;
|
||||
|
||||
if (estate) {
|
||||
struct __jmp_buf *regs = &(*estate)[0];
|
||||
shellLog("EIP: 0x%08lx CS: 0x%04x", regs->__eip, regs->__cs);
|
||||
shellLog("EAX: 0x%08lx EBX: 0x%08lx ECX: 0x%08lx EDX: 0x%08lx", regs->__eax, regs->__ebx, regs->__ecx, regs->__edx);
|
||||
shellLog("ESI: 0x%08lx EDI: 0x%08lx EBP: 0x%08lx ESP: 0x%08lx", regs->__esi, regs->__edi, regs->__ebp, regs->__esp);
|
||||
shellLog("DS: 0x%04x ES: 0x%04x FS: 0x%04x GS: 0x%04x SS: 0x%04x", regs->__ds, regs->__es, regs->__fs, regs->__gs, regs->__ss);
|
||||
shellLog("EFLAGS: 0x%08lx", regs->__eflags);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void logVideoMode(int32_t w, int32_t h, int32_t bpp, void *userData) {
|
||||
(void)userData;
|
||||
shellLog(" %ldx%ld %ldbpp", (long)w, (long)h, (long)bpp);
|
||||
|
|
@ -268,14 +173,10 @@ int shellMain(int argc, char *argv[]) {
|
|||
(void)argc;
|
||||
(void)argv;
|
||||
|
||||
// Truncate the log file, then use append-per-write so the file
|
||||
// isn't held open (allows Notepad to read it while the shell runs).
|
||||
// The loader already truncates and writes boot info to dvx.log.
|
||||
// We just append from here on, opening/closing per-write so the
|
||||
// file isn't held open (allows Notepad to read it while the shell runs).
|
||||
sLogPath = "dvx.log";
|
||||
FILE *logInit = fopen(sLogPath, "w");
|
||||
|
||||
if (logInit) {
|
||||
fclose(logInit);
|
||||
}
|
||||
|
||||
shellLog("DVX Shell starting...");
|
||||
|
||||
|
|
@ -403,21 +304,22 @@ int shellMain(int argc, char *argv[]) {
|
|||
sCtx.onTitleChange = titleChangeHandler;
|
||||
sCtx.titleChangeCtx = &sCtx;
|
||||
|
||||
// Load the desktop app
|
||||
int32_t desktopId = shellLoadApp(&sCtx, SHELL_DESKTOP_APP);
|
||||
// Load the desktop app (configurable via [shell] desktop= in dvx.ini)
|
||||
const char *desktopApp = prefsGetString("shell", "desktop", SHELL_DESKTOP_APP);
|
||||
int32_t desktopId = shellLoadApp(&sCtx, desktopApp);
|
||||
|
||||
if (desktopId < 0) {
|
||||
shellLog("Failed to load desktop app '%s'", SHELL_DESKTOP_APP);
|
||||
shellLog("Failed to load desktop app '%s'", desktopApp);
|
||||
tsShutdown();
|
||||
dvxShutdown(&sCtx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Install crash handler after everything is initialized -- if
|
||||
// initialization itself crashes, we want the default DJGPP behavior
|
||||
// initialization itself crashes, we want the default behavior
|
||||
// (abort with register dump) rather than our recovery path, because
|
||||
// the system isn't in a recoverable state yet.
|
||||
installCrashHandler();
|
||||
platformInstallCrashHandler(&sCrashJmp, &sCrashSignal, shellLog);
|
||||
|
||||
shellLog("DVX Shell ready.");
|
||||
|
||||
|
|
@ -432,6 +334,23 @@ int shellMain(int argc, char *argv[]) {
|
|||
// Fix it before doing anything else so the scheduler is consistent.
|
||||
tsRecoverToMain();
|
||||
|
||||
// Platform handler already logged signal name and register dump.
|
||||
// Log app-specific info here.
|
||||
shellLog("Current app ID: %ld", (long)sCurrentAppId);
|
||||
|
||||
if (sCurrentAppId > 0) {
|
||||
ShellAppT *crashedApp = shellGetApp(sCurrentAppId);
|
||||
|
||||
if (crashedApp) {
|
||||
shellLog("App name: %s", crashedApp->name);
|
||||
shellLog("App path: %s", crashedApp->path);
|
||||
shellLog("Has main loop: %s", crashedApp->hasMainLoop ? "yes" : "no");
|
||||
shellLog("Task ID: %lu", (unsigned long)crashedApp->mainTaskId);
|
||||
}
|
||||
} else {
|
||||
shellLog("Crashed in shell (task 0)");
|
||||
}
|
||||
|
||||
shellLog("Recovering from crash, killing app %ld", (long)sCurrentAppId);
|
||||
|
||||
if (sCurrentAppId > 0) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,10 @@
|
|||
#include "shellApp.h"
|
||||
#include "dvxDialog.h"
|
||||
#include "dvxWidget.h"
|
||||
#include "widgetBox.h"
|
||||
#include "widgetButton.h"
|
||||
#include "widgetLabel.h"
|
||||
#include "widgetListView.h"
|
||||
#include "dvxWm.h"
|
||||
#include "dvxPlatform.h"
|
||||
#include "stb_ds.h"
|
||||
|
|
@ -65,7 +69,7 @@ static TmRowStringsT *sRowStrs = NULL; // dynamic array of per-row strings
|
|||
static ShellAppT *getRunningAppByIndex(int32_t sel) {
|
||||
int32_t idx = 0;
|
||||
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
for (int32_t i = 1; i < shellAppSlotCount(); i++) {
|
||||
ShellAppT *app = shellGetApp(i);
|
||||
|
||||
if (app && app->state == AppStateRunningE) {
|
||||
|
|
@ -202,7 +206,7 @@ static void refreshTaskList(void) {
|
|||
static int32_t *appIds = NULL;
|
||||
arrsetlen(appIds, 0);
|
||||
|
||||
for (int32_t i = 1; i < SHELL_MAX_APPS; i++) {
|
||||
for (int32_t i = 1; i < shellAppSlotCount(); i++) {
|
||||
ShellAppT *app = shellGetApp(i);
|
||||
|
||||
if (app && app->state == AppStateRunningE) {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ TARGET = $(LIBSDIR)/libtasks.lib
|
|||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBSDIR)
|
||||
$(DXE3GEN) -o $@ -E _ts -E _stbds_ -U $(OBJS)
|
||||
$(DXE3GEN) -o $(LIBSDIR)/libtasks.dxe -E _ts -U $(OBJS)
|
||||
mv $(LIBSDIR)/libtasks.dxe $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
|
@ -31,7 +32,7 @@ $(LIBSDIR):
|
|||
mkdir -p $(LIBSDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/taskswitch.o: taskswitch.c taskswitch.h thirdparty/stb_ds.h
|
||||
$(OBJDIR)/taskswitch.o: taskswitch.c taskswitch.h ../core/thirdparty/stb_ds.h
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET)
|
||||
|
|
|
|||
144
tasks/demo.c
144
tasks/demo.c
|
|
@ -1,144 +0,0 @@
|
|||
// demo.c -- Demonstration of the cooperative task switching library
|
||||
//
|
||||
// Shows priority scheduling, round-robin, pausing/resuming, dynamic
|
||||
// priority changes, and slot reuse after task termination.
|
||||
//
|
||||
// This is a standalone test harness that runs on the host (Linux x86_64)
|
||||
// or DJGPP target to verify the task switcher without the GUI stack.
|
||||
// Each phase exercises a different scheduler feature and prints output
|
||||
// so you can verify the scheduling order visually.
|
||||
|
||||
#include <stdio.h>
|
||||
#include "taskswitch.h"
|
||||
|
||||
// Forward declarations
|
||||
static void pauseTarget(void *arg);
|
||||
static void shortLived(void *arg);
|
||||
static void worker(void *arg);
|
||||
|
||||
|
||||
static void pauseTarget(void *arg) {
|
||||
(void)arg;
|
||||
for (int32_t i = 0; i < 10; i++) {
|
||||
printf(" [pauser] iteration %d\n", (int)i);
|
||||
tsYield();
|
||||
}
|
||||
printf(" [pauser] finished\n");
|
||||
}
|
||||
|
||||
|
||||
static void shortLived(void *arg) {
|
||||
int32_t id = (int32_t)(intptr_t)arg;
|
||||
printf(" [short-%d] running (id=%d)\n", (int)id, (int)tsCurrentId());
|
||||
tsYield();
|
||||
printf(" [short-%d] done\n", (int)id);
|
||||
}
|
||||
|
||||
|
||||
static void worker(void *arg) {
|
||||
const char *name = (const char *)arg;
|
||||
int32_t pri = tsGetPriority(tsCurrentId());
|
||||
|
||||
for (int32_t i = 0; i < 5; i++) {
|
||||
printf(" [%s] step %d (priority %d)\n", name, (int)i, (int)pri);
|
||||
tsYield();
|
||||
}
|
||||
printf(" [%s] finished\n", name);
|
||||
}
|
||||
|
||||
|
||||
int main(void) {
|
||||
printf("=== Cooperative Task Switching Demo ===\n\n");
|
||||
|
||||
if (tsInit() != TS_OK) {
|
||||
printf("ERROR: failed to initialize task system\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int32_t tHigh = tsCreate("high", worker, "high", 0, TS_PRIORITY_HIGH);
|
||||
int32_t tNorm = tsCreate("normal", worker, "normal", 0, TS_PRIORITY_NORMAL);
|
||||
int32_t tLow = tsCreate("low", worker, "low", 0, TS_PRIORITY_LOW);
|
||||
int32_t tPauser = tsCreate("pauser", pauseTarget, NULL, 0, TS_PRIORITY_NORMAL);
|
||||
|
||||
if (tHigh < 0 || tNorm < 0 || tLow < 0 || tPauser < 0) {
|
||||
printf("ERROR: failed to create tasks\n");
|
||||
tsShutdown();
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Created: high=%d normal=%d low=%d pauser=%d\n\n", (int)tHigh, (int)tNorm, (int)tLow, (int)tPauser);
|
||||
|
||||
// Phase 1: all tasks run, but high-priority tasks get more turns per round.
|
||||
// Low (priority 0) gets 1 turn, normal (5) gets 6, high (10) gets 11.
|
||||
// In the output you'll see "high" appearing much more frequently than
|
||||
// "low", demonstrating the weighted fair-share scheduling.
|
||||
printf("--- Phase 1: Priority scheduling ---\n");
|
||||
for (int32_t i = 0; i < 6; i++) {
|
||||
printf("[main] yield %d\n", (int)i);
|
||||
tsYield();
|
||||
}
|
||||
|
||||
// Phase 2: pause the pauser task so it stops being scheduled
|
||||
printf("\n--- Phase 2: Pause 'pauser' ---\n");
|
||||
tsPause((uint32_t)tPauser);
|
||||
printf("[main] pauser state: %d (expect %d=paused)\n", tsGetState((uint32_t)tPauser), TaskStatePaused);
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
printf("[main] yield %d (pauser is paused)\n", (int)i);
|
||||
tsYield();
|
||||
}
|
||||
|
||||
// Phase 3: resume pauser
|
||||
printf("\n--- Phase 3: Resume 'pauser' ---\n");
|
||||
tsResume((uint32_t)tPauser);
|
||||
for (int32_t i = 0; i < 4; i++) {
|
||||
printf("[main] yield %d\n", (int)i);
|
||||
tsYield();
|
||||
}
|
||||
|
||||
// Phase 4: boost low to the highest priority so it gets more turns
|
||||
printf("\n--- Phase 4: Boost 'low' to highest ---\n");
|
||||
tsSetPriority((uint32_t)tLow, TS_PRIORITY_HIGH + 5);
|
||||
for (int32_t i = 0; i < 6; i++) {
|
||||
printf("[main] yield %d\n", (int)i);
|
||||
tsYield();
|
||||
}
|
||||
|
||||
// Let remaining tasks finish
|
||||
printf("\n--- Draining remaining tasks ---\n");
|
||||
while (tsActiveCount() > 1) {
|
||||
tsYield();
|
||||
}
|
||||
|
||||
// Phase 5: dynamic creation with slot reuse.
|
||||
// After all tasks from phases 1-4 have terminated, their slots are free.
|
||||
// Creating new tasks should recycle those slots (IDs 1-4) rather than
|
||||
// growing the array. Each "wave" creates 3 tasks, lets them finish,
|
||||
// then creates 3 more -- the IDs should repeat across waves.
|
||||
printf("\n--- Phase 5: Slot reuse ---\n");
|
||||
printf("[main] active before: %u\n", (unsigned)tsActiveCount());
|
||||
|
||||
// Create short-lived tasks that will terminate and free their slots
|
||||
for (int32_t wave = 0; wave < 3; wave++) {
|
||||
printf("[main] wave %d: creating 3 tasks\n", (int)wave);
|
||||
int32_t ids[3];
|
||||
for (int32_t j = 0; j < 3; j++) {
|
||||
int32_t tag = wave * 3 + j;
|
||||
char name[TS_NAME_MAX];
|
||||
snprintf(name, sizeof(name), "short-%d", (int)tag);
|
||||
ids[j] = tsCreate(name, shortLived, (void *)(intptr_t)tag, 0, TS_PRIORITY_NORMAL);
|
||||
printf("[main] created id=%d\n", (int)ids[j]);
|
||||
}
|
||||
|
||||
// Let them all finish
|
||||
while (tsActiveCount() > 1) {
|
||||
tsYield();
|
||||
}
|
||||
printf("[main] wave %d complete, active=%u\n", (int)wave, (unsigned)tsActiveCount());
|
||||
}
|
||||
|
||||
printf("\nActive tasks: %u\n", (unsigned)tsActiveCount());
|
||||
printf("=== Demo complete ===\n");
|
||||
|
||||
tsShutdown();
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -21,8 +21,7 @@
|
|||
// scheduler's linear scan. The downside (holes after termination) is
|
||||
// mitigated by slot recycling in findFreeSlot().
|
||||
|
||||
#define STB_DS_IMPLEMENTATION
|
||||
#include "thirdparty/stb_ds.h"
|
||||
#include "../core/thirdparty/stb_ds.h"
|
||||
|
||||
#include "taskswitch.h"
|
||||
#include <stdlib.h>
|
||||
|
|
@ -219,7 +218,7 @@ static void __attribute__((noinline)) contextSwitch(TaskContextT *save, TaskCont
|
|||
// Find a free (terminated or unallocated) slot in the task array.
|
||||
// Returns the index, or -1 if no free slot exists.
|
||||
// Starts at 1 because slot 0 is always the main task and cannot be reused.
|
||||
// Linear scan is fine -- SHELL_MAX_APPS caps the practical limit at ~32 tasks.
|
||||
// Linear scan is fine for the typical task count (tens, not hundreds).
|
||||
static int32_t findFreeSlot(void) {
|
||||
ptrdiff_t count = arrlen(tasks);
|
||||
for (ptrdiff_t i = 1; i < count; i++) {
|
||||
|
|
|
|||
42
texthelp/Makefile
Normal file
42
texthelp/Makefile
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
# Text Help Library Makefile for DJGPP cross-compilation
|
||||
#
|
||||
# Builds texthelp.lib -- shared text editing infrastructure
|
||||
# (clipboard, multi-click, word boundaries, single-line editing engine).
|
||||
|
||||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I. -I../core -I../core/platform -I../tasks -I../core/thirdparty
|
||||
|
||||
OBJDIR = ../obj/texthelp
|
||||
LIBSDIR = ../bin/libs
|
||||
|
||||
SRCS = textHelp.c
|
||||
OBJS = $(patsubst %.c,$(OBJDIR)/%.o,$(SRCS))
|
||||
TARGET = $(LIBSDIR)/texthelp.lib
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET) $(LIBSDIR)/texthelp.dep
|
||||
|
||||
$(LIBSDIR)/texthelp.dep: ../config/texthelp.dep | $(LIBSDIR)
|
||||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
$(TARGET): $(OBJS) | $(LIBSDIR)
|
||||
$(DXE3GEN) -o $(LIBSDIR)/texthelp.dxe -E _clipboard -E _clearOther -E _isWordChar -E _multiClick -E _widgetText -E _wordStart -E _wordEnd -U $(OBJS)
|
||||
mv $(LIBSDIR)/texthelp.dxe $@
|
||||
|
||||
$(OBJDIR)/%.o: %.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
$(LIBSDIR):
|
||||
mkdir -p $(LIBSDIR)
|
||||
|
||||
# Dependencies
|
||||
$(OBJDIR)/textHelp.o: textHelp.c textHelp.h ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET) $(LIBSDIR)/texthelp.dep
|
||||
739
texthelp/textHelp.c
Normal file
739
texthelp/textHelp.c
Normal file
|
|
@ -0,0 +1,739 @@
|
|||
// textHelp.c -- Shared text editing infrastructure
|
||||
//
|
||||
// Provides clipboard, multi-click detection, word boundary logic,
|
||||
// cross-widget selection clearing, and the single-line text editing
|
||||
// engine. Shared across multiple widget DXEs (TextInput, TextArea,
|
||||
// ComboBox, Spinner, AnsiTerm) via the texthelp.lib DXE.
|
||||
|
||||
#include "textHelp.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <time.h>
|
||||
|
||||
// ============================================================
|
||||
// Static state
|
||||
// ============================================================
|
||||
|
||||
// Track the widget that last had an active selection so we can
|
||||
// clear it in O(1) instead of walking every widget in every window.
|
||||
static WidgetT *sLastSelectedWidget = NULL;
|
||||
|
||||
// ============================================================
|
||||
// Static helper prototypes
|
||||
// ============================================================
|
||||
|
||||
static bool clearSelectionOnWidget(WidgetT *w);
|
||||
static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd);
|
||||
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
|
||||
static int32_t wordBoundaryLeft(const char *buf, int32_t pos);
|
||||
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// clearOtherSelections
|
||||
// ============================================================
|
||||
//
|
||||
// Clears selection on the previously-selected widget (if different
|
||||
// from the newly-focused one). Validates that the previous widget's
|
||||
// window is still in the window stack before touching it -- the
|
||||
// window may have been closed since sLastSelectedWidget was set.
|
||||
// If the previous widget was in a different window, that window
|
||||
// gets a full repaint to clear the stale selection highlight.
|
||||
|
||||
void clearOtherSelections(WidgetT *except) {
|
||||
if (!except || !except->window || !except->window->widgetRoot) {
|
||||
return;
|
||||
}
|
||||
|
||||
WidgetT *prev = sLastSelectedWidget;
|
||||
sLastSelectedWidget = except;
|
||||
|
||||
if (!prev || prev == except) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the widget is still alive (its window still in the stack)
|
||||
WindowT *prevWin = prev->window;
|
||||
|
||||
if (!prevWin) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppContextT *ctx = wgtGetContext(except);
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
||||
if (ctx->stack.windows[i] == prevWin) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clearSelectionOnWidget(prev) && prevWin != except->window) {
|
||||
dvxInvalidateWindow(ctx, prevWin);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool clearSelectionOnWidget(WidgetT *w) {
|
||||
if (w->wclass && w->wclass->clearSelection) {
|
||||
return w->wclass->clearSelection(w);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
bool isWordChar(char c) {
|
||||
return isalnum((unsigned char)c) || c == '_';
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Shared undo/selection helpers
|
||||
// ============================================================
|
||||
|
||||
static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd) {
|
||||
int32_t lo = *pSelStart < *pSelEnd ? *pSelStart : *pSelEnd;
|
||||
int32_t hi = *pSelStart < *pSelEnd ? *pSelEnd : *pSelStart;
|
||||
|
||||
if (lo < 0) {
|
||||
lo = 0;
|
||||
}
|
||||
|
||||
if (hi > *pLen) {
|
||||
hi = *pLen;
|
||||
}
|
||||
|
||||
if (lo >= hi) {
|
||||
*pSelStart = -1;
|
||||
*pSelEnd = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
memmove(buf + lo, buf + hi, *pLen - hi + 1);
|
||||
*pLen -= (hi - lo);
|
||||
*pCursor = lo;
|
||||
*pSelStart = -1;
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
|
||||
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize) {
|
||||
if (!undoBuf) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t copyLen = len < bufSize ? len : bufSize - 1;
|
||||
memcpy(undoBuf, buf, copyLen);
|
||||
undoBuf[copyLen] = '\0';
|
||||
*pUndoLen = copyLen;
|
||||
*pUndoCursor = cursor;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextEditDragUpdateLine
|
||||
// ============================================================
|
||||
//
|
||||
// Called during mouse drag to extend the selection for single-line
|
||||
// text widgets. Auto-scrolls when the mouse moves past the visible
|
||||
// text edges.
|
||||
|
||||
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd) {
|
||||
int32_t rightEdge = leftEdge + maxChars * font->charWidth;
|
||||
|
||||
if (vx < leftEdge && *pScrollOff > 0) {
|
||||
(*pScrollOff)--;
|
||||
} else if (vx >= rightEdge && *pScrollOff + maxChars < len) {
|
||||
(*pScrollOff)++;
|
||||
}
|
||||
|
||||
int32_t relX = vx - leftEdge;
|
||||
int32_t charPos = relX / font->charWidth + *pScrollOff;
|
||||
|
||||
if (charPos < 0) {
|
||||
charPos = 0;
|
||||
}
|
||||
|
||||
if (charPos > len) {
|
||||
charPos = len;
|
||||
}
|
||||
|
||||
*pCursorPos = charPos;
|
||||
*pSelEnd = charPos;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextEditMouseClick
|
||||
// ============================================================
|
||||
//
|
||||
// Computes cursor position from pixel coordinates, handles multi-click
|
||||
// (double = word select, triple = select all), and optionally starts
|
||||
// drag-select.
|
||||
|
||||
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect) {
|
||||
int32_t relX = vx - textLeftX;
|
||||
int32_t charPos = relX / font->charWidth + scrollOff;
|
||||
|
||||
if (charPos < 0) {
|
||||
charPos = 0;
|
||||
}
|
||||
|
||||
if (charPos > len) {
|
||||
charPos = len;
|
||||
}
|
||||
|
||||
int32_t clicks = multiClickDetect(vx, vy);
|
||||
|
||||
if (clicks >= 3) {
|
||||
*pSelStart = 0;
|
||||
*pSelEnd = len;
|
||||
*pCursorPos = len;
|
||||
sDragTextSelect = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (clicks == 2) {
|
||||
if (wordSelect && buf) {
|
||||
int32_t ws = wordStart(buf, charPos);
|
||||
int32_t we = wordEnd(buf, len, charPos);
|
||||
*pSelStart = ws;
|
||||
*pSelEnd = we;
|
||||
*pCursorPos = we;
|
||||
} else {
|
||||
*pSelStart = 0;
|
||||
*pSelEnd = len;
|
||||
*pCursorPos = len;
|
||||
}
|
||||
|
||||
sDragTextSelect = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Single click: place cursor
|
||||
*pCursorPos = charPos;
|
||||
*pSelStart = charPos;
|
||||
*pSelEnd = charPos;
|
||||
sDragTextSelect = dragSelect ? w : NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextEditOnKey -- shared single-line text editing logic
|
||||
// ============================================================
|
||||
//
|
||||
// This is the core single-line text editing engine, parameterized by
|
||||
// pointer to allow reuse across TextInput, Spinner, and ComboBox.
|
||||
|
||||
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor) {
|
||||
bool shift = (mod & KEY_MOD_SHIFT) != 0;
|
||||
bool hasSel = (pSelStart && pSelEnd && *pSelStart >= 0 && *pSelEnd >= 0 && *pSelStart != *pSelEnd);
|
||||
int32_t selLo = hasSel ? (*pSelStart < *pSelEnd ? *pSelStart : *pSelEnd) : -1;
|
||||
int32_t selHi = hasSel ? (*pSelStart < *pSelEnd ? *pSelEnd : *pSelStart) : -1;
|
||||
|
||||
// Clamp selection to buffer bounds
|
||||
if (hasSel) {
|
||||
if (selLo < 0) {
|
||||
selLo = 0;
|
||||
}
|
||||
|
||||
if (selHi > *pLen) {
|
||||
selHi = *pLen;
|
||||
}
|
||||
|
||||
if (selLo >= selHi) {
|
||||
hasSel = false;
|
||||
selLo = -1;
|
||||
selHi = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Ctrl+A -- select all
|
||||
if (key == 1 && pSelStart && pSelEnd) {
|
||||
*pSelStart = 0;
|
||||
*pSelEnd = *pLen;
|
||||
*pCursor = *pLen;
|
||||
goto adjustScroll;
|
||||
}
|
||||
|
||||
// Ctrl+C -- copy
|
||||
if (key == 3) {
|
||||
if (hasSel) {
|
||||
clipboardCopy(buf + selLo, selHi - selLo);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+V -- paste
|
||||
if (key == 22) {
|
||||
int32_t cbLen = 0;
|
||||
const char *cb = clipboardGet(&cbLen);
|
||||
|
||||
if (cbLen > 0) {
|
||||
if (undoBuf) {
|
||||
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
|
||||
}
|
||||
|
||||
if (hasSel) {
|
||||
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
|
||||
}
|
||||
|
||||
int32_t canFit = bufSize - 1 - *pLen;
|
||||
// For single-line, skip newlines in clipboard
|
||||
int32_t paste = 0;
|
||||
|
||||
for (int32_t i = 0; i < cbLen && paste < canFit; i++) {
|
||||
if (cb[i] != '\n' && cb[i] != '\r') {
|
||||
paste++;
|
||||
}
|
||||
}
|
||||
|
||||
if (paste > 0) {
|
||||
int32_t pos = *pCursor;
|
||||
memmove(buf + pos + paste, buf + pos, *pLen - pos + 1);
|
||||
|
||||
int32_t j = 0;
|
||||
|
||||
for (int32_t i = 0; i < cbLen && j < paste; i++) {
|
||||
if (cb[i] != '\n' && cb[i] != '\r') {
|
||||
buf[pos + j] = cb[i];
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
||||
*pLen += paste;
|
||||
*pCursor += paste;
|
||||
}
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
|
||||
goto adjustScroll;
|
||||
}
|
||||
|
||||
// Ctrl+X -- cut
|
||||
if (key == 24) {
|
||||
if (hasSel) {
|
||||
clipboardCopy(buf + selLo, selHi - selLo);
|
||||
|
||||
if (undoBuf) {
|
||||
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
|
||||
}
|
||||
|
||||
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
|
||||
goto adjustScroll;
|
||||
}
|
||||
|
||||
// Ctrl+Z -- undo
|
||||
if (key == 26 && undoBuf && pUndoLen && pUndoCursor) {
|
||||
// Swap current and undo
|
||||
char tmpBuf[clipboardMaxLen() + 1];
|
||||
int32_t tmpLen = *pLen;
|
||||
int32_t tmpCursor = *pCursor;
|
||||
int32_t copyLen = tmpLen < (int32_t)sizeof(tmpBuf) - 1 ? tmpLen : (int32_t)sizeof(tmpBuf) - 1;
|
||||
|
||||
memcpy(tmpBuf, buf, copyLen);
|
||||
tmpBuf[copyLen] = '\0';
|
||||
|
||||
int32_t restLen = *pUndoLen < bufSize - 1 ? *pUndoLen : bufSize - 1;
|
||||
memcpy(buf, undoBuf, restLen);
|
||||
buf[restLen] = '\0';
|
||||
*pLen = restLen;
|
||||
|
||||
int32_t restoreCursor = *pUndoCursor < *pLen ? *pUndoCursor : *pLen;
|
||||
|
||||
// Save old as new undo
|
||||
int32_t saveLen = copyLen < bufSize - 1 ? copyLen : bufSize - 1;
|
||||
memcpy(undoBuf, tmpBuf, saveLen);
|
||||
undoBuf[saveLen] = '\0';
|
||||
*pUndoLen = saveLen;
|
||||
*pUndoCursor = tmpCursor;
|
||||
|
||||
*pCursor = restoreCursor;
|
||||
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
|
||||
goto adjustScroll;
|
||||
}
|
||||
|
||||
if (key >= 32 && key < 127) {
|
||||
// Printable character
|
||||
if (undoBuf) {
|
||||
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
|
||||
}
|
||||
|
||||
if (hasSel) {
|
||||
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
|
||||
}
|
||||
|
||||
if (*pLen < bufSize - 1) {
|
||||
int32_t pos = *pCursor;
|
||||
memmove(buf + pos + 1, buf + pos, *pLen - pos + 1);
|
||||
buf[pos] = (char)key;
|
||||
(*pLen)++;
|
||||
(*pCursor)++;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
} else if (key == 8) {
|
||||
// Backspace
|
||||
if (hasSel) {
|
||||
if (undoBuf) {
|
||||
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
|
||||
}
|
||||
|
||||
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
} else if (*pCursor > 0) {
|
||||
if (undoBuf) {
|
||||
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
|
||||
}
|
||||
|
||||
int32_t pos = *pCursor;
|
||||
memmove(buf + pos - 1, buf + pos, *pLen - pos + 1);
|
||||
(*pLen)--;
|
||||
(*pCursor)--;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
} else if (key == (0x4B | 0x100)) {
|
||||
// Left arrow
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
if (*pCursor > 0) {
|
||||
(*pCursor)--;
|
||||
}
|
||||
|
||||
*pSelEnd = *pCursor;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
if (*pCursor > 0) {
|
||||
(*pCursor)--;
|
||||
}
|
||||
}
|
||||
} else if (key == (0x4D | 0x100)) {
|
||||
// Right arrow
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
if (*pCursor < *pLen) {
|
||||
(*pCursor)++;
|
||||
}
|
||||
|
||||
*pSelEnd = *pCursor;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
if (*pCursor < *pLen) {
|
||||
(*pCursor)++;
|
||||
}
|
||||
}
|
||||
} else if (key == (0x73 | 0x100)) {
|
||||
// Ctrl+Left -- word left
|
||||
int32_t newPos = wordBoundaryLeft(buf, *pCursor);
|
||||
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
*pSelEnd = newPos;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
}
|
||||
} else if (key == (0x74 | 0x100)) {
|
||||
// Ctrl+Right -- word right
|
||||
int32_t newPos = wordBoundaryRight(buf, *pLen, *pCursor);
|
||||
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
*pSelEnd = newPos;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
}
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
*pCursor = 0;
|
||||
*pSelEnd = 0;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
*pCursor = 0;
|
||||
}
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
// End
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
*pCursor = *pLen;
|
||||
*pSelEnd = *pLen;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
*pCursor = *pLen;
|
||||
}
|
||||
} else if (key == (0x53 | 0x100)) {
|
||||
// Delete
|
||||
if (hasSel) {
|
||||
if (undoBuf) {
|
||||
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
|
||||
}
|
||||
|
||||
textEditDeleteSelection(buf, pLen, pCursor, pSelStart, pSelEnd);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
} else if (*pCursor < *pLen) {
|
||||
if (undoBuf) {
|
||||
textEditSaveUndo(buf, *pLen, *pCursor, undoBuf, pUndoLen, pUndoCursor, bufSize);
|
||||
}
|
||||
|
||||
int32_t pos = *pCursor;
|
||||
memmove(buf + pos, buf + pos + 1, *pLen - pos);
|
||||
(*pLen)--;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
adjustScroll:
|
||||
// Adjust scroll offset to keep cursor visible
|
||||
{
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t fieldW = w->w;
|
||||
|
||||
if (w->wclass && w->wclass->getTextFieldWidth) {
|
||||
fieldW = w->wclass->getTextFieldWidth(w);
|
||||
}
|
||||
|
||||
int32_t visibleChars = (fieldW - TEXT_INPUT_PAD * 2) / font->charWidth;
|
||||
|
||||
if (*pCursor < *pScrollOff) {
|
||||
*pScrollOff = *pCursor;
|
||||
}
|
||||
|
||||
if (*pCursor >= *pScrollOff + visibleChars) {
|
||||
*pScrollOff = *pCursor - visibleChars + 1;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextEditPaintLine
|
||||
// ============================================================
|
||||
//
|
||||
// Renders a single line of text with optional selection highlighting
|
||||
// and a blinking cursor.
|
||||
|
||||
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX) {
|
||||
// Normalize selection to low/high
|
||||
int32_t selLo = -1;
|
||||
int32_t selHi = -1;
|
||||
|
||||
if (selStart >= 0 && selEnd >= 0 && selStart != selEnd) {
|
||||
selLo = selStart < selEnd ? selStart : selEnd;
|
||||
selHi = selStart < selEnd ? selEnd : selStart;
|
||||
}
|
||||
|
||||
// Map selection to visible range
|
||||
int32_t visSelLo = selLo - scrollOff;
|
||||
int32_t visSelHi = selHi - scrollOff;
|
||||
|
||||
if (visSelLo < 0) { visSelLo = 0; }
|
||||
if (visSelHi > visLen) { visSelHi = visLen; }
|
||||
|
||||
if (selLo >= 0 && visSelLo < visSelHi) {
|
||||
if (visSelLo > 0) {
|
||||
drawTextN(d, ops, font, textX, textY, buf, visSelLo, fg, bg, true);
|
||||
}
|
||||
|
||||
drawTextN(d, ops, font, textX + visSelLo * font->charWidth, textY, buf + visSelLo, visSelHi - visSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
||||
|
||||
if (visSelHi < visLen) {
|
||||
drawTextN(d, ops, font, textX + visSelHi * font->charWidth, textY, buf + visSelHi, visLen - visSelHi, fg, bg, true);
|
||||
}
|
||||
} else if (visLen > 0) {
|
||||
drawTextN(d, ops, font, textX, textY, buf, visLen, fg, bg, true);
|
||||
}
|
||||
|
||||
// Blinking cursor
|
||||
if (showCursor && sCursorBlinkOn) {
|
||||
int32_t cursorX = textX + (cursorPos - scrollOff) * font->charWidth;
|
||||
|
||||
if (cursorX >= cursorMinX && cursorX < cursorMaxX) {
|
||||
drawVLine(d, ops, cursorX, textY, font->charHeight, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Word boundary helpers
|
||||
// ============================================================
|
||||
|
||||
static int32_t wordBoundaryLeft(const char *buf, int32_t pos) {
|
||||
if (pos <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip non-word characters
|
||||
while (pos > 0 && !isalnum((unsigned char)buf[pos - 1]) && buf[pos - 1] != '_') {
|
||||
pos--;
|
||||
}
|
||||
|
||||
// Skip word characters
|
||||
while (pos > 0 && (isalnum((unsigned char)buf[pos - 1]) || buf[pos - 1] == '_')) {
|
||||
pos--;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos) {
|
||||
if (pos >= len) {
|
||||
return len;
|
||||
}
|
||||
|
||||
// Skip word characters
|
||||
while (pos < len && (isalnum((unsigned char)buf[pos]) || buf[pos] == '_')) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Skip non-word characters
|
||||
while (pos < len && !isalnum((unsigned char)buf[pos]) && buf[pos] != '_') {
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
int32_t wordEnd(const char *buf, int32_t len, int32_t pos) {
|
||||
while (pos < len && isWordChar(buf[pos])) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
int32_t wordStart(const char *buf, int32_t pos) {
|
||||
while (pos > 0 && isWordChar(buf[pos - 1])) {
|
||||
pos--;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
38
texthelp/textHelp.h
Normal file
38
texthelp/textHelp.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// textHelp.h -- Public API for the shared text editing infrastructure library
|
||||
//
|
||||
// Declares clipboard operations, multi-click detection, word boundary
|
||||
// logic, cross-widget selection clearing, and the single-line text
|
||||
// editing engine shared across widget DXEs (TextInput, Spinner,
|
||||
// ComboBox, AnsiTerm, etc.).
|
||||
|
||||
#ifndef TEXT_HELP_H
|
||||
#define TEXT_HELP_H
|
||||
|
||||
#include "../core/dvxWidgetPlugin.h"
|
||||
|
||||
#define TEXT_INPUT_PAD 3
|
||||
|
||||
// ============================================================
|
||||
// Selection management
|
||||
// ============================================================
|
||||
|
||||
void clearOtherSelections(WidgetT *except);
|
||||
|
||||
// ============================================================
|
||||
// Word / character helpers
|
||||
// ============================================================
|
||||
|
||||
bool isWordChar(char c);
|
||||
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
||||
int32_t wordStart(const char *buf, int32_t pos);
|
||||
|
||||
// ============================================================
|
||||
// Single-line text editing engine
|
||||
// ============================================================
|
||||
|
||||
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
|
||||
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
|
||||
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
||||
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
|
||||
|
||||
#endif // TEXT_HELP_H
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
DJGPP_PREFIX = $(HOME)/djgpp/djgpp
|
||||
CC = $(DJGPP_PREFIX)/bin/i586-pc-msdosdjgpp-gcc
|
||||
DXE3GEN = PATH=$(DJGPP_PREFIX)/bin:$(PATH) DJDIR=$(DJGPP_PREFIX)/i586-pc-msdosdjgpp $(DJGPP_PREFIX)/i586-pc-msdosdjgpp/bin/dxe3gen
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../tasks/thirdparty
|
||||
CFLAGS = -O2 -Wall -Wextra -march=i486 -mtune=i586 -I../core -I../core/platform -I../tasks -I../core/thirdparty
|
||||
|
||||
OBJDIR = ../obj/widgets
|
||||
WGTDIR = ../bin/widgets
|
||||
|
|
@ -69,11 +69,18 @@ WGT_MODS = $(WGTDIR)/box.wgt \
|
|||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(WGT_MODS)
|
||||
DEPFILES = textinpt combobox spinner terminal listbox dropdown listview treeview
|
||||
WGT_DEPS = $(DEPFILES:%=$(WGTDIR)/%.dep)
|
||||
|
||||
# Generic widget module rule: -E _wgt exports all public API + wgtRegister
|
||||
all: $(WGT_MODS) $(WGT_DEPS)
|
||||
|
||||
$(WGTDIR)/%.dep: ../config/%.dep | $(WGTDIR)
|
||||
sed 's/$$/\r/' $< > $@
|
||||
|
||||
# Generic widget module rule: export only the registration function
|
||||
$(WGTDIR)/%.wgt: | $(WGTDIR)
|
||||
$(DXE3GEN) -o $@ -E _wgt -U $<
|
||||
$(DXE3GEN) -o $(WGTDIR)/$*.dxe -E _wgtRegister -U $<
|
||||
mv $(WGTDIR)/$*.dxe $@
|
||||
|
||||
# Map .wgt name to .o file
|
||||
$(WGTDIR)/box.wgt: $(OBJDIR)/widgetBox.o
|
||||
|
|
@ -114,7 +121,7 @@ $(WGTDIR):
|
|||
mkdir -p $(WGTDIR)
|
||||
|
||||
# Dependencies
|
||||
WIDGET_DEPS = ../core/widgetInternal.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
|
||||
WIDGET_DEPS = ../core/dvxWidgetPlugin.h ../core/dvxWidget.h ../core/dvxTypes.h ../core/dvxApp.h ../core/dvxDraw.h ../core/dvxWm.h ../core/dvxVideo.h
|
||||
$(OBJDIR)/widgetAnsiTerm.o: widgetAnsiTerm.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetBox.o: widgetBox.c $(WIDGET_DEPS)
|
||||
$(OBJDIR)/widgetButton.o: widgetButton.c $(WIDGET_DEPS)
|
||||
|
|
@ -143,5 +150,5 @@ $(OBJDIR)/widgetToolbar.o: widgetToolbar.c $(WIDGET_DEPS)
|
|||
$(OBJDIR)/widgetTreeView.o: widgetTreeView.c $(WIDGET_DEPS)
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(WGT_MODS)
|
||||
rm -f $(OBJS) $(WGT_MODS) $(WGT_DEPS)
|
||||
-rmdir $(WGTDIR) 2>/dev/null
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ multiple related types).
|
|||
## Writing a New Widget
|
||||
|
||||
1. Create `widgets/myWidget.c`
|
||||
2. Include `"widgetInternal.h"` for infrastructure access
|
||||
2. Include `"dvxWidgetPlugin.h"` for infrastructure access
|
||||
3. Implement paint, calcMinSize, and event handlers as needed
|
||||
4. Define a `static const WidgetClassT` with your vtable
|
||||
5. Add a `wgtRegister()` function:
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
31
widgets/widgetAnsiTerm.h
Normal file
31
widgets/widgetAnsiTerm.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// widgetAnsiTerm.h -- ANSI terminal widget API
|
||||
#ifndef WIDGET_ANSITERM_H
|
||||
#define WIDGET_ANSITERM_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t cols, int32_t rows);
|
||||
void (*write)(WidgetT *w, const uint8_t *data, int32_t len);
|
||||
void (*clear)(WidgetT *w);
|
||||
void (*setComm)(WidgetT *w, void *ctx, int32_t (*readFn)(void *, uint8_t *, int32_t), int32_t (*writeFn)(void *, const uint8_t *, int32_t));
|
||||
void (*setScrollback)(WidgetT *w, int32_t maxLines);
|
||||
void (*poll)(WidgetT *w);
|
||||
int32_t (*repaint)(WidgetT *w, int32_t *outY, int32_t *outH);
|
||||
} AnsiTermApiT;
|
||||
|
||||
static inline const AnsiTermApiT *dvxAnsiTermApi(void) {
|
||||
static const AnsiTermApiT *sApi;
|
||||
if (!sApi) { sApi = (const AnsiTermApiT *)wgtGetApi("ansiterm"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtAnsiTerm(parent, cols, rows) dvxAnsiTermApi()->create(parent, cols, rows)
|
||||
#define wgtAnsiTermWrite(w, data, len) dvxAnsiTermApi()->write(w, data, len)
|
||||
#define wgtAnsiTermClear(w) dvxAnsiTermApi()->clear(w)
|
||||
#define wgtAnsiTermSetComm(w, ctx, readFn, writeFn) dvxAnsiTermApi()->setComm(w, ctx, readFn, writeFn)
|
||||
#define wgtAnsiTermSetScrollback(w, maxLines) dvxAnsiTermApi()->setScrollback(w, maxLines)
|
||||
#define wgtAnsiTermPoll(w) dvxAnsiTermApi()->poll(w)
|
||||
#define wgtAnsiTermRepaint(w, outY, outH) dvxAnsiTermApi()->repaint(w, outY, outH)
|
||||
|
||||
#endif // WIDGET_ANSITERM_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetBox.c -- VBox, HBox, and Frame container widgets
|
||||
//
|
||||
// VBox and HBox are the primary layout containers. They have no visual
|
||||
|
|
@ -18,7 +19,23 @@
|
|||
// "break" the border behind the title -- this is the classic Win3.1/Motif
|
||||
// group box appearance.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
static int32_t sVBoxTypeId = -1;
|
||||
static int32_t sHBoxTypeId = -1;
|
||||
static int32_t sFrameTypeId = -1;
|
||||
|
||||
typedef enum {
|
||||
FrameInE,
|
||||
FrameOutE,
|
||||
FrameFlatE
|
||||
} FrameStyleE;
|
||||
|
||||
typedef struct {
|
||||
const char *title;
|
||||
FrameStyleE style;
|
||||
uint32_t color;
|
||||
} FrameDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -37,15 +54,16 @@
|
|||
// FrameOutE -- Motif "ridge" (outset bevel: highlight-then-shadow)
|
||||
// The groove/ridge are each two nested 1px bevels with swapped colors.
|
||||
void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
FrameDataT *fd = (FrameDataT *)w->data;
|
||||
int32_t fb = widgetFrameBorderWidth(w);
|
||||
int32_t boxY = w->y + font->charHeight / 2;
|
||||
int32_t boxH = w->h - font->charHeight / 2;
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
if (w->as.frame.style == FrameFlatE) {
|
||||
if (fd->style == FrameFlatE) {
|
||||
// Flat: solid color rectangle outline
|
||||
uint32_t fc = w->as.frame.color ? w->as.frame.color : colors->windowShadow;
|
||||
uint32_t fc = fd->color ? fd->color : colors->windowShadow;
|
||||
|
||||
drawHLine(d, ops, w->x, boxY, w->w, fc);
|
||||
drawHLine(d, ops, w->x, boxY + boxH - 1, w->w, fc);
|
||||
|
|
@ -56,7 +74,7 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
BevelStyleT outer;
|
||||
BevelStyleT inner;
|
||||
|
||||
if (w->as.frame.style == FrameInE) {
|
||||
if (fd->style == FrameInE) {
|
||||
outer.highlight = colors->windowShadow;
|
||||
outer.shadow = colors->windowHighlight;
|
||||
inner.highlight = colors->windowHighlight;
|
||||
|
|
@ -77,8 +95,8 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
}
|
||||
|
||||
// Draw title centered vertically on the top border line
|
||||
if (w->as.frame.title && w->as.frame.title[0]) {
|
||||
int32_t titleW = textWidthAccel(font, w->as.frame.title);
|
||||
if (fd->title && fd->title[0]) {
|
||||
int32_t titleW = textWidthAccel(font, fd->title);
|
||||
int32_t titleX = w->x + DEFAULT_PADDING + fb;
|
||||
int32_t titleY = boxY + (fb - font->charHeight) / 2;
|
||||
|
||||
|
|
@ -86,50 +104,33 @@ void widgetFramePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
titleW + 4, font->charHeight, bg);
|
||||
|
||||
if (!w->enabled) {
|
||||
drawTextAccelEmbossed(d, ops, font, titleX, titleY, w->as.frame.title, colors);
|
||||
drawTextAccelEmbossed(d, ops, font, titleX, titleY, fd->title, colors);
|
||||
} else {
|
||||
drawTextAccel(d, ops, font, titleX, titleY, w->as.frame.title, fg, bg, true);
|
||||
drawTextAccel(d, ops, font, titleX, titleY, fd->title, fg, bg, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtFrame
|
||||
// widgetFrameGetLayoutMetrics
|
||||
// ============================================================
|
||||
|
||||
// Create a Frame container. The title string supports accelerator keys
|
||||
// (prefixed with '&') which are parsed by accelParse. The title pointer
|
||||
// is stored directly (not copied) -- the caller must ensure it remains valid.
|
||||
WidgetT *wgtFrame(WidgetT *parent, const char *title) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetFrameE);
|
||||
|
||||
if (w) {
|
||||
w->as.frame.title = title;
|
||||
w->as.frame.style = FrameInE;
|
||||
w->as.frame.color = 0;
|
||||
w->accelKey = accelParse(title);
|
||||
}
|
||||
|
||||
return w;
|
||||
void widgetFrameGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW) {
|
||||
FrameDataT *fd = (FrameDataT *)w->data;
|
||||
*pad = DEFAULT_PADDING;
|
||||
*gap = 0;
|
||||
*extraTop = font ? font->charHeight / 2 : 0;
|
||||
*borderW = (fd && fd->style == FrameFlatE) ? 1 : 2;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtHBox
|
||||
// widgetFrameDestroy
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtHBox(WidgetT *parent) {
|
||||
return widgetAlloc(parent, WidgetHBoxE);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtVBox
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtVBox(WidgetT *parent) {
|
||||
return widgetAlloc(parent, WidgetVBoxE);
|
||||
void widgetFrameDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -165,20 +166,68 @@ static const WidgetClassT sClassHBox = {
|
|||
};
|
||||
|
||||
static const WidgetClassT sClassFrame = {
|
||||
.flags = WCLASS_BOX_CONTAINER,
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_FOCUS_FORWARD,
|
||||
.paint = widgetFramePaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetFrameDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
.setText = NULL,
|
||||
.getLayoutMetrics = widgetFrameGetLayoutMetrics
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtFrame(WidgetT *parent, const char *title) {
|
||||
WidgetT *w = widgetAlloc(parent, sFrameTypeId);
|
||||
|
||||
if (w) {
|
||||
FrameDataT *fd = calloc(1, sizeof(FrameDataT));
|
||||
fd->title = title;
|
||||
fd->style = FrameInE;
|
||||
fd->color = 0;
|
||||
w->data = fd;
|
||||
w->accelKey = accelParse(title);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
WidgetT *wgtHBox(WidgetT *parent) {
|
||||
return widgetAlloc(parent, sHBoxTypeId);
|
||||
}
|
||||
|
||||
|
||||
WidgetT *wgtVBox(WidgetT *parent) {
|
||||
return widgetAlloc(parent, sVBoxTypeId);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*vBox)(WidgetT *parent);
|
||||
WidgetT *(*hBox)(WidgetT *parent);
|
||||
WidgetT *(*frame)(WidgetT *parent, const char *title);
|
||||
} sApi = {
|
||||
.vBox = wgtVBox,
|
||||
.hBox = wgtHBox,
|
||||
.frame = wgtFrame
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetVBoxE] = &sClassVBox;
|
||||
widgetClassTable[WidgetHBoxE] = &sClassHBox;
|
||||
widgetClassTable[WidgetFrameE] = &sClassFrame;
|
||||
sVBoxTypeId = wgtRegisterClass(&sClassVBox);
|
||||
sHBoxTypeId = wgtRegisterClass(&sClassHBox);
|
||||
sFrameTypeId = wgtRegisterClass(&sClassFrame);
|
||||
wgtRegisterApi("box", &sApi);
|
||||
}
|
||||
|
|
|
|||
23
widgets/widgetBox.h
Normal file
23
widgets/widgetBox.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// widgetBox.h -- Box container widget API (VBox, HBox, Frame)
|
||||
#ifndef WIDGET_BOX_H
|
||||
#define WIDGET_BOX_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*vBox)(WidgetT *parent);
|
||||
WidgetT *(*hBox)(WidgetT *parent);
|
||||
WidgetT *(*frame)(WidgetT *parent, const char *title);
|
||||
} BoxApiT;
|
||||
|
||||
static inline const BoxApiT *dvxBoxApi(void) {
|
||||
static const BoxApiT *sApi;
|
||||
if (!sApi) { sApi = (const BoxApiT *)wgtGetApi("box"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtVBox(parent) dvxBoxApi()->vBox(parent)
|
||||
#define wgtHBox(parent) dvxBoxApi()->hBox(parent)
|
||||
#define wgtFrame(parent, title) dvxBoxApi()->frame(parent, title)
|
||||
|
||||
#endif // WIDGET_BOX_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetButton.c -- Button widget
|
||||
//
|
||||
// Standard push button with text label, Motif-style 2px beveled border,
|
||||
|
|
@ -19,42 +20,38 @@
|
|||
// Text supports accelerator keys via '&' prefix (e.g., "&OK" underlines 'O'
|
||||
// and Alt+O activates the button).
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#define BUTTON_PAD_H 8
|
||||
#define BUTTON_PAD_V 4
|
||||
#define BUTTON_FOCUS_INSET 3
|
||||
#define BUTTON_PRESS_OFFSET 1
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
const char *text;
|
||||
bool pressed;
|
||||
} ButtonDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtButton
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtButton(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetButtonE);
|
||||
|
||||
if (w) {
|
||||
w->as.button.text = text;
|
||||
w->as.button.pressed = false;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
static void widgetButtonDestroy(WidgetT *w);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonGetText
|
||||
// widgetButtonAccelActivate
|
||||
// ============================================================
|
||||
|
||||
const char *widgetButtonGetText(const WidgetT *w) {
|
||||
return w->as.button.text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetButtonSetText(WidgetT *w, const char *text) {
|
||||
w->as.button.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
void widgetButtonAccelActivate(WidgetT *w, WidgetT *root) {
|
||||
(void)root;
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
d->pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -66,11 +63,32 @@ void widgetButtonSetText(WidgetT *w, const char *text) {
|
|||
// width is computed by textWidthAccel which excludes the '&' accelerator prefix
|
||||
// character from the measurement.
|
||||
void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
w->calcMinW = textWidthAccel(font, w->as.button.text) + BUTTON_PAD_H * 2;
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
w->calcMinW = textWidthAccel(font, d->text) + BUTTON_PAD_H * 2;
|
||||
w->calcMinH = font->charHeight + BUTTON_PAD_V * 2;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetButtonDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonGetText
|
||||
// ============================================================
|
||||
|
||||
const char *widgetButtonGetText(const WidgetT *w) {
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
return d->text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonOnKey
|
||||
// ============================================================
|
||||
|
|
@ -79,7 +97,8 @@ void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
(void)mod;
|
||||
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
w->as.button.pressed = true;
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
d->pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
|
@ -94,8 +113,9 @@ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
w->focused = true;
|
||||
w->as.button.pressed = true;
|
||||
d->pressed = true;
|
||||
sPressedButton = w;
|
||||
}
|
||||
|
||||
|
|
@ -111,56 +131,112 @@ void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// shadow color at 0,0) to create a chiseled/etched look -- this is the
|
||||
// standard Windows 3.1 disabled control appearance.
|
||||
void widgetButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
ButtonDataT *bd = (ButtonDataT *)w->data;
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
|
||||
|
||||
BevelStyleT bevel;
|
||||
bevel.highlight = w->as.button.pressed ? colors->windowShadow : colors->windowHighlight;
|
||||
bevel.shadow = w->as.button.pressed ? colors->windowHighlight : colors->windowShadow;
|
||||
bevel.highlight = bd->pressed ? colors->windowShadow : colors->windowHighlight;
|
||||
bevel.shadow = bd->pressed ? colors->windowHighlight : colors->windowShadow;
|
||||
bevel.face = bgFace;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
|
||||
int32_t textW = textWidthAccel(font, w->as.button.text);
|
||||
int32_t textW = textWidthAccel(font, bd->text);
|
||||
int32_t textX = w->x + (w->w - textW) / 2;
|
||||
int32_t textY = w->y + (w->h - font->charHeight) / 2;
|
||||
|
||||
if (w->as.button.pressed) {
|
||||
if (bd->pressed) {
|
||||
textX += BUTTON_PRESS_OFFSET;
|
||||
textY += BUTTON_PRESS_OFFSET;
|
||||
}
|
||||
|
||||
if (!w->enabled) {
|
||||
drawTextAccelEmbossed(d, ops, font, textX, textY, w->as.button.text, colors);
|
||||
drawTextAccelEmbossed(d, ops, font, textX, textY, bd->text, colors);
|
||||
} else {
|
||||
drawTextAccel(d, ops, font, textX, textY, w->as.button.text, fg, bgFace, true);
|
||||
drawTextAccel(d, ops, font, textX, textY, bd->text, fg, bgFace, true);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
int32_t off = w->as.button.pressed ? BUTTON_PRESS_OFFSET : 0;
|
||||
int32_t off = bd->pressed ? BUTTON_PRESS_OFFSET : 0;
|
||||
drawFocusRect(d, ops, w->x + BUTTON_FOCUS_INSET + off, w->y + BUTTON_FOCUS_INSET + off, w->w - BUTTON_FOCUS_INSET * 2, w->h - BUTTON_FOCUS_INSET * 2, fg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonSetPressed
|
||||
// ============================================================
|
||||
|
||||
void widgetButtonSetPressed(WidgetT *w, bool pressed) {
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
d->pressed = pressed;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetButtonSetText(WidgetT *w, const char *text) {
|
||||
ButtonDataT *d = (ButtonDataT *)w->data;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const WidgetClassT sClassButton = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
|
||||
.paint = widgetButtonPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetButtonCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetButtonOnMouse,
|
||||
.onKey = widgetButtonOnKey,
|
||||
.destroy = NULL,
|
||||
.onAccelActivate = widgetButtonAccelActivate,
|
||||
.destroy = widgetButtonDestroy,
|
||||
.getText = widgetButtonGetText,
|
||||
.setText = widgetButtonSetText
|
||||
.setText = widgetButtonSetText,
|
||||
.setPressed = widgetButtonSetPressed
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtButton(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
ButtonDataT *d = calloc(1, sizeof(ButtonDataT));
|
||||
w->data = d;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
} sApi = {
|
||||
.create = wgtButton
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetButtonE] = &sClassButton;
|
||||
sTypeId = wgtRegisterClass(&sClassButton);
|
||||
wgtRegisterApi("button", &sApi);
|
||||
}
|
||||
|
|
|
|||
19
widgets/widgetButton.h
Normal file
19
widgets/widgetButton.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// widgetButton.h -- Button widget API
|
||||
#ifndef WIDGET_BUTTON_H
|
||||
#define WIDGET_BUTTON_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
} ButtonApiT;
|
||||
|
||||
static inline const ButtonApiT *dvxButtonApi(void) {
|
||||
static const ButtonApiT *sApi;
|
||||
if (!sApi) { sApi = (const ButtonApiT *)wgtGetApi("button"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtButton(parent, text) dvxButtonApi()->create(parent, text)
|
||||
|
||||
#endif // WIDGET_BUTTON_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetCanvas.c -- Drawable canvas widget (freehand draw, PNG save/load)
|
||||
//
|
||||
// The canvas widget provides a pixel buffer in the display's native pixel
|
||||
|
|
@ -21,17 +22,32 @@
|
|||
// top-left of the canvas content, not the widget. Mouse events translate
|
||||
// widget-space coordinates to canvas-space by subtracting the border offset.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
#define CANVAS_BORDER 2
|
||||
|
||||
typedef struct {
|
||||
uint8_t *pixelData; // pixel buffer in display format
|
||||
int32_t canvasW;
|
||||
int32_t canvasH;
|
||||
int32_t canvasPitch;
|
||||
int32_t canvasBpp; // cached bytes per pixel
|
||||
uint32_t penColor;
|
||||
int32_t penSize;
|
||||
int32_t lastX;
|
||||
int32_t lastY;
|
||||
void (*onMouse)(struct WidgetT *w, int32_t cx, int32_t cy, bool drag);
|
||||
} CanvasDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy);
|
||||
static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
|
||||
static void canvasDrawDot(CanvasDataT *cd, int32_t cx, int32_t cy);
|
||||
static void canvasDrawLine(CanvasDataT *cd, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -72,14 +88,14 @@ static inline void canvasPutPixel(uint8_t *dst, uint32_t color, int32_t bpp) {
|
|||
//
|
||||
// Draw a filled circle of diameter penSize at (cx, cy) in canvas coords.
|
||||
|
||||
static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
||||
int32_t bpp = w->as.canvas.canvasBpp;
|
||||
int32_t pitch = w->as.canvas.canvasPitch;
|
||||
uint8_t *data = w->as.canvas.data;
|
||||
int32_t cw = w->as.canvas.canvasW;
|
||||
int32_t ch = w->as.canvas.canvasH;
|
||||
uint32_t color = w->as.canvas.penColor;
|
||||
int32_t rad = w->as.canvas.penSize / 2;
|
||||
static void canvasDrawDot(CanvasDataT *cd, int32_t cx, int32_t cy) {
|
||||
int32_t bpp = cd->canvasBpp;
|
||||
int32_t pitch = cd->canvasPitch;
|
||||
uint8_t *data = cd->pixelData;
|
||||
int32_t cw = cd->canvasW;
|
||||
int32_t ch = cd->canvasH;
|
||||
uint32_t color = cd->penColor;
|
||||
int32_t rad = cd->penSize / 2;
|
||||
|
||||
if (rad < 1) {
|
||||
// Single pixel
|
||||
|
|
@ -161,7 +177,7 @@ static void canvasDrawDot(WidgetT *w, int32_t cx, int32_t cy) {
|
|||
// chosen over DDA because it's pure integer arithmetic -- no floating point
|
||||
// needed, which matters on 486SX (no FPU).
|
||||
|
||||
static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
|
||||
static void canvasDrawLine(CanvasDataT *cd, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
|
||||
int32_t dx = x1 - x0;
|
||||
int32_t dy = y1 - y0;
|
||||
int32_t sx = (dx >= 0) ? 1 : -1;
|
||||
|
|
@ -173,7 +189,7 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
|
|||
int32_t err = dx - dy;
|
||||
|
||||
for (;;) {
|
||||
canvasDrawDot(w, x0, y0);
|
||||
canvasDrawDot(cd, x0, y0);
|
||||
|
||||
if (x0 == x1 && y0 == y1) {
|
||||
break;
|
||||
|
|
@ -194,79 +210,18 @@ static void canvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvas
|
||||
// ============================================================
|
||||
|
||||
// Create a canvas widget with the specified dimensions. The canvas buffer
|
||||
// is allocated in the display's native pixel format by walking up the widget
|
||||
// tree to find the AppContextT (which holds the display format info). This
|
||||
// tree-walk pattern is necessary because the widget doesn't have direct access
|
||||
// to the display -- only the root widget's userData points to the AppContextT.
|
||||
// The buffer is initialized to white using spanFill for performance.
|
||||
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
|
||||
if (!parent || w <= 0 || h <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find the AppContextT to get display format
|
||||
WidgetT *root = parent;
|
||||
|
||||
while (root->parent) {
|
||||
root = root->parent;
|
||||
}
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
|
||||
if (!ctx) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const DisplayT *d = &ctx->display;
|
||||
int32_t bpp = d->format.bytesPerPixel;
|
||||
int32_t pitch = w * bpp;
|
||||
|
||||
uint8_t *data = (uint8_t *)malloc(pitch * h);
|
||||
|
||||
if (!data) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Fill with white using span fill for performance
|
||||
uint32_t white = packColor(d, 255, 255, 255);
|
||||
BlitOpsT canvasOps;
|
||||
drawInit(&canvasOps, d);
|
||||
|
||||
for (int32_t y = 0; y < h; y++) {
|
||||
canvasOps.spanFill(data + y * pitch, white, w);
|
||||
}
|
||||
|
||||
WidgetT *wgt = widgetAlloc(parent, WidgetCanvasE);
|
||||
|
||||
if (wgt) {
|
||||
wgt->as.canvas.data = data;
|
||||
wgt->as.canvas.canvasW = w;
|
||||
wgt->as.canvas.canvasH = h;
|
||||
wgt->as.canvas.canvasPitch = pitch;
|
||||
wgt->as.canvas.canvasBpp = bpp;
|
||||
wgt->as.canvas.penColor = packColor(d, 0, 0, 0);
|
||||
wgt->as.canvas.penSize = 2;
|
||||
wgt->as.canvas.lastX = -1;
|
||||
wgt->as.canvas.lastY = -1;
|
||||
} else {
|
||||
free(data);
|
||||
}
|
||||
|
||||
return wgt;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvasClear
|
||||
// ============================================================
|
||||
|
||||
void wgtCanvasClear(WidgetT *w, uint32_t color) {
|
||||
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -280,12 +235,12 @@ void wgtCanvasClear(WidgetT *w, uint32_t color) {
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t pitch = w->as.canvas.canvasPitch;
|
||||
int32_t cw = w->as.canvas.canvasW;
|
||||
int32_t ch = w->as.canvas.canvasH;
|
||||
int32_t pitch = cd->canvasPitch;
|
||||
int32_t cw = cd->canvasW;
|
||||
int32_t ch = cd->canvasH;
|
||||
|
||||
for (int32_t y = 0; y < ch; y++) {
|
||||
ctx->blitOps.spanFill(w->as.canvas.data + y * pitch, color, cw);
|
||||
ctx->blitOps.spanFill(cd->pixelData + y * pitch, color, cw);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -297,11 +252,17 @@ void wgtCanvasClear(WidgetT *w, uint32_t color) {
|
|||
// Draw a line using the current pen color and pen size.
|
||||
|
||||
void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1) {
|
||||
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvasDrawLine(w, x0, y0, x1, y1);
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
canvasDrawLine(cd, x0, y0, x1, y1);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -312,7 +273,13 @@ void wgtCanvasDrawLine(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y
|
|||
// Draw a 1px outlined rectangle using the current pen color.
|
||||
|
||||
void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height) {
|
||||
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -320,12 +287,12 @@ void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t bpp = w->as.canvas.canvasBpp;
|
||||
int32_t pitch = w->as.canvas.canvasPitch;
|
||||
uint8_t *data = w->as.canvas.data;
|
||||
int32_t cw = w->as.canvas.canvasW;
|
||||
int32_t ch = w->as.canvas.canvasH;
|
||||
uint32_t color = w->as.canvas.penColor;
|
||||
int32_t bpp = cd->canvasBpp;
|
||||
int32_t pitch = cd->canvasPitch;
|
||||
uint8_t *data = cd->pixelData;
|
||||
int32_t cw = cd->canvasW;
|
||||
int32_t ch = cd->canvasH;
|
||||
uint32_t color = cd->penColor;
|
||||
|
||||
// Top and bottom edges
|
||||
for (int32_t px = x; px < x + width; px++) {
|
||||
|
|
@ -378,7 +345,13 @@ void wgtCanvasDrawRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
|
|||
// Draw a filled circle using the current pen color.
|
||||
|
||||
void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
|
||||
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -386,12 +359,12 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t bpp = w->as.canvas.canvasBpp;
|
||||
int32_t pitch = w->as.canvas.canvasPitch;
|
||||
uint8_t *data = w->as.canvas.data;
|
||||
int32_t cw = w->as.canvas.canvasW;
|
||||
int32_t ch = w->as.canvas.canvasH;
|
||||
uint32_t color = w->as.canvas.penColor;
|
||||
int32_t bpp = cd->canvasBpp;
|
||||
int32_t pitch = cd->canvasPitch;
|
||||
uint8_t *data = cd->pixelData;
|
||||
int32_t cw = cd->canvasW;
|
||||
int32_t ch = cd->canvasH;
|
||||
uint32_t color = cd->penColor;
|
||||
int32_t r2 = radius * radius;
|
||||
|
||||
for (int32_t dy = -radius; dy <= radius; dy++) {
|
||||
|
|
@ -449,7 +422,13 @@ void wgtCanvasFillCircle(WidgetT *w, int32_t cx, int32_t cy, int32_t radius) {
|
|||
// Draw a filled rectangle using the current pen color.
|
||||
|
||||
void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height) {
|
||||
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -457,12 +436,12 @@ void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
|
|||
return;
|
||||
}
|
||||
|
||||
int32_t bpp = w->as.canvas.canvasBpp;
|
||||
int32_t pitch = w->as.canvas.canvasPitch;
|
||||
uint8_t *data = w->as.canvas.data;
|
||||
int32_t cw = w->as.canvas.canvasW;
|
||||
int32_t ch = w->as.canvas.canvasH;
|
||||
uint32_t color = w->as.canvas.penColor;
|
||||
int32_t bpp = cd->canvasBpp;
|
||||
int32_t pitch = cd->canvasPitch;
|
||||
uint8_t *data = cd->pixelData;
|
||||
int32_t cw = cd->canvasW;
|
||||
int32_t ch = cd->canvasH;
|
||||
uint32_t color = cd->penColor;
|
||||
|
||||
// Clip to canvas bounds
|
||||
int32_t x0 = x < 0 ? 0 : x;
|
||||
|
|
@ -505,129 +484,48 @@ void wgtCanvasFillRect(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t
|
|||
// ============================================================
|
||||
|
||||
uint32_t wgtCanvasGetPixel(const WidgetT *w, int32_t x, int32_t y) {
|
||||
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= w->as.canvas.canvasW || y < 0 || y >= w->as.canvas.canvasH) {
|
||||
const CanvasDataT *cd = (const CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t bpp = w->as.canvas.canvasBpp;
|
||||
const uint8_t *src = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
|
||||
if (x < 0 || x >= cd->canvasW || y < 0 || y >= cd->canvasH) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t bpp = cd->canvasBpp;
|
||||
const uint8_t *src = cd->pixelData + y * cd->canvasPitch + x * bpp;
|
||||
|
||||
return canvasGetPixel(src, bpp);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvasLoad
|
||||
// ============================================================
|
||||
|
||||
// Load an image file into the canvas, replacing the current content.
|
||||
// Delegates to dvxLoadImage for format decoding and pixel conversion.
|
||||
// The old buffer is freed and replaced with the new one -- canvas
|
||||
// dimensions change to match the loaded image.
|
||||
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
|
||||
if (!w || w->type != WidgetCanvasE || !path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t imgW;
|
||||
int32_t imgH;
|
||||
int32_t pitch;
|
||||
uint8_t *data = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
|
||||
|
||||
if (!data) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(w->as.canvas.data);
|
||||
w->as.canvas.data = data;
|
||||
w->as.canvas.canvasW = imgW;
|
||||
w->as.canvas.canvasH = imgH;
|
||||
w->as.canvas.canvasPitch = pitch;
|
||||
w->as.canvas.canvasBpp = ctx->display.format.bytesPerPixel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvasSave
|
||||
// ============================================================
|
||||
|
||||
// Save the canvas content to a PNG file. Delegates to dvxSaveImage
|
||||
// which handles native-to-RGB conversion and PNG encoding.
|
||||
int32_t wgtCanvasSave(WidgetT *w, const char *path) {
|
||||
if (!w || w->type != WidgetCanvasE || !path || !w->as.canvas.data) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return dvxSaveImage(ctx, w->as.canvas.data, w->as.canvas.canvasW, w->as.canvas.canvasH, w->as.canvas.canvasPitch, path);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvasSetMouseCallback
|
||||
// ============================================================
|
||||
|
||||
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag)) {
|
||||
if (w && w->type == WidgetCanvasE) {
|
||||
w->as.canvas.onMouse = cb;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvasSetPenColor
|
||||
// ============================================================
|
||||
|
||||
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color) {
|
||||
if (w && w->type == WidgetCanvasE) {
|
||||
w->as.canvas.penColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvasSetPenSize
|
||||
// ============================================================
|
||||
|
||||
void wgtCanvasSetPenSize(WidgetT *w, int32_t size) {
|
||||
if (w && w->type == WidgetCanvasE && size > 0) {
|
||||
w->as.canvas.penSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCanvasSetPixel
|
||||
// ============================================================
|
||||
|
||||
void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color) {
|
||||
if (!w || w->type != WidgetCanvasE || !w->as.canvas.data) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (x < 0 || x >= w->as.canvas.canvasW || y < 0 || y >= w->as.canvas.canvasH) {
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t bpp = w->as.canvas.canvasBpp;
|
||||
uint8_t *dst = w->as.canvas.data + y * w->as.canvas.canvasPitch + x * bpp;
|
||||
if (x < 0 || x >= cd->canvasW || y < 0 || y >= cd->canvasH) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t bpp = cd->canvasBpp;
|
||||
uint8_t *dst = cd->pixelData + y * cd->canvasPitch + x * bpp;
|
||||
|
||||
canvasPutPixel(dst, color, bpp);
|
||||
}
|
||||
|
|
@ -638,7 +536,10 @@ void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color) {
|
|||
// ============================================================
|
||||
|
||||
void widgetCanvasDestroy(WidgetT *w) {
|
||||
free(w->as.canvas.data);
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
free(cd->pixelData);
|
||||
free(cd);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -652,8 +553,10 @@ void widgetCanvasDestroy(WidgetT *w) {
|
|||
// minimum, and the layout engine should respect that.
|
||||
void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
w->calcMinW = w->as.canvas.canvasW + CANVAS_BORDER * 2;
|
||||
w->calcMinH = w->as.canvas.canvasH + CANVAS_BORDER * 2;
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
w->calcMinW = cd->canvasW + CANVAS_BORDER * 2;
|
||||
w->calcMinH = cd->canvasH + CANVAS_BORDER * 2;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -668,8 +571,9 @@ void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// and continuing one.
|
||||
void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
CanvasDataT *cd = (CanvasDataT *)hit->data;
|
||||
|
||||
if (!hit->as.canvas.onMouse) {
|
||||
if (!cd->onMouse) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -681,12 +585,14 @@ void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
|
||||
if (!drag) {
|
||||
sDrawingCanvas = hit;
|
||||
cd->lastX = -1;
|
||||
cd->lastY = -1;
|
||||
}
|
||||
|
||||
hit->as.canvas.lastX = cx;
|
||||
hit->as.canvas.lastY = cy;
|
||||
cd->lastX = cx;
|
||||
cd->lastY = cy;
|
||||
|
||||
hit->as.canvas.onMouse(hit, cx, cy, drag);
|
||||
cd->onMouse(hit, cx, cy, drag);
|
||||
wgtInvalidatePaint(hit);
|
||||
}
|
||||
|
||||
|
|
@ -701,8 +607,9 @@ void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// repaint essentially free relative to the display bandwidth.
|
||||
void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!w->as.canvas.data) {
|
||||
if (!cd->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -715,13 +622,13 @@ void widgetCanvasPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
|
|||
drawBevel(d, ops, w->x, w->y, w->w, w->h, &sunken);
|
||||
|
||||
// Blit the canvas data inside the border
|
||||
int32_t imgW = w->as.canvas.canvasW;
|
||||
int32_t imgH = w->as.canvas.canvasH;
|
||||
int32_t imgW = cd->canvasW;
|
||||
int32_t imgH = cd->canvasH;
|
||||
int32_t dx = w->x + CANVAS_BORDER;
|
||||
int32_t dy = w->y + CANVAS_BORDER;
|
||||
|
||||
rectCopy(d, ops, dx, dy,
|
||||
w->as.canvas.data, w->as.canvas.canvasPitch,
|
||||
cd->pixelData, cd->canvasPitch,
|
||||
0, 0, imgW, imgH);
|
||||
}
|
||||
|
||||
|
|
@ -744,6 +651,188 @@ static const WidgetClassT sClassCanvas = {
|
|||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetCanvasE] = &sClassCanvas;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtCanvas(WidgetT *parent, int32_t w, int32_t h) {
|
||||
if (!parent || w <= 0 || h <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find the AppContextT to get display format
|
||||
WidgetT *root = parent;
|
||||
|
||||
while (root->parent) {
|
||||
root = root->parent;
|
||||
}
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
|
||||
if (!ctx) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const DisplayT *d = &ctx->display;
|
||||
int32_t bpp = d->format.bytesPerPixel;
|
||||
int32_t pitch = w * bpp;
|
||||
|
||||
uint8_t *data = (uint8_t *)malloc(pitch * h);
|
||||
|
||||
if (!data) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Fill with white using span fill for performance
|
||||
uint32_t white = packColor(d, 255, 255, 255);
|
||||
BlitOpsT canvasOps;
|
||||
drawInit(&canvasOps, d);
|
||||
|
||||
for (int32_t y = 0; y < h; y++) {
|
||||
canvasOps.spanFill(data + y * pitch, white, w);
|
||||
}
|
||||
|
||||
WidgetT *wgt = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (wgt) {
|
||||
CanvasDataT *cd = (CanvasDataT *)calloc(1, sizeof(CanvasDataT));
|
||||
|
||||
cd->pixelData = data;
|
||||
cd->canvasW = w;
|
||||
cd->canvasH = h;
|
||||
cd->canvasPitch = pitch;
|
||||
cd->canvasBpp = bpp;
|
||||
cd->penColor = packColor(d, 0, 0, 0);
|
||||
cd->penSize = 2;
|
||||
cd->lastX = -1;
|
||||
cd->lastY = -1;
|
||||
wgt->data = cd;
|
||||
} else {
|
||||
free(data);
|
||||
}
|
||||
|
||||
return wgt;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtCanvasLoad(WidgetT *w, const char *path) {
|
||||
if (!w || w->type != sTypeId || !path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int32_t imgW;
|
||||
int32_t imgH;
|
||||
int32_t pitch;
|
||||
uint8_t *data = dvxLoadImage(ctx, path, &imgW, &imgH, &pitch);
|
||||
|
||||
if (!data) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
free(cd->pixelData);
|
||||
cd->pixelData = data;
|
||||
cd->canvasW = imgW;
|
||||
cd->canvasH = imgH;
|
||||
cd->canvasPitch = pitch;
|
||||
cd->canvasBpp = ctx->display.format.bytesPerPixel;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtCanvasSave(WidgetT *w, const char *path) {
|
||||
if (!w || w->type != sTypeId || !path) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
if (!cd->pixelData) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
|
||||
if (!ctx) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return dvxSaveImage(ctx, cd->pixelData, cd->canvasW, cd->canvasH, cd->canvasPitch, path);
|
||||
}
|
||||
|
||||
|
||||
void wgtCanvasSetMouseCallback(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag)) {
|
||||
if (w && w->type == sTypeId) {
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
cd->onMouse = cb;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void wgtCanvasSetPenColor(WidgetT *w, uint32_t color) {
|
||||
if (w && w->type == sTypeId) {
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
cd->penColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void wgtCanvasSetPenSize(WidgetT *w, int32_t size) {
|
||||
if (w && w->type == sTypeId && size > 0) {
|
||||
CanvasDataT *cd = (CanvasDataT *)w->data;
|
||||
|
||||
cd->penSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t w, int32_t h);
|
||||
void (*clear)(WidgetT *w, uint32_t color);
|
||||
void (*setPenColor)(WidgetT *w, uint32_t color);
|
||||
void (*setPenSize)(WidgetT *w, int32_t size);
|
||||
void (*setMouseCallback)(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
|
||||
int32_t (*save)(WidgetT *w, const char *path);
|
||||
int32_t (*load)(WidgetT *w, const char *path);
|
||||
void (*drawLine)(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
|
||||
void (*drawRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
||||
void (*fillRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
||||
void (*fillCircle)(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
|
||||
void (*setPixel)(WidgetT *w, int32_t x, int32_t y, uint32_t color);
|
||||
uint32_t (*getPixel)(const WidgetT *w, int32_t x, int32_t y);
|
||||
} sApi = {
|
||||
.create = wgtCanvas,
|
||||
.clear = wgtCanvasClear,
|
||||
.setPenColor = wgtCanvasSetPenColor,
|
||||
.setPenSize = wgtCanvasSetPenSize,
|
||||
.setMouseCallback = wgtCanvasSetMouseCallback,
|
||||
.save = wgtCanvasSave,
|
||||
.load = wgtCanvasLoad,
|
||||
.drawLine = wgtCanvasDrawLine,
|
||||
.drawRect = wgtCanvasDrawRect,
|
||||
.fillRect = wgtCanvasFillRect,
|
||||
.fillCircle = wgtCanvasFillCircle,
|
||||
.setPixel = wgtCanvasSetPixel,
|
||||
.getPixel = wgtCanvasGetPixel
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassCanvas);
|
||||
wgtRegisterApi("canvas", &sApi);
|
||||
}
|
||||
|
|
|
|||
43
widgets/widgetCanvas.h
Normal file
43
widgets/widgetCanvas.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
// widgetCanvas.h -- Canvas widget API
|
||||
#ifndef WIDGET_CANVAS_H
|
||||
#define WIDGET_CANVAS_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t w, int32_t h);
|
||||
void (*clear)(WidgetT *w, uint32_t color);
|
||||
void (*setPenColor)(WidgetT *w, uint32_t color);
|
||||
void (*setPenSize)(WidgetT *w, int32_t size);
|
||||
void (*setMouseCallback)(WidgetT *w, void (*cb)(WidgetT *w, int32_t cx, int32_t cy, bool drag));
|
||||
void (*save)(WidgetT *w, const char *path);
|
||||
void (*load)(WidgetT *w, const char *path);
|
||||
void (*drawLine)(WidgetT *w, int32_t x0, int32_t y0, int32_t x1, int32_t y1);
|
||||
void (*drawRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
||||
void (*fillRect)(WidgetT *w, int32_t x, int32_t y, int32_t width, int32_t height);
|
||||
void (*fillCircle)(WidgetT *w, int32_t cx, int32_t cy, int32_t radius);
|
||||
void (*setPixel)(WidgetT *w, int32_t x, int32_t y, uint32_t color);
|
||||
uint32_t (*getPixel)(const WidgetT *w, int32_t x, int32_t y);
|
||||
} CanvasApiT;
|
||||
|
||||
static inline const CanvasApiT *dvxCanvasApi(void) {
|
||||
static const CanvasApiT *sApi;
|
||||
if (!sApi) { sApi = (const CanvasApiT *)wgtGetApi("canvas"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtCanvas(parent, w, h) dvxCanvasApi()->create(parent, w, h)
|
||||
#define wgtCanvasClear(w, color) dvxCanvasApi()->clear(w, color)
|
||||
#define wgtCanvasSetPenColor(w, color) dvxCanvasApi()->setPenColor(w, color)
|
||||
#define wgtCanvasSetPenSize(w, size) dvxCanvasApi()->setPenSize(w, size)
|
||||
#define wgtCanvasSetMouseCallback(w, cb) dvxCanvasApi()->setMouseCallback(w, cb)
|
||||
#define wgtCanvasSave(w, path) dvxCanvasApi()->save(w, path)
|
||||
#define wgtCanvasLoad(w, path) dvxCanvasApi()->load(w, path)
|
||||
#define wgtCanvasDrawLine(w, x0, y0, x1, y1) dvxCanvasApi()->drawLine(w, x0, y0, x1, y1)
|
||||
#define wgtCanvasDrawRect(w, x, y, width, height) dvxCanvasApi()->drawRect(w, x, y, width, height)
|
||||
#define wgtCanvasFillRect(w, x, y, width, height) dvxCanvasApi()->fillRect(w, x, y, width, height)
|
||||
#define wgtCanvasFillCircle(w, cx, cy, radius) dvxCanvasApi()->fillCircle(w, cx, cy, radius)
|
||||
#define wgtCanvasSetPixel(w, x, y, color) dvxCanvasApi()->setPixel(w, x, y, color)
|
||||
#define wgtCanvasGetPixel(w, x, y) dvxCanvasApi()->getPixel(w, x, y)
|
||||
|
||||
#endif // WIDGET_CANVAS_H
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetCheckbox.c -- Checkbox widget
|
||||
//
|
||||
// Classic checkbox: a small box with a sunken bevel (1px) on the left, a text
|
||||
// label to the right. The check mark is drawn as two diagonal lines forming an
|
||||
// "X" pattern rather than a traditional checkmark glyph -- this is simpler to
|
||||
// render with drawHLine primitives and matches the DV/X aesthetic.
|
||||
// render with drawHLine primitives and matches the DVX aesthetic.
|
||||
//
|
||||
// State management is simple: a boolean 'checked' flag toggles on each click
|
||||
// or Space/Enter keypress. The onChange callback fires after each toggle so
|
||||
|
|
@ -12,42 +13,34 @@
|
|||
// Focus is shown via a dotted rectangle around the label text (not the box),
|
||||
// matching the Win3.1 convention where the focus indicator wraps the label.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#define CHECKBOX_BOX_SIZE 12
|
||||
#define CHECKBOX_GAP 4
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
const char *text;
|
||||
bool checked;
|
||||
} CheckboxDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtCheckbox
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetCheckboxE);
|
||||
|
||||
if (w) {
|
||||
w->as.checkbox.text = text;
|
||||
w->as.checkbox.checked = false;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
static void widgetCheckboxDestroy(WidgetT *w);
|
||||
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxGetText
|
||||
// widgetCheckboxAccelActivate
|
||||
// ============================================================
|
||||
|
||||
const char *widgetCheckboxGetText(const WidgetT *w) {
|
||||
return w->as.checkbox.text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetCheckboxSetText(WidgetT *w, const char *text) {
|
||||
w->as.checkbox.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
void widgetCheckboxAccelActivate(WidgetT *w, WidgetT *root) {
|
||||
(void)root;
|
||||
widgetCheckboxOnMouse(w, NULL, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -59,12 +52,33 @@ void widgetCheckboxSetText(WidgetT *w, const char *text) {
|
|||
// the font). This ensures the box and text are always vertically centered
|
||||
// relative to each other regardless of font size.
|
||||
void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
|
||||
textWidthAccel(font, w->as.checkbox.text);
|
||||
textWidthAccel(font, d->text);
|
||||
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetCheckboxDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxGetText
|
||||
// ============================================================
|
||||
|
||||
const char *widgetCheckboxGetText(const WidgetT *w) {
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
return d->text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxOnKey
|
||||
// ============================================================
|
||||
|
|
@ -73,7 +87,8 @@ void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
(void)mod;
|
||||
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
w->as.checkbox.checked = !w->as.checkbox.checked;
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
d->checked = !d->checked;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -92,8 +107,9 @@ void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
w->focused = true;
|
||||
w->as.checkbox.checked = !w->as.checkbox.checked;
|
||||
d->checked = !d->checked;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -106,6 +122,7 @@ void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// ============================================================
|
||||
|
||||
void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
CheckboxDataT *cd = (CheckboxDataT *)w->data;
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
int32_t boxY = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;
|
||||
|
|
@ -123,7 +140,7 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
// instead of a real line drawing algorithm avoids Bresenham overhead
|
||||
// for what's always a small fixed-size glyph (6x6 pixels). The 3px
|
||||
// inset from the box edge keeps the mark visually centered.
|
||||
if (w->as.checkbox.checked) {
|
||||
if (cd->checked) {
|
||||
int32_t cx = w->x + 3;
|
||||
int32_t cy = boxY + 3;
|
||||
int32_t cs = CHECKBOX_BOX_SIZE - 6;
|
||||
|
|
@ -138,12 +155,12 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
// Draw label
|
||||
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
|
||||
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
|
||||
int32_t labelW = textWidthAccel(font, w->as.checkbox.text);
|
||||
int32_t labelW = textWidthAccel(font, cd->text);
|
||||
|
||||
if (!w->enabled) {
|
||||
drawTextAccelEmbossed(d, ops, font, labelX, labelY, w->as.checkbox.text, colors);
|
||||
drawTextAccelEmbossed(d, ops, font, labelX, labelY, cd->text, colors);
|
||||
} else {
|
||||
drawTextAccel(d, ops, font, labelX, labelY, w->as.checkbox.text, fg, bg, false);
|
||||
drawTextAccel(d, ops, font, labelX, labelY, cd->text, fg, bg, false);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
|
|
@ -152,6 +169,17 @@ void widgetCheckboxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetCheckboxSetText(WidgetT *w, const char *text) {
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
|
@ -165,11 +193,64 @@ static const WidgetClassT sClassCheckbox = {
|
|||
.layout = NULL,
|
||||
.onMouse = widgetCheckboxOnMouse,
|
||||
.onKey = widgetCheckboxOnKey,
|
||||
.destroy = NULL,
|
||||
.onAccelActivate = widgetCheckboxAccelActivate,
|
||||
.destroy = widgetCheckboxDestroy,
|
||||
.getText = widgetCheckboxGetText,
|
||||
.setText = widgetCheckboxSetText
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetCheckboxE] = &sClassCheckbox;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
CheckboxDataT *d = calloc(1, sizeof(CheckboxDataT));
|
||||
w->data = d;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
bool wgtCheckboxIsChecked(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, false);
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
|
||||
return d->checked;
|
||||
}
|
||||
|
||||
|
||||
void wgtCheckboxSetChecked(WidgetT *w, bool checked) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
CheckboxDataT *d = (CheckboxDataT *)w->data;
|
||||
|
||||
d->checked = checked;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
bool (*isChecked)(const WidgetT *w);
|
||||
void (*setChecked)(WidgetT *w, bool checked);
|
||||
} sApi = {
|
||||
.create = wgtCheckbox,
|
||||
.isChecked = wgtCheckboxIsChecked,
|
||||
.setChecked = wgtCheckboxSetChecked
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassCheckbox);
|
||||
wgtRegisterApi("checkbox", &sApi);
|
||||
}
|
||||
|
|
|
|||
23
widgets/widgetCheckbox.h
Normal file
23
widgets/widgetCheckbox.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// widgetCheckbox.h -- Checkbox widget API
|
||||
#ifndef WIDGET_CHECKBOX_H
|
||||
#define WIDGET_CHECKBOX_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
bool (*isChecked)(const WidgetT *w);
|
||||
void (*setChecked)(WidgetT *w, bool checked);
|
||||
} CheckboxApiT;
|
||||
|
||||
static inline const CheckboxApiT *dvxCheckboxApi(void) {
|
||||
static const CheckboxApiT *sApi;
|
||||
if (!sApi) { sApi = (const CheckboxApiT *)wgtGetApi("checkbox"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtCheckbox(parent, text) dvxCheckboxApi()->create(parent, text)
|
||||
#define wgtCheckboxIsChecked(w) dvxCheckboxApi()->isChecked(w)
|
||||
#define wgtCheckboxSetChecked(w, checked) dvxCheckboxApi()->setChecked(w, checked)
|
||||
|
||||
#endif // WIDGET_CHECKBOX_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetComboBox.c -- ComboBox widget (editable text + dropdown list)
|
||||
//
|
||||
// Combines a single-line text input with a dropdown list. The text area
|
||||
|
|
@ -20,104 +21,31 @@
|
|||
// double-click (word select), and triple-click (select all). Drag-select
|
||||
// is tracked via the sDragTextSelect global.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../texthelp/textHelp.h"
|
||||
#include "../listhelp/listHelp.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
// ============================================================
|
||||
// wgtComboBox
|
||||
// ============================================================
|
||||
|
||||
// Create a combo box. maxLen controls the edit buffer size (0 = default 256).
|
||||
// Two buffers are allocated: the edit buffer and an undo buffer (for Ctrl+Z
|
||||
// single-level undo). Selection indices start at -1 (nothing selected).
|
||||
// Default weight=100 makes combo boxes expand to fill available space in
|
||||
// a layout container, which is the typical desired behavior.
|
||||
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetComboBoxE);
|
||||
|
||||
if (w) {
|
||||
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
|
||||
w->as.comboBox.buf = (char *)malloc(bufSize);
|
||||
w->as.comboBox.undoBuf = (char *)malloc(bufSize);
|
||||
w->as.comboBox.bufSize = bufSize;
|
||||
|
||||
if (!w->as.comboBox.buf || !w->as.comboBox.undoBuf) {
|
||||
free(w->as.comboBox.buf);
|
||||
free(w->as.comboBox.undoBuf);
|
||||
w->as.comboBox.buf = NULL;
|
||||
w->as.comboBox.undoBuf = NULL;
|
||||
} else {
|
||||
w->as.comboBox.buf[0] = '\0';
|
||||
}
|
||||
|
||||
w->as.comboBox.selStart = -1;
|
||||
w->as.comboBox.selEnd = -1;
|
||||
w->as.comboBox.selectedIdx = -1;
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtComboBoxGetSelected
|
||||
// ============================================================
|
||||
|
||||
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, WidgetComboBoxE, -1);
|
||||
|
||||
return w->as.comboBox.selectedIdx;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtComboBoxSetItems
|
||||
// ============================================================
|
||||
|
||||
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
|
||||
|
||||
w->as.comboBox.items = items;
|
||||
w->as.comboBox.itemCount = count;
|
||||
|
||||
// Cache max item string length so calcMinSize doesn't need to re-scan
|
||||
// the entire item array on every layout pass. Items are stored as
|
||||
// external pointers (not copied) -- the caller owns the string data.
|
||||
w->as.comboBox.maxItemLen = widgetMaxItemLen(items, count);
|
||||
|
||||
if (w->as.comboBox.selectedIdx >= count) {
|
||||
w->as.comboBox.selectedIdx = -1;
|
||||
}
|
||||
|
||||
// wgtInvalidate (not wgtInvalidatePaint) triggers a full relayout because
|
||||
// changing items may change the widget's minimum width
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtComboBoxSetSelected
|
||||
// ============================================================
|
||||
|
||||
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetComboBoxE);
|
||||
|
||||
w->as.comboBox.selectedIdx = idx;
|
||||
|
||||
// Copy selected item text to buffer
|
||||
if (idx >= 0 && idx < w->as.comboBox.itemCount && w->as.comboBox.buf) {
|
||||
strncpy(w->as.comboBox.buf, w->as.comboBox.items[idx], w->as.comboBox.bufSize - 1);
|
||||
w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0';
|
||||
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
||||
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
||||
w->as.comboBox.scrollOff = 0;
|
||||
w->as.comboBox.selStart = -1;
|
||||
w->as.comboBox.selEnd = -1;
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
typedef struct {
|
||||
char *buf;
|
||||
int32_t bufSize;
|
||||
int32_t len;
|
||||
int32_t cursorPos;
|
||||
int32_t scrollOff;
|
||||
int32_t selStart;
|
||||
int32_t selEnd;
|
||||
char *undoBuf;
|
||||
int32_t undoLen;
|
||||
int32_t undoCursor;
|
||||
const char **items;
|
||||
int32_t itemCount;
|
||||
int32_t selectedIdx;
|
||||
bool open;
|
||||
int32_t hoverIdx;
|
||||
int32_t listScrollPos;
|
||||
int32_t maxItemLen;
|
||||
} ComboBoxDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -125,7 +53,8 @@ void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
|||
// ============================================================
|
||||
|
||||
void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
int32_t maxItemW = w->as.comboBox.maxItemLen * font->charWidth;
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
int32_t maxItemW = d->maxItemLen * font->charWidth;
|
||||
int32_t minW = font->charWidth * 8;
|
||||
|
||||
if (maxItemW < minW) {
|
||||
|
|
@ -142,8 +71,14 @@ void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// ============================================================
|
||||
|
||||
void widgetComboBoxDestroy(WidgetT *w) {
|
||||
free(w->as.comboBox.buf);
|
||||
free(w->as.comboBox.undoBuf);
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
|
||||
if (d) {
|
||||
free(d->buf);
|
||||
free(d->undoBuf);
|
||||
free(d);
|
||||
w->data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -152,7 +87,8 @@ void widgetComboBoxDestroy(WidgetT *w) {
|
|||
// ============================================================
|
||||
|
||||
const char *widgetComboBoxGetText(const WidgetT *w) {
|
||||
return w->as.comboBox.buf ? w->as.comboBox.buf : "";
|
||||
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
||||
return d->buf ? d->buf : "";
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -167,13 +103,15 @@ const char *widgetComboBoxGetText(const WidgetT *w) {
|
|||
// and a list selector depending on popup state.
|
||||
// Key codes: 0x48|0x100 = Up, 0x50|0x100 = Down (BIOS scan codes with extended bit).
|
||||
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
if (w->as.comboBox.open) {
|
||||
if (key == (0x48 | 0x100)) {
|
||||
if (w->as.comboBox.hoverIdx > 0) {
|
||||
w->as.comboBox.hoverIdx--;
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
|
||||
if (w->as.comboBox.hoverIdx < w->as.comboBox.listScrollPos) {
|
||||
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx;
|
||||
if (d->open) {
|
||||
if (key == (0x48 | 0x100)) {
|
||||
if (d->hoverIdx > 0) {
|
||||
d->hoverIdx--;
|
||||
|
||||
if (d->hoverIdx < d->listScrollPos) {
|
||||
d->listScrollPos = d->hoverIdx;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,11 +120,11 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
if (key == (0x50 | 0x100)) {
|
||||
if (w->as.comboBox.hoverIdx < w->as.comboBox.itemCount - 1) {
|
||||
w->as.comboBox.hoverIdx++;
|
||||
if (d->hoverIdx < d->itemCount - 1) {
|
||||
d->hoverIdx++;
|
||||
|
||||
if (w->as.comboBox.hoverIdx >= w->as.comboBox.listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -195,22 +133,22 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
if (key == 0x0D) {
|
||||
int32_t idx = w->as.comboBox.hoverIdx;
|
||||
int32_t idx = d->hoverIdx;
|
||||
|
||||
if (idx >= 0 && idx < w->as.comboBox.itemCount) {
|
||||
w->as.comboBox.selectedIdx = idx;
|
||||
if (idx >= 0 && idx < d->itemCount) {
|
||||
d->selectedIdx = idx;
|
||||
|
||||
const char *itemText = w->as.comboBox.items[idx];
|
||||
strncpy(w->as.comboBox.buf, itemText, w->as.comboBox.bufSize - 1);
|
||||
w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0';
|
||||
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
||||
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
||||
w->as.comboBox.scrollOff = 0;
|
||||
w->as.comboBox.selStart = -1;
|
||||
w->as.comboBox.selEnd = -1;
|
||||
const char *itemText = d->items[idx];
|
||||
strncpy(d->buf, itemText, d->bufSize - 1);
|
||||
d->buf[d->bufSize - 1] = '\0';
|
||||
d->len = (int32_t)strlen(d->buf);
|
||||
d->cursorPos = d->len;
|
||||
d->scrollOff = 0;
|
||||
d->selStart = -1;
|
||||
d->selEnd = -1;
|
||||
}
|
||||
|
||||
w->as.comboBox.open = false;
|
||||
d->open = false;
|
||||
sOpenPopup = NULL;
|
||||
|
||||
if (w->onChange) {
|
||||
|
|
@ -223,17 +161,17 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
// Down arrow on closed combobox opens the popup
|
||||
if (!w->as.comboBox.open && key == (0x50 | 0x100)) {
|
||||
w->as.comboBox.open = true;
|
||||
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
|
||||
if (!d->open && key == (0x50 | 0x100)) {
|
||||
d->open = true;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = w;
|
||||
|
||||
if (w->as.comboBox.hoverIdx >= w->as.comboBox.listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
if (d->hoverIdx >= d->listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
d->listScrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
|
||||
if (w->as.comboBox.hoverIdx < w->as.comboBox.listScrollPos) {
|
||||
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx;
|
||||
if (d->hoverIdx < d->listScrollPos) {
|
||||
d->listScrollPos = d->hoverIdx;
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
|
|
@ -241,18 +179,18 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
// Text editing (when popup is closed, or non-navigation keys with popup open)
|
||||
if (!w->as.comboBox.buf) {
|
||||
if (!d->buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearOtherSelections(w);
|
||||
|
||||
widgetTextEditOnKey(w, key, mod, w->as.comboBox.buf, w->as.comboBox.bufSize,
|
||||
&w->as.comboBox.len, &w->as.comboBox.cursorPos,
|
||||
&w->as.comboBox.scrollOff,
|
||||
&w->as.comboBox.selStart, &w->as.comboBox.selEnd,
|
||||
w->as.comboBox.undoBuf, &w->as.comboBox.undoLen,
|
||||
&w->as.comboBox.undoCursor);
|
||||
widgetTextEditOnKey(w, key, mod, d->buf, d->bufSize,
|
||||
&d->len, &d->cursorPos,
|
||||
&d->scrollOff,
|
||||
&d->selStart, &d->selEnd,
|
||||
d->undoBuf, &d->undoLen,
|
||||
&d->undoCursor);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -262,6 +200,7 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
w->focused = true;
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
|
||||
// Check if click is on the button area
|
||||
int32_t textAreaW = w->w - DROPDOWN_BTN_WIDTH;
|
||||
|
|
@ -273,15 +212,15 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
}
|
||||
|
||||
// Button click -- toggle popup
|
||||
w->as.comboBox.open = !w->as.comboBox.open;
|
||||
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
|
||||
sOpenPopup = w->as.comboBox.open ? w : NULL;
|
||||
d->open = !d->open;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = d->open ? w : NULL;
|
||||
} else {
|
||||
// Text area click -- focus for editing
|
||||
clearOtherSelections(w);
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, w->as.comboBox.buf, w->as.comboBox.len, w->as.comboBox.scrollOff, &w->as.comboBox.cursorPos, &w->as.comboBox.selStart, &w->as.comboBox.selEnd, true, true);
|
||||
widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, d->buf, d->len, d->scrollOff, &d->cursorPos, &d->selStart, &d->selEnd, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,7 +235,8 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// post-selection). The dropdown button has a small triangular arrow glyph
|
||||
// drawn as horizontal lines of decreasing width. When the popup is open,
|
||||
// the button bevel is inverted (sunken) to show it's active.
|
||||
void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
void widgetComboBoxPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->contentFg) : colors->windowShadow;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
|
|
@ -307,36 +247,36 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
bevel.shadow = colors->windowHighlight;
|
||||
bevel.face = bg;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, w->x, w->y, textAreaW, w->h, &bevel);
|
||||
drawBevel(disp, ops, w->x, w->y, textAreaW, w->h, &bevel);
|
||||
|
||||
// Draw text content
|
||||
if (w->as.comboBox.buf) {
|
||||
if (d->buf) {
|
||||
int32_t textX = w->x + TEXT_INPUT_PAD;
|
||||
int32_t textY = w->y + (w->h - font->charHeight) / 2;
|
||||
int32_t maxChars = (textAreaW - TEXT_INPUT_PAD * 2 - 4) / font->charWidth;
|
||||
int32_t off = w->as.comboBox.scrollOff;
|
||||
int32_t len = w->as.comboBox.len - off;
|
||||
int32_t off = d->scrollOff;
|
||||
int32_t len = d->len - off;
|
||||
|
||||
if (len > maxChars) {
|
||||
len = maxChars;
|
||||
}
|
||||
|
||||
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, w->as.comboBox.buf + off, len, off, w->as.comboBox.cursorPos, w->as.comboBox.selStart, w->as.comboBox.selEnd, fg, bg, w->focused && w->enabled && !w->as.comboBox.open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
|
||||
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, d->buf + off, len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w->focused && w->enabled && !d->open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
|
||||
}
|
||||
|
||||
// Drop button
|
||||
BevelStyleT btnBevel;
|
||||
btnBevel.highlight = w->as.comboBox.open ? colors->windowShadow : colors->windowHighlight;
|
||||
btnBevel.shadow = w->as.comboBox.open ? colors->windowHighlight : colors->windowShadow;
|
||||
btnBevel.highlight = d->open ? colors->windowShadow : colors->windowHighlight;
|
||||
btnBevel.shadow = d->open ? colors->windowHighlight : colors->windowShadow;
|
||||
btnBevel.face = colors->buttonFace;
|
||||
btnBevel.width = 2;
|
||||
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
|
||||
drawBevel(disp, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
|
||||
|
||||
// Down arrow
|
||||
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
|
||||
int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2;
|
||||
int32_t arrowY = w->y + w->h / 2 - 1;
|
||||
widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg);
|
||||
widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -344,14 +284,15 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
// widgetComboBoxPaintPopup
|
||||
// ============================================================
|
||||
|
||||
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
int32_t popX;
|
||||
int32_t popY;
|
||||
int32_t popW;
|
||||
int32_t popH;
|
||||
|
||||
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
|
||||
widgetPaintPopupList(d, ops, font, colors, popX, popY, popW, popH, w->as.comboBox.items, w->as.comboBox.itemCount, w->as.comboBox.hoverIdx, w->as.comboBox.listScrollPos);
|
||||
widgetDropdownPopupRect(w, font, disp->clipH, &popX, &popY, &popW, &popH);
|
||||
widgetPaintPopupList(disp, ops, font, colors, popX, popY, popW, popH, d->items, d->itemCount, d->hoverIdx, d->listScrollPos);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -360,36 +301,199 @@ void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
|
|||
// ============================================================
|
||||
|
||||
void widgetComboBoxSetText(WidgetT *w, const char *text) {
|
||||
if (w->as.comboBox.buf) {
|
||||
strncpy(w->as.comboBox.buf, text, w->as.comboBox.bufSize - 1);
|
||||
w->as.comboBox.buf[w->as.comboBox.bufSize - 1] = '\0';
|
||||
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
||||
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
||||
w->as.comboBox.scrollOff = 0;
|
||||
w->as.comboBox.selStart = -1;
|
||||
w->as.comboBox.selEnd = -1;
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
|
||||
if (d->buf) {
|
||||
strncpy(d->buf, text, d->bufSize - 1);
|
||||
d->buf[d->bufSize - 1] = '\0';
|
||||
d->len = (int32_t)strlen(d->buf);
|
||||
d->cursorPos = d->len;
|
||||
d->scrollOff = 0;
|
||||
d->selStart = -1;
|
||||
d->selEnd = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxAccelActivate
|
||||
// ============================================================
|
||||
|
||||
void widgetComboBoxAccelActivate(WidgetT *w, WidgetT *root) {
|
||||
(void)root;
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
d->open = true;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxClosePopup
|
||||
// ============================================================
|
||||
|
||||
void widgetComboBoxClosePopup(WidgetT *w) {
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
d->open = false;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxGetPopupItemCount
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetComboBoxGetPopupItemCount(const WidgetT *w) {
|
||||
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
||||
return d->itemCount;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxOpenPopup
|
||||
// ============================================================
|
||||
|
||||
void widgetComboBoxOpenPopup(WidgetT *w) {
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
d->open = true;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
static void widgetComboBoxGetPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
|
||||
widgetDropdownPopupRect((WidgetT *)w, font, contentH, popX, popY, popW, popH);
|
||||
}
|
||||
|
||||
|
||||
static const WidgetClassT sClassComboBox = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_HAS_POPUP | WCLASS_SCROLLABLE,
|
||||
.paint = widgetComboBoxPaint,
|
||||
.paintOverlay = widgetComboBoxPaintPopup,
|
||||
.calcMinSize = widgetComboBoxCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetComboBoxOnMouse,
|
||||
.onKey = widgetComboBoxOnKey,
|
||||
.onAccelActivate = widgetComboBoxAccelActivate,
|
||||
.destroy = widgetComboBoxDestroy,
|
||||
.getText = widgetComboBoxGetText,
|
||||
.setText = widgetComboBoxSetText
|
||||
.setText = widgetComboBoxSetText,
|
||||
.openPopup = widgetComboBoxOpenPopup,
|
||||
.closePopup = widgetComboBoxClosePopup,
|
||||
.getPopupItemCount = widgetComboBoxGetPopupItemCount,
|
||||
.getPopupRect = widgetComboBoxGetPopupRect
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetComboBoxE] = &sClassComboBox;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)calloc(1, sizeof(ComboBoxDataT));
|
||||
|
||||
if (!d) {
|
||||
return w;
|
||||
}
|
||||
|
||||
w->data = d;
|
||||
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
|
||||
d->buf = (char *)malloc(bufSize);
|
||||
d->undoBuf = (char *)malloc(bufSize);
|
||||
d->bufSize = bufSize;
|
||||
|
||||
if (!d->buf || !d->undoBuf) {
|
||||
free(d->buf);
|
||||
free(d->undoBuf);
|
||||
d->buf = NULL;
|
||||
d->undoBuf = NULL;
|
||||
} else {
|
||||
d->buf[0] = '\0';
|
||||
}
|
||||
|
||||
d->selStart = -1;
|
||||
d->selEnd = -1;
|
||||
d->selectedIdx = -1;
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtComboBoxGetSelected(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, -1);
|
||||
const ComboBoxDataT *d = (const ComboBoxDataT *)w->data;
|
||||
|
||||
return d->selectedIdx;
|
||||
}
|
||||
|
||||
|
||||
void wgtComboBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
|
||||
d->items = items;
|
||||
d->itemCount = count;
|
||||
d->maxItemLen = widgetMaxItemLen(items, count);
|
||||
|
||||
if (d->selectedIdx >= count) {
|
||||
d->selectedIdx = -1;
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ComboBoxDataT *d = (ComboBoxDataT *)w->data;
|
||||
|
||||
d->selectedIdx = idx;
|
||||
|
||||
// Copy selected item text to buffer
|
||||
if (idx >= 0 && idx < d->itemCount && d->buf) {
|
||||
strncpy(d->buf, d->items[idx], d->bufSize - 1);
|
||||
d->buf[d->bufSize - 1] = '\0';
|
||||
d->len = (int32_t)strlen(d->buf);
|
||||
d->cursorPos = d->len;
|
||||
d->scrollOff = 0;
|
||||
d->selStart = -1;
|
||||
d->selEnd = -1;
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
|
||||
void (*setItems)(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t (*getSelected)(const WidgetT *w);
|
||||
void (*setSelected)(WidgetT *w, int32_t index);
|
||||
} sApi = {
|
||||
.create = wgtComboBox,
|
||||
.setItems = wgtComboBoxSetItems,
|
||||
.getSelected = wgtComboBoxGetSelected,
|
||||
.setSelected = wgtComboBoxSetSelected
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassComboBox);
|
||||
wgtRegisterApi("combobox", &sApi);
|
||||
}
|
||||
|
|
|
|||
25
widgets/widgetComboBox.h
Normal file
25
widgets/widgetComboBox.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// widgetComboBox.h -- ComboBox widget API
|
||||
#ifndef WIDGET_COMBOBOX_H
|
||||
#define WIDGET_COMBOBOX_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t maxLen);
|
||||
void (*setItems)(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t (*getSelected)(const WidgetT *w);
|
||||
void (*setSelected)(WidgetT *w, int32_t idx);
|
||||
} ComboBoxApiT;
|
||||
|
||||
static inline const ComboBoxApiT *dvxComboBoxApi(void) {
|
||||
static const ComboBoxApiT *sApi;
|
||||
if (!sApi) { sApi = (const ComboBoxApiT *)wgtGetApi("combobox"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtComboBox(parent, maxLen) dvxComboBoxApi()->create(parent, maxLen)
|
||||
#define wgtComboBoxSetItems(w, items, count) dvxComboBoxApi()->setItems(w, items, count)
|
||||
#define wgtComboBoxGetSelected(w) dvxComboBoxApi()->getSelected(w)
|
||||
#define wgtComboBoxSetSelected(w, idx) dvxComboBoxApi()->setSelected(w, idx)
|
||||
|
||||
#endif // WIDGET_COMBOBOX_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetDropdown.c -- Dropdown (select) widget
|
||||
//
|
||||
// A non-editable dropdown list (HTML <select> equivalent). Unlike ComboBox,
|
||||
|
|
@ -18,66 +19,29 @@
|
|||
// Popup overlay painting and hit testing are shared with ComboBox via
|
||||
// widgetDropdownPopupRect and widgetPaintPopupList in widgetCore.c.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../texthelp/textHelp.h"
|
||||
#include "../listhelp/listHelp.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
const char **items;
|
||||
int32_t itemCount;
|
||||
int32_t selectedIdx;
|
||||
bool open;
|
||||
int32_t hoverIdx;
|
||||
int32_t scrollPos;
|
||||
int32_t maxItemLen;
|
||||
} DropdownDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtDropdown
|
||||
// widgetDropdownDestroy
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtDropdown(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetDropdownE);
|
||||
|
||||
if (w) {
|
||||
w->as.dropdown.selectedIdx = -1;
|
||||
w->as.dropdown.hoverIdx = -1;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtDropdownGetSelected
|
||||
// ============================================================
|
||||
|
||||
int32_t wgtDropdownGetSelected(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, WidgetDropdownE, -1);
|
||||
|
||||
return w->as.dropdown.selectedIdx;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtDropdownSetItems
|
||||
// ============================================================
|
||||
|
||||
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
|
||||
|
||||
w->as.dropdown.items = items;
|
||||
w->as.dropdown.itemCount = count;
|
||||
|
||||
// Cache max item strlen to avoid recomputing in calcMinSize
|
||||
w->as.dropdown.maxItemLen = widgetMaxItemLen(items, count);
|
||||
|
||||
if (w->as.dropdown.selectedIdx >= count) {
|
||||
w->as.dropdown.selectedIdx = -1;
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtDropdownSetSelected
|
||||
// ============================================================
|
||||
|
||||
void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetDropdownE);
|
||||
|
||||
w->as.dropdown.selectedIdx = idx;
|
||||
wgtInvalidatePaint(w);
|
||||
void widgetDropdownDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -86,8 +50,10 @@ void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
|
|||
// ============================================================
|
||||
|
||||
const char *widgetDropdownGetText(const WidgetT *w) {
|
||||
if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) {
|
||||
return w->as.dropdown.items[w->as.dropdown.selectedIdx];
|
||||
const DropdownDataT *d = (const DropdownDataT *)w->data;
|
||||
|
||||
if (d->selectedIdx >= 0 && d->selectedIdx < d->itemCount) {
|
||||
return d->items[d->selectedIdx];
|
||||
}
|
||||
|
||||
return "";
|
||||
|
|
@ -99,8 +65,10 @@ const char *widgetDropdownGetText(const WidgetT *w) {
|
|||
// ============================================================
|
||||
|
||||
void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
|
||||
// Width: widest item + button width + border
|
||||
int32_t maxItemW = w->as.dropdown.maxItemLen * font->charWidth;
|
||||
int32_t maxItemW = d->maxItemLen * font->charWidth;
|
||||
int32_t minW = font->charWidth * 8;
|
||||
|
||||
if (maxItemW < minW) {
|
||||
|
|
@ -123,28 +91,29 @@ void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// arrow-key cycling doesn't require the popup to be visible.
|
||||
void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
|
||||
if (w->as.dropdown.open) {
|
||||
if (d->open) {
|
||||
// Popup is open -- navigate items
|
||||
if (key == (0x48 | 0x100)) {
|
||||
if (w->as.dropdown.hoverIdx > 0) {
|
||||
w->as.dropdown.hoverIdx--;
|
||||
if (d->hoverIdx > 0) {
|
||||
d->hoverIdx--;
|
||||
|
||||
if (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) {
|
||||
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx;
|
||||
if (d->hoverIdx < d->scrollPos) {
|
||||
d->scrollPos = d->hoverIdx;
|
||||
}
|
||||
}
|
||||
} else if (key == (0x50 | 0x100)) {
|
||||
if (w->as.dropdown.hoverIdx < w->as.dropdown.itemCount - 1) {
|
||||
w->as.dropdown.hoverIdx++;
|
||||
if (d->hoverIdx < d->itemCount - 1) {
|
||||
d->hoverIdx++;
|
||||
|
||||
if (w->as.dropdown.hoverIdx >= w->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
if (d->hoverIdx >= d->scrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
d->scrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
}
|
||||
} else if (key == 0x0D || key == ' ') {
|
||||
w->as.dropdown.selectedIdx = w->as.dropdown.hoverIdx;
|
||||
w->as.dropdown.open = false;
|
||||
d->selectedIdx = d->hoverIdx;
|
||||
d->open = false;
|
||||
sOpenPopup = NULL;
|
||||
|
||||
if (w->onChange) {
|
||||
|
|
@ -154,20 +123,20 @@ void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
} else {
|
||||
// Popup is closed
|
||||
if (key == (0x50 | 0x100) || key == ' ' || key == 0x0D) {
|
||||
w->as.dropdown.open = true;
|
||||
w->as.dropdown.hoverIdx = w->as.dropdown.selectedIdx;
|
||||
d->open = true;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = w;
|
||||
|
||||
if (w->as.dropdown.hoverIdx >= w->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
if (d->hoverIdx >= d->scrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
d->scrollPos = d->hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
|
||||
if (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) {
|
||||
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx;
|
||||
if (d->hoverIdx < d->scrollPos) {
|
||||
d->scrollPos = d->hoverIdx;
|
||||
}
|
||||
} else if (key == (0x48 | 0x100)) {
|
||||
if (w->as.dropdown.selectedIdx > 0) {
|
||||
w->as.dropdown.selectedIdx--;
|
||||
if (d->selectedIdx > 0) {
|
||||
d->selectedIdx--;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -201,9 +170,10 @@ void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
return;
|
||||
}
|
||||
|
||||
w->as.dropdown.open = !w->as.dropdown.open;
|
||||
w->as.dropdown.hoverIdx = w->as.dropdown.selectedIdx;
|
||||
sOpenPopup = w->as.dropdown.open ? w : NULL;
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
d->open = !d->open;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = d->open ? w : NULL;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -211,7 +181,8 @@ void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// widgetDropdownPaint
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
void widgetDropdownPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
|
|
@ -222,36 +193,36 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
bevel.shadow = colors->windowHighlight;
|
||||
bevel.face = bg;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, w->x, w->y, textAreaW, w->h, &bevel);
|
||||
drawBevel(disp, ops, w->x, w->y, textAreaW, w->h, &bevel);
|
||||
|
||||
// Draw selected item text
|
||||
if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) {
|
||||
if (d->selectedIdx >= 0 && d->selectedIdx < d->itemCount) {
|
||||
int32_t textX = w->x + TEXT_INPUT_PAD;
|
||||
int32_t textY = w->y + (w->h - font->charHeight) / 2;
|
||||
|
||||
if (!w->enabled) {
|
||||
drawTextEmbossed(d, ops, font, textX, textY, w->as.dropdown.items[w->as.dropdown.selectedIdx], colors);
|
||||
drawTextEmbossed(disp, ops, font, textX, textY, d->items[d->selectedIdx], colors);
|
||||
} else {
|
||||
drawText(d, ops, font, textX, textY, w->as.dropdown.items[w->as.dropdown.selectedIdx], fg, bg, true);
|
||||
drawText(disp, ops, font, textX, textY, d->items[d->selectedIdx], fg, bg, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Drop button
|
||||
BevelStyleT btnBevel;
|
||||
btnBevel.highlight = w->as.dropdown.open ? colors->windowShadow : colors->windowHighlight;
|
||||
btnBevel.shadow = w->as.dropdown.open ? colors->windowHighlight : colors->windowShadow;
|
||||
btnBevel.highlight = d->open ? colors->windowShadow : colors->windowHighlight;
|
||||
btnBevel.shadow = d->open ? colors->windowHighlight : colors->windowShadow;
|
||||
btnBevel.face = colors->buttonFace;
|
||||
btnBevel.width = 2;
|
||||
drawBevel(d, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
|
||||
drawBevel(disp, ops, w->x + textAreaW, w->y, DROPDOWN_BTN_WIDTH, w->h, &btnBevel);
|
||||
|
||||
// Down arrow glyph in button
|
||||
uint32_t arrowFg = w->enabled ? colors->contentFg : colors->windowShadow;
|
||||
int32_t arrowX = w->x + textAreaW + DROPDOWN_BTN_WIDTH / 2;
|
||||
int32_t arrowY = w->y + w->h / 2 - 1;
|
||||
widgetDrawDropdownArrow(d, ops, arrowX, arrowY, arrowFg);
|
||||
widgetDrawDropdownArrow(disp, ops, arrowX, arrowY, arrowFg);
|
||||
|
||||
if (w->focused) {
|
||||
drawFocusRect(d, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg);
|
||||
drawFocusRect(disp, ops, w->x + 3, w->y + 3, textAreaW - 6, w->h - 6, fg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,14 +236,63 @@ void widgetDropdownPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
// on-screen) and widgetPaintPopupList (which renders the bordered, scrollable
|
||||
// item list with hover highlighting). These shared helpers are also used by
|
||||
// ComboBox, keeping the popup rendering consistent between both widgets.
|
||||
void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
void widgetDropdownPaintPopup(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
int32_t popX;
|
||||
int32_t popY;
|
||||
int32_t popW;
|
||||
int32_t popH;
|
||||
|
||||
widgetDropdownPopupRect(w, font, d->clipH, &popX, &popY, &popW, &popH);
|
||||
widgetPaintPopupList(d, ops, font, colors, popX, popY, popW, popH, w->as.dropdown.items, w->as.dropdown.itemCount, w->as.dropdown.hoverIdx, w->as.dropdown.scrollPos);
|
||||
widgetDropdownPopupRect(w, font, disp->clipH, &popX, &popY, &popW, &popH);
|
||||
widgetPaintPopupList(disp, ops, font, colors, popX, popY, popW, popH, d->items, d->itemCount, d->hoverIdx, d->scrollPos);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownAccelActivate
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownAccelActivate(WidgetT *w, WidgetT *root) {
|
||||
(void)root;
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
d->open = true;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownClosePopup
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownClosePopup(WidgetT *w) {
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
d->open = false;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownGetPopupItemCount
|
||||
// ============================================================
|
||||
|
||||
int32_t widgetDropdownGetPopupItemCount(const WidgetT *w) {
|
||||
const DropdownDataT *d = (const DropdownDataT *)w->data;
|
||||
return d->itemCount;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownOpenPopup
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownOpenPopup(WidgetT *w) {
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
d->open = true;
|
||||
d->hoverIdx = d->selectedIdx;
|
||||
sOpenPopup = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -281,19 +301,104 @@ void widgetDropdownPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
|
|||
// ============================================================
|
||||
|
||||
|
||||
static void widgetDropdownGetPopupRect(const WidgetT *w, const BitmapFontT *font, int32_t contentH, int32_t *popX, int32_t *popY, int32_t *popW, int32_t *popH) {
|
||||
widgetDropdownPopupRect((WidgetT *)w, font, contentH, popX, popY, popW, popH);
|
||||
}
|
||||
|
||||
|
||||
static const WidgetClassT sClassDropdown = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_HAS_POPUP | WCLASS_SCROLLABLE,
|
||||
.paint = widgetDropdownPaint,
|
||||
.paintOverlay = widgetDropdownPaintPopup,
|
||||
.calcMinSize = widgetDropdownCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetDropdownOnMouse,
|
||||
.onKey = widgetDropdownOnKey,
|
||||
.destroy = NULL,
|
||||
.onAccelActivate = widgetDropdownAccelActivate,
|
||||
.destroy = widgetDropdownDestroy,
|
||||
.getText = widgetDropdownGetText,
|
||||
.setText = NULL
|
||||
.setText = NULL,
|
||||
.openPopup = widgetDropdownOpenPopup,
|
||||
.closePopup = widgetDropdownClosePopup,
|
||||
.getPopupItemCount = widgetDropdownGetPopupItemCount,
|
||||
.getPopupRect = widgetDropdownGetPopupRect
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtDropdown(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
DropdownDataT *d = (DropdownDataT *)calloc(1, sizeof(DropdownDataT));
|
||||
|
||||
if (!d) {
|
||||
return w;
|
||||
}
|
||||
|
||||
w->data = d;
|
||||
d->selectedIdx = -1;
|
||||
d->hoverIdx = -1;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtDropdownGetSelected(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, -1);
|
||||
const DropdownDataT *d = (const DropdownDataT *)w->data;
|
||||
|
||||
return d->selectedIdx;
|
||||
}
|
||||
|
||||
|
||||
void wgtDropdownSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
|
||||
d->items = items;
|
||||
d->itemCount = count;
|
||||
d->maxItemLen = widgetMaxItemLen(items, count);
|
||||
|
||||
if (d->selectedIdx >= count) {
|
||||
d->selectedIdx = -1;
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
DropdownDataT *d = (DropdownDataT *)w->data;
|
||||
|
||||
d->selectedIdx = idx;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
void (*setItems)(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t (*getSelected)(const WidgetT *w);
|
||||
void (*setSelected)(WidgetT *w, int32_t index);
|
||||
} sApi = {
|
||||
.create = wgtDropdown,
|
||||
.setItems = wgtDropdownSetItems,
|
||||
.getSelected = wgtDropdownGetSelected,
|
||||
.setSelected = wgtDropdownSetSelected
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetDropdownE] = &sClassDropdown;
|
||||
sTypeId = wgtRegisterClass(&sClassDropdown);
|
||||
wgtRegisterApi("dropdown", &sApi);
|
||||
}
|
||||
|
|
|
|||
25
widgets/widgetDropdown.h
Normal file
25
widgets/widgetDropdown.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// widgetDropdown.h -- Dropdown widget API
|
||||
#ifndef WIDGET_DROPDOWN_H
|
||||
#define WIDGET_DROPDOWN_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
void (*setItems)(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t (*getSelected)(const WidgetT *w);
|
||||
void (*setSelected)(WidgetT *w, int32_t idx);
|
||||
} DropdownApiT;
|
||||
|
||||
static inline const DropdownApiT *dvxDropdownApi(void) {
|
||||
static const DropdownApiT *sApi;
|
||||
if (!sApi) { sApi = (const DropdownApiT *)wgtGetApi("dropdown"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtDropdown(parent) dvxDropdownApi()->create(parent)
|
||||
#define wgtDropdownSetItems(w, items, count) dvxDropdownApi()->setItems(w, items, count)
|
||||
#define wgtDropdownGetSelected(w) dvxDropdownApi()->getSelected(w)
|
||||
#define wgtDropdownSetSelected(w, idx) dvxDropdownApi()->setSelected(w, idx)
|
||||
|
||||
#endif // WIDGET_DROPDOWN_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetImage.c -- Image widget (displays bitmap, responds to clicks)
|
||||
//
|
||||
// Displays a bitmap image, optionally responding to clicks. The image data
|
||||
|
|
@ -16,38 +17,141 @@
|
|||
// No border or bevel is drawn -- the image fills its widget bounds with
|
||||
// centering if the widget is larger than the image.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
uint8_t *pixelData;
|
||||
int32_t imgW;
|
||||
int32_t imgH;
|
||||
int32_t imgPitch;
|
||||
bool pressed;
|
||||
} ImageDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtImage
|
||||
// widgetImageDestroy
|
||||
// ============================================================
|
||||
//
|
||||
// Create an image widget from raw pixel data already in display format.
|
||||
// Takes ownership of the data buffer (freed on destroy).
|
||||
|
||||
WidgetT *wgtImage(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch) {
|
||||
WidgetT *wgt = widgetAlloc(parent, WidgetImageE);
|
||||
void widgetImageDestroy(WidgetT *w) {
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
|
||||
if (d) {
|
||||
free(d->pixelData);
|
||||
free(d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
w->calcMinW = d->imgW;
|
||||
w->calcMinH = d->imgH;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageOnMouse
|
||||
// ============================================================
|
||||
|
||||
// Mouse click: briefly sets pressed=true (for the 1px offset effect),
|
||||
// invalidates to show the press, fires onClick, then immediately clears
|
||||
// pressed. The press is purely visual -- there's no release tracking like
|
||||
// Button has, since Image clicks are meant for instant actions (e.g.,
|
||||
// clicking a logo or icon area).
|
||||
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
d->pressed = true;
|
||||
wgtInvalidatePaint(w);
|
||||
|
||||
if (w->onClick) {
|
||||
w->onClick(w);
|
||||
}
|
||||
|
||||
d->pressed = false;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImagePaint
|
||||
// ============================================================
|
||||
|
||||
void widgetImagePaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
(void)colors;
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
|
||||
if (!d->pixelData) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Center the image within the widget bounds
|
||||
int32_t imgW = d->imgW;
|
||||
int32_t imgH = d->imgH;
|
||||
int32_t dx = w->x + (w->w - imgW) / 2;
|
||||
int32_t dy = w->y + (w->h - imgH) / 2;
|
||||
|
||||
// Offset by 1px when pressed (button-press effect)
|
||||
if (d->pressed) {
|
||||
dx++;
|
||||
dy++;
|
||||
}
|
||||
|
||||
rectCopy(disp, ops, dx, dy,
|
||||
d->pixelData, d->imgPitch,
|
||||
0, 0, imgW, imgH);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
static const WidgetClassT sClassImage = {
|
||||
.flags = 0,
|
||||
.paint = widgetImagePaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetImageCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetImageOnMouse,
|
||||
.onKey = NULL,
|
||||
.destroy = widgetImageDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtImage(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch) {
|
||||
WidgetT *wgt = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (wgt) {
|
||||
wgt->as.image.data = data;
|
||||
wgt->as.image.imgW = w;
|
||||
wgt->as.image.imgH = h;
|
||||
wgt->as.image.imgPitch = pitch;
|
||||
wgt->as.image.pressed = false;
|
||||
ImageDataT *d = calloc(1, sizeof(ImageDataT));
|
||||
d->pixelData = pixelData;
|
||||
d->imgW = w;
|
||||
d->imgH = h;
|
||||
d->imgPitch = pitch;
|
||||
d->pressed = false;
|
||||
wgt->data = d;
|
||||
}
|
||||
|
||||
return wgt;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtImageFromFile
|
||||
// ============================================================
|
||||
//
|
||||
// Load an image from a file (BMP, PNG, JPEG, GIF), convert to
|
||||
// display pixel format, and create an image widget. Delegates to
|
||||
// dvxLoadImage for format decoding and pixel conversion.
|
||||
WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) {
|
||||
if (!parent || !path) {
|
||||
return NULL;
|
||||
|
|
@ -72,114 +176,35 @@ WidgetT *wgtImageFromFile(WidgetT *parent, const char *path) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtImageSetData
|
||||
// ============================================================
|
||||
void wgtImageSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ImageDataT *d = (ImageDataT *)w->data;
|
||||
|
||||
void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetImageE);
|
||||
|
||||
free(w->as.image.data);
|
||||
w->as.image.data = data;
|
||||
w->as.image.imgW = imgW;
|
||||
w->as.image.imgH = imgH;
|
||||
w->as.image.imgPitch = pitch;
|
||||
free(d->pixelData);
|
||||
d->pixelData = pixelData;
|
||||
d->imgW = imgW;
|
||||
d->imgH = imgH;
|
||||
d->imgPitch = pitch;
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetImageDestroy(WidgetT *w) {
|
||||
free(w->as.image.data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
w->calcMinW = w->as.image.imgW;
|
||||
w->calcMinH = w->as.image.imgH;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageOnMouse
|
||||
// ============================================================
|
||||
|
||||
// Mouse click: briefly sets pressed=true (for the 1px offset effect),
|
||||
// invalidates to show the press, fires onClick, then immediately clears
|
||||
// pressed. The press is purely visual -- there's no release tracking like
|
||||
// Button has, since Image clicks are meant for instant actions (e.g.,
|
||||
// clicking a logo or icon area).
|
||||
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->as.image.pressed = true;
|
||||
wgtInvalidatePaint(w);
|
||||
|
||||
if (w->onClick) {
|
||||
w->onClick(w);
|
||||
}
|
||||
|
||||
w->as.image.pressed = false;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImagePaint
|
||||
// ============================================================
|
||||
|
||||
void widgetImagePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
(void)colors;
|
||||
|
||||
if (!w->as.image.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Center the image within the widget bounds
|
||||
int32_t imgW = w->as.image.imgW;
|
||||
int32_t imgH = w->as.image.imgH;
|
||||
int32_t dx = w->x + (w->w - imgW) / 2;
|
||||
int32_t dy = w->y + (w->h - imgH) / 2;
|
||||
|
||||
// Offset by 1px when pressed (button-press effect)
|
||||
if (w->as.image.pressed) {
|
||||
dx++;
|
||||
dy++;
|
||||
}
|
||||
|
||||
rectCopy(d, ops, dx, dy,
|
||||
w->as.image.data, w->as.image.imgPitch,
|
||||
0, 0, imgW, imgH);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
static const WidgetClassT sClassImage = {
|
||||
.flags = 0,
|
||||
.paint = widgetImagePaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetImageCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetImageOnMouse,
|
||||
.onKey = NULL,
|
||||
.destroy = widgetImageDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
|
||||
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
|
||||
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
|
||||
} sApi = {
|
||||
.create = wgtImage,
|
||||
.fromFile = wgtImageFromFile,
|
||||
.setData = wgtImageSetData
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetImageE] = &sClassImage;
|
||||
sTypeId = wgtRegisterClass(&sClassImage);
|
||||
wgtRegisterApi("image", &sApi);
|
||||
}
|
||||
|
|
|
|||
23
widgets/widgetImage.h
Normal file
23
widgets/widgetImage.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// widgetImage.h -- Image widget API
|
||||
#ifndef WIDGET_IMAGE_H
|
||||
#define WIDGET_IMAGE_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
|
||||
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
|
||||
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
|
||||
} ImageApiT;
|
||||
|
||||
static inline const ImageApiT *dvxImageApi(void) {
|
||||
static const ImageApiT *sApi;
|
||||
if (!sApi) { sApi = (const ImageApiT *)wgtGetApi("image"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtImage(parent, data, w, h, pitch) dvxImageApi()->create(parent, data, w, h, pitch)
|
||||
#define wgtImageFromFile(parent, path) dvxImageApi()->fromFile(parent, path)
|
||||
#define wgtImageSetData(w, data, imgW, imgH, pitch) dvxImageApi()->setData(w, data, imgW, imgH, pitch)
|
||||
|
||||
#endif // WIDGET_IMAGE_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetImageButton.c -- Image button widget (button with image instead of text)
|
||||
//
|
||||
// Combines a Button's beveled border and press behavior with an Image's
|
||||
|
|
@ -16,41 +17,190 @@
|
|||
// the 2px bevel on each side -- no extra padding is added beyond that,
|
||||
// keeping image buttons compact for toolbar use.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
uint8_t *pixelData;
|
||||
int32_t imgW;
|
||||
int32_t imgH;
|
||||
int32_t imgPitch;
|
||||
bool pressed;
|
||||
} ImageButtonDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtImageButton
|
||||
// widgetImageButtonDestroy
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch) {
|
||||
if (!parent || !data || w <= 0 || h <= 0) {
|
||||
void widgetImageButtonDestroy(WidgetT *w) {
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
|
||||
if (d) {
|
||||
free(d->pixelData);
|
||||
free(d);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
|
||||
// Bevel border only, no extra padding
|
||||
w->calcMinW = d->imgW + 4;
|
||||
w->calcMinH = d->imgH + 4;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
d->pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
w->focused = true;
|
||||
d->pressed = true;
|
||||
sPressedButton = w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonPaint
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
|
||||
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
|
||||
bool pressed = d->pressed && w->enabled;
|
||||
|
||||
BevelStyleT bevel;
|
||||
bevel.highlight = pressed ? colors->windowShadow : colors->windowHighlight;
|
||||
bevel.shadow = pressed ? colors->windowHighlight : colors->windowShadow;
|
||||
bevel.face = bgFace;
|
||||
bevel.width = 2;
|
||||
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
|
||||
if (d->pixelData) {
|
||||
int32_t imgX = w->x + (w->w - d->imgW) / 2;
|
||||
int32_t imgY = w->y + (w->h - d->imgH) / 2;
|
||||
|
||||
if (pressed) {
|
||||
imgX++;
|
||||
imgY++;
|
||||
}
|
||||
|
||||
rectCopy(disp, ops, imgX, imgY,
|
||||
d->pixelData, d->imgPitch,
|
||||
0, 0, d->imgW, d->imgH);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
int32_t off = pressed ? 1 : 0;
|
||||
drawFocusRect(disp, ops, w->x + 3 + off, w->y + 3 + off, w->w - 6, w->h - 6, fg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonAccelActivate
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonAccelActivate(WidgetT *w, WidgetT *root) {
|
||||
(void)root;
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
d->pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonSetPressed
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonSetPressed(WidgetT *w, bool pressed) {
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
d->pressed = pressed;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
static const WidgetClassT sClassImageButton = {
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_PRESS_RELEASE,
|
||||
.paint = widgetImageButtonPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetImageButtonCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetImageButtonOnMouse,
|
||||
.onKey = widgetImageButtonOnKey,
|
||||
.onAccelActivate = widgetImageButtonAccelActivate,
|
||||
.destroy = widgetImageButtonDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL,
|
||||
.setPressed = widgetImageButtonSetPressed
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtImageButton(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch) {
|
||||
if (!parent || !pixelData || w <= 0 || h <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
WidgetT *wgt = widgetAlloc(parent, WidgetImageButtonE);
|
||||
WidgetT *wgt = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (wgt) {
|
||||
wgt->as.imageButton.data = data;
|
||||
wgt->as.imageButton.imgW = w;
|
||||
wgt->as.imageButton.imgH = h;
|
||||
wgt->as.imageButton.imgPitch = pitch;
|
||||
wgt->as.imageButton.pressed = false;
|
||||
ImageButtonDataT *d = calloc(1, sizeof(ImageButtonDataT));
|
||||
d->pixelData = pixelData;
|
||||
d->imgW = w;
|
||||
d->imgH = h;
|
||||
d->imgPitch = pitch;
|
||||
d->pressed = false;
|
||||
wgt->data = d;
|
||||
} else {
|
||||
free(data);
|
||||
free(pixelData);
|
||||
}
|
||||
|
||||
return wgt;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtImageButtonFromFile
|
||||
// ============================================================
|
||||
//
|
||||
// Load an image from disk and create an ImageButton widget.
|
||||
// Delegates to dvxLoadImage for format decoding and pixel conversion.
|
||||
|
||||
WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path) {
|
||||
if (!parent || !path) {
|
||||
return NULL;
|
||||
|
|
@ -75,130 +225,35 @@ WidgetT *wgtImageButtonFromFile(WidgetT *parent, const char *path) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtImageButtonSetData
|
||||
// ============================================================
|
||||
void wgtImageButtonSetData(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ImageButtonDataT *d = (ImageButtonDataT *)w->data;
|
||||
|
||||
void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetImageButtonE);
|
||||
|
||||
free(w->as.imageButton.data);
|
||||
w->as.imageButton.data = data;
|
||||
w->as.imageButton.imgW = imgW;
|
||||
w->as.imageButton.imgH = imgH;
|
||||
w->as.imageButton.imgPitch = pitch;
|
||||
free(d->pixelData);
|
||||
d->pixelData = pixelData;
|
||||
d->imgW = imgW;
|
||||
d->imgH = imgH;
|
||||
d->imgPitch = pitch;
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonDestroy(WidgetT *w) {
|
||||
free(w->as.imageButton.data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
|
||||
// Bevel border only, no extra padding
|
||||
w->calcMinW = w->as.imageButton.imgW + 4;
|
||||
w->calcMinH = w->as.imageButton.imgH + 4;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
w->as.imageButton.pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
w->as.imageButton.pressed = true;
|
||||
sPressedButton = w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonPaint
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
|
||||
uint32_t bgFace = w->bgColor ? w->bgColor : colors->buttonFace;
|
||||
bool pressed = w->as.imageButton.pressed && w->enabled;
|
||||
|
||||
BevelStyleT bevel;
|
||||
bevel.highlight = pressed ? colors->windowShadow : colors->windowHighlight;
|
||||
bevel.shadow = pressed ? colors->windowHighlight : colors->windowShadow;
|
||||
bevel.face = bgFace;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
|
||||
if (w->as.imageButton.data) {
|
||||
int32_t imgX = w->x + (w->w - w->as.imageButton.imgW) / 2;
|
||||
int32_t imgY = w->y + (w->h - w->as.imageButton.imgH) / 2;
|
||||
|
||||
if (pressed) {
|
||||
imgX++;
|
||||
imgY++;
|
||||
}
|
||||
|
||||
rectCopy(d, ops, imgX, imgY,
|
||||
w->as.imageButton.data, w->as.imageButton.imgPitch,
|
||||
0, 0, w->as.imageButton.imgW, w->as.imageButton.imgH);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
int32_t off = pressed ? 1 : 0;
|
||||
drawFocusRect(d, ops, w->x + 3 + off, w->y + 3 + off, w->w - 6, w->h - 6, fg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
static const WidgetClassT sClassImageButton = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetImageButtonPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetImageButtonCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetImageButtonOnMouse,
|
||||
.onKey = widgetImageButtonOnKey,
|
||||
.destroy = widgetImageButtonDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, uint8_t *pixelData, int32_t w, int32_t h, int32_t pitch);
|
||||
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
|
||||
void (*setData)(WidgetT *w, uint8_t *pixelData, int32_t imgW, int32_t imgH, int32_t pitch);
|
||||
} sApi = {
|
||||
.create = wgtImageButton,
|
||||
.fromFile = wgtImageButtonFromFile,
|
||||
.setData = wgtImageButtonSetData
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetImageButtonE] = &sClassImageButton;
|
||||
sTypeId = wgtRegisterClass(&sClassImageButton);
|
||||
wgtRegisterApi("imagebutton", &sApi);
|
||||
}
|
||||
|
|
|
|||
23
widgets/widgetImageButton.h
Normal file
23
widgets/widgetImageButton.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// widgetImageButton.h -- ImageButton widget API
|
||||
#ifndef WIDGET_IMAGEBUTTON_H
|
||||
#define WIDGET_IMAGEBUTTON_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, uint8_t *data, int32_t w, int32_t h, int32_t pitch);
|
||||
WidgetT *(*fromFile)(WidgetT *parent, const char *path);
|
||||
void (*setData)(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int32_t pitch);
|
||||
} ImageButtonApiT;
|
||||
|
||||
static inline const ImageButtonApiT *dvxImageButtonApi(void) {
|
||||
static const ImageButtonApiT *sApi;
|
||||
if (!sApi) { sApi = (const ImageButtonApiT *)wgtGetApi("imagebutton"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtImageButton(parent, data, w, h, pitch) dvxImageButtonApi()->create(parent, data, w, h, pitch)
|
||||
#define wgtImageButtonFromFile(parent, path) dvxImageButtonApi()->fromFile(parent, path)
|
||||
#define wgtImageButtonSetData(w, data, imgW, imgH, pitch) dvxImageButtonApi()->setData(w, data, imgW, imgH, pitch)
|
||||
|
||||
#endif // WIDGET_IMAGEBUTTON_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetLabel.c -- Label widget
|
||||
//
|
||||
// Static text display -- the simplest widget. Not focusable, not interactive.
|
||||
|
|
@ -15,22 +16,41 @@
|
|||
// content background color from the color scheme). The +1 in calcMinH adds a
|
||||
// pixel of breathing room below the text baseline.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
const char *text;
|
||||
WidgetAlignE textAlign;
|
||||
} LabelDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtLabel
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtLabel(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetLabelE);
|
||||
static void widgetLabelDestroy(WidgetT *w);
|
||||
|
||||
if (w) {
|
||||
w->as.label.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
return w;
|
||||
// ============================================================
|
||||
// widgetLabelCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
LabelDataT *d = (LabelDataT *)w->data;
|
||||
w->calcMinW = textWidthAccel(font, d->text);
|
||||
w->calcMinH = font->charHeight + 1;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetLabelDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetLabelDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -39,27 +59,8 @@ WidgetT *wgtLabel(WidgetT *parent, const char *text) {
|
|||
// ============================================================
|
||||
|
||||
const char *widgetLabelGetText(const WidgetT *w) {
|
||||
return w->as.label.text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetLabelSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetLabelSetText(WidgetT *w, const char *text) {
|
||||
w->as.label.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetLabelCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
w->calcMinW = textWidthAccel(font, w->as.label.text);
|
||||
w->calcMinH = font->charHeight + 1;
|
||||
LabelDataT *d = (LabelDataT *)w->data;
|
||||
return d->text;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -68,36 +69,37 @@ void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// ============================================================
|
||||
|
||||
void widgetLabelPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
int32_t textW = textWidthAccel(font, w->as.label.text);
|
||||
LabelDataT *ld = (LabelDataT *)w->data;
|
||||
int32_t textW = textWidthAccel(font, ld->text);
|
||||
int32_t textX = w->x;
|
||||
int32_t textY = w->y + (w->h - font->charHeight) / 2;
|
||||
|
||||
if (w->as.label.textAlign == AlignCenterE) {
|
||||
if (ld->textAlign == AlignCenterE) {
|
||||
textX = w->x + (w->w - textW) / 2;
|
||||
} else if (w->as.label.textAlign == AlignEndE) {
|
||||
} else if (ld->textAlign == AlignEndE) {
|
||||
textX = w->x + w->w - textW;
|
||||
}
|
||||
|
||||
if (!w->enabled) {
|
||||
drawTextAccelEmbossed(d, ops, font, textX, textY, w->as.label.text, colors);
|
||||
drawTextAccelEmbossed(d, ops, font, textX, textY, ld->text, colors);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
drawTextAccel(d, ops, font, textX, textY, w->as.label.text, fg, bg, false);
|
||||
drawTextAccel(d, ops, font, textX, textY, ld->text, fg, bg, false);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtLabelSetAlign
|
||||
// widgetLabelSetText
|
||||
// ============================================================
|
||||
|
||||
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
|
||||
if (w && w->type == WidgetLabelE) {
|
||||
w->as.label.textAlign = align;
|
||||
}
|
||||
void widgetLabelSetText(WidgetT *w, const char *text) {
|
||||
LabelDataT *d = (LabelDataT *)w->data;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -107,18 +109,59 @@ void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
|
|||
|
||||
|
||||
static const WidgetClassT sClassLabel = {
|
||||
.flags = 0,
|
||||
.flags = WCLASS_FOCUS_FORWARD,
|
||||
.paint = widgetLabelPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetLabelCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetLabelDestroy,
|
||||
.getText = widgetLabelGetText,
|
||||
.setText = widgetLabelSetText
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetLabelE] = &sClassLabel;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtLabel(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
LabelDataT *d = calloc(1, sizeof(LabelDataT));
|
||||
w->data = d;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
void wgtLabelSetAlign(WidgetT *w, WidgetAlignE align) {
|
||||
if (w && w->type == sTypeId) {
|
||||
LabelDataT *d = (LabelDataT *)w->data;
|
||||
d->textAlign = align;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
void (*setAlign)(WidgetT *w, WidgetAlignE align);
|
||||
} sApi = {
|
||||
.create = wgtLabel,
|
||||
.setAlign = wgtLabelSetAlign
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassLabel);
|
||||
wgtRegisterApi("label", &sApi);
|
||||
}
|
||||
|
|
|
|||
21
widgets/widgetLabel.h
Normal file
21
widgets/widgetLabel.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// widgetLabel.h -- Label widget API
|
||||
#ifndef WIDGET_LABEL_H
|
||||
#define WIDGET_LABEL_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
void (*setAlign)(WidgetT *w, WidgetAlignE align);
|
||||
} LabelApiT;
|
||||
|
||||
static inline const LabelApiT *dvxLabelApi(void) {
|
||||
static const LabelApiT *sApi;
|
||||
if (!sApi) { sApi = (const LabelApiT *)wgtGetApi("label"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtLabel(parent, text) dvxLabelApi()->create(parent, text)
|
||||
#define wgtLabelSetAlign(w, align) dvxLabelApi()->setAlign(w, align)
|
||||
|
||||
#endif // WIDGET_LABEL_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetListBox.c -- ListBox widget (single and multi-select)
|
||||
//
|
||||
// Scrollable list of text items with single-select or multi-select modes.
|
||||
|
|
@ -27,7 +28,26 @@
|
|||
// and a 2px horizontal line indicator shows the insertion point. The actual
|
||||
// reordering is handled by the application's onReorder callback.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../listhelp/listHelp.h"
|
||||
|
||||
#define LISTBOX_BORDER 2
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
const char **items;
|
||||
int32_t itemCount;
|
||||
int32_t selectedIdx;
|
||||
int32_t scrollPos;
|
||||
int32_t maxItemLen;
|
||||
bool multiSelect;
|
||||
int32_t anchorIdx;
|
||||
uint8_t *selBits;
|
||||
bool reorderable;
|
||||
int32_t dragIdx;
|
||||
int32_t dropIdx;
|
||||
} ListBoxDataT;
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
|
@ -40,32 +60,10 @@
|
|||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void allocSelBits(WidgetT *w);
|
||||
static void ensureScrollVisible(WidgetT *w, int32_t idx);
|
||||
static void selectRange(WidgetT *w, int32_t from, int32_t to);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// allocSelBits
|
||||
// ============================================================
|
||||
|
||||
// Allocate (or re-allocate) the selection bits array. Only allocated in
|
||||
// multi-select mode -- in single-select, selectedIdx alone tracks the
|
||||
// selection. calloc initializes all bits to 0 (nothing selected).
|
||||
static void allocSelBits(WidgetT *w) {
|
||||
if (w->as.listBox.selBits) {
|
||||
free(w->as.listBox.selBits);
|
||||
w->as.listBox.selBits = NULL;
|
||||
}
|
||||
|
||||
int32_t count = w->as.listBox.itemCount;
|
||||
|
||||
if (count > 0 && w->as.listBox.multiSelect) {
|
||||
w->as.listBox.selBits = (uint8_t *)calloc(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ensureScrollVisible
|
||||
// ============================================================
|
||||
|
|
@ -78,6 +76,7 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) {
|
|||
return;
|
||||
}
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t innerH = w->h - LISTBOX_BORDER * 2;
|
||||
|
|
@ -87,10 +86,10 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) {
|
|||
visibleRows = 1;
|
||||
}
|
||||
|
||||
if (idx < w->as.listBox.scrollPos) {
|
||||
w->as.listBox.scrollPos = idx;
|
||||
} else if (idx >= w->as.listBox.scrollPos + visibleRows) {
|
||||
w->as.listBox.scrollPos = idx - visibleRows + 1;
|
||||
if (idx < d->scrollPos) {
|
||||
d->scrollPos = idx;
|
||||
} else if (idx >= d->scrollPos + visibleRows) {
|
||||
d->scrollPos = idx - visibleRows + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -102,7 +101,9 @@ static void ensureScrollVisible(WidgetT *w, int32_t idx) {
|
|||
// Set selection bits for all items in the range [from, to] (inclusive,
|
||||
// order-independent). Used for shift-click and shift-arrow range selection.
|
||||
static void selectRange(WidgetT *w, int32_t from, int32_t to) {
|
||||
if (!w->as.listBox.selBits) {
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
if (!d->selBits) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -113,12 +114,12 @@ static void selectRange(WidgetT *w, int32_t from, int32_t to) {
|
|||
lo = 0;
|
||||
}
|
||||
|
||||
if (hi >= w->as.listBox.itemCount) {
|
||||
hi = w->as.listBox.itemCount - 1;
|
||||
if (hi >= d->itemCount) {
|
||||
hi = d->itemCount - 1;
|
||||
}
|
||||
|
||||
for (int32_t i = lo; i <= hi; i++) {
|
||||
w->as.listBox.selBits[i] = 1;
|
||||
d->selBits[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -128,191 +129,16 @@ static void selectRange(WidgetT *w, int32_t from, int32_t to) {
|
|||
// ============================================================
|
||||
|
||||
void widgetListBoxDestroy(WidgetT *w) {
|
||||
if (w->as.listBox.selBits) {
|
||||
free(w->as.listBox.selBits);
|
||||
w->as.listBox.selBits = NULL;
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
if (d) {
|
||||
free(d->selBits);
|
||||
free(d);
|
||||
w->data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBox
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtListBox(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetListBoxE);
|
||||
|
||||
if (w) {
|
||||
w->as.listBox.selectedIdx = -1;
|
||||
w->as.listBox.anchorIdx = -1;
|
||||
w->as.listBox.dragIdx = -1;
|
||||
w->as.listBox.dropIdx = -1;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxClearSelection
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxClearSelection(WidgetT *w) {
|
||||
if (!w || w->type != WidgetListBoxE || !w->as.listBox.selBits) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxGetSelected
|
||||
// ============================================================
|
||||
|
||||
int32_t wgtListBoxGetSelected(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, WidgetListBoxE, -1);
|
||||
|
||||
return w->as.listBox.selectedIdx;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxIsItemSelected
|
||||
// ============================================================
|
||||
|
||||
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET(w, WidgetListBoxE, false);
|
||||
|
||||
if (!w->as.listBox.multiSelect) {
|
||||
return idx == w->as.listBox.selectedIdx;
|
||||
}
|
||||
|
||||
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return w->as.listBox.selBits[idx] != 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxSelectAll
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxSelectAll(WidgetT *w) {
|
||||
if (!w || w->type != WidgetListBoxE || !w->as.listBox.selBits) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(w->as.listBox.selBits, 1, w->as.listBox.itemCount);
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxSetItemSelected
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||
|
||||
if (!w->as.listBox.selBits || idx < 0 || idx >= w->as.listBox.itemCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
w->as.listBox.selBits[idx] = selected ? 1 : 0;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxSetItems
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||
|
||||
w->as.listBox.items = items;
|
||||
w->as.listBox.itemCount = count;
|
||||
|
||||
// Cache max item strlen to avoid recomputing in calcMinSize
|
||||
w->as.listBox.maxItemLen = widgetMaxItemLen(items, count);
|
||||
|
||||
if (w->as.listBox.selectedIdx >= count) {
|
||||
w->as.listBox.selectedIdx = count > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
if (w->as.listBox.selectedIdx < 0 && count > 0) {
|
||||
w->as.listBox.selectedIdx = 0;
|
||||
}
|
||||
|
||||
w->as.listBox.anchorIdx = w->as.listBox.selectedIdx;
|
||||
|
||||
// Reallocate selection bits
|
||||
allocSelBits(w);
|
||||
|
||||
// Pre-select the cursor item
|
||||
if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) {
|
||||
w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1;
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxSetReorderable
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||
|
||||
w->as.listBox.reorderable = reorderable;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxSetMultiSelect
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||
|
||||
w->as.listBox.multiSelect = multi;
|
||||
allocSelBits(w);
|
||||
|
||||
// Sync: mark current selection
|
||||
if (w->as.listBox.selBits && w->as.listBox.selectedIdx >= 0) {
|
||||
w->as.listBox.selBits[w->as.listBox.selectedIdx] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtListBoxSetSelected
|
||||
// ============================================================
|
||||
|
||||
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetListBoxE);
|
||||
|
||||
w->as.listBox.selectedIdx = idx;
|
||||
w->as.listBox.anchorIdx = idx;
|
||||
|
||||
// In multi-select, clear all then select just this one
|
||||
if (w->as.listBox.selBits) {
|
||||
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
|
||||
|
||||
if (idx >= 0 && idx < w->as.listBox.itemCount) {
|
||||
w->as.listBox.selBits[idx] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetListBoxCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -322,7 +148,8 @@ void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
|
|||
// minimum height even when empty. The 8-character minimum width prevents
|
||||
// the listbox from collapsing too narrow when items are short or absent.
|
||||
void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
int32_t maxItemW = w->as.listBox.maxItemLen * font->charWidth;
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
int32_t maxItemW = d->maxItemLen * font->charWidth;
|
||||
int32_t minW = font->charWidth * 8;
|
||||
|
||||
if (maxItemW < minW) {
|
||||
|
|
@ -343,14 +170,16 @@ void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// navigation. This shared helper ensures consistent keyboard navigation
|
||||
// across ListBox, ListView, and other scrollable widgets.
|
||||
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
if (!w || w->type != WidgetListBoxE || w->as.listBox.itemCount == 0) {
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
if (!w || w->type != sTypeId || d->itemCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool multi = w->as.listBox.multiSelect;
|
||||
bool multi = d->multiSelect;
|
||||
bool shift = (mod & KEY_MOD_SHIFT) != 0;
|
||||
bool ctrl = (mod & KEY_MOD_CTRL) != 0;
|
||||
int32_t sel = w->as.listBox.selectedIdx;
|
||||
int32_t sel = d->selectedIdx;
|
||||
|
||||
// Ctrl+A -- select all (multi-select only)
|
||||
if (multi && ctrl && (key == 'a' || key == 'A' || key == 1)) {
|
||||
|
|
@ -360,9 +189,9 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
// Space -- toggle current item (multi-select only)
|
||||
if (multi && key == ' ') {
|
||||
if (sel >= 0 && w->as.listBox.selBits) {
|
||||
w->as.listBox.selBits[sel] ^= 1;
|
||||
w->as.listBox.anchorIdx = sel;
|
||||
if (sel >= 0 && d->selBits) {
|
||||
d->selBits[sel] ^= 1;
|
||||
d->anchorIdx = sel;
|
||||
}
|
||||
|
||||
if (w->onChange) {
|
||||
|
|
@ -390,7 +219,7 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
visibleRows = 1;
|
||||
}
|
||||
|
||||
int32_t newSel = widgetNavigateIndex(key, sel, w->as.listBox.itemCount, visibleRows);
|
||||
int32_t newSel = widgetNavigateIndex(key, sel, d->itemCount, visibleRows);
|
||||
|
||||
if (newSel < 0) {
|
||||
return;
|
||||
|
|
@ -400,14 +229,14 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
return;
|
||||
}
|
||||
|
||||
w->as.listBox.selectedIdx = newSel;
|
||||
d->selectedIdx = newSel;
|
||||
|
||||
// Update selection
|
||||
if (multi && w->as.listBox.selBits) {
|
||||
if (multi && d->selBits) {
|
||||
if (shift) {
|
||||
// Shift+arrow: range from anchor to new cursor
|
||||
memset(w->as.listBox.selBits, 0, w->as.listBox.itemCount);
|
||||
selectRange(w, w->as.listBox.anchorIdx, newSel);
|
||||
memset(d->selBits, 0, d->itemCount);
|
||||
selectRange(w, d->anchorIdx, newSel);
|
||||
}
|
||||
// Plain arrow: just move cursor, leave selections untouched
|
||||
}
|
||||
|
|
@ -432,21 +261,22 @@ void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// regions, and thumb based on the click position. Item clicks are translated
|
||||
// from pixel coordinates to item index using integer division by charHeight.
|
||||
void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
ListBoxDataT *d = (ListBoxDataT *)hit->data;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
int32_t innerH = hit->h - LISTBOX_BORDER * 2;
|
||||
int32_t visibleRows = innerH / font->charHeight;
|
||||
bool needSb = (hit->as.listBox.itemCount > visibleRows);
|
||||
bool needSb = (d->itemCount > visibleRows);
|
||||
|
||||
// Clamp scroll
|
||||
int32_t maxScroll = hit->as.listBox.itemCount - visibleRows;
|
||||
int32_t maxScroll = d->itemCount - visibleRows;
|
||||
|
||||
if (maxScroll < 0) {
|
||||
maxScroll = 0;
|
||||
}
|
||||
|
||||
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
|
||||
d->scrollPos = clampInt(d->scrollPos, 0, maxScroll);
|
||||
|
||||
// Check if click is on the scrollbar
|
||||
if (needSb) {
|
||||
|
|
@ -454,27 +284,27 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
|
||||
if (vx >= sbX) {
|
||||
int32_t relY = vy - (hit->y + LISTBOX_BORDER);
|
||||
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos);
|
||||
ScrollHitE sh = widgetScrollbarHitTest(innerH, relY, d->itemCount, visibleRows, d->scrollPos);
|
||||
|
||||
if (sh == ScrollHitArrowDecE) {
|
||||
if (hit->as.listBox.scrollPos > 0) {
|
||||
hit->as.listBox.scrollPos--;
|
||||
if (d->scrollPos > 0) {
|
||||
d->scrollPos--;
|
||||
}
|
||||
} else if (sh == ScrollHitArrowIncE) {
|
||||
if (hit->as.listBox.scrollPos < maxScroll) {
|
||||
hit->as.listBox.scrollPos++;
|
||||
if (d->scrollPos < maxScroll) {
|
||||
d->scrollPos++;
|
||||
}
|
||||
} else if (sh == ScrollHitPageDecE) {
|
||||
hit->as.listBox.scrollPos -= visibleRows;
|
||||
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
|
||||
d->scrollPos -= visibleRows;
|
||||
d->scrollPos = clampInt(d->scrollPos, 0, maxScroll);
|
||||
} else if (sh == ScrollHitPageIncE) {
|
||||
hit->as.listBox.scrollPos += visibleRows;
|
||||
hit->as.listBox.scrollPos = clampInt(hit->as.listBox.scrollPos, 0, maxScroll);
|
||||
d->scrollPos += visibleRows;
|
||||
d->scrollPos = clampInt(d->scrollPos, 0, maxScroll);
|
||||
} else if (sh == ScrollHitThumbE) {
|
||||
int32_t trackLen = innerH - WGT_SB_W * 2;
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, hit->as.listBox.itemCount, visibleRows, hit->as.listBox.scrollPos, &thumbPos, &thumbSize);
|
||||
widgetScrollbarThumb(trackLen, d->itemCount, visibleRows, d->scrollPos, &thumbPos, &thumbSize);
|
||||
|
||||
sDragScrollbar = hit;
|
||||
sDragScrollbarOrient = 0;
|
||||
|
|
@ -495,33 +325,33 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
}
|
||||
|
||||
int32_t clickedRow = relY / font->charHeight;
|
||||
int32_t idx = hit->as.listBox.scrollPos + clickedRow;
|
||||
int32_t idx = d->scrollPos + clickedRow;
|
||||
|
||||
if (idx < 0 || idx >= hit->as.listBox.itemCount) {
|
||||
if (idx < 0 || idx >= d->itemCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool multi = hit->as.listBox.multiSelect;
|
||||
bool multi = d->multiSelect;
|
||||
bool shift = (ctx->keyModifiers & KEY_MOD_SHIFT) != 0;
|
||||
bool ctrl = (ctx->keyModifiers & KEY_MOD_CTRL) != 0;
|
||||
|
||||
hit->as.listBox.selectedIdx = idx;
|
||||
d->selectedIdx = idx;
|
||||
hit->focused = true;
|
||||
|
||||
if (multi && hit->as.listBox.selBits) {
|
||||
if (multi && d->selBits) {
|
||||
if (ctrl) {
|
||||
// Ctrl+click: toggle item, update anchor
|
||||
hit->as.listBox.selBits[idx] ^= 1;
|
||||
hit->as.listBox.anchorIdx = idx;
|
||||
d->selBits[idx] ^= 1;
|
||||
d->anchorIdx = idx;
|
||||
} else if (shift) {
|
||||
// Shift+click: range from anchor to clicked
|
||||
memset(hit->as.listBox.selBits, 0, hit->as.listBox.itemCount);
|
||||
selectRange(hit, hit->as.listBox.anchorIdx, idx);
|
||||
memset(d->selBits, 0, d->itemCount);
|
||||
selectRange(hit, d->anchorIdx, idx);
|
||||
} else {
|
||||
// Plain click: select only this item, update anchor
|
||||
memset(hit->as.listBox.selBits, 0, hit->as.listBox.itemCount);
|
||||
hit->as.listBox.selBits[idx] = 1;
|
||||
hit->as.listBox.anchorIdx = idx;
|
||||
memset(d->selBits, 0, d->itemCount);
|
||||
d->selBits[idx] = 1;
|
||||
d->anchorIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -532,9 +362,9 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// onDblClick is handled by the central dispatcher in widgetEvent.c
|
||||
|
||||
// Initiate drag-reorder if enabled (not from modifier clicks)
|
||||
if (hit->as.listBox.reorderable && !shift && !ctrl) {
|
||||
hit->as.listBox.dragIdx = idx;
|
||||
hit->as.listBox.dropIdx = idx;
|
||||
if (d->reorderable && !shift && !ctrl) {
|
||||
d->dragIdx = idx;
|
||||
d->dropIdx = idx;
|
||||
sDragReorder = hit;
|
||||
}
|
||||
}
|
||||
|
|
@ -552,12 +382,13 @@ void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// items are selected. The scrollbar is drawn only when needed (item count
|
||||
// exceeds visible rows), and the content width is reduced accordingly.
|
||||
void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
ListBoxDataT *lb = (ListBoxDataT *)w->data;
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
int32_t innerH = w->h - LISTBOX_BORDER * 2;
|
||||
int32_t visibleRows = innerH / font->charHeight;
|
||||
bool needSb = (w->as.listBox.itemCount > visibleRows);
|
||||
bool needSb = (lb->itemCount > visibleRows);
|
||||
int32_t contentW = w->w - LISTBOX_BORDER * 2;
|
||||
|
||||
if (needSb) {
|
||||
|
|
@ -569,22 +400,22 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
|
||||
// Clamp scroll position
|
||||
int32_t maxScroll = w->as.listBox.itemCount - visibleRows;
|
||||
int32_t maxScroll = lb->itemCount - visibleRows;
|
||||
|
||||
if (maxScroll < 0) {
|
||||
maxScroll = 0;
|
||||
}
|
||||
|
||||
w->as.listBox.scrollPos = clampInt(w->as.listBox.scrollPos, 0, maxScroll);
|
||||
lb->scrollPos = clampInt(lb->scrollPos, 0, maxScroll);
|
||||
|
||||
// Draw items
|
||||
int32_t innerX = w->x + LISTBOX_BORDER + LISTBOX_PAD;
|
||||
int32_t innerY = w->y + LISTBOX_BORDER;
|
||||
int32_t scrollPos = w->as.listBox.scrollPos;
|
||||
bool multi = w->as.listBox.multiSelect;
|
||||
uint8_t *selBits = w->as.listBox.selBits;
|
||||
int32_t scrollPos = lb->scrollPos;
|
||||
bool multi = lb->multiSelect;
|
||||
uint8_t *selBits = lb->selBits;
|
||||
|
||||
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < w->as.listBox.itemCount; i++) {
|
||||
for (int32_t i = 0; i < visibleRows && (scrollPos + i) < lb->itemCount; i++) {
|
||||
int32_t idx = scrollPos + i;
|
||||
int32_t iy = innerY + i * font->charHeight;
|
||||
uint32_t ifg = fg;
|
||||
|
|
@ -595,7 +426,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
if (multi && selBits) {
|
||||
isSelected = selBits[idx] != 0;
|
||||
} else {
|
||||
isSelected = (idx == w->as.listBox.selectedIdx);
|
||||
isSelected = (idx == lb->selectedIdx);
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
|
|
@ -604,10 +435,10 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
rectFill(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, ibg);
|
||||
}
|
||||
|
||||
drawText(d, ops, font, innerX, iy, w->as.listBox.items[idx], ifg, ibg, false);
|
||||
drawText(d, ops, font, innerX, iy, lb->items[idx], ifg, ibg, false);
|
||||
|
||||
// Draw cursor indicator in multi-select (dotted focus rect on cursor item)
|
||||
if (multi && idx == w->as.listBox.selectedIdx && w->focused) {
|
||||
if (multi && idx == lb->selectedIdx && w->focused) {
|
||||
drawFocusRect(d, ops, w->x + LISTBOX_BORDER, iy, contentW, font->charHeight, fg);
|
||||
}
|
||||
}
|
||||
|
|
@ -615,8 +446,8 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
// Draw drag-reorder insertion indicator: a 2px horizontal line at the
|
||||
// drop position. The line is drawn between items (not on top of them)
|
||||
// so it's clear where the dragged item will be inserted.
|
||||
if (w->as.listBox.reorderable && w->as.listBox.dragIdx >= 0 && w->as.listBox.dropIdx >= 0) {
|
||||
int32_t drop = w->as.listBox.dropIdx;
|
||||
if (lb->reorderable && lb->dragIdx >= 0 && lb->dropIdx >= 0) {
|
||||
int32_t drop = lb->dropIdx;
|
||||
int32_t lineY = innerY + (drop - scrollPos) * font->charHeight;
|
||||
|
||||
if (lineY >= innerY && lineY <= innerY + innerH) {
|
||||
|
|
@ -629,7 +460,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
if (needSb) {
|
||||
int32_t sbX = w->x + w->w - LISTBOX_BORDER - WGT_SB_W;
|
||||
int32_t sbY = w->y + LISTBOX_BORDER;
|
||||
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, w->as.listBox.itemCount, visibleRows, w->as.listBox.scrollPos);
|
||||
widgetDrawScrollbarV(d, ops, colors, sbX, sbY, innerH, lb->itemCount, visibleRows, lb->scrollPos);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
|
|
@ -644,7 +475,7 @@ void widgetListBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
|
||||
|
||||
static const WidgetClassT sClassListBox = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_SCROLLABLE,
|
||||
.paint = widgetListBoxPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetListBoxCalcMinSize,
|
||||
|
|
@ -656,6 +487,227 @@ static const WidgetClassT sClassListBox = {
|
|||
.setText = NULL
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetListBoxE] = &sClassListBox;
|
||||
// ============================================================
|
||||
// Widget creation functions and accessors
|
||||
// ============================================================
|
||||
|
||||
|
||||
// Allocate (or re-allocate) the selection bits array for multi-select mode.
|
||||
static void listBoxAllocSelBits(WidgetT *w) {
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
free(d->selBits);
|
||||
d->selBits = NULL;
|
||||
|
||||
int32_t count = d->itemCount;
|
||||
|
||||
if (count > 0 && d->multiSelect) {
|
||||
d->selBits = (uint8_t *)calloc(count, 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
WidgetT *wgtListBox(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
ListBoxDataT *d = (ListBoxDataT *)calloc(1, sizeof(ListBoxDataT));
|
||||
|
||||
if (!d) {
|
||||
return w;
|
||||
}
|
||||
|
||||
w->data = d;
|
||||
d->selectedIdx = -1;
|
||||
d->anchorIdx = -1;
|
||||
d->dragIdx = -1;
|
||||
d->dropIdx = -1;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
void wgtListBoxClearSelection(WidgetT *w) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
if (!d->selBits) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(d->selBits, 0, d->itemCount);
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtListBoxGetSelected(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, -1);
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
return d->selectedIdx;
|
||||
}
|
||||
|
||||
|
||||
bool wgtListBoxIsItemSelected(const WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET(w, sTypeId, false);
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
if (!d->multiSelect) {
|
||||
return idx == d->selectedIdx;
|
||||
}
|
||||
|
||||
if (!d->selBits || idx < 0 || idx >= d->itemCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return d->selBits[idx] != 0;
|
||||
}
|
||||
|
||||
|
||||
void wgtListBoxSelectAll(WidgetT *w) {
|
||||
if (!w || w->type != sTypeId) {
|
||||
return;
|
||||
}
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
if (!d->selBits) {
|
||||
return;
|
||||
}
|
||||
|
||||
memset(d->selBits, 1, d->itemCount);
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
void wgtListBoxSetItemSelected(WidgetT *w, int32_t idx, bool selected) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
if (!d->selBits || idx < 0 || idx >= d->itemCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
d->selBits[idx] = selected ? 1 : 0;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
void wgtListBoxSetItems(WidgetT *w, const char **items, int32_t count) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
d->items = items;
|
||||
d->itemCount = count;
|
||||
d->maxItemLen = widgetMaxItemLen(items, count);
|
||||
|
||||
if (d->selectedIdx >= count) {
|
||||
d->selectedIdx = count > 0 ? 0 : -1;
|
||||
}
|
||||
|
||||
if (d->selectedIdx < 0 && count > 0) {
|
||||
d->selectedIdx = 0;
|
||||
}
|
||||
|
||||
d->anchorIdx = d->selectedIdx;
|
||||
|
||||
// Reallocate selection bits
|
||||
listBoxAllocSelBits(w);
|
||||
|
||||
// Pre-select the cursor item
|
||||
if (d->selBits && d->selectedIdx >= 0) {
|
||||
d->selBits[d->selectedIdx] = 1;
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
void wgtListBoxSetMultiSelect(WidgetT *w, bool multi) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
d->multiSelect = multi;
|
||||
listBoxAllocSelBits(w);
|
||||
|
||||
// Sync: mark current selection
|
||||
if (d->selBits && d->selectedIdx >= 0) {
|
||||
d->selBits[d->selectedIdx] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void wgtListBoxSetReorderable(WidgetT *w, bool reorderable) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
d->reorderable = reorderable;
|
||||
}
|
||||
|
||||
|
||||
void wgtListBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
|
||||
ListBoxDataT *d = (ListBoxDataT *)w->data;
|
||||
|
||||
d->selectedIdx = idx;
|
||||
d->anchorIdx = idx;
|
||||
d->scrollPos = 0;
|
||||
|
||||
// In multi-select, clear all then select just this one
|
||||
if (d->selBits) {
|
||||
memset(d->selBits, 0, d->itemCount);
|
||||
|
||||
if (idx >= 0 && idx < d->itemCount) {
|
||||
d->selBits[idx] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll to make the selected item visible
|
||||
ensureScrollVisible(w, idx);
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
void (*setItems)(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t (*getSelected)(const WidgetT *w);
|
||||
void (*setSelected)(WidgetT *w, int32_t idx);
|
||||
void (*setMultiSelect)(WidgetT *w, bool multi);
|
||||
bool (*isItemSelected)(const WidgetT *w, int32_t idx);
|
||||
void (*setItemSelected)(WidgetT *w, int32_t idx, bool selected);
|
||||
void (*selectAll)(WidgetT *w);
|
||||
void (*clearSelection)(WidgetT *w);
|
||||
void (*setReorderable)(WidgetT *w, bool reorderable);
|
||||
} sApi = {
|
||||
.create = wgtListBox,
|
||||
.setItems = wgtListBoxSetItems,
|
||||
.getSelected = wgtListBoxGetSelected,
|
||||
.setSelected = wgtListBoxSetSelected,
|
||||
.setMultiSelect = wgtListBoxSetMultiSelect,
|
||||
.isItemSelected = wgtListBoxIsItemSelected,
|
||||
.setItemSelected = wgtListBoxSetItemSelected,
|
||||
.selectAll = wgtListBoxSelectAll,
|
||||
.clearSelection = wgtListBoxClearSelection,
|
||||
.setReorderable = wgtListBoxSetReorderable
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassListBox);
|
||||
wgtRegisterApi("listbox", &sApi);
|
||||
}
|
||||
|
|
|
|||
37
widgets/widgetListBox.h
Normal file
37
widgets/widgetListBox.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// widgetListBox.h -- ListBox widget API
|
||||
#ifndef WIDGET_LISTBOX_H
|
||||
#define WIDGET_LISTBOX_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
void (*setItems)(WidgetT *w, const char **items, int32_t count);
|
||||
int32_t (*getSelected)(const WidgetT *w);
|
||||
void (*setSelected)(WidgetT *w, int32_t idx);
|
||||
void (*setMultiSelect)(WidgetT *w, bool multi);
|
||||
bool (*isItemSelected)(const WidgetT *w, int32_t idx);
|
||||
void (*setItemSelected)(WidgetT *w, int32_t idx, bool selected);
|
||||
void (*selectAll)(WidgetT *w);
|
||||
void (*clearSelection)(WidgetT *w);
|
||||
void (*setReorderable)(WidgetT *w, bool reorderable);
|
||||
} ListBoxApiT;
|
||||
|
||||
static inline const ListBoxApiT *dvxListBoxApi(void) {
|
||||
static const ListBoxApiT *sApi;
|
||||
if (!sApi) { sApi = (const ListBoxApiT *)wgtGetApi("listbox"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtListBox(parent) dvxListBoxApi()->create(parent)
|
||||
#define wgtListBoxSetItems(w, items, count) dvxListBoxApi()->setItems(w, items, count)
|
||||
#define wgtListBoxGetSelected(w) dvxListBoxApi()->getSelected(w)
|
||||
#define wgtListBoxSetSelected(w, idx) dvxListBoxApi()->setSelected(w, idx)
|
||||
#define wgtListBoxSetMultiSelect(w, multi) dvxListBoxApi()->setMultiSelect(w, multi)
|
||||
#define wgtListBoxIsItemSelected(w, idx) dvxListBoxApi()->isItemSelected(w, idx)
|
||||
#define wgtListBoxSetItemSelected(w, idx, selected) dvxListBoxApi()->setItemSelected(w, idx, selected)
|
||||
#define wgtListBoxSelectAll(w) dvxListBoxApi()->selectAll(w)
|
||||
#define wgtListBoxClearSelection(w) dvxListBoxApi()->clearSelection(w)
|
||||
#define wgtListBoxSetReorderable(w, reorderable) dvxListBoxApi()->setReorderable(w, reorderable)
|
||||
|
||||
#endif // WIDGET_LISTBOX_H
|
||||
File diff suppressed because it is too large
Load diff
47
widgets/widgetListView.h
Normal file
47
widgets/widgetListView.h
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// widgetListView.h -- ListView widget API
|
||||
//
|
||||
// Include this header in app/shell code that uses the ListView widget.
|
||||
// Column types (ListViewColT, ListViewAlignE, ListViewSortE) are
|
||||
// defined in dvxWidget.h so widget DXEs can reference them.
|
||||
#ifndef WIDGET_LISTVIEW_H
|
||||
#define WIDGET_LISTVIEW_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
void (*setColumns)(WidgetT *w, const ListViewColT *cols, int32_t count);
|
||||
void (*setData)(WidgetT *w, const char **cellData, int32_t rowCount);
|
||||
int32_t (*getSelected)(const WidgetT *w);
|
||||
void (*setSelected)(WidgetT *w, int32_t idx);
|
||||
void (*setSort)(WidgetT *w, int32_t col, ListViewSortE dir);
|
||||
void (*setHeaderClickCallback)(WidgetT *w, void (*cb)(WidgetT *w, int32_t col, ListViewSortE dir));
|
||||
void (*setMultiSelect)(WidgetT *w, bool multi);
|
||||
bool (*isItemSelected)(const WidgetT *w, int32_t idx);
|
||||
void (*setItemSelected)(WidgetT *w, int32_t idx, bool selected);
|
||||
void (*selectAll)(WidgetT *w);
|
||||
void (*clearSelection)(WidgetT *w);
|
||||
void (*setReorderable)(WidgetT *w, bool reorderable);
|
||||
} ListViewApiT;
|
||||
|
||||
static inline const ListViewApiT *dvxListViewApi(void) {
|
||||
static const ListViewApiT *sApi;
|
||||
if (!sApi) { sApi = (const ListViewApiT *)wgtGetApi("listview"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtListView(parent) dvxListViewApi()->create(parent)
|
||||
#define wgtListViewSetColumns(w, cols, count) dvxListViewApi()->setColumns(w, cols, count)
|
||||
#define wgtListViewSetData(w, cellData, rowCount) dvxListViewApi()->setData(w, cellData, rowCount)
|
||||
#define wgtListViewGetSelected(w) dvxListViewApi()->getSelected(w)
|
||||
#define wgtListViewSetSelected(w, idx) dvxListViewApi()->setSelected(w, idx)
|
||||
#define wgtListViewSetSort(w, col, dir) dvxListViewApi()->setSort(w, col, dir)
|
||||
#define wgtListViewSetHeaderClickCallback(w, cb) dvxListViewApi()->setHeaderClickCallback(w, cb)
|
||||
#define wgtListViewSetMultiSelect(w, multi) dvxListViewApi()->setMultiSelect(w, multi)
|
||||
#define wgtListViewIsItemSelected(w, idx) dvxListViewApi()->isItemSelected(w, idx)
|
||||
#define wgtListViewSetItemSelected(w, idx, selected) dvxListViewApi()->setItemSelected(w, idx, selected)
|
||||
#define wgtListViewSelectAll(w) dvxListViewApi()->selectAll(w)
|
||||
#define wgtListViewClearSelection(w) dvxListViewApi()->clearSelection(w)
|
||||
#define wgtListViewSetReorderable(w, reorderable) dvxListViewApi()->setReorderable(w, reorderable)
|
||||
|
||||
#endif // WIDGET_LISTVIEW_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetProgressBar.c -- ProgressBar widget
|
||||
//
|
||||
// A non-interactive fill-bar widget for displaying bounded progress.
|
||||
|
|
@ -14,72 +15,15 @@
|
|||
// The widget has no mouse or keyboard handlers -- it is purely display.
|
||||
// Value is controlled externally through the set/get API.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
// ============================================================
|
||||
// wgtProgressBar
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtProgressBar(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetProgressBarE);
|
||||
|
||||
if (w) {
|
||||
w->as.progressBar.value = 0;
|
||||
w->as.progressBar.maxValue = 100;
|
||||
w->as.progressBar.vertical = false;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtProgressBarV
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtProgressBarV(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetProgressBarE);
|
||||
|
||||
if (w) {
|
||||
w->as.progressBar.value = 0;
|
||||
w->as.progressBar.maxValue = 100;
|
||||
w->as.progressBar.vertical = true;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtProgressBarGetValue
|
||||
// ============================================================
|
||||
|
||||
int32_t wgtProgressBarGetValue(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, WidgetProgressBarE, 0);
|
||||
|
||||
return w->as.progressBar.value;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtProgressBarSetValue
|
||||
// ============================================================
|
||||
|
||||
void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetProgressBarE);
|
||||
|
||||
if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (value > w->as.progressBar.maxValue) {
|
||||
value = w->as.progressBar.maxValue;
|
||||
}
|
||||
|
||||
w->as.progressBar.value = value;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
typedef struct {
|
||||
int32_t value;
|
||||
int32_t maxValue;
|
||||
bool vertical;
|
||||
} ProgressBarDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -91,7 +35,9 @@ void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
|
|||
// bar plus 2px bevel on each side). The main axis uses 12 char widths
|
||||
// so the bar is wide enough to show meaningful progress granularity.
|
||||
void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
if (w->as.progressBar.vertical) {
|
||||
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
|
||||
|
||||
if (d->vertical) {
|
||||
w->calcMinW = font->charHeight + 4;
|
||||
w->calcMinH = font->charWidth * 12;
|
||||
} else {
|
||||
|
|
@ -113,8 +59,9 @@ void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
//
|
||||
// Vertical bars fill from bottom-up (natural "filling" metaphor),
|
||||
// which requires computing the Y offset as (innerH - fillH).
|
||||
void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
void widgetProgressBarPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
|
||||
uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->activeTitleBg) : colors->windowShadow;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
|
|
@ -125,10 +72,10 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
|||
bevel.shadow = colors->windowHighlight;
|
||||
bevel.face = bg;
|
||||
bevel.width = 2;
|
||||
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
|
||||
// Fill bar
|
||||
int32_t maxVal = w->as.progressBar.maxValue;
|
||||
int32_t maxVal = d->maxValue;
|
||||
|
||||
if (maxVal <= 0) {
|
||||
maxVal = 100;
|
||||
|
|
@ -137,30 +84,39 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
|||
int32_t innerW = w->w - 4;
|
||||
int32_t innerH = w->h - 4;
|
||||
|
||||
if (w->as.progressBar.vertical) {
|
||||
int32_t fillH = (innerH * w->as.progressBar.value) / maxVal;
|
||||
if (d->vertical) {
|
||||
int32_t fillH = (innerH * d->value) / maxVal;
|
||||
|
||||
if (fillH > innerH) {
|
||||
fillH = innerH;
|
||||
}
|
||||
|
||||
if (fillH > 0) {
|
||||
rectFill(d, ops, w->x + 2, w->y + 2 + innerH - fillH, innerW, fillH, fg);
|
||||
rectFill(disp, ops, w->x + 2, w->y + 2 + innerH - fillH, innerW, fillH, fg);
|
||||
}
|
||||
} else {
|
||||
int32_t fillW = (innerW * w->as.progressBar.value) / maxVal;
|
||||
int32_t fillW = (innerW * d->value) / maxVal;
|
||||
|
||||
if (fillW > innerW) {
|
||||
fillW = innerW;
|
||||
}
|
||||
|
||||
if (fillW > 0) {
|
||||
rectFill(d, ops, w->x + 2, w->y + 2, fillW, innerH, fg);
|
||||
rectFill(disp, ops, w->x + 2, w->y + 2, fillW, innerH, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetProgressBarDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetProgressBarDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
|
@ -173,12 +129,90 @@ static const WidgetClassT sClassProgressBar = {
|
|||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetProgressBarDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetProgressBarE] = &sClassProgressBar;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtProgressBar(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT));
|
||||
d->value = 0;
|
||||
d->maxValue = 100;
|
||||
d->vertical = false;
|
||||
w->data = d;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
WidgetT *wgtProgressBarV(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
ProgressBarDataT *d = calloc(1, sizeof(ProgressBarDataT));
|
||||
d->value = 0;
|
||||
d->maxValue = 100;
|
||||
d->vertical = true;
|
||||
w->data = d;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtProgressBarGetValue(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, 0);
|
||||
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
|
||||
|
||||
return d->value;
|
||||
}
|
||||
|
||||
|
||||
void wgtProgressBarSetValue(WidgetT *w, int32_t value) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
ProgressBarDataT *d = (ProgressBarDataT *)w->data;
|
||||
|
||||
if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (value > d->maxValue) {
|
||||
value = d->maxValue;
|
||||
}
|
||||
|
||||
d->value = value;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
WidgetT *(*createV)(WidgetT *parent);
|
||||
void (*setValue)(WidgetT *w, int32_t value);
|
||||
int32_t (*getValue)(const WidgetT *w);
|
||||
} sApi = {
|
||||
.create = wgtProgressBar,
|
||||
.createV = wgtProgressBarV,
|
||||
.setValue = wgtProgressBarSetValue,
|
||||
.getValue = wgtProgressBarGetValue
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassProgressBar);
|
||||
wgtRegisterApi("progressbar", &sApi);
|
||||
}
|
||||
|
|
|
|||
25
widgets/widgetProgressBar.h
Normal file
25
widgets/widgetProgressBar.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// widgetProgressBar.h -- ProgressBar widget API
|
||||
#ifndef WIDGET_PROGRESSBAR_H
|
||||
#define WIDGET_PROGRESSBAR_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
WidgetT *(*createV)(WidgetT *parent);
|
||||
void (*setValue)(WidgetT *w, int32_t value);
|
||||
int32_t (*getValue)(const WidgetT *w);
|
||||
} ProgressBarApiT;
|
||||
|
||||
static inline const ProgressBarApiT *dvxProgressBarApi(void) {
|
||||
static const ProgressBarApiT *sApi;
|
||||
if (!sApi) { sApi = (const ProgressBarApiT *)wgtGetApi("progressbar"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtProgressBar(parent) dvxProgressBarApi()->create(parent)
|
||||
#define wgtProgressBarV(parent) dvxProgressBarApi()->createV(parent)
|
||||
#define wgtProgressBarSetValue(w, value) dvxProgressBarApi()->setValue(w, value)
|
||||
#define wgtProgressBarGetValue(w) dvxProgressBarApi()->getValue(w)
|
||||
|
||||
#endif // WIDGET_PROGRESSBAR_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetRadio.c -- RadioGroup and Radio button widgets
|
||||
//
|
||||
// Two-level architecture: RadioGroupE is an invisible container that
|
||||
|
|
@ -21,70 +22,40 @@
|
|||
// focus AND selection together (standard radio group behavior --
|
||||
// focus and selection are coupled, unlike checkboxes).
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#define CHECKBOX_BOX_SIZE 12
|
||||
#define CHECKBOX_GAP 4
|
||||
|
||||
static int32_t sRadioGroupTypeId = -1;
|
||||
static int32_t sRadioTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
const char *text;
|
||||
int32_t index;
|
||||
} RadioDataT;
|
||||
|
||||
typedef struct {
|
||||
int32_t selectedIdx;
|
||||
} RadioGroupDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtRadio
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
// Auto-index is computed by counting RadioE siblings before this one.
|
||||
// This means radio buttons must be added in order and not reordered
|
||||
// after construction -- which is the normal pattern for radio groups.
|
||||
WidgetT *wgtRadio(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetRadioE);
|
||||
|
||||
if (w) {
|
||||
w->as.radio.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
|
||||
// Auto-assign index based on position in parent
|
||||
int32_t idx = 0;
|
||||
|
||||
for (WidgetT *c = parent->firstChild; c != w; c = c->nextSibling) {
|
||||
if (c->type == WidgetRadioE) {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
w->as.radio.index = idx;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
static void widgetRadioDestroy(WidgetT *w);
|
||||
static void widgetRadioGroupDestroy(WidgetT *w);
|
||||
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtRadioGroup
|
||||
// widgetRadioAccelActivate
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtRadioGroup(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetRadioGroupE);
|
||||
|
||||
if (w) {
|
||||
w->as.radioGroup.selectedIdx = 0;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioGetText
|
||||
// ============================================================
|
||||
|
||||
const char *widgetRadioGetText(const WidgetT *w) {
|
||||
return w->as.radio.text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetRadioSetText(WidgetT *w, const char *text) {
|
||||
w->as.radio.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
void widgetRadioAccelActivate(WidgetT *w, WidgetT *root) {
|
||||
(void)root;
|
||||
widgetRadioOnMouse(w, NULL, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -96,12 +67,43 @@ void widgetRadioSetText(WidgetT *w, const char *text) {
|
|||
// so radio buttons and checkboxes align when placed in the same
|
||||
// column, and the indicator + label layout is visually consistent.
|
||||
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
RadioDataT *d = (RadioDataT *)w->data;
|
||||
w->calcMinW = CHECKBOX_BOX_SIZE + CHECKBOX_GAP +
|
||||
textWidthAccel(font, w->as.radio.text);
|
||||
textWidthAccel(font, d->text);
|
||||
w->calcMinH = DVX_MAX(CHECKBOX_BOX_SIZE, font->charHeight);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetRadioDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioGetText
|
||||
// ============================================================
|
||||
|
||||
const char *widgetRadioGetText(const WidgetT *w) {
|
||||
RadioDataT *d = (RadioDataT *)w->data;
|
||||
return d->text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioGroupDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetRadioGroupDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioOnKey
|
||||
// ============================================================
|
||||
|
|
@ -117,11 +119,13 @@ void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// ORed with the scancode. 0x50=Down, 0x4D=Right, 0x48=Up, 0x4B=Left.
|
||||
void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
RadioDataT *rd = (RadioDataT *)w->data;
|
||||
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
// Select this radio
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||
w->parent->as.radioGroup.selectedIdx = w->as.radio.index;
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
|
||||
gd->selectedIdx = rd->index;
|
||||
|
||||
if (w->parent->onChange) {
|
||||
w->parent->onChange(w->parent);
|
||||
|
|
@ -131,21 +135,23 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
wgtInvalidatePaint(w);
|
||||
} else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) {
|
||||
// Down or Right -- next radio in group
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
WidgetT *next = NULL;
|
||||
|
||||
for (WidgetT *s = w->nextSibling; s; s = s->nextSibling) {
|
||||
if (s->type == WidgetRadioE && s->visible && s->enabled) {
|
||||
if (s->type == sRadioTypeId && s->visible && s->enabled) {
|
||||
next = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next) {
|
||||
RadioDataT *nd = (RadioDataT *)next->data;
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)next->parent->data;
|
||||
w->focused = false;
|
||||
next->focused = true;
|
||||
sFocusedWidget = next;
|
||||
next->parent->as.radioGroup.selectedIdx = next->as.radio.index;
|
||||
gd->selectedIdx = nd->index;
|
||||
|
||||
if (next->parent->onChange) {
|
||||
next->parent->onChange(next->parent);
|
||||
|
|
@ -156,20 +162,22 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
} else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) {
|
||||
// Up or Left -- previous radio in group
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
WidgetT *prev = NULL;
|
||||
|
||||
for (WidgetT *s = w->parent->firstChild; s && s != w; s = s->nextSibling) {
|
||||
if (s->type == WidgetRadioE && s->visible && s->enabled) {
|
||||
if (s->type == sRadioTypeId && s->visible && s->enabled) {
|
||||
prev = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
RadioDataT *pd = (RadioDataT *)prev->data;
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)prev->parent->data;
|
||||
w->focused = false;
|
||||
prev->focused = true;
|
||||
sFocusedWidget = prev;
|
||||
prev->parent->as.radioGroup.selectedIdx = prev->as.radio.index;
|
||||
gd->selectedIdx = pd->index;
|
||||
|
||||
if (prev->parent->onChange) {
|
||||
prev->parent->onChange(prev->parent);
|
||||
|
|
@ -192,8 +200,10 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
(void)vy;
|
||||
w->focused = true;
|
||||
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||
w->parent->as.radioGroup.selectedIdx = w->as.radio.index;
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioDataT *rd = (RadioDataT *)w->data;
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
|
||||
gd->selectedIdx = rd->index;
|
||||
|
||||
if (w->parent->onChange) {
|
||||
w->parent->onChange(w->parent);
|
||||
|
|
@ -217,6 +227,7 @@ void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// left/right extent is computed with simple integer distance from
|
||||
// the midpoint, making the diamond perfectly symmetric at any size.
|
||||
void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
RadioDataT *rd = (RadioDataT *)w->data;
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
int32_t boxY = w->y + (w->h - CHECKBOX_BOX_SIZE) / 2;
|
||||
|
|
@ -254,8 +265,10 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
}
|
||||
|
||||
// Draw filled diamond if selected
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE &&
|
||||
w->parent->as.radioGroup.selectedIdx == w->as.radio.index) {
|
||||
if (w->parent && w->parent->type == sRadioGroupTypeId) {
|
||||
RadioGroupDataT *gd = (RadioGroupDataT *)w->parent->data;
|
||||
|
||||
if (gd->selectedIdx == rd->index) {
|
||||
uint32_t dotFg = w->enabled ? fg : colors->windowShadow;
|
||||
|
||||
static const int32_t dotW[] = {2, 4, 6, 6, 4, 2};
|
||||
|
|
@ -265,15 +278,16 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
drawHLine(d, ops, bx + mid - dw / 2, boxY + mid - 3 + i, dw, dotFg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t labelX = w->x + CHECKBOX_BOX_SIZE + CHECKBOX_GAP;
|
||||
int32_t labelY = w->y + (w->h - font->charHeight) / 2;
|
||||
int32_t labelW = textWidthAccel(font, w->as.radio.text);
|
||||
int32_t labelW = textWidthAccel(font, rd->text);
|
||||
|
||||
if (!w->enabled) {
|
||||
drawTextAccelEmbossed(d, ops, font, labelX, labelY, w->as.radio.text, colors);
|
||||
drawTextAccelEmbossed(d, ops, font, labelX, labelY, rd->text, colors);
|
||||
} else {
|
||||
drawTextAccel(d, ops, font, labelX, labelY, w->as.radio.text, fg, bg, false);
|
||||
drawTextAccel(d, ops, font, labelX, labelY, rd->text, fg, bg, false);
|
||||
}
|
||||
|
||||
if (w->focused) {
|
||||
|
|
@ -282,6 +296,17 @@ void widgetRadioPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitmap
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetRadioSetText(WidgetT *w, const char *text) {
|
||||
RadioDataT *d = (RadioDataT *)w->data;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
|
@ -295,7 +320,7 @@ static const WidgetClassT sClassRadioGroup = {
|
|||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetRadioGroupDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
|
@ -308,12 +333,90 @@ static const WidgetClassT sClassRadio = {
|
|||
.layout = NULL,
|
||||
.onMouse = widgetRadioOnMouse,
|
||||
.onKey = widgetRadioOnKey,
|
||||
.destroy = NULL,
|
||||
.onAccelActivate = widgetRadioAccelActivate,
|
||||
.destroy = widgetRadioDestroy,
|
||||
.getText = widgetRadioGetText,
|
||||
.setText = widgetRadioSetText
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetRadioGroupE] = &sClassRadioGroup;
|
||||
widgetClassTable[WidgetRadioE] = &sClassRadio;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtRadio(WidgetT *parent, const char *text) {
|
||||
WidgetT *w = widgetAlloc(parent, sRadioTypeId);
|
||||
|
||||
if (w) {
|
||||
RadioDataT *d = calloc(1, sizeof(RadioDataT));
|
||||
w->data = d;
|
||||
d->text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
|
||||
// Auto-assign index based on position in parent
|
||||
int32_t idx = 0;
|
||||
|
||||
for (WidgetT *c = parent->firstChild; c != w; c = c->nextSibling) {
|
||||
if (c->type == sRadioTypeId) {
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
d->index = idx;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
WidgetT *wgtRadioGroup(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sRadioGroupTypeId);
|
||||
|
||||
if (w) {
|
||||
RadioGroupDataT *d = calloc(1, sizeof(RadioGroupDataT));
|
||||
w->data = d;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
void wgtRadioGroupSetSelected(WidgetT *w, int32_t idx) {
|
||||
VALIDATE_WIDGET_VOID(w, sRadioGroupTypeId);
|
||||
RadioGroupDataT *d = (RadioGroupDataT *)w->data;
|
||||
|
||||
d->selectedIdx = idx;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtRadioGetIndex(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sRadioTypeId, -1);
|
||||
RadioDataT *d = (RadioDataT *)w->data;
|
||||
|
||||
return d->index;
|
||||
}
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*group)(WidgetT *parent);
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
void (*groupSetSelected)(WidgetT *group, int32_t index);
|
||||
int32_t (*getIndex)(const WidgetT *w);
|
||||
} sApi = {
|
||||
.group = wgtRadioGroup,
|
||||
.create = wgtRadio,
|
||||
.groupSetSelected = wgtRadioGroupSetSelected,
|
||||
.getIndex = wgtRadioGetIndex
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sRadioGroupTypeId = wgtRegisterClass(&sClassRadioGroup);
|
||||
sRadioTypeId = wgtRegisterClass(&sClassRadio);
|
||||
wgtRegisterApi("radio", &sApi);
|
||||
}
|
||||
|
|
|
|||
25
widgets/widgetRadio.h
Normal file
25
widgets/widgetRadio.h
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// widgetRadio.h -- Radio button widget API
|
||||
#ifndef WIDGET_RADIO_H
|
||||
#define WIDGET_RADIO_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*group)(WidgetT *parent);
|
||||
WidgetT *(*create)(WidgetT *parent, const char *text);
|
||||
void (*groupSetSelected)(WidgetT *w, int32_t idx);
|
||||
int32_t (*getIndex)(const WidgetT *w);
|
||||
} RadioApiT;
|
||||
|
||||
static inline const RadioApiT *dvxRadioApi(void) {
|
||||
static const RadioApiT *sApi;
|
||||
if (!sApi) { sApi = (const RadioApiT *)wgtGetApi("radio"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtRadioGroup(parent) dvxRadioApi()->group(parent)
|
||||
#define wgtRadio(parent, text) dvxRadioApi()->create(parent, text)
|
||||
#define wgtRadioGroupSetSelected(w, idx) dvxRadioApi()->groupSetSelected(w, idx)
|
||||
#define wgtRadioGetIndex(w) dvxRadioApi()->getIndex(w)
|
||||
|
||||
#endif // WIDGET_RADIO_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetScrollPane.c -- ScrollPane container widget
|
||||
//
|
||||
// A clipping container that allows its children (laid out as a
|
||||
|
|
@ -27,7 +28,14 @@
|
|||
// This is a minor duplication tradeoff for allowing different scrollbar
|
||||
// widths in different contexts.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
int32_t scrollPosV;
|
||||
int32_t scrollPosH;
|
||||
} ScrollPaneDataT;
|
||||
|
||||
#define SP_BORDER 2
|
||||
#define SP_SB_W 14
|
||||
|
|
@ -41,6 +49,7 @@
|
|||
static void drawSPHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbW, int32_t totalW, int32_t visibleW);
|
||||
static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const ColorSchemeT *colors, int32_t sbX, int32_t sbY, int32_t sbH, int32_t totalH, int32_t visibleH);
|
||||
static void spCalcNeeds(WidgetT *w, const BitmapFontT *font, int32_t *contentMinW, int32_t *contentMinH, int32_t *innerW, int32_t *innerH, bool *needVSb, bool *needHSb);
|
||||
static void widgetScrollPaneDestroy(WidgetT *w);
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -52,6 +61,7 @@ static void drawSPHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
|||
return;
|
||||
}
|
||||
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
||||
uint32_t fg = colors->contentFg;
|
||||
|
||||
// Trough
|
||||
|
|
@ -90,7 +100,7 @@ static void drawSPHScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
|||
if (trackLen > 0 && totalW > 0) {
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, totalW, visibleW, w->as.scrollPane.scrollPosH, &thumbPos, &thumbSize);
|
||||
widgetScrollbarThumb(trackLen, totalW, visibleW, sp->scrollPosH, &thumbPos, &thumbSize);
|
||||
drawBevel(d, ops, sbX + SP_SB_W + thumbPos, sbY, thumbSize, SP_SB_W, &btnBevel);
|
||||
}
|
||||
}
|
||||
|
|
@ -105,6 +115,7 @@ static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
|||
return;
|
||||
}
|
||||
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
||||
uint32_t fg = colors->contentFg;
|
||||
|
||||
// Trough
|
||||
|
|
@ -143,7 +154,7 @@ static void drawSPVScrollbar(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
|||
if (trackLen > 0 && totalH > 0) {
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, totalH, visibleH, w->as.scrollPane.scrollPosV, &thumbPos, &thumbSize);
|
||||
widgetScrollbarThumb(trackLen, totalH, visibleH, sp->scrollPosV, &thumbPos, &thumbSize);
|
||||
drawBevel(d, ops, sbX, sbY + SP_SB_W + thumbPos, SP_SB_W, thumbSize, &btnBevel);
|
||||
}
|
||||
}
|
||||
|
|
@ -240,6 +251,16 @@ void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetScrollPaneDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetScrollPaneDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetScrollPaneLayout
|
||||
// ============================================================
|
||||
|
|
@ -257,6 +278,8 @@ void widgetScrollPaneCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// proportional to its weight/totalWeight ratio. This allows stretch
|
||||
// children inside the scrollable area.
|
||||
void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
||||
|
||||
int32_t contentMinW;
|
||||
int32_t contentMinH;
|
||||
int32_t innerW;
|
||||
|
|
@ -278,16 +301,16 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
maxScrollH = 0;
|
||||
}
|
||||
|
||||
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
|
||||
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
|
||||
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
|
||||
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||
|
||||
// Layout children as a vertical box at virtual size
|
||||
int32_t pad = wgtResolveSize(w->padding, 0, font->charWidth);
|
||||
int32_t gap = wgtResolveSize(w->spacing, 0, font->charWidth);
|
||||
int32_t virtualW = DVX_MAX(innerW, contentMinW);
|
||||
int32_t virtualH = DVX_MAX(innerH, contentMinH);
|
||||
int32_t baseX = w->x + SP_BORDER - w->as.scrollPane.scrollPosH;
|
||||
int32_t baseY = w->y + SP_BORDER - w->as.scrollPane.scrollPosV;
|
||||
int32_t baseX = w->x + SP_BORDER - sp->scrollPosH;
|
||||
int32_t baseY = w->y + SP_BORDER - sp->scrollPosV;
|
||||
int32_t childW = virtualW - pad * 2;
|
||||
int32_t pos = baseY + pad;
|
||||
|
||||
|
|
@ -369,6 +392,7 @@ void widgetScrollPaneLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
||||
AppContextT *ctx = wgtGetContext(w);
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
|
|
@ -396,34 +420,34 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
if (key == (0x48 | 0x100)) {
|
||||
// Up
|
||||
w->as.scrollPane.scrollPosV -= step;
|
||||
sp->scrollPosV -= step;
|
||||
} else if (key == (0x50 | 0x100)) {
|
||||
// Down
|
||||
w->as.scrollPane.scrollPosV += step;
|
||||
sp->scrollPosV += step;
|
||||
} else if (key == (0x49 | 0x100)) {
|
||||
// Page Up
|
||||
w->as.scrollPane.scrollPosV -= innerH;
|
||||
sp->scrollPosV -= innerH;
|
||||
} else if (key == (0x51 | 0x100)) {
|
||||
// Page Down
|
||||
w->as.scrollPane.scrollPosV += innerH;
|
||||
sp->scrollPosV += innerH;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home
|
||||
w->as.scrollPane.scrollPosV = 0;
|
||||
sp->scrollPosV = 0;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
// End
|
||||
w->as.scrollPane.scrollPosV = maxScrollV;
|
||||
sp->scrollPosV = maxScrollV;
|
||||
} else if (key == (0x4B | 0x100)) {
|
||||
// Left
|
||||
w->as.scrollPane.scrollPosH -= font->charWidth;
|
||||
sp->scrollPosH -= font->charWidth;
|
||||
} else if (key == (0x4D | 0x100)) {
|
||||
// Right
|
||||
w->as.scrollPane.scrollPosH += font->charWidth;
|
||||
sp->scrollPosH += font->charWidth;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
|
||||
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
|
||||
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
|
||||
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
|
@ -442,6 +466,7 @@ void widgetScrollPaneOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// into scroll pane children since their coordinates may be outside
|
||||
// the pane's visible bounds.
|
||||
void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)hit->data;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
|
|
@ -466,8 +491,8 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
maxScrollH = 0;
|
||||
}
|
||||
|
||||
hit->as.scrollPane.scrollPosV = clampInt(hit->as.scrollPane.scrollPosV, 0, maxScrollV);
|
||||
hit->as.scrollPane.scrollPosH = clampInt(hit->as.scrollPane.scrollPosH, 0, maxScrollH);
|
||||
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
|
||||
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||
|
||||
// Check vertical scrollbar
|
||||
if (needVSb) {
|
||||
|
|
@ -485,20 +510,20 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
}
|
||||
|
||||
if (relY < SP_SB_W) {
|
||||
hit->as.scrollPane.scrollPosV -= font->charHeight;
|
||||
sp->scrollPosV -= font->charHeight;
|
||||
} else if (relY >= sbH - SP_SB_W) {
|
||||
hit->as.scrollPane.scrollPosV += font->charHeight;
|
||||
sp->scrollPosV += font->charHeight;
|
||||
} else if (trackLen > 0) {
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, contentMinH, innerH, hit->as.scrollPane.scrollPosV, &thumbPos, &thumbSize);
|
||||
widgetScrollbarThumb(trackLen, contentMinH, innerH, sp->scrollPosV, &thumbPos, &thumbSize);
|
||||
|
||||
int32_t trackRelY = relY - SP_SB_W;
|
||||
|
||||
if (trackRelY < thumbPos) {
|
||||
hit->as.scrollPane.scrollPosV -= pageSize;
|
||||
sp->scrollPosV -= pageSize;
|
||||
} else if (trackRelY >= thumbPos + thumbSize) {
|
||||
hit->as.scrollPane.scrollPosV += pageSize;
|
||||
sp->scrollPosV += pageSize;
|
||||
} else {
|
||||
sDragScrollbar = hit;
|
||||
sDragScrollbarOrient = 0;
|
||||
|
|
@ -507,7 +532,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
}
|
||||
}
|
||||
|
||||
hit->as.scrollPane.scrollPosV = clampInt(hit->as.scrollPane.scrollPosV, 0, maxScrollV);
|
||||
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
|
||||
wgtInvalidate(hit);
|
||||
return;
|
||||
}
|
||||
|
|
@ -529,20 +554,20 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
}
|
||||
|
||||
if (relX < SP_SB_W) {
|
||||
hit->as.scrollPane.scrollPosH -= font->charWidth;
|
||||
sp->scrollPosH -= font->charWidth;
|
||||
} else if (relX >= sbW - SP_SB_W) {
|
||||
hit->as.scrollPane.scrollPosH += font->charWidth;
|
||||
sp->scrollPosH += font->charWidth;
|
||||
} else if (trackLen > 0) {
|
||||
int32_t thumbPos;
|
||||
int32_t thumbSize;
|
||||
widgetScrollbarThumb(trackLen, contentMinW, innerW, hit->as.scrollPane.scrollPosH, &thumbPos, &thumbSize);
|
||||
widgetScrollbarThumb(trackLen, contentMinW, innerW, sp->scrollPosH, &thumbPos, &thumbSize);
|
||||
|
||||
int32_t trackRelX = relX - SP_SB_W;
|
||||
|
||||
if (trackRelX < thumbPos) {
|
||||
hit->as.scrollPane.scrollPosH -= pageSize;
|
||||
sp->scrollPosH -= pageSize;
|
||||
} else if (trackRelX >= thumbPos + thumbSize) {
|
||||
hit->as.scrollPane.scrollPosH += pageSize;
|
||||
sp->scrollPosH += pageSize;
|
||||
} else {
|
||||
sDragScrollbar = hit;
|
||||
sDragScrollbarOrient = 1;
|
||||
|
|
@ -551,7 +576,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
}
|
||||
}
|
||||
|
||||
hit->as.scrollPane.scrollPosH = clampInt(hit->as.scrollPane.scrollPosH, 0, maxScrollH);
|
||||
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||
wgtInvalidate(hit);
|
||||
return;
|
||||
}
|
||||
|
|
@ -609,6 +634,7 @@ void widgetScrollPaneOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy
|
|||
// clip rect so they're always fully visible. The dead corner (when
|
||||
// both scrollbars are present) is filled with windowFace color.
|
||||
void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)w->data;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
int32_t contentMinW;
|
||||
|
|
@ -632,8 +658,8 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
|||
maxScrollH = 0;
|
||||
}
|
||||
|
||||
w->as.scrollPane.scrollPosV = clampInt(w->as.scrollPane.scrollPosV, 0, maxScrollV);
|
||||
w->as.scrollPane.scrollPosH = clampInt(w->as.scrollPane.scrollPosH, 0, maxScrollH);
|
||||
sp->scrollPosV = clampInt(sp->scrollPosV, 0, maxScrollV);
|
||||
sp->scrollPosH = clampInt(sp->scrollPosH, 0, maxScrollH);
|
||||
|
||||
// Sunken border
|
||||
BevelStyleT bevel = BEVEL_SUNKEN(colors, bg, SP_BORDER);
|
||||
|
|
@ -681,18 +707,38 @@ void widgetScrollPanePaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const B
|
|||
|
||||
|
||||
// ============================================================
|
||||
// wgtScrollPane
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
// Default weight=100 so the scroll pane stretches to fill available
|
||||
// space in its parent container. Without this, a scroll pane in a
|
||||
// vertical box would collapse to its minimal size.
|
||||
static const WidgetClassT sClassScrollPane = {
|
||||
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE | WCLASS_SCROLL_CONTAINER | WCLASS_RELAYOUT_ON_SCROLL,
|
||||
.paint = widgetScrollPanePaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetScrollPaneCalcMinSize,
|
||||
.layout = widgetScrollPaneLayout,
|
||||
.onMouse = widgetScrollPaneOnMouse,
|
||||
.onKey = widgetScrollPaneOnKey,
|
||||
.destroy = widgetScrollPaneDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtScrollPane(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetScrollPaneE);
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
w->as.scrollPane.scrollPosV = 0;
|
||||
w->as.scrollPane.scrollPosH = 0;
|
||||
ScrollPaneDataT *sp = (ScrollPaneDataT *)calloc(1, sizeof(ScrollPaneDataT));
|
||||
|
||||
if (sp) {
|
||||
w->data = sp;
|
||||
}
|
||||
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
|
|
@ -704,20 +750,14 @@ WidgetT *wgtScrollPane(WidgetT *parent) {
|
|||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
static const WidgetClassT sClassScrollPane = {
|
||||
.flags = WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
|
||||
.paint = widgetScrollPanePaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetScrollPaneCalcMinSize,
|
||||
.layout = widgetScrollPaneLayout,
|
||||
.onMouse = widgetScrollPaneOnMouse,
|
||||
.onKey = widgetScrollPaneOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
} sApi = {
|
||||
.create = wgtScrollPane
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetScrollPaneE] = &sClassScrollPane;
|
||||
sTypeId = wgtRegisterClass(&sClassScrollPane);
|
||||
wgtRegisterApi("scrollpane", &sApi);
|
||||
}
|
||||
|
|
|
|||
19
widgets/widgetScrollPane.h
Normal file
19
widgets/widgetScrollPane.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// widgetScrollPane.h -- ScrollPane widget API
|
||||
#ifndef WIDGET_SCROLLPANE_H
|
||||
#define WIDGET_SCROLLPANE_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
} ScrollPaneApiT;
|
||||
|
||||
static inline const ScrollPaneApiT *dvxScrollPaneApi(void) {
|
||||
static const ScrollPaneApiT *sApi;
|
||||
if (!sApi) { sApi = (const ScrollPaneApiT *)wgtGetApi("scrollpane"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtScrollPane(parent) dvxScrollPaneApi()->create(parent)
|
||||
|
||||
#endif // WIDGET_SCROLLPANE_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetSeparator.c -- Separator widget (horizontal and vertical)
|
||||
//
|
||||
// A purely decorative widget that draws a 2px etched line (shadow +
|
||||
|
|
@ -17,37 +18,22 @@
|
|||
//
|
||||
// No mouse/key handlers -- purely decorative, non-focusable.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#define SEPARATOR_THICKNESS 2
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
bool vertical;
|
||||
} SeparatorDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtHSeparator
|
||||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtHSeparator(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetSeparatorE);
|
||||
|
||||
if (w) {
|
||||
w->as.separator.vertical = false;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtVSeparator
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtVSeparator(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetSeparatorE);
|
||||
|
||||
if (w) {
|
||||
w->as.separator.vertical = true;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
static void widgetSeparatorDestroy(WidgetT *w);
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -56,8 +42,9 @@ WidgetT *wgtVSeparator(WidgetT *parent) {
|
|||
|
||||
void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
SeparatorDataT *d = (SeparatorDataT *)w->data;
|
||||
|
||||
if (w->as.separator.vertical) {
|
||||
if (d->vertical) {
|
||||
w->calcMinW = SEPARATOR_THICKNESS;
|
||||
w->calcMinH = 0;
|
||||
} else {
|
||||
|
|
@ -67,14 +54,25 @@ void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSeparatorDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetSeparatorDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
w->data = NULL;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSeparatorPaint
|
||||
// ============================================================
|
||||
|
||||
void widgetSeparatorPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
SeparatorDataT *sd = (SeparatorDataT *)w->data;
|
||||
|
||||
if (w->as.separator.vertical) {
|
||||
if (sd->vertical) {
|
||||
int32_t cx = w->x + w->w / 2;
|
||||
drawVLine(d, ops, cx, w->y, w->h, colors->windowShadow);
|
||||
drawVLine(d, ops, cx + 1, w->y, w->h, colors->windowHighlight);
|
||||
|
|
@ -99,11 +97,55 @@ static const WidgetClassT sClassSeparator = {
|
|||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetSeparatorDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetSeparatorE] = &sClassSeparator;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtHSeparator(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
SeparatorDataT *d = calloc(1, sizeof(SeparatorDataT));
|
||||
w->data = d;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
WidgetT *wgtVSeparator(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
SeparatorDataT *d = calloc(1, sizeof(SeparatorDataT));
|
||||
w->data = d;
|
||||
d->vertical = true;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*hSeparator)(WidgetT *parent);
|
||||
WidgetT *(*vSeparator)(WidgetT *parent);
|
||||
} sApi = {
|
||||
.hSeparator = wgtHSeparator,
|
||||
.vSeparator = wgtVSeparator
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassSeparator);
|
||||
wgtRegisterApi("separator", &sApi);
|
||||
}
|
||||
|
|
|
|||
21
widgets/widgetSeparator.h
Normal file
21
widgets/widgetSeparator.h
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// widgetSeparator.h -- Separator widget API
|
||||
#ifndef WIDGET_SEPARATOR_H
|
||||
#define WIDGET_SEPARATOR_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*hSeparator)(WidgetT *parent);
|
||||
WidgetT *(*vSeparator)(WidgetT *parent);
|
||||
} SeparatorApiT;
|
||||
|
||||
static inline const SeparatorApiT *dvxSeparatorApi(void) {
|
||||
static const SeparatorApiT *sApi;
|
||||
if (!sApi) { sApi = (const SeparatorApiT *)wgtGetApi("separator"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtHSeparator(parent) dvxSeparatorApi()->hSeparator(parent)
|
||||
#define wgtVSeparator(parent) dvxSeparatorApi()->vSeparator(parent)
|
||||
|
||||
#endif // WIDGET_SEPARATOR_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetSlider.c -- Slider (trackbar) widget
|
||||
//
|
||||
// A continuous-value selector with a draggable thumb on a groove track.
|
||||
|
|
@ -20,58 +21,19 @@
|
|||
// Home/End jump to min/max. This gives ~100 keyboard steps regardless
|
||||
// of the actual value range.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#define SLIDER_TRACK_H 4
|
||||
#define SLIDER_THUMB_W 11
|
||||
|
||||
// ============================================================
|
||||
// wgtSlider
|
||||
// ============================================================
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
// Default weight=100 so the slider stretches in its parent layout.
|
||||
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetSliderE);
|
||||
|
||||
if (w) {
|
||||
w->as.slider.value = minVal;
|
||||
w->as.slider.minValue = minVal;
|
||||
w->as.slider.maxValue = maxVal;
|
||||
w->as.slider.vertical = false;
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSliderGetValue
|
||||
// ============================================================
|
||||
|
||||
int32_t wgtSliderGetValue(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, WidgetSliderE, 0);
|
||||
|
||||
return w->as.slider.value;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSliderSetValue
|
||||
// ============================================================
|
||||
|
||||
void wgtSliderSetValue(WidgetT *w, int32_t value) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetSliderE);
|
||||
|
||||
if (value < w->as.slider.minValue) {
|
||||
value = w->as.slider.minValue;
|
||||
}
|
||||
|
||||
if (value > w->as.slider.maxValue) {
|
||||
value = w->as.slider.maxValue;
|
||||
}
|
||||
|
||||
w->as.slider.value = value;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
typedef struct {
|
||||
int32_t value;
|
||||
int32_t minValue;
|
||||
int32_t maxValue;
|
||||
bool vertical;
|
||||
} SliderDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -83,8 +45,9 @@ void wgtSliderSetValue(WidgetT *w, int32_t value) {
|
|||
// margin so the groove has visual breathing room around the thumb.
|
||||
void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
SliderDataT *d = (SliderDataT *)w->data;
|
||||
|
||||
if (w->as.slider.vertical) {
|
||||
if (d->vertical) {
|
||||
w->calcMinW = SLIDER_THUMB_W + 4;
|
||||
w->calcMinH = SLIDER_THUMB_W * 5;
|
||||
} else {
|
||||
|
|
@ -100,8 +63,9 @@ void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
|
||||
void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
(void)mod;
|
||||
SliderDataT *d = (SliderDataT *)w->data;
|
||||
|
||||
int32_t range = w->as.slider.maxValue - w->as.slider.minValue;
|
||||
int32_t range = d->maxValue - d->minValue;
|
||||
int32_t pageStep = range / 10;
|
||||
|
||||
if (pageStep < 1) {
|
||||
|
|
@ -110,41 +74,41 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
// Arrow keys: step by 1. Page Up/Down: step by 10% of range.
|
||||
// Home/End: jump to min/max.
|
||||
if (w->as.slider.vertical) {
|
||||
if (d->vertical) {
|
||||
if (key == (0x48 | 0x100)) {
|
||||
w->as.slider.value -= 1;
|
||||
d->value -= 1;
|
||||
} else if (key == (0x50 | 0x100)) {
|
||||
w->as.slider.value += 1;
|
||||
d->value += 1;
|
||||
} else if (key == (0x49 | 0x100)) {
|
||||
w->as.slider.value -= pageStep;
|
||||
d->value -= pageStep;
|
||||
} else if (key == (0x51 | 0x100)) {
|
||||
w->as.slider.value += pageStep;
|
||||
d->value += pageStep;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.minValue;
|
||||
d->value = d->minValue;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.maxValue;
|
||||
d->value = d->maxValue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (key == (0x4B | 0x100) || key == (0x48 | 0x100)) {
|
||||
w->as.slider.value -= 1;
|
||||
d->value -= 1;
|
||||
} else if (key == (0x4D | 0x100) || key == (0x50 | 0x100)) {
|
||||
w->as.slider.value += 1;
|
||||
d->value += 1;
|
||||
} else if (key == (0x49 | 0x100)) {
|
||||
w->as.slider.value -= pageStep;
|
||||
d->value -= pageStep;
|
||||
} else if (key == (0x51 | 0x100)) {
|
||||
w->as.slider.value += pageStep;
|
||||
d->value += pageStep;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.minValue;
|
||||
d->value = d->minValue;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.maxValue;
|
||||
d->value = d->maxValue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
w->as.slider.value = clampInt(w->as.slider.value, w->as.slider.minValue, w->as.slider.maxValue);
|
||||
d->value = clampInt(d->value, d->minValue, d->maxValue);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -170,7 +134,8 @@ void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
hit->focused = true;
|
||||
int32_t range = hit->as.slider.maxValue - hit->as.slider.minValue;
|
||||
SliderDataT *d = (SliderDataT *)hit->data;
|
||||
int32_t range = d->maxValue - d->minValue;
|
||||
|
||||
if (range <= 0) {
|
||||
return;
|
||||
|
|
@ -180,9 +145,9 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
int32_t thumbPos;
|
||||
int32_t mousePos;
|
||||
|
||||
if (hit->as.slider.vertical) {
|
||||
if (d->vertical) {
|
||||
thumbRange = hit->h - SLIDER_THUMB_W;
|
||||
thumbPos = ((hit->as.slider.value - hit->as.slider.minValue) * thumbRange) / range;
|
||||
thumbPos = ((d->value - d->minValue) * thumbRange) / range;
|
||||
mousePos = vy - hit->y;
|
||||
|
||||
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
|
||||
|
|
@ -191,12 +156,12 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
sDragOffset = mousePos - thumbPos;
|
||||
} else {
|
||||
// Click on track -- jump to position
|
||||
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
||||
int32_t newVal = d->minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
||||
|
||||
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
|
||||
if (newVal > hit->as.slider.maxValue) { newVal = hit->as.slider.maxValue; }
|
||||
if (newVal < d->minValue) { newVal = d->minValue; }
|
||||
if (newVal > d->maxValue) { newVal = d->maxValue; }
|
||||
|
||||
hit->as.slider.value = newVal;
|
||||
d->value = newVal;
|
||||
|
||||
if (hit->onChange) {
|
||||
hit->onChange(hit);
|
||||
|
|
@ -204,7 +169,7 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
}
|
||||
} else {
|
||||
thumbRange = hit->w - SLIDER_THUMB_W;
|
||||
thumbPos = ((hit->as.slider.value - hit->as.slider.minValue) * thumbRange) / range;
|
||||
thumbPos = ((d->value - d->minValue) * thumbRange) / range;
|
||||
mousePos = vx - hit->x;
|
||||
|
||||
if (mousePos >= thumbPos && mousePos < thumbPos + SLIDER_THUMB_W) {
|
||||
|
|
@ -213,12 +178,12 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
sDragOffset = mousePos - thumbPos;
|
||||
} else {
|
||||
// Click on track -- jump to position
|
||||
int32_t newVal = hit->as.slider.minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
||||
int32_t newVal = d->minValue + ((mousePos - SLIDER_THUMB_W / 2) * range) / thumbRange;
|
||||
|
||||
if (newVal < hit->as.slider.minValue) { newVal = hit->as.slider.minValue; }
|
||||
if (newVal > hit->as.slider.maxValue) { newVal = hit->as.slider.maxValue; }
|
||||
if (newVal < d->minValue) { newVal = d->minValue; }
|
||||
if (newVal > d->maxValue) { newVal = d->maxValue; }
|
||||
|
||||
hit->as.slider.value = newVal;
|
||||
d->value = newVal;
|
||||
|
||||
if (hit->onChange) {
|
||||
hit->onChange(hit);
|
||||
|
|
@ -232,27 +197,20 @@ void widgetSliderOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// widgetSliderPaint
|
||||
// ============================================================
|
||||
|
||||
// Paint: groove track centered in the cross-axis, thumb at value
|
||||
// position. The thumb position formula is:
|
||||
// thumbPos = ((value - minValue) * thumbRange) / range
|
||||
// where thumbRange = widgetSize - SLIDER_THUMB_W.
|
||||
// This maps value linearly to pixel position using integer math only.
|
||||
// The groove is thin (SLIDER_TRACK_H = 4px) with a reversed bevel
|
||||
// for the recessed look, while the thumb is the full widget height
|
||||
// with a raised bevel for the 3D grab handle appearance.
|
||||
void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
(void)font;
|
||||
SliderDataT *sd = (SliderDataT *)w->data;
|
||||
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||
uint32_t tickFg = w->enabled ? fg : colors->windowShadow;
|
||||
uint32_t thumbFg = w->enabled ? colors->buttonFace : colors->scrollbarTrough;
|
||||
|
||||
int32_t range = w->as.slider.maxValue - w->as.slider.minValue;
|
||||
int32_t range = sd->maxValue - sd->minValue;
|
||||
|
||||
if (range <= 0) {
|
||||
range = 1;
|
||||
}
|
||||
|
||||
if (w->as.slider.vertical) {
|
||||
if (sd->vertical) {
|
||||
// Track groove
|
||||
int32_t trackX = w->x + (w->w - SLIDER_TRACK_H) / 2;
|
||||
BevelStyleT groove;
|
||||
|
|
@ -264,7 +222,7 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
|
|||
|
||||
// Thumb
|
||||
int32_t thumbRange = w->h - SLIDER_THUMB_W;
|
||||
int32_t thumbY = w->y + ((w->as.slider.value - w->as.slider.minValue) * thumbRange) / range;
|
||||
int32_t thumbY = w->y + ((sd->value - sd->minValue) * thumbRange) / range;
|
||||
|
||||
BevelStyleT thumb;
|
||||
thumb.highlight = colors->windowHighlight;
|
||||
|
|
@ -287,7 +245,7 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
|
|||
|
||||
// Thumb
|
||||
int32_t thumbRange = w->w - SLIDER_THUMB_W;
|
||||
int32_t thumbX = w->x + ((w->as.slider.value - w->as.slider.minValue) * thumbRange) / range;
|
||||
int32_t thumbX = w->x + ((sd->value - sd->minValue) * thumbRange) / range;
|
||||
|
||||
BevelStyleT thumb;
|
||||
thumb.highlight = colors->windowHighlight;
|
||||
|
|
@ -306,24 +264,136 @@ void widgetSliderPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitma
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSliderScrollDragUpdate
|
||||
// ============================================================
|
||||
//
|
||||
// Called by the event loop during slider thumb drag. Computes the
|
||||
// new value from mouse position and the stored drag offset.
|
||||
|
||||
static void widgetSliderScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) {
|
||||
(void)orient;
|
||||
SliderDataT *d = (SliderDataT *)w->data;
|
||||
int32_t range = d->maxValue - d->minValue;
|
||||
|
||||
if (range <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t newVal;
|
||||
|
||||
if (d->vertical) {
|
||||
int32_t thumbRange = w->h - SLIDER_THUMB_W;
|
||||
int32_t relY = mouseY - w->y - dragOff;
|
||||
newVal = d->minValue + (relY * range) / thumbRange;
|
||||
} else {
|
||||
int32_t thumbRange = w->w - SLIDER_THUMB_W;
|
||||
int32_t relX = mouseX - w->x - dragOff;
|
||||
newVal = d->minValue + (relX * range) / thumbRange;
|
||||
}
|
||||
|
||||
newVal = clampInt(newVal, d->minValue, d->maxValue);
|
||||
|
||||
if (newVal != d->value) {
|
||||
d->value = newVal;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSliderDestroy
|
||||
// ============================================================
|
||||
|
||||
static void widgetSliderDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const WidgetClassT sClassSlider = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_SCROLLABLE,
|
||||
.paint = widgetSliderPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetSliderCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetSliderOnMouse,
|
||||
.onKey = widgetSliderOnKey,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetSliderDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
.setText = NULL,
|
||||
.scrollDragUpdate = widgetSliderScrollDragUpdate
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
SliderDataT *d = (SliderDataT *)calloc(1, sizeof(SliderDataT));
|
||||
d->value = minVal;
|
||||
d->minValue = minVal;
|
||||
d->maxValue = maxVal;
|
||||
w->data = d;
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtSliderGetValue(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, 0);
|
||||
SliderDataT *d = (SliderDataT *)w->data;
|
||||
|
||||
return d->value;
|
||||
}
|
||||
|
||||
|
||||
void wgtSliderSetValue(WidgetT *w, int32_t value) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
SliderDataT *d = (SliderDataT *)w->data;
|
||||
|
||||
if (value < d->minValue) {
|
||||
value = d->minValue;
|
||||
}
|
||||
|
||||
if (value > d->maxValue) {
|
||||
value = d->maxValue;
|
||||
}
|
||||
|
||||
d->value = value;
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal);
|
||||
void (*setValue)(WidgetT *w, int32_t value);
|
||||
int32_t (*getValue)(const WidgetT *w);
|
||||
} sApi = {
|
||||
.create = wgtSlider,
|
||||
.setValue = wgtSliderSetValue,
|
||||
.getValue = wgtSliderGetValue
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetSliderE] = &sClassSlider;
|
||||
sTypeId = wgtRegisterClass(&sClassSlider);
|
||||
wgtRegisterApi("slider", &sApi);
|
||||
}
|
||||
|
|
|
|||
23
widgets/widgetSlider.h
Normal file
23
widgets/widgetSlider.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// widgetSlider.h -- Slider widget API
|
||||
#ifndef WIDGET_SLIDER_H
|
||||
#define WIDGET_SLIDER_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal);
|
||||
void (*setValue)(WidgetT *w, int32_t value);
|
||||
int32_t (*getValue)(const WidgetT *w);
|
||||
} SliderApiT;
|
||||
|
||||
static inline const SliderApiT *dvxSliderApi(void) {
|
||||
static const SliderApiT *sApi;
|
||||
if (!sApi) { sApi = (const SliderApiT *)wgtGetApi("slider"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtSlider(parent, minVal, maxVal) dvxSliderApi()->create(parent, minVal, maxVal)
|
||||
#define wgtSliderSetValue(w, value) dvxSliderApi()->setValue(w, value)
|
||||
#define wgtSliderGetValue(w) dvxSliderApi()->getValue(w)
|
||||
|
||||
#endif // WIDGET_SLIDER_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetSpacer.c -- Spacer widget (invisible stretching element)
|
||||
//
|
||||
// A zero-sized invisible widget with weight=100, used purely for
|
||||
|
|
@ -12,22 +13,9 @@
|
|||
// no mouse, no keyboard, no state. The entire widget is effectively
|
||||
// just a weight value attached to a position in the sibling list.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSpacer
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtSpacer(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetSpacerE);
|
||||
|
||||
if (w) {
|
||||
w->weight = 100; // spacers stretch by default
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -59,6 +47,34 @@ static const WidgetClassT sClassSpacer = {
|
|||
.setText = NULL
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetSpacerE] = &sClassSpacer;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtSpacer(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
} sApi = {
|
||||
.create = wgtSpacer
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassSpacer);
|
||||
wgtRegisterApi("spacer", &sApi);
|
||||
}
|
||||
|
|
|
|||
19
widgets/widgetSpacer.h
Normal file
19
widgets/widgetSpacer.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// widgetSpacer.h -- Spacer widget API
|
||||
#ifndef WIDGET_SPACER_H
|
||||
#define WIDGET_SPACER_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
} SpacerApiT;
|
||||
|
||||
static inline const SpacerApiT *dvxSpacerApi(void) {
|
||||
static const SpacerApiT *sApi;
|
||||
if (!sApi) { sApi = (const SpacerApiT *)wgtGetApi("spacer"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtSpacer(parent) dvxSpacerApi()->create(parent)
|
||||
|
||||
#endif // WIDGET_SPACER_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetSpinner.c -- Spinner (numeric up/down) widget
|
||||
//
|
||||
// A hybrid widget combining a single-line text editor with up/down
|
||||
|
|
@ -29,11 +30,31 @@
|
|||
// look like they're part of the border chrome. The up/down buttons
|
||||
// split the widget height evenly.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
#include "../texthelp/textHelp.h"
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef struct {
|
||||
int32_t value;
|
||||
int32_t minValue;
|
||||
int32_t maxValue;
|
||||
int32_t step;
|
||||
char buf[16];
|
||||
int32_t len;
|
||||
int32_t cursorPos;
|
||||
int32_t scrollOff;
|
||||
int32_t selStart;
|
||||
int32_t selEnd;
|
||||
char undoBuf[16];
|
||||
int32_t undoLen;
|
||||
int32_t undoCursor;
|
||||
bool editing;
|
||||
} SpinnerDataT;
|
||||
|
||||
#define SPINNER_BTN_W 14
|
||||
#define SPINNER_BORDER 2
|
||||
#define SPINNER_PAD 3
|
||||
|
|
@ -43,26 +64,26 @@
|
|||
// Prototypes
|
||||
// ============================================================
|
||||
|
||||
static void spinnerClampAndFormat(WidgetT *w);
|
||||
static void spinnerCommitEdit(WidgetT *w);
|
||||
static void spinnerFormat(WidgetT *w);
|
||||
static void spinnerStartEdit(WidgetT *w);
|
||||
static void spinnerClampAndFormat(SpinnerDataT *d);
|
||||
static void spinnerCommitEdit(SpinnerDataT *d);
|
||||
static void spinnerFormat(SpinnerDataT *d);
|
||||
static void spinnerStartEdit(SpinnerDataT *d);
|
||||
|
||||
|
||||
// ============================================================
|
||||
// spinnerClampAndFormat
|
||||
// ============================================================
|
||||
|
||||
static void spinnerClampAndFormat(WidgetT *w) {
|
||||
if (w->as.spinner.value < w->as.spinner.minValue) {
|
||||
w->as.spinner.value = w->as.spinner.minValue;
|
||||
static void spinnerClampAndFormat(SpinnerDataT *d) {
|
||||
if (d->value < d->minValue) {
|
||||
d->value = d->minValue;
|
||||
}
|
||||
|
||||
if (w->as.spinner.value > w->as.spinner.maxValue) {
|
||||
w->as.spinner.value = w->as.spinner.maxValue;
|
||||
if (d->value > d->maxValue) {
|
||||
d->value = d->maxValue;
|
||||
}
|
||||
|
||||
spinnerFormat(w);
|
||||
spinnerFormat(d);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -70,17 +91,17 @@ static void spinnerClampAndFormat(WidgetT *w) {
|
|||
// spinnerCommitEdit
|
||||
// ============================================================
|
||||
|
||||
static void spinnerCommitEdit(WidgetT *w) {
|
||||
if (!w->as.spinner.editing) {
|
||||
static void spinnerCommitEdit(SpinnerDataT *d) {
|
||||
if (!d->editing) {
|
||||
return;
|
||||
}
|
||||
|
||||
w->as.spinner.editing = false;
|
||||
w->as.spinner.buf[w->as.spinner.len] = '\0';
|
||||
d->editing = false;
|
||||
d->buf[d->len] = '\0';
|
||||
|
||||
int32_t val = (int32_t)strtol(w->as.spinner.buf, NULL, 10);
|
||||
w->as.spinner.value = val;
|
||||
spinnerClampAndFormat(w);
|
||||
int32_t val = (int32_t)strtol(d->buf, NULL, 10);
|
||||
d->value = val;
|
||||
spinnerClampAndFormat(d);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -92,12 +113,12 @@ static void spinnerCommitEdit(WidgetT *w) {
|
|||
// This is called after any value change to synchronize the text buffer
|
||||
// with the numeric value. The cursor-at-end position matches user
|
||||
// expectation after arrow-key increment/decrement.
|
||||
static void spinnerFormat(WidgetT *w) {
|
||||
w->as.spinner.len = snprintf(w->as.spinner.buf, sizeof(w->as.spinner.buf), "%d", (int)w->as.spinner.value);
|
||||
w->as.spinner.cursorPos = w->as.spinner.len;
|
||||
w->as.spinner.scrollOff = 0;
|
||||
w->as.spinner.selStart = -1;
|
||||
w->as.spinner.selEnd = -1;
|
||||
static void spinnerFormat(SpinnerDataT *d) {
|
||||
d->len = snprintf(d->buf, sizeof(d->buf), "%d", (int)d->value);
|
||||
d->cursorPos = d->len;
|
||||
d->scrollOff = 0;
|
||||
d->selStart = -1;
|
||||
d->selEnd = -1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -109,14 +130,14 @@ static void spinnerFormat(WidgetT *w) {
|
|||
// revert to the pre-edit formatted value. The snapshot is only taken
|
||||
// on the transition to editing, not on every keystroke, so repeated
|
||||
// typing within one edit session can be undone all at once.
|
||||
static void spinnerStartEdit(WidgetT *w) {
|
||||
if (!w->as.spinner.editing) {
|
||||
w->as.spinner.editing = true;
|
||||
static void spinnerStartEdit(SpinnerDataT *d) {
|
||||
if (!d->editing) {
|
||||
d->editing = true;
|
||||
|
||||
// Snapshot for undo
|
||||
memcpy(w->as.spinner.undoBuf, w->as.spinner.buf, sizeof(w->as.spinner.buf));
|
||||
w->as.spinner.undoLen = w->as.spinner.len;
|
||||
w->as.spinner.undoCursor = w->as.spinner.cursorPos;
|
||||
memcpy(d->undoBuf, d->buf, sizeof(d->buf));
|
||||
d->undoLen = d->len;
|
||||
d->undoCursor = d->cursorPos;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +157,8 @@ void widgetSpinnerCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// ============================================================
|
||||
|
||||
const char *widgetSpinnerGetText(const WidgetT *w) {
|
||||
return w->as.spinner.buf;
|
||||
const SpinnerDataT *d = (const SpinnerDataT *)w->data;
|
||||
return d->buf;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -153,13 +175,14 @@ const char *widgetSpinnerGetText(const WidgetT *w) {
|
|||
// Page Up/Down use step*10 for coarser adjustment, matching the
|
||||
// convention used by Windows spin controls.
|
||||
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||
int32_t step = w->as.spinner.step;
|
||||
SpinnerDataT *d = (SpinnerDataT *)w->data;
|
||||
int32_t step = d->step;
|
||||
|
||||
// Up arrow -- increment
|
||||
if (key == (0x48 | 0x100)) {
|
||||
spinnerCommitEdit(w);
|
||||
w->as.spinner.value += step;
|
||||
spinnerClampAndFormat(w);
|
||||
spinnerCommitEdit(d);
|
||||
d->value += step;
|
||||
spinnerClampAndFormat(d);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -171,9 +194,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
// Down arrow -- decrement
|
||||
if (key == (0x50 | 0x100)) {
|
||||
spinnerCommitEdit(w);
|
||||
w->as.spinner.value -= step;
|
||||
spinnerClampAndFormat(w);
|
||||
spinnerCommitEdit(d);
|
||||
d->value -= step;
|
||||
spinnerClampAndFormat(d);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -185,9 +208,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
// Page Up -- increment by step * 10
|
||||
if (key == (0x49 | 0x100)) {
|
||||
spinnerCommitEdit(w);
|
||||
w->as.spinner.value += step * 10;
|
||||
spinnerClampAndFormat(w);
|
||||
spinnerCommitEdit(d);
|
||||
d->value += step * 10;
|
||||
spinnerClampAndFormat(d);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -199,9 +222,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
// Page Down -- decrement by step * 10
|
||||
if (key == (0x51 | 0x100)) {
|
||||
spinnerCommitEdit(w);
|
||||
w->as.spinner.value -= step * 10;
|
||||
spinnerClampAndFormat(w);
|
||||
spinnerCommitEdit(d);
|
||||
d->value -= step * 10;
|
||||
spinnerClampAndFormat(d);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -213,8 +236,8 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
// Enter -- commit edit
|
||||
if (key == '\r' || key == '\n') {
|
||||
if (w->as.spinner.editing) {
|
||||
spinnerCommitEdit(w);
|
||||
if (d->editing) {
|
||||
spinnerCommitEdit(d);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
|
|
@ -228,9 +251,9 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
// Escape -- cancel edit, revert to current value
|
||||
if (key == 27) {
|
||||
if (w->as.spinner.editing) {
|
||||
w->as.spinner.editing = false;
|
||||
spinnerFormat(w);
|
||||
if (d->editing) {
|
||||
d->editing = false;
|
||||
spinnerFormat(d);
|
||||
wgtInvalidatePaint(w);
|
||||
}
|
||||
|
||||
|
|
@ -247,33 +270,33 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
}
|
||||
|
||||
// Minus only at position 0 (and only if min is negative)
|
||||
if (isMinus && (w->as.spinner.cursorPos != 0 || w->as.spinner.minValue >= 0)) {
|
||||
if (isMinus && (d->cursorPos != 0 || d->minValue >= 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Enter edit mode on first text-modifying key
|
||||
if (isDigit || isMinus || key == 8 || key == 127 || key == (0x53 | 0x100)) {
|
||||
spinnerStartEdit(w);
|
||||
spinnerStartEdit(d);
|
||||
}
|
||||
|
||||
// Delegate to shared text editing logic (handles cursor movement,
|
||||
// selection, cut/copy/paste, undo/redo, backspace, delete, etc.)
|
||||
widgetTextEditOnKey(w, key, mod,
|
||||
w->as.spinner.buf, (int32_t)sizeof(w->as.spinner.buf),
|
||||
&w->as.spinner.len, &w->as.spinner.cursorPos,
|
||||
&w->as.spinner.scrollOff,
|
||||
&w->as.spinner.selStart, &w->as.spinner.selEnd,
|
||||
w->as.spinner.undoBuf, &w->as.spinner.undoLen,
|
||||
&w->as.spinner.undoCursor);
|
||||
d->buf, (int32_t)sizeof(d->buf),
|
||||
&d->len, &d->cursorPos,
|
||||
&d->scrollOff,
|
||||
&d->selStart, &d->selEnd,
|
||||
d->undoBuf, &d->undoLen,
|
||||
&d->undoCursor);
|
||||
|
||||
// Validate buffer after paste -- reject non-numeric content.
|
||||
// Allow optional leading minus and digits only.
|
||||
bool valid = true;
|
||||
|
||||
for (int32_t i = 0; i < w->as.spinner.len; i++) {
|
||||
char c = w->as.spinner.buf[i];
|
||||
for (int32_t i = 0; i < d->len; i++) {
|
||||
char c = d->buf[i];
|
||||
|
||||
if (c == '-' && i == 0 && w->as.spinner.minValue < 0) {
|
||||
if (c == '-' && i == 0 && d->minValue < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -285,11 +308,11 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
|
||||
if (!valid) {
|
||||
// Revert to the undo buffer (pre-paste state)
|
||||
memcpy(w->as.spinner.buf, w->as.spinner.undoBuf, sizeof(w->as.spinner.buf));
|
||||
w->as.spinner.len = w->as.spinner.undoLen;
|
||||
w->as.spinner.cursorPos = w->as.spinner.undoCursor;
|
||||
w->as.spinner.selStart = 0;
|
||||
w->as.spinner.selEnd = 0;
|
||||
memcpy(d->buf, d->undoBuf, sizeof(d->buf));
|
||||
d->len = d->undoLen;
|
||||
d->cursorPos = d->undoCursor;
|
||||
d->selStart = 0;
|
||||
d->selEnd = 0;
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(w);
|
||||
|
|
@ -310,21 +333,22 @@ void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
// make sense for numbers), entering edit mode to allow replacement.
|
||||
void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
hit->focused = true;
|
||||
SpinnerDataT *d = (SpinnerDataT *)hit->data;
|
||||
|
||||
int32_t btnX = hit->x + hit->w - SPINNER_BORDER - SPINNER_BTN_W;
|
||||
int32_t midY = hit->y + hit->h / 2;
|
||||
|
||||
if (vx >= btnX) {
|
||||
// Click on button area
|
||||
spinnerCommitEdit(hit);
|
||||
spinnerCommitEdit(d);
|
||||
|
||||
if (vy < midY) {
|
||||
hit->as.spinner.value += hit->as.spinner.step;
|
||||
d->value += d->step;
|
||||
} else {
|
||||
hit->as.spinner.value -= hit->as.spinner.step;
|
||||
d->value -= d->step;
|
||||
}
|
||||
|
||||
spinnerClampAndFormat(hit);
|
||||
spinnerClampAndFormat(d);
|
||||
|
||||
if (hit->onChange) {
|
||||
hit->onChange(hit);
|
||||
|
|
@ -332,8 +356,8 @@ void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
} else {
|
||||
// Click on text area
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
widgetTextEditMouseClick(hit, vx, vy, hit->x + SPINNER_BORDER + SPINNER_PAD, &ctx->font, hit->as.spinner.buf, hit->as.spinner.len, hit->as.spinner.scrollOff, &hit->as.spinner.cursorPos, &hit->as.spinner.selStart, &hit->as.spinner.selEnd, false, false);
|
||||
spinnerStartEdit(hit);
|
||||
widgetTextEditMouseClick(hit, vx, vy, hit->x + SPINNER_BORDER + SPINNER_PAD, &ctx->font, d->buf, d->len, d->scrollOff, &d->cursorPos, &d->selStart, &d->selEnd, false, false);
|
||||
spinnerStartEdit(d);
|
||||
}
|
||||
|
||||
wgtInvalidatePaint(hit);
|
||||
|
|
@ -355,7 +379,8 @@ void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
// button area into the widget's right border so they visually merge
|
||||
// with the outer bevel -- this is why btnW is btnW + SPINNER_BORDER
|
||||
// in the drawBevel calls.
|
||||
void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
void widgetSpinnerPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
SpinnerDataT *d = (SpinnerDataT *)w->data;
|
||||
uint32_t fg = w->enabled ? (w->fgColor ? w->fgColor : colors->contentFg) : colors->windowShadow;
|
||||
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||
|
||||
|
|
@ -368,7 +393,7 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
bevel.shadow = colors->windowHighlight;
|
||||
bevel.face = bg;
|
||||
bevel.width = SPINNER_BORDER;
|
||||
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
drawBevel(disp, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||
|
||||
// Text area
|
||||
int32_t textX = w->x + SPINNER_BORDER + SPINNER_PAD;
|
||||
|
|
@ -381,14 +406,14 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
}
|
||||
|
||||
// Scroll to keep cursor visible
|
||||
if (w->as.spinner.cursorPos < w->as.spinner.scrollOff) {
|
||||
w->as.spinner.scrollOff = w->as.spinner.cursorPos;
|
||||
} else if (w->as.spinner.cursorPos > w->as.spinner.scrollOff + maxChars) {
|
||||
w->as.spinner.scrollOff = w->as.spinner.cursorPos - maxChars;
|
||||
if (d->cursorPos < d->scrollOff) {
|
||||
d->scrollOff = d->cursorPos;
|
||||
} else if (d->cursorPos > d->scrollOff + maxChars) {
|
||||
d->scrollOff = d->cursorPos - maxChars;
|
||||
}
|
||||
|
||||
int32_t off = w->as.spinner.scrollOff;
|
||||
int32_t len = w->as.spinner.len - off;
|
||||
int32_t off = d->scrollOff;
|
||||
int32_t len = d->len - off;
|
||||
|
||||
if (len > maxChars) {
|
||||
len = maxChars;
|
||||
|
|
@ -398,14 +423,14 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
len = 0;
|
||||
}
|
||||
|
||||
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, &w->as.spinner.buf[off], len, off, w->as.spinner.cursorPos, w->as.spinner.selStart, w->as.spinner.selEnd, fg, bg, w->focused, w->x + SPINNER_BORDER, btnX - SPINNER_PAD);
|
||||
widgetTextEditPaintLine(disp, ops, font, colors, textX, textY, &d->buf[off], len, off, d->cursorPos, d->selStart, d->selEnd, fg, bg, w->focused, w->x + SPINNER_BORDER, btnX - SPINNER_PAD);
|
||||
|
||||
// Up button (top half)
|
||||
int32_t btnTopH = w->h / 2;
|
||||
int32_t btnBotH = w->h - btnTopH;
|
||||
|
||||
BevelStyleT btnBevel = BEVEL_RAISED(colors, 1);
|
||||
drawBevel(d, ops, btnX, w->y, btnW + SPINNER_BORDER, btnTopH, &btnBevel);
|
||||
drawBevel(disp, ops, btnX, w->y, btnW + SPINNER_BORDER, btnTopH, &btnBevel);
|
||||
|
||||
// Up arrow triangle
|
||||
{
|
||||
|
|
@ -413,12 +438,12 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
int32_t cy = w->y + btnTopH / 2;
|
||||
|
||||
for (int32_t i = 0; i < 3; i++) {
|
||||
drawHLine(d, ops, cx - i, cy - 1 + i, 1 + i * 2, fg);
|
||||
drawHLine(disp, ops, cx - i, cy - 1 + i, 1 + i * 2, fg);
|
||||
}
|
||||
}
|
||||
|
||||
// Down button (bottom half)
|
||||
drawBevel(d, ops, btnX, w->y + btnTopH, btnW + SPINNER_BORDER, btnBotH, &btnBevel);
|
||||
drawBevel(disp, ops, btnX, w->y + btnTopH, btnW + SPINNER_BORDER, btnBotH, &btnBevel);
|
||||
|
||||
// Down arrow triangle
|
||||
{
|
||||
|
|
@ -426,13 +451,13 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
int32_t cy = w->y + btnTopH + btnBotH / 2;
|
||||
|
||||
for (int32_t i = 0; i < 3; i++) {
|
||||
drawHLine(d, ops, cx - i, cy + 1 - i, 1 + i * 2, fg);
|
||||
drawHLine(disp, ops, cx - i, cy + 1 - i, 1 + i * 2, fg);
|
||||
}
|
||||
}
|
||||
|
||||
// Focus rect around entire widget
|
||||
if (w->focused) {
|
||||
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
drawFocusRect(disp, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -442,82 +467,19 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
// ============================================================
|
||||
|
||||
void widgetSpinnerSetText(WidgetT *w, const char *text) {
|
||||
SpinnerDataT *d = (SpinnerDataT *)w->data;
|
||||
int32_t val = (int32_t)strtol(text, NULL, 10);
|
||||
w->as.spinner.value = val;
|
||||
spinnerClampAndFormat(w);
|
||||
d->value = val;
|
||||
spinnerClampAndFormat(d);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSpinner
|
||||
// widgetSpinnerDestroy
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetSpinnerE);
|
||||
|
||||
if (w) {
|
||||
w->as.spinner.minValue = minVal;
|
||||
w->as.spinner.maxValue = maxVal;
|
||||
w->as.spinner.step = step > 0 ? step : 1;
|
||||
w->as.spinner.value = minVal;
|
||||
w->as.spinner.editing = false;
|
||||
w->as.spinner.selStart = -1;
|
||||
w->as.spinner.selEnd = -1;
|
||||
w->as.spinner.undoLen = 0;
|
||||
w->as.spinner.undoCursor = 0;
|
||||
spinnerFormat(w);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSpinnerGetValue
|
||||
// ============================================================
|
||||
|
||||
int32_t wgtSpinnerGetValue(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, WidgetSpinnerE, 0);
|
||||
|
||||
return w->as.spinner.value;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSpinnerSetRange
|
||||
// ============================================================
|
||||
|
||||
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
|
||||
|
||||
w->as.spinner.minValue = minVal;
|
||||
w->as.spinner.maxValue = maxVal;
|
||||
spinnerClampAndFormat(w);
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSpinnerSetStep
|
||||
// ============================================================
|
||||
|
||||
void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
|
||||
|
||||
w->as.spinner.step = step > 0 ? step : 1;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSpinnerSetValue
|
||||
// ============================================================
|
||||
|
||||
void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
|
||||
VALIDATE_WIDGET_VOID(w, WidgetSpinnerE);
|
||||
|
||||
w->as.spinner.value = value;
|
||||
spinnerClampAndFormat(w);
|
||||
wgtInvalidate(w);
|
||||
void widgetSpinnerDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -526,19 +488,105 @@ void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
|
|||
// ============================================================
|
||||
|
||||
static const WidgetClassT sClassSpinner = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_SCROLLABLE,
|
||||
.paint = widgetSpinnerPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetSpinnerCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetSpinnerOnMouse,
|
||||
.onKey = widgetSpinnerOnKey,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetSpinnerDestroy,
|
||||
.getText = widgetSpinnerGetText,
|
||||
.setText = widgetSpinnerSetText
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetSpinnerE] = &sClassSpinner;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtSpinner(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
SpinnerDataT *d = (SpinnerDataT *)calloc(1, sizeof(SpinnerDataT));
|
||||
|
||||
if (!d) {
|
||||
return w;
|
||||
}
|
||||
|
||||
w->data = d;
|
||||
d->minValue = minVal;
|
||||
d->maxValue = maxVal;
|
||||
d->step = step > 0 ? step : 1;
|
||||
d->value = minVal;
|
||||
d->selStart = -1;
|
||||
d->selEnd = -1;
|
||||
spinnerFormat(d);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtSpinnerGetValue(const WidgetT *w) {
|
||||
VALIDATE_WIDGET(w, sTypeId, 0);
|
||||
const SpinnerDataT *d = (const SpinnerDataT *)w->data;
|
||||
|
||||
return d->value;
|
||||
}
|
||||
|
||||
|
||||
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
SpinnerDataT *d = (SpinnerDataT *)w->data;
|
||||
|
||||
d->minValue = minVal;
|
||||
d->maxValue = maxVal;
|
||||
spinnerClampAndFormat(d);
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
SpinnerDataT *d = (SpinnerDataT *)w->data;
|
||||
|
||||
d->step = step > 0 ? step : 1;
|
||||
}
|
||||
|
||||
|
||||
void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
|
||||
VALIDATE_WIDGET_VOID(w, sTypeId);
|
||||
SpinnerDataT *d = (SpinnerDataT *)w->data;
|
||||
|
||||
d->value = value;
|
||||
spinnerClampAndFormat(d);
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step);
|
||||
void (*setValue)(WidgetT *w, int32_t value);
|
||||
int32_t (*getValue)(const WidgetT *w);
|
||||
void (*setRange)(WidgetT *w, int32_t minVal, int32_t maxVal);
|
||||
void (*setStep)(WidgetT *w, int32_t step);
|
||||
} sApi = {
|
||||
.create = wgtSpinner,
|
||||
.setValue = wgtSpinnerSetValue,
|
||||
.getValue = wgtSpinnerGetValue,
|
||||
.setRange = wgtSpinnerSetRange,
|
||||
.setStep = wgtSpinnerSetStep
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassSpinner);
|
||||
wgtRegisterApi("spinner", &sApi);
|
||||
}
|
||||
|
|
|
|||
27
widgets/widgetSpinner.h
Normal file
27
widgets/widgetSpinner.h
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// widgetSpinner.h -- Spinner widget API
|
||||
#ifndef WIDGET_SPINNER_H
|
||||
#define WIDGET_SPINNER_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, int32_t minVal, int32_t maxVal, int32_t step);
|
||||
void (*setValue)(WidgetT *w, int32_t value);
|
||||
int32_t (*getValue)(const WidgetT *w);
|
||||
void (*setRange)(WidgetT *w, int32_t minVal, int32_t maxVal);
|
||||
void (*setStep)(WidgetT *w, int32_t step);
|
||||
} SpinnerApiT;
|
||||
|
||||
static inline const SpinnerApiT *dvxSpinnerApi(void) {
|
||||
static const SpinnerApiT *sApi;
|
||||
if (!sApi) { sApi = (const SpinnerApiT *)wgtGetApi("spinner"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtSpinner(parent, minVal, maxVal, step) dvxSpinnerApi()->create(parent, minVal, maxVal, step)
|
||||
#define wgtSpinnerSetValue(w, value) dvxSpinnerApi()->setValue(w, value)
|
||||
#define wgtSpinnerGetValue(w) dvxSpinnerApi()->getValue(w)
|
||||
#define wgtSpinnerSetRange(w, minVal, maxVal) dvxSpinnerApi()->setRange(w, minVal, maxVal)
|
||||
#define wgtSpinnerSetStep(w, step) dvxSpinnerApi()->setStep(w, step)
|
||||
|
||||
#endif // WIDGET_SPINNER_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetSplitter.c -- Splitter (draggable divider between two panes)
|
||||
//
|
||||
// A container that divides its area between exactly two child widgets
|
||||
|
|
@ -27,7 +28,17 @@
|
|||
// nothing. The clamp also ensures the divider can't be dragged past
|
||||
// where the second pane would violate its minimum.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#define SPLITTER_BAR_W 5
|
||||
#define SPLITTER_MIN_PANE 20
|
||||
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
typedef struct {
|
||||
int32_t dividerPos;
|
||||
bool vertical;
|
||||
} SplitterDataT;
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -36,6 +47,8 @@
|
|||
|
||||
static WidgetT *spFirstChild(WidgetT *w);
|
||||
static WidgetT *spSecondChild(WidgetT *w);
|
||||
static void widgetSplitterDestroy(WidgetT *w);
|
||||
static void widgetSplitterScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY);
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -83,9 +96,10 @@ static WidgetT *spSecondChild(WidgetT *w) {
|
|||
// ============================================================
|
||||
|
||||
void widgetSplitterClampPos(WidgetT *w, int32_t *pos) {
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
WidgetT *c1 = spFirstChild(w);
|
||||
WidgetT *c2 = spSecondChild(w);
|
||||
bool vert = w->as.splitter.vertical;
|
||||
bool vert = d->vertical;
|
||||
int32_t totalSize = vert ? w->w : w->h;
|
||||
|
||||
int32_t minFirst = c1 ? (vert ? c1->calcMinW : c1->calcMinH) : SPLITTER_MIN_PANE;
|
||||
|
|
@ -126,7 +140,9 @@ void widgetSplitterCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
int32_t m2w = c2 ? c2->calcMinW : 0;
|
||||
int32_t m2h = c2 ? c2->calcMinH : 0;
|
||||
|
||||
if (w->as.splitter.vertical) {
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
|
||||
if (d->vertical) {
|
||||
w->calcMinW = m1w + m2w + SPLITTER_BAR_W;
|
||||
w->calcMinH = DVX_MAX(m1h, m2h);
|
||||
} else {
|
||||
|
|
@ -147,14 +163,15 @@ void widgetSplitterCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// user hasn't dragged yet (dividerPos=0 from construction gets
|
||||
// clamped to minFirst).
|
||||
void widgetSplitterLayout(WidgetT *w, const BitmapFontT *font) {
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
WidgetT *c1 = spFirstChild(w);
|
||||
WidgetT *c2 = spSecondChild(w);
|
||||
int32_t pos = w->as.splitter.dividerPos;
|
||||
int32_t pos = d->dividerPos;
|
||||
|
||||
widgetSplitterClampPos(w, &pos);
|
||||
w->as.splitter.dividerPos = pos;
|
||||
d->dividerPos = pos;
|
||||
|
||||
if (w->as.splitter.vertical) {
|
||||
if (d->vertical) {
|
||||
// Left pane
|
||||
if (c1) {
|
||||
c1->x = w->x;
|
||||
|
|
@ -215,12 +232,13 @@ void widgetSplitterLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
// generic hit-test would find the splitter but not recurse into its
|
||||
// clipped children).
|
||||
void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
int32_t pos = hit->as.splitter.dividerPos;
|
||||
SplitterDataT *d = (SplitterDataT *)hit->data;
|
||||
int32_t pos = d->dividerPos;
|
||||
|
||||
// Check if click is on the divider bar
|
||||
bool onDivider;
|
||||
|
||||
if (hit->as.splitter.vertical) {
|
||||
if (d->vertical) {
|
||||
int32_t barX = hit->x + pos;
|
||||
onDivider = (vx >= barX && vx < barX + SPLITTER_BAR_W);
|
||||
} else {
|
||||
|
|
@ -232,7 +250,7 @@ void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
|||
// Start dragging
|
||||
sDragSplitter = hit;
|
||||
|
||||
if (hit->as.splitter.vertical) {
|
||||
if (d->vertical) {
|
||||
sDragSplitStart = vx - hit->x - pos;
|
||||
} else {
|
||||
sDragSplitStart = vy - hit->y - pos;
|
||||
|
|
@ -282,46 +300,47 @@ void widgetSplitterOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
|||
// a tiny raised bump. 11 bumps spaced 3px apart center vertically
|
||||
// (or horizontally) on the bar. This is purely decorative but
|
||||
// provides the expected visual affordance for "draggable".
|
||||
void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
int32_t pos = w->as.splitter.dividerPos;
|
||||
void widgetSplitterPaint(WidgetT *w, DisplayT *disp, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
int32_t pos = d->dividerPos;
|
||||
|
||||
// Paint first child with clip rect
|
||||
WidgetT *c1 = spFirstChild(w);
|
||||
WidgetT *c2 = spSecondChild(w);
|
||||
|
||||
int32_t oldClipX = d->clipX;
|
||||
int32_t oldClipY = d->clipY;
|
||||
int32_t oldClipW = d->clipW;
|
||||
int32_t oldClipH = d->clipH;
|
||||
int32_t oldClipX = disp->clipX;
|
||||
int32_t oldClipY = disp->clipY;
|
||||
int32_t oldClipW = disp->clipW;
|
||||
int32_t oldClipH = disp->clipH;
|
||||
|
||||
if (c1) {
|
||||
if (w->as.splitter.vertical) {
|
||||
setClipRect(d, w->x, w->y, pos, w->h);
|
||||
if (d->vertical) {
|
||||
setClipRect(disp, w->x, w->y, pos, w->h);
|
||||
} else {
|
||||
setClipRect(d, w->x, w->y, w->w, pos);
|
||||
setClipRect(disp, w->x, w->y, w->w, pos);
|
||||
}
|
||||
|
||||
widgetPaintOne(c1, d, ops, font, colors);
|
||||
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
|
||||
widgetPaintOne(c1, disp, ops, font, colors);
|
||||
setClipRect(disp, oldClipX, oldClipY, oldClipW, oldClipH);
|
||||
}
|
||||
|
||||
if (c2) {
|
||||
if (w->as.splitter.vertical) {
|
||||
setClipRect(d, w->x + pos + SPLITTER_BAR_W, w->y, w->w - pos - SPLITTER_BAR_W, w->h);
|
||||
if (d->vertical) {
|
||||
setClipRect(disp, w->x + pos + SPLITTER_BAR_W, w->y, w->w - pos - SPLITTER_BAR_W, w->h);
|
||||
} else {
|
||||
setClipRect(d, w->x, w->y + pos + SPLITTER_BAR_W, w->w, w->h - pos - SPLITTER_BAR_W);
|
||||
setClipRect(disp, w->x, w->y + pos + SPLITTER_BAR_W, w->w, w->h - pos - SPLITTER_BAR_W);
|
||||
}
|
||||
|
||||
widgetPaintOne(c2, d, ops, font, colors);
|
||||
setClipRect(d, oldClipX, oldClipY, oldClipW, oldClipH);
|
||||
widgetPaintOne(c2, disp, ops, font, colors);
|
||||
setClipRect(disp, oldClipX, oldClipY, oldClipW, oldClipH);
|
||||
}
|
||||
|
||||
// Draw divider bar -- raised bevel
|
||||
BevelStyleT bevel = BEVEL_RAISED(colors, 1);
|
||||
|
||||
if (w->as.splitter.vertical) {
|
||||
if (d->vertical) {
|
||||
int32_t barX = w->x + pos;
|
||||
drawBevel(d, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
|
||||
drawBevel(disp, ops, barX, w->y, SPLITTER_BAR_W, w->h, &bevel);
|
||||
|
||||
// Gripper -- row of embossed 2x2 bumps centered vertically
|
||||
int32_t gx = barX + 1;
|
||||
|
|
@ -330,12 +349,12 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
|
||||
for (int32_t i = -count; i <= count; i++) {
|
||||
int32_t dy = midY + i * 3;
|
||||
drawHLine(d, ops, gx, dy, 2, colors->windowHighlight);
|
||||
drawHLine(d, ops, gx + 1, dy + 1, 2, colors->windowShadow);
|
||||
drawHLine(disp, ops, gx, dy, 2, colors->windowHighlight);
|
||||
drawHLine(disp, ops, gx + 1, dy + 1, 2, colors->windowShadow);
|
||||
}
|
||||
} else {
|
||||
int32_t barY = w->y + pos;
|
||||
drawBevel(d, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
|
||||
drawBevel(disp, ops, w->x, barY, w->w, SPLITTER_BAR_W, &bevel);
|
||||
|
||||
// Gripper -- row of embossed 2x2 bumps centered horizontally
|
||||
int32_t gy = barY + 1;
|
||||
|
|
@ -344,48 +363,82 @@ void widgetSplitterPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
|
||||
for (int32_t i = -count; i <= count; i++) {
|
||||
int32_t dx = midX + i * 3;
|
||||
drawHLine(d, ops, dx, gy, 1, colors->windowHighlight);
|
||||
drawHLine(d, ops, dx + 1, gy, 1, colors->windowShadow);
|
||||
drawHLine(d, ops, dx, gy + 1, 1, colors->windowHighlight);
|
||||
drawHLine(d, ops, dx + 1, gy + 1, 1, colors->windowShadow);
|
||||
drawHLine(disp, ops, dx, gy, 1, colors->windowHighlight);
|
||||
drawHLine(disp, ops, dx + 1, gy, 1, colors->windowShadow);
|
||||
drawHLine(disp, ops, dx, gy + 1, 1, colors->windowHighlight);
|
||||
drawHLine(disp, ops, dx + 1, gy + 1, 1, colors->windowShadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSplitter
|
||||
// widgetSplitterGetCursorShape
|
||||
// ============================================================
|
||||
|
||||
WidgetT *wgtSplitter(WidgetT *parent, bool vertical) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetSplitterE);
|
||||
int32_t widgetSplitterGetCursorShape(const WidgetT *w, int32_t vx, int32_t vy) {
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
|
||||
if (w) {
|
||||
w->as.splitter.vertical = vertical;
|
||||
w->as.splitter.dividerPos = 0;
|
||||
w->weight = 100;
|
||||
// During active drag, always show resize cursor
|
||||
if (sDragSplitter == w) {
|
||||
return d->vertical ? CURSOR_RESIZE_H : CURSOR_RESIZE_V;
|
||||
}
|
||||
|
||||
return w;
|
||||
int32_t pos = d->dividerPos;
|
||||
|
||||
if (d->vertical) {
|
||||
int32_t barX = w->x + pos;
|
||||
|
||||
if (vx >= barX && vx < barX + SPLITTER_BAR_W) {
|
||||
return CURSOR_RESIZE_H;
|
||||
}
|
||||
} else {
|
||||
int32_t barY = w->y + pos;
|
||||
|
||||
if (vy >= barY && vy < barY + SPLITTER_BAR_W) {
|
||||
return CURSOR_RESIZE_V;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSplitterGetPos
|
||||
// widgetSplitterDestroy
|
||||
// ============================================================
|
||||
|
||||
int32_t wgtSplitterGetPos(const WidgetT *w) {
|
||||
return w->as.splitter.dividerPos;
|
||||
static void widgetSplitterDestroy(WidgetT *w) {
|
||||
free(w->data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtSplitterSetPos
|
||||
// widgetSplitterScrollDragUpdate
|
||||
// ============================================================
|
||||
|
||||
void wgtSplitterSetPos(WidgetT *w, int32_t pos) {
|
||||
w->as.splitter.dividerPos = pos;
|
||||
static void widgetSplitterScrollDragUpdate(WidgetT *w, int32_t orient, int32_t dragOff, int32_t mouseX, int32_t mouseY) {
|
||||
(void)orient;
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
int32_t pos;
|
||||
|
||||
if (d->vertical) {
|
||||
pos = mouseX - w->x - dragOff;
|
||||
} else {
|
||||
pos = mouseY - w->y - dragOff;
|
||||
}
|
||||
|
||||
widgetSplitterClampPos(w, &pos);
|
||||
|
||||
if (pos != d->dividerPos) {
|
||||
d->dividerPos = pos;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -401,13 +454,63 @@ static const WidgetClassT sClassSplitter = {
|
|||
.layout = widgetSplitterLayout,
|
||||
.onMouse = widgetSplitterOnMouse,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.destroy = widgetSplitterDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
.setText = NULL,
|
||||
.getCursorShape = widgetSplitterGetCursorShape,
|
||||
.scrollDragUpdate = widgetSplitterScrollDragUpdate
|
||||
};
|
||||
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetSplitterE] = &sClassSplitter;
|
||||
sSplitterClampPosFn = widgetSplitterClampPos;
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtSplitter(WidgetT *parent, bool vertical) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
SplitterDataT *d = (SplitterDataT *)calloc(1, sizeof(SplitterDataT));
|
||||
d->vertical = vertical;
|
||||
d->dividerPos = 0;
|
||||
w->data = d;
|
||||
w->weight = 100;
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
int32_t wgtSplitterGetPos(const WidgetT *w) {
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
return d->dividerPos;
|
||||
}
|
||||
|
||||
|
||||
void wgtSplitterSetPos(WidgetT *w, int32_t pos) {
|
||||
SplitterDataT *d = (SplitterDataT *)w->data;
|
||||
d->dividerPos = pos;
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent, bool vertical);
|
||||
void (*setPos)(WidgetT *w, int32_t pos);
|
||||
int32_t (*getPos)(const WidgetT *w);
|
||||
} sApi = {
|
||||
.create = wgtSplitter,
|
||||
.setPos = wgtSplitterSetPos,
|
||||
.getPos = wgtSplitterGetPos
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
sTypeId = wgtRegisterClass(&sClassSplitter);
|
||||
wgtRegisterApi("splitter", &sApi);
|
||||
}
|
||||
|
|
|
|||
23
widgets/widgetSplitter.h
Normal file
23
widgets/widgetSplitter.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// widgetSplitter.h -- Splitter widget API
|
||||
#ifndef WIDGET_SPLITTER_H
|
||||
#define WIDGET_SPLITTER_H
|
||||
|
||||
#include "../core/dvxWidget.h"
|
||||
|
||||
typedef struct {
|
||||
WidgetT *(*create)(WidgetT *parent, bool vertical);
|
||||
void (*setPos)(WidgetT *w, int32_t pos);
|
||||
int32_t (*getPos)(const WidgetT *w);
|
||||
} SplitterApiT;
|
||||
|
||||
static inline const SplitterApiT *dvxSplitterApi(void) {
|
||||
static const SplitterApiT *sApi;
|
||||
if (!sApi) { sApi = (const SplitterApiT *)wgtGetApi("splitter"); }
|
||||
return sApi;
|
||||
}
|
||||
|
||||
#define wgtSplitter(parent, vertical) dvxSplitterApi()->create(parent, vertical)
|
||||
#define wgtSplitterSetPos(w, pos) dvxSplitterApi()->setPos(w, pos)
|
||||
#define wgtSplitterGetPos(w) dvxSplitterApi()->getPos(w)
|
||||
|
||||
#endif // WIDGET_SPLITTER_H
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
#define DVX_WIDGET_IMPL
|
||||
// widgetStatusBar.c -- StatusBar widget
|
||||
//
|
||||
// A horizontal container that draws a sunken border around each visible
|
||||
|
|
@ -16,25 +17,12 @@
|
|||
// No mouse/key handlers -- the status bar is purely display. Children
|
||||
// that are interactive (e.g., a clickable label) handle their own events.
|
||||
|
||||
#include "widgetInternal.h"
|
||||
#include "dvxWidgetPlugin.h"
|
||||
|
||||
#define TOOLBAR_PAD 2
|
||||
#define TOOLBAR_GAP 2
|
||||
|
||||
// ============================================================
|
||||
// wgtStatusBar
|
||||
// ============================================================
|
||||
|
||||
// Tight 2px padding and spacing keeps the status bar compact, using
|
||||
// minimal vertical space at the bottom of a window.
|
||||
WidgetT *wgtStatusBar(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, WidgetStatusBarE);
|
||||
|
||||
if (w) {
|
||||
w->padding = wgtPixels(2);
|
||||
w->spacing = wgtPixels(2);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
static int32_t sTypeId = -1;
|
||||
|
||||
|
||||
// ============================================================
|
||||
|
|
@ -63,6 +51,20 @@ void widgetStatusBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetStatusBarGetLayoutMetrics
|
||||
// ============================================================
|
||||
|
||||
void widgetStatusBarGetLayoutMetrics(const WidgetT *w, const BitmapFontT *font, int32_t *pad, int32_t *gap, int32_t *extraTop, int32_t *borderW) {
|
||||
(void)w;
|
||||
(void)font;
|
||||
*pad = TOOLBAR_PAD;
|
||||
*gap = TOOLBAR_GAP;
|
||||
*extraTop = 0;
|
||||
*borderW = 0;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
|
@ -78,9 +80,39 @@ static const WidgetClassT sClassStatusBar = {
|
|||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
.setText = NULL,
|
||||
.getLayoutMetrics = widgetStatusBarGetLayoutMetrics
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Widget creation functions
|
||||
// ============================================================
|
||||
|
||||
|
||||
WidgetT *wgtStatusBar(WidgetT *parent) {
|
||||
WidgetT *w = widgetAlloc(parent, sTypeId);
|
||||
|
||||
if (w) {
|
||||
w->padding = wgtPixels(2);
|
||||
w->spacing = wgtPixels(2);
|
||||
}
|
||||
|
||||
return w;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// DXE registration
|
||||
// ============================================================
|
||||
|
||||
|
||||
static const struct {
|
||||
WidgetT *(*create)(WidgetT *parent);
|
||||
} sApi = {
|
||||
.create = wgtStatusBar
|
||||
};
|
||||
|
||||
void wgtRegister(void) {
|
||||
widgetClassTable[WidgetStatusBarE] = &sClassStatusBar;
|
||||
sTypeId = wgtRegisterClass(&sClassStatusBar);
|
||||
wgtRegisterApi("statusbar", &sApi);
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue