(mostly) Decoupled widget code from GUI code.
This commit is contained in:
parent
9fc21c2f83
commit
291eaf0c35
24 changed files with 1325 additions and 886 deletions
|
|
@ -14,6 +14,7 @@ LIBDIR = ../lib
|
|||
SRCS = dvxVideo.c dvxDraw.c dvxComp.c dvxWm.c dvxIcon.c dvxImageWrite.c dvxApp.c
|
||||
|
||||
WSRCS = widgets/widgetAnsiTerm.c \
|
||||
widgets/widgetClass.c \
|
||||
widgets/widgetCore.c \
|
||||
widgets/widgetLayout.c \
|
||||
widgets/widgetEvent.c \
|
||||
|
|
@ -77,6 +78,7 @@ $(OBJDIR)/dvxApp.o: dvxApp.c dvxApp.h dvxTypes.h dvxVideo.h dvxDraw.h dvx
|
|||
|
||||
# Widget file dependencies
|
||||
WIDGET_DEPS = widgets/widgetInternal.h dvxWidget.h dvxTypes.h dvxApp.h dvxDraw.h dvxWm.h dvxVideo.h
|
||||
$(WOBJDIR)/widgetClass.o: widgets/widgetClass.c $(WIDGET_DEPS)
|
||||
$(WOBJDIR)/widgetAnsiTerm.o: widgets/widgetAnsiTerm.c $(WIDGET_DEPS)
|
||||
$(WOBJDIR)/widgetCore.o: widgets/widgetCore.c $(WIDGET_DEPS)
|
||||
$(WOBJDIR)/widgetLayout.o: widgets/widgetLayout.c $(WIDGET_DEPS)
|
||||
|
|
|
|||
|
|
@ -329,15 +329,13 @@ static bool dispatchAccelKey(AppContextT *ctx, char key) {
|
|||
|
||||
case WidgetCheckboxE:
|
||||
widgetClearFocus(win->widgetRoot);
|
||||
target->focused = true;
|
||||
widgetCheckboxOnMouse(target);
|
||||
widgetCheckboxOnMouse(target, win->widgetRoot, 0, 0);
|
||||
wgtInvalidate(target);
|
||||
return true;
|
||||
|
||||
case WidgetRadioE:
|
||||
widgetClearFocus(win->widgetRoot);
|
||||
target->focused = true;
|
||||
widgetRadioOnMouse(target);
|
||||
widgetRadioOnMouse(target, win->widgetRoot, 0, 0);
|
||||
wgtInvalidate(target);
|
||||
return true;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@
|
|||
|
||||
#include <time.h>
|
||||
|
||||
// Forward declaration
|
||||
// Forward declarations
|
||||
struct AppContextT;
|
||||
struct WidgetClassT;
|
||||
|
||||
// ============================================================
|
||||
// Size specifications
|
||||
|
|
@ -100,8 +101,9 @@ typedef enum {
|
|||
#define MAX_WIDGET_NAME 32
|
||||
|
||||
typedef struct WidgetT {
|
||||
WidgetTypeE type;
|
||||
char name[MAX_WIDGET_NAME];
|
||||
WidgetTypeE type;
|
||||
const struct WidgetClassT *wclass;
|
||||
char name[MAX_WIDGET_NAME];
|
||||
|
||||
// Tree linkage
|
||||
struct WidgetT *parent;
|
||||
|
|
|
|||
|
|
@ -1303,6 +1303,16 @@ void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetAnsiTermDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermDestroy(WidgetT *w) {
|
||||
free(w->as.ansiTerm.cells);
|
||||
free(w->as.ansiTerm.scrollback);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetAnsiTermCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -1389,6 +1399,8 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key) {
|
|||
if (len > 0) {
|
||||
w->as.ansiTerm.commWrite(w->as.ansiTerm.commCtx, buf, len);
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1398,7 +1410,9 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key) {
|
|||
//
|
||||
// Handle mouse clicks: scrollbar interaction and focus.
|
||||
|
||||
void widgetAnsiTermOnMouse(WidgetT *hit, int32_t vx, int32_t vy, const BitmapFontT *font) {
|
||||
void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
AppContextT *actx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &actx->font;
|
||||
hit->focused = true;
|
||||
|
||||
int32_t sbCount = hit->as.ansiTerm.scrollbackCount;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,25 @@ WidgetT *wgtButton(WidgetT *parent, const char *text) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonGetText
|
||||
// ============================================================
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -30,13 +49,30 @@ void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetButtonOnKey(WidgetT *w, int32_t key) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
w->as.button.pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetButtonOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetButtonOnMouse(WidgetT *hit) {
|
||||
hit->as.button.pressed = true;
|
||||
sPressedButton = hit;
|
||||
void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
w->as.button.pressed = true;
|
||||
sPressedButton = w;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -594,6 +594,15 @@ void wgtCanvasSetPixel(WidgetT *w, int32_t x, int32_t y, uint32_t color) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCanvasDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetCanvasDestroy(WidgetT *w) {
|
||||
free(w->as.canvas.data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCanvasCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -609,7 +618,8 @@ void widgetCanvasCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// widgetCanvasOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetCanvasOnMouse(WidgetT *hit, int32_t vx, int32_t vy) {
|
||||
void widgetCanvasOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
// Convert widget coords to canvas coords
|
||||
int32_t cx = vx - hit->x - CANVAS_BORDER;
|
||||
int32_t cy = vy - hit->y - CANVAS_BORDER;
|
||||
|
|
|
|||
|
|
@ -20,6 +20,25 @@ WidgetT *wgtCheckbox(WidgetT *parent, const char *text) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxGetText
|
||||
// ============================================================
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -31,15 +50,36 @@ void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetCheckboxOnKey(WidgetT *w, int32_t key) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
w->as.checkbox.checked = !w->as.checkbox.checked;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetCheckboxOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetCheckboxOnMouse(WidgetT *hit) {
|
||||
hit->as.checkbox.checked = !hit->as.checkbox.checked;
|
||||
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
w->as.checkbox.checked = !w->as.checkbox.checked;
|
||||
|
||||
if (hit->onChange) {
|
||||
hit->onChange(hit);
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
392
dvx/widgets/widgetClass.c
Normal file
392
dvx/widgets/widgetClass.c
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
// widgetClass.c — Widget class vtable definitions
|
||||
|
||||
#include "widgetInternal.h"
|
||||
|
||||
// ============================================================
|
||||
// Per-type class definitions
|
||||
// ============================================================
|
||||
|
||||
static const WidgetClassT sClassVBox = {
|
||||
.flags = WCLASS_BOX_CONTAINER,
|
||||
.paint = NULL,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassHBox = {
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
|
||||
.paint = NULL,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassLabel = {
|
||||
.flags = 0,
|
||||
.paint = widgetLabelPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetLabelCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = widgetLabelGetText,
|
||||
.setText = widgetLabelSetText
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassButton = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetButtonPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetButtonCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetButtonOnMouse,
|
||||
.onKey = widgetButtonOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = widgetButtonGetText,
|
||||
.setText = widgetButtonSetText
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassCheckbox = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetCheckboxPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetCheckboxCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetCheckboxOnMouse,
|
||||
.onKey = widgetCheckboxOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = widgetCheckboxGetText,
|
||||
.setText = widgetCheckboxSetText
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassRadioGroup = {
|
||||
.flags = WCLASS_BOX_CONTAINER,
|
||||
.paint = NULL,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassRadio = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetRadioPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetRadioCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetRadioOnMouse,
|
||||
.onKey = widgetRadioOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = widgetRadioGetText,
|
||||
.setText = widgetRadioSetText
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassTextInput = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetTextInputPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetTextInputCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetTextInputOnMouse,
|
||||
.onKey = widgetTextInputOnKey,
|
||||
.destroy = widgetTextInputDestroy,
|
||||
.getText = widgetTextInputGetText,
|
||||
.setText = widgetTextInputSetText
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassTextArea = {
|
||||
.flags = 0,
|
||||
.paint = NULL,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = widgetTextAreaDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassListBox = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetListBoxPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetListBoxCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetListBoxOnMouse,
|
||||
.onKey = widgetListBoxOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassSpacer = {
|
||||
.flags = 0,
|
||||
.paint = NULL,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetSpacerCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassSeparator = {
|
||||
.flags = 0,
|
||||
.paint = widgetSeparatorPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetSeparatorCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassFrame = {
|
||||
.flags = WCLASS_BOX_CONTAINER,
|
||||
.paint = widgetFramePaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassDropdown = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetDropdownPaint,
|
||||
.paintOverlay = widgetDropdownPaintPopup,
|
||||
.calcMinSize = widgetDropdownCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetDropdownOnMouse,
|
||||
.onKey = widgetDropdownOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = widgetDropdownGetText,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassComboBox = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetComboBoxPaint,
|
||||
.paintOverlay = widgetComboBoxPaintPopup,
|
||||
.calcMinSize = widgetComboBoxCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetComboBoxOnMouse,
|
||||
.onKey = widgetComboBoxOnKey,
|
||||
.destroy = widgetComboBoxDestroy,
|
||||
.getText = widgetComboBoxGetText,
|
||||
.setText = widgetComboBoxSetText
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassProgressBar = {
|
||||
.flags = 0,
|
||||
.paint = widgetProgressBarPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetProgressBarCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassSlider = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetSliderPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetSliderCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetSliderOnMouse,
|
||||
.onKey = widgetSliderOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassTabControl = {
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN,
|
||||
.paint = widgetTabControlPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetTabControlCalcMinSize,
|
||||
.layout = widgetTabControlLayout,
|
||||
.onMouse = widgetTabControlOnMouse,
|
||||
.onKey = widgetTabControlOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassTabPage = {
|
||||
.flags = WCLASS_BOX_CONTAINER,
|
||||
.paint = NULL,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassStatusBar = {
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
|
||||
.paint = widgetStatusBarPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassToolbar = {
|
||||
.flags = WCLASS_BOX_CONTAINER | WCLASS_HORIZ_CONTAINER,
|
||||
.paint = widgetToolbarPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassTreeView = {
|
||||
.flags = WCLASS_FOCUSABLE | WCLASS_PAINTS_CHILDREN | WCLASS_NO_HIT_RECURSE,
|
||||
.paint = widgetTreeViewPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetTreeViewCalcMinSize,
|
||||
.layout = widgetTreeViewLayout,
|
||||
.onMouse = widgetTreeViewOnMouse,
|
||||
.onKey = widgetTreeViewOnKey,
|
||||
.destroy = NULL,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassTreeItem = {
|
||||
.flags = 0,
|
||||
.paint = NULL,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = NULL,
|
||||
.layout = NULL,
|
||||
.onMouse = NULL,
|
||||
.onKey = NULL,
|
||||
.destroy = NULL,
|
||||
.getText = widgetTreeItemGetText,
|
||||
.setText = widgetTreeItemSetText
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassImage = {
|
||||
.flags = 0,
|
||||
.paint = widgetImagePaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetImageCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetImageOnMouse,
|
||||
.onKey = NULL,
|
||||
.destroy = widgetImageDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassImageButton = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetImageButtonPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetImageButtonCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetImageButtonOnMouse,
|
||||
.onKey = widgetImageButtonOnKey,
|
||||
.destroy = widgetImageButtonDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassCanvas = {
|
||||
.flags = 0,
|
||||
.paint = widgetCanvasPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetCanvasCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetCanvasOnMouse,
|
||||
.onKey = NULL,
|
||||
.destroy = widgetCanvasDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
static const WidgetClassT sClassAnsiTerm = {
|
||||
.flags = WCLASS_FOCUSABLE,
|
||||
.paint = widgetAnsiTermPaint,
|
||||
.paintOverlay = NULL,
|
||||
.calcMinSize = widgetAnsiTermCalcMinSize,
|
||||
.layout = NULL,
|
||||
.onMouse = widgetAnsiTermOnMouse,
|
||||
.onKey = widgetAnsiTermOnKey,
|
||||
.destroy = widgetAnsiTermDestroy,
|
||||
.getText = NULL,
|
||||
.setText = NULL
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// Class table — indexed by WidgetTypeE
|
||||
// ============================================================
|
||||
|
||||
const WidgetClassT *widgetClassTable[] = {
|
||||
[WidgetVBoxE] = &sClassVBox,
|
||||
[WidgetHBoxE] = &sClassHBox,
|
||||
[WidgetLabelE] = &sClassLabel,
|
||||
[WidgetButtonE] = &sClassButton,
|
||||
[WidgetCheckboxE] = &sClassCheckbox,
|
||||
[WidgetRadioGroupE] = &sClassRadioGroup,
|
||||
[WidgetRadioE] = &sClassRadio,
|
||||
[WidgetTextInputE] = &sClassTextInput,
|
||||
[WidgetTextAreaE] = &sClassTextArea,
|
||||
[WidgetListBoxE] = &sClassListBox,
|
||||
[WidgetSpacerE] = &sClassSpacer,
|
||||
[WidgetSeparatorE] = &sClassSeparator,
|
||||
[WidgetFrameE] = &sClassFrame,
|
||||
[WidgetDropdownE] = &sClassDropdown,
|
||||
[WidgetComboBoxE] = &sClassComboBox,
|
||||
[WidgetProgressBarE] = &sClassProgressBar,
|
||||
[WidgetSliderE] = &sClassSlider,
|
||||
[WidgetTabControlE] = &sClassTabControl,
|
||||
[WidgetTabPageE] = &sClassTabPage,
|
||||
[WidgetStatusBarE] = &sClassStatusBar,
|
||||
[WidgetToolbarE] = &sClassToolbar,
|
||||
[WidgetTreeViewE] = &sClassTreeView,
|
||||
[WidgetTreeItemE] = &sClassTreeItem,
|
||||
[WidgetImageE] = &sClassImage,
|
||||
[WidgetImageButtonE] = &sClassImageButton,
|
||||
[WidgetCanvasE] = &sClassCanvas,
|
||||
[WidgetAnsiTermE] = &sClassAnsiTerm
|
||||
};
|
||||
|
|
@ -80,6 +80,39 @@ void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetComboBoxDestroy(WidgetT *w) {
|
||||
free(w->as.comboBox.buf);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxGetText
|
||||
// ============================================================
|
||||
|
||||
const char *widgetComboBoxGetText(const WidgetT *w) {
|
||||
return w->as.comboBox.buf ? w->as.comboBox.buf : "";
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxSetText
|
||||
// ============================================================
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -100,36 +133,130 @@ void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetComboBoxOnKey(WidgetT *w, int32_t key) {
|
||||
if (w->as.comboBox.open) {
|
||||
if (key == (0x48 | 0x100)) {
|
||||
if (w->as.comboBox.hoverIdx > 0) {
|
||||
w->as.comboBox.hoverIdx--;
|
||||
|
||||
if (w->as.comboBox.hoverIdx < w->as.comboBox.listScrollPos) {
|
||||
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == (0x50 | 0x100)) {
|
||||
if (w->as.comboBox.hoverIdx < w->as.comboBox.itemCount - 1) {
|
||||
w->as.comboBox.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;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == 0x0D) {
|
||||
int32_t idx = w->as.comboBox.hoverIdx;
|
||||
|
||||
if (idx >= 0 && idx < w->as.comboBox.itemCount) {
|
||||
w->as.comboBox.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.open = false;
|
||||
sOpenPopup = NULL;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
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 (w->as.comboBox.hoverIdx < w->as.comboBox.listScrollPos) {
|
||||
w->as.comboBox.listScrollPos = w->as.comboBox.hoverIdx;
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
return;
|
||||
}
|
||||
|
||||
// Text editing (when popup is closed, or non-navigation keys with popup open)
|
||||
if (!w->as.comboBox.buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetTextEditOnKey(w, key, w->as.comboBox.buf, w->as.comboBox.bufSize,
|
||||
&w->as.comboBox.len, &w->as.comboBox.cursorPos,
|
||||
&w->as.comboBox.scrollOff);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetComboBoxOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetComboBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx) {
|
||||
// Check if click is on the button area
|
||||
int32_t textAreaW = hit->w - DROPDOWN_BTN_WIDTH;
|
||||
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
|
||||
// Check if click is on the button area
|
||||
int32_t textAreaW = w->w - DROPDOWN_BTN_WIDTH;
|
||||
|
||||
if (vx >= w->x + textAreaW) {
|
||||
// If this combobox's popup was just closed by click-outside, don't re-open
|
||||
if (w == sClosedPopup) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (vx >= hit->x + textAreaW) {
|
||||
// Button click — toggle popup
|
||||
hit->as.comboBox.open = !hit->as.comboBox.open;
|
||||
hit->as.comboBox.hoverIdx = hit->as.comboBox.selectedIdx;
|
||||
sOpenPopup = hit->as.comboBox.open ? hit : NULL;
|
||||
w->as.comboBox.open = !w->as.comboBox.open;
|
||||
w->as.comboBox.hoverIdx = w->as.comboBox.selectedIdx;
|
||||
sOpenPopup = w->as.comboBox.open ? w : NULL;
|
||||
} else {
|
||||
// Text area click — focus for editing
|
||||
hit->focused = true;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t relX = vx - hit->x - TEXT_INPUT_PAD;
|
||||
int32_t charPos = relX / font->charWidth + hit->as.comboBox.scrollOff;
|
||||
int32_t relX = vx - w->x - TEXT_INPUT_PAD;
|
||||
int32_t charPos = relX / font->charWidth + w->as.comboBox.scrollOff;
|
||||
|
||||
if (charPos < 0) {
|
||||
charPos = 0;
|
||||
}
|
||||
|
||||
if (charPos > hit->as.comboBox.len) {
|
||||
charPos = hit->as.comboBox.len;
|
||||
if (charPos > w->as.comboBox.len) {
|
||||
charPos = w->as.comboBox.len;
|
||||
}
|
||||
|
||||
hit->as.comboBox.cursorPos = charPos;
|
||||
w->as.comboBox.cursorPos = charPos;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ WidgetT *widgetAlloc(WidgetT *parent, WidgetTypeE type) {
|
|||
|
||||
memset(w, 0, sizeof(*w));
|
||||
w->type = type;
|
||||
w->wclass = widgetClassTable[type];
|
||||
w->visible = true;
|
||||
w->enabled = true;
|
||||
|
||||
|
|
@ -102,21 +103,8 @@ void widgetDestroyChildren(WidgetT *w) {
|
|||
WidgetT *next = child->nextSibling;
|
||||
widgetDestroyChildren(child);
|
||||
|
||||
if (child->type == WidgetTextInputE) {
|
||||
free(child->as.textInput.buf);
|
||||
} else if (child->type == WidgetTextAreaE) {
|
||||
free(child->as.textArea.buf);
|
||||
} else if (child->type == WidgetComboBoxE) {
|
||||
free(child->as.comboBox.buf);
|
||||
} else if (child->type == WidgetImageE) {
|
||||
free(child->as.image.data);
|
||||
} else if (child->type == WidgetCanvasE) {
|
||||
free(child->as.canvas.data);
|
||||
} else if (child->type == WidgetImageButtonE) {
|
||||
free(child->as.imageButton.data);
|
||||
} else if (child->type == WidgetAnsiTermE) {
|
||||
free(child->as.ansiTerm.cells);
|
||||
free(child->as.ansiTerm.scrollback);
|
||||
if (child->wclass && child->wclass->destroy) {
|
||||
child->wclass->destroy(child);
|
||||
}
|
||||
|
||||
// Clear popup/drag references if they point to destroyed widgets
|
||||
|
|
@ -366,8 +354,8 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// TreeView manages its own children — don't recurse
|
||||
if (w->type == WidgetTreeViewE) {
|
||||
// Widgets with WCLASS_NO_HIT_RECURSE manage their own children
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_NO_HIT_RECURSE)) {
|
||||
return w;
|
||||
}
|
||||
|
||||
|
|
@ -391,12 +379,7 @@ WidgetT *widgetHitTest(WidgetT *w, int32_t x, int32_t y) {
|
|||
// ============================================================
|
||||
|
||||
bool widgetIsFocusable(WidgetTypeE type) {
|
||||
return type == WidgetTextInputE || type == WidgetComboBoxE ||
|
||||
type == WidgetDropdownE || type == WidgetCheckboxE ||
|
||||
type == WidgetRadioE || type == WidgetButtonE ||
|
||||
type == WidgetImageButtonE || type == WidgetSliderE ||
|
||||
type == WidgetListBoxE || type == WidgetTreeViewE ||
|
||||
type == WidgetAnsiTermE || type == WidgetTabControlE;
|
||||
return (widgetClassTable[type]->flags & WCLASS_FOCUSABLE) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -407,9 +390,7 @@ bool widgetIsFocusable(WidgetTypeE type) {
|
|||
// Returns true for widget types that use the generic box layout.
|
||||
|
||||
bool widgetIsBoxContainer(WidgetTypeE type) {
|
||||
return type == WidgetVBoxE || type == WidgetHBoxE || type == WidgetFrameE ||
|
||||
type == WidgetRadioGroupE || type == WidgetTabPageE ||
|
||||
type == WidgetStatusBarE || type == WidgetToolbarE;
|
||||
return (widgetClassTable[type]->flags & WCLASS_BOX_CONTAINER) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -420,7 +401,7 @@ bool widgetIsBoxContainer(WidgetTypeE type) {
|
|||
// Returns true for container types that lay out children horizontally.
|
||||
|
||||
bool widgetIsHorizContainer(WidgetTypeE type) {
|
||||
return type == WidgetHBoxE || type == WidgetStatusBarE || type == WidgetToolbarE;
|
||||
return (widgetClassTable[type]->flags & WCLASS_HORIZ_CONTAINER) != 0;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -63,6 +63,19 @@ void wgtDropdownSetSelected(WidgetT *w, int32_t idx) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownGetText
|
||||
// ============================================================
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -84,14 +97,85 @@ void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownOnKey(WidgetT *w, int32_t key) {
|
||||
if (w->as.dropdown.open) {
|
||||
// Popup is open — navigate items
|
||||
if (key == (0x48 | 0x100)) {
|
||||
if (w->as.dropdown.hoverIdx > 0) {
|
||||
w->as.dropdown.hoverIdx--;
|
||||
|
||||
if (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) {
|
||||
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx;
|
||||
}
|
||||
}
|
||||
} else if (key == (0x50 | 0x100)) {
|
||||
if (w->as.dropdown.hoverIdx < w->as.dropdown.itemCount - 1) {
|
||||
w->as.dropdown.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;
|
||||
}
|
||||
}
|
||||
} else if (key == 0x0D || key == ' ') {
|
||||
w->as.dropdown.selectedIdx = w->as.dropdown.hoverIdx;
|
||||
w->as.dropdown.open = false;
|
||||
sOpenPopup = NULL;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Popup is closed
|
||||
if (key == (0x50 | 0x100) || key == ' ' || key == 0x0D) {
|
||||
w->as.dropdown.open = true;
|
||||
w->as.dropdown.hoverIdx = w->as.dropdown.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 (w->as.dropdown.hoverIdx < w->as.dropdown.scrollPos) {
|
||||
w->as.dropdown.scrollPos = w->as.dropdown.hoverIdx;
|
||||
}
|
||||
} else if (key == (0x48 | 0x100)) {
|
||||
if (w->as.dropdown.selectedIdx > 0) {
|
||||
w->as.dropdown.selectedIdx--;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetDropdownOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetDropdownOnMouse(WidgetT *hit) {
|
||||
hit->as.dropdown.open = !hit->as.dropdown.open;
|
||||
hit->as.dropdown.hoverIdx = hit->as.dropdown.selectedIdx;
|
||||
sOpenPopup = hit->as.dropdown.open ? hit : NULL;
|
||||
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
|
||||
// If this dropdown's popup was just closed by click-outside, don't re-open
|
||||
if (w == sClosedPopup) {
|
||||
return;
|
||||
}
|
||||
|
||||
w->as.dropdown.open = !w->as.dropdown.open;
|
||||
w->as.dropdown.hoverIdx = w->as.dropdown.selectedIdx;
|
||||
sOpenPopup = w->as.dropdown.open ? w : NULL;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@
|
|||
|
||||
#include "widgetInternal.h"
|
||||
|
||||
// Widget whose popup was just closed by click-outside — prevents
|
||||
// immediate re-open on the same click.
|
||||
WidgetT *sClosedPopup = NULL;
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetManageScrollbars
|
||||
|
|
@ -137,476 +141,10 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Handle ANSI terminal key input
|
||||
if (focus->type == WidgetAnsiTermE) {
|
||||
widgetAnsiTermOnKey(focus, key);
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
// Dispatch to per-widget onKey handler via vtable
|
||||
if (focus->wclass && focus->wclass->onKey) {
|
||||
focus->wclass->onKey(focus, key);
|
||||
}
|
||||
|
||||
// Handle tree view keyboard navigation
|
||||
if (focus->type == WidgetTreeViewE) {
|
||||
widgetTreeViewOnKey(focus, key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle list box keyboard navigation
|
||||
if (focus->type == WidgetListBoxE) {
|
||||
widgetListBoxOnKey(focus, key);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle button keyboard activation
|
||||
if (focus->type == WidgetButtonE) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
focus->as.button.pressed = true;
|
||||
sKeyPressedBtn = focus;
|
||||
wgtInvalidate(focus);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle image button keyboard activation
|
||||
if (focus->type == WidgetImageButtonE) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
focus->as.imageButton.pressed = true;
|
||||
sKeyPressedBtn = focus;
|
||||
wgtInvalidate(focus);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle checkbox keyboard toggle
|
||||
if (focus->type == WidgetCheckboxE) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
focus->as.checkbox.checked = !focus->as.checkbox.checked;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle radio button keyboard navigation
|
||||
if (focus->type == WidgetRadioE) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
// Select this radio
|
||||
if (focus->parent && focus->parent->type == WidgetRadioGroupE) {
|
||||
focus->parent->as.radioGroup.selectedIdx = focus->as.radio.index;
|
||||
|
||||
if (focus->parent->onChange) {
|
||||
focus->parent->onChange(focus->parent);
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
} else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) {
|
||||
// Down or Right — next radio in group
|
||||
if (focus->parent && focus->parent->type == WidgetRadioGroupE) {
|
||||
WidgetT *next = NULL;
|
||||
|
||||
for (WidgetT *s = focus->nextSibling; s; s = s->nextSibling) {
|
||||
if (s->type == WidgetRadioE && s->visible && s->enabled) {
|
||||
next = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next) {
|
||||
focus->focused = false;
|
||||
next->focused = true;
|
||||
next->parent->as.radioGroup.selectedIdx = next->as.radio.index;
|
||||
|
||||
if (next->parent->onChange) {
|
||||
next->parent->onChange(next->parent);
|
||||
}
|
||||
|
||||
wgtInvalidate(next);
|
||||
}
|
||||
}
|
||||
} else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) {
|
||||
// Up or Left — previous radio in group
|
||||
if (focus->parent && focus->parent->type == WidgetRadioGroupE) {
|
||||
WidgetT *prev = NULL;
|
||||
|
||||
for (WidgetT *s = focus->parent->firstChild; s && s != focus; s = s->nextSibling) {
|
||||
if (s->type == WidgetRadioE && s->visible && s->enabled) {
|
||||
prev = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
focus->focused = false;
|
||||
prev->focused = true;
|
||||
prev->parent->as.radioGroup.selectedIdx = prev->as.radio.index;
|
||||
|
||||
if (prev->parent->onChange) {
|
||||
prev->parent->onChange(prev->parent);
|
||||
}
|
||||
|
||||
wgtInvalidate(prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle slider keyboard adjustment
|
||||
if (focus->type == WidgetSliderE) {
|
||||
int32_t step = 1;
|
||||
int32_t range = focus->as.slider.maxValue - focus->as.slider.minValue;
|
||||
|
||||
if (range > 100) {
|
||||
step = range / 100;
|
||||
}
|
||||
|
||||
if (focus->as.slider.vertical) {
|
||||
if (key == (0x48 | 0x100)) {
|
||||
// Up — decrease value
|
||||
focus->as.slider.value -= step;
|
||||
} else if (key == (0x50 | 0x100)) {
|
||||
// Down — increase value
|
||||
focus->as.slider.value += step;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home — minimum
|
||||
focus->as.slider.value = focus->as.slider.minValue;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
// End — maximum
|
||||
focus->as.slider.value = focus->as.slider.maxValue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (key == (0x4B | 0x100)) {
|
||||
// Left — decrease value
|
||||
focus->as.slider.value -= step;
|
||||
} else if (key == (0x4D | 0x100)) {
|
||||
// Right — increase value
|
||||
focus->as.slider.value += step;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home — minimum
|
||||
focus->as.slider.value = focus->as.slider.minValue;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
// End — maximum
|
||||
focus->as.slider.value = focus->as.slider.maxValue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Clamp
|
||||
focus->as.slider.value = clampInt(focus->as.slider.value, focus->as.slider.minValue, focus->as.slider.maxValue);
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle tab control keyboard navigation
|
||||
if (focus->type == WidgetTabControlE) {
|
||||
int32_t tabCount = 0;
|
||||
|
||||
for (WidgetT *c = focus->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type == WidgetTabPageE) {
|
||||
tabCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tabCount > 1) {
|
||||
int32_t active = focus->as.tabControl.activeTab;
|
||||
|
||||
if (key == (0x4D | 0x100)) {
|
||||
// Right — next tab (wrap)
|
||||
active = (active + 1) % tabCount;
|
||||
} else if (key == (0x4B | 0x100)) {
|
||||
// Left — previous tab (wrap)
|
||||
active = (active - 1 + tabCount) % tabCount;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home — first tab
|
||||
active = 0;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
// End — last tab
|
||||
active = tabCount - 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active != focus->as.tabControl.activeTab) {
|
||||
if (sOpenPopup) {
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
sOpenPopup->as.dropdown.open = false;
|
||||
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
||||
sOpenPopup->as.comboBox.open = false;
|
||||
}
|
||||
|
||||
sOpenPopup = NULL;
|
||||
}
|
||||
|
||||
focus->as.tabControl.activeTab = active;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle dropdown keyboard navigation
|
||||
if (focus->type == WidgetDropdownE) {
|
||||
if (focus->as.dropdown.open) {
|
||||
// Popup is open — navigate items
|
||||
if (key == (0x48 | 0x100)) {
|
||||
// Up arrow
|
||||
if (focus->as.dropdown.hoverIdx > 0) {
|
||||
focus->as.dropdown.hoverIdx--;
|
||||
|
||||
if (focus->as.dropdown.hoverIdx < focus->as.dropdown.scrollPos) {
|
||||
focus->as.dropdown.scrollPos = focus->as.dropdown.hoverIdx;
|
||||
}
|
||||
}
|
||||
} else if (key == (0x50 | 0x100)) {
|
||||
// Down arrow
|
||||
if (focus->as.dropdown.hoverIdx < focus->as.dropdown.itemCount - 1) {
|
||||
focus->as.dropdown.hoverIdx++;
|
||||
|
||||
if (focus->as.dropdown.hoverIdx >= focus->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
focus->as.dropdown.scrollPos = focus->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
}
|
||||
} else if (key == 0x0D || key == ' ') {
|
||||
// Enter or Space — select item and close
|
||||
focus->as.dropdown.selectedIdx = focus->as.dropdown.hoverIdx;
|
||||
focus->as.dropdown.open = false;
|
||||
sOpenPopup = NULL;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Popup is closed
|
||||
if (key == (0x50 | 0x100) || key == ' ' || key == 0x0D) {
|
||||
// Down arrow, Space, or Enter — open popup
|
||||
focus->as.dropdown.open = true;
|
||||
focus->as.dropdown.hoverIdx = focus->as.dropdown.selectedIdx;
|
||||
sOpenPopup = focus;
|
||||
|
||||
// Ensure scroll position shows the selected item
|
||||
if (focus->as.dropdown.hoverIdx >= focus->as.dropdown.scrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
focus->as.dropdown.scrollPos = focus->as.dropdown.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
|
||||
if (focus->as.dropdown.hoverIdx < focus->as.dropdown.scrollPos) {
|
||||
focus->as.dropdown.scrollPos = focus->as.dropdown.hoverIdx;
|
||||
}
|
||||
} else if (key == (0x48 | 0x100)) {
|
||||
// Up arrow — select previous item without opening
|
||||
if (focus->as.dropdown.selectedIdx > 0) {
|
||||
focus->as.dropdown.selectedIdx--;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle combobox popup keyboard navigation
|
||||
if (focus->type == WidgetComboBoxE && focus->as.comboBox.open) {
|
||||
if (key == (0x48 | 0x100)) {
|
||||
// Up arrow
|
||||
if (focus->as.comboBox.hoverIdx > 0) {
|
||||
focus->as.comboBox.hoverIdx--;
|
||||
|
||||
if (focus->as.comboBox.hoverIdx < focus->as.comboBox.listScrollPos) {
|
||||
focus->as.comboBox.listScrollPos = focus->as.comboBox.hoverIdx;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == (0x50 | 0x100)) {
|
||||
// Down arrow
|
||||
if (focus->as.comboBox.hoverIdx < focus->as.comboBox.itemCount - 1) {
|
||||
focus->as.comboBox.hoverIdx++;
|
||||
|
||||
if (focus->as.comboBox.hoverIdx >= focus->as.comboBox.listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
focus->as.comboBox.listScrollPos = focus->as.comboBox.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
}
|
||||
|
||||
if (key == 0x0D) {
|
||||
// Enter — select item, copy text, close
|
||||
int32_t idx = focus->as.comboBox.hoverIdx;
|
||||
|
||||
if (idx >= 0 && idx < focus->as.comboBox.itemCount) {
|
||||
focus->as.comboBox.selectedIdx = idx;
|
||||
|
||||
const char *itemText = focus->as.comboBox.items[idx];
|
||||
strncpy(focus->as.comboBox.buf, itemText, focus->as.comboBox.bufSize - 1);
|
||||
focus->as.comboBox.buf[focus->as.comboBox.bufSize - 1] = '\0';
|
||||
focus->as.comboBox.len = (int32_t)strlen(focus->as.comboBox.buf);
|
||||
focus->as.comboBox.cursorPos = focus->as.comboBox.len;
|
||||
focus->as.comboBox.scrollOff = 0;
|
||||
}
|
||||
|
||||
focus->as.comboBox.open = false;
|
||||
sOpenPopup = NULL;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Down arrow on closed combobox opens the popup
|
||||
if (focus->type == WidgetComboBoxE && !focus->as.comboBox.open && key == (0x50 | 0x100)) {
|
||||
focus->as.comboBox.open = true;
|
||||
focus->as.comboBox.hoverIdx = focus->as.comboBox.selectedIdx;
|
||||
sOpenPopup = focus;
|
||||
|
||||
if (focus->as.comboBox.hoverIdx >= focus->as.comboBox.listScrollPos + DROPDOWN_MAX_VISIBLE) {
|
||||
focus->as.comboBox.listScrollPos = focus->as.comboBox.hoverIdx - DROPDOWN_MAX_VISIBLE + 1;
|
||||
}
|
||||
|
||||
if (focus->as.comboBox.hoverIdx < focus->as.comboBox.listScrollPos) {
|
||||
focus->as.comboBox.listScrollPos = focus->as.comboBox.hoverIdx;
|
||||
}
|
||||
|
||||
wgtInvalidate(focus);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle text input for TextInput and ComboBox
|
||||
char *buf = NULL;
|
||||
int32_t bufSize = 0;
|
||||
int32_t *pLen = NULL;
|
||||
int32_t *pCursor = NULL;
|
||||
int32_t *pScrollOff = NULL;
|
||||
|
||||
if (focus->type == WidgetTextInputE) {
|
||||
buf = focus->as.textInput.buf;
|
||||
bufSize = focus->as.textInput.bufSize;
|
||||
pLen = &focus->as.textInput.len;
|
||||
pCursor = &focus->as.textInput.cursorPos;
|
||||
pScrollOff = &focus->as.textInput.scrollOff;
|
||||
} else if (focus->type == WidgetComboBoxE) {
|
||||
buf = focus->as.comboBox.buf;
|
||||
bufSize = focus->as.comboBox.bufSize;
|
||||
pLen = &focus->as.comboBox.len;
|
||||
pCursor = &focus->as.comboBox.cursorPos;
|
||||
pScrollOff = &focus->as.comboBox.scrollOff;
|
||||
}
|
||||
|
||||
if (!buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key >= 32 && key < 127) {
|
||||
// Printable character
|
||||
if (*pLen < bufSize - 1) {
|
||||
int32_t pos = *pCursor;
|
||||
|
||||
memmove(buf + pos + 1, buf + pos, *pLen - pos + 1);
|
||||
buf[pos] = (char)key;
|
||||
(*pLen)++;
|
||||
(*pCursor)++;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
}
|
||||
} else if (key == 8) {
|
||||
// Backspace
|
||||
if (*pCursor > 0) {
|
||||
int32_t pos = *pCursor;
|
||||
|
||||
memmove(buf + pos - 1, buf + pos, *pLen - pos + 1);
|
||||
(*pLen)--;
|
||||
(*pCursor)--;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
}
|
||||
} else if (key == (0x4B | 0x100)) {
|
||||
// Left arrow
|
||||
if (*pCursor > 0) {
|
||||
(*pCursor)--;
|
||||
}
|
||||
} else if (key == (0x4D | 0x100)) {
|
||||
// Right arrow
|
||||
if (*pCursor < *pLen) {
|
||||
(*pCursor)++;
|
||||
}
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home
|
||||
*pCursor = 0;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
// End
|
||||
*pCursor = *pLen;
|
||||
} else if (key == (0x53 | 0x100)) {
|
||||
// Delete
|
||||
if (*pCursor < *pLen) {
|
||||
int32_t pos = *pCursor;
|
||||
|
||||
memmove(buf + pos, buf + pos + 1, *pLen - pos);
|
||||
(*pLen)--;
|
||||
|
||||
if (focus->onChange) {
|
||||
focus->onChange(focus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust scroll offset to keep cursor visible
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t fieldW = focus->w;
|
||||
|
||||
if (focus->type == WidgetComboBoxE) {
|
||||
fieldW -= DROPDOWN_BTN_WIDTH;
|
||||
}
|
||||
|
||||
int32_t visibleChars = (fieldW - TEXT_INPUT_PAD * 2) / font->charWidth;
|
||||
|
||||
if (*pCursor < *pScrollOff) {
|
||||
*pScrollOff = *pCursor;
|
||||
}
|
||||
|
||||
if (*pCursor >= *pScrollOff + visibleChars) {
|
||||
*pScrollOff = *pCursor - visibleChars + 1;
|
||||
}
|
||||
|
||||
// Repaint the window
|
||||
wgtInvalidate(focus);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -616,7 +154,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
|||
|
||||
void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||
WidgetT *root = win->widgetRoot;
|
||||
WidgetT *closedPopup = NULL;
|
||||
sClosedPopup = NULL;
|
||||
|
||||
if (!root) {
|
||||
return;
|
||||
|
|
@ -644,7 +182,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
|
||||
// Handle canvas drawing (mouse move while pressed)
|
||||
if (sDrawingCanvas && (buttons & 1)) {
|
||||
widgetCanvasOnMouse(sDrawingCanvas, x, y);
|
||||
widgetCanvasOnMouse(sDrawingCanvas, root, x, y);
|
||||
wgtInvalidate(root);
|
||||
return;
|
||||
}
|
||||
|
|
@ -813,7 +351,7 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
}
|
||||
|
||||
// Click outside popup — close it and remember which widget it was
|
||||
closedPopup = sOpenPopup;
|
||||
sClosedPopup = sOpenPopup;
|
||||
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
sOpenPopup->as.dropdown.open = false;
|
||||
|
|
@ -858,73 +396,9 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
|||
}
|
||||
}
|
||||
|
||||
// Dispatch to per-widget mouse handlers
|
||||
if (hit->type == WidgetTextInputE) {
|
||||
widgetTextInputOnMouse(hit, root, vx);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetButtonE && hit->enabled) {
|
||||
hit->focused = true;
|
||||
widgetButtonOnMouse(hit);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetCheckboxE && hit->enabled) {
|
||||
hit->focused = true;
|
||||
widgetCheckboxOnMouse(hit);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetRadioE && hit->enabled) {
|
||||
hit->focused = true;
|
||||
widgetRadioOnMouse(hit);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetImageE && hit->enabled) {
|
||||
widgetImageOnMouse(hit);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetImageButtonE && hit->enabled) {
|
||||
hit->focused = true;
|
||||
widgetImageButtonOnMouse(hit);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetCanvasE && hit->enabled) {
|
||||
widgetCanvasOnMouse(hit, vx, vy);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetListBoxE && hit->enabled) {
|
||||
widgetListBoxOnMouse(hit, root, vx, vy);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetDropdownE && hit->enabled) {
|
||||
if (hit != closedPopup) {
|
||||
widgetDropdownOnMouse(hit);
|
||||
}
|
||||
}
|
||||
|
||||
if (hit->type == WidgetComboBoxE && hit->enabled) {
|
||||
if (hit != closedPopup || vx < hit->x + hit->w - DROPDOWN_BTN_WIDTH) {
|
||||
widgetComboBoxOnMouse(hit, root, vx);
|
||||
}
|
||||
}
|
||||
|
||||
if (hit->type == WidgetSliderE && hit->enabled) {
|
||||
hit->focused = true;
|
||||
widgetSliderOnMouse(hit, vx, vy);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetTabControlE && hit->enabled) {
|
||||
hit->focused = true;
|
||||
widgetTabControlOnMouse(hit, root, vx, vy);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetTreeViewE && hit->enabled) {
|
||||
hit->focused = true;
|
||||
widgetTreeViewOnMouse(hit, root, vx, vy);
|
||||
}
|
||||
|
||||
if (hit->type == WidgetAnsiTermE && hit->enabled) {
|
||||
AppContextT *actx = (AppContextT *)root->userData;
|
||||
widgetAnsiTermOnMouse(hit, vx, vy, &actx->font);
|
||||
// Dispatch to per-widget mouse handler via vtable
|
||||
if (hit->enabled && hit->wclass && hit->wclass->onMouse) {
|
||||
hit->wclass->onMouse(hit, root, vx, vy);
|
||||
}
|
||||
|
||||
wgtInvalidate(root);
|
||||
|
|
|
|||
|
|
@ -112,6 +112,15 @@ void wgtImageSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH, int3
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetImageDestroy(WidgetT *w) {
|
||||
free(w->as.image.data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -127,15 +136,18 @@ void widgetImageCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// widgetImageOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetImageOnMouse(WidgetT *hit) {
|
||||
hit->as.image.pressed = true;
|
||||
wgtInvalidate(hit);
|
||||
void widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->as.image.pressed = true;
|
||||
wgtInvalidate(w);
|
||||
|
||||
if (hit->onClick) {
|
||||
hit->onClick(hit);
|
||||
if (w->onClick) {
|
||||
w->onClick(w);
|
||||
}
|
||||
|
||||
hit->as.image.pressed = false;
|
||||
w->as.image.pressed = false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,15 @@ void wgtImageButtonSetData(WidgetT *w, uint8_t *data, int32_t imgW, int32_t imgH
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonDestroy(WidgetT *w) {
|
||||
free(w->as.imageButton.data);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -58,13 +67,30 @@ void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonOnKey(WidgetT *w, int32_t key) {
|
||||
if (key == ' ' || key == 0x0D) {
|
||||
w->as.imageButton.pressed = true;
|
||||
sKeyPressedBtn = w;
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetImageButtonOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetImageButtonOnMouse(WidgetT *hit) {
|
||||
hit->as.imageButton.pressed = true;
|
||||
sPressedButton = hit;
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,31 @@
|
|||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// ============================================================
|
||||
// Widget class vtable
|
||||
// ============================================================
|
||||
|
||||
#define WCLASS_FOCUSABLE 0x0001
|
||||
#define WCLASS_BOX_CONTAINER 0x0002
|
||||
#define WCLASS_HORIZ_CONTAINER 0x0004
|
||||
#define WCLASS_PAINTS_CHILDREN 0x0008
|
||||
#define WCLASS_NO_HIT_RECURSE 0x0010
|
||||
|
||||
typedef struct WidgetClassT {
|
||||
uint32_t flags;
|
||||
void (*paint)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void (*paintOverlay)(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||
void (*calcMinSize)(WidgetT *w, const BitmapFontT *font);
|
||||
void (*layout)(WidgetT *w, const BitmapFontT *font);
|
||||
void (*onMouse)(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void (*onKey)(WidgetT *w, int32_t key);
|
||||
void (*destroy)(WidgetT *w);
|
||||
const char *(*getText)(const WidgetT *w);
|
||||
void (*setText)(WidgetT *w, const char *text);
|
||||
} WidgetClassT;
|
||||
|
||||
extern const WidgetClassT *widgetClassTable[];
|
||||
|
||||
// ============================================================
|
||||
// Constants
|
||||
// ============================================================
|
||||
|
|
@ -56,6 +81,7 @@ static inline int32_t clampInt(int32_t val, int32_t lo, int32_t hi) {
|
|||
// ============================================================
|
||||
|
||||
extern bool sDebugLayout;
|
||||
extern WidgetT *sClosedPopup;
|
||||
extern WidgetT *sKeyPressedBtn;
|
||||
extern WidgetT *sOpenPopup;
|
||||
extern WidgetT *sPressedButton;
|
||||
|
|
@ -154,6 +180,7 @@ void widgetLabelCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
|||
void widgetListBoxCalcMinSize(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 widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetSpacerCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
void widgetTabControlCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||
|
|
@ -168,25 +195,67 @@ void widgetTabControlLayout(WidgetT *w, const BitmapFontT *font);
|
|||
void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget mouse functions
|
||||
// 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 *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 widgetTextAreaDestroy(WidgetT *w);
|
||||
void widgetTextInputDestroy(WidgetT *w);
|
||||
|
||||
// ============================================================
|
||||
// Per-widget mouse/key functions
|
||||
// ============================================================
|
||||
|
||||
void widgetAnsiTermOnMouse(WidgetT *hit, int32_t vx, int32_t vy, const BitmapFontT *font);
|
||||
void widgetAnsiTermOnKey(WidgetT *w, int32_t key);
|
||||
void widgetButtonOnMouse(WidgetT *hit);
|
||||
void widgetImageButtonOnMouse(WidgetT *hit);
|
||||
void widgetCanvasOnMouse(WidgetT *hit, int32_t vx, int32_t vy);
|
||||
void widgetCheckboxOnMouse(WidgetT *hit);
|
||||
void widgetComboBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx);
|
||||
void widgetDropdownOnMouse(WidgetT *hit);
|
||||
void widgetImageOnMouse(WidgetT *hit);
|
||||
void widgetAnsiTermOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetButtonOnKey(WidgetT *w, int32_t key);
|
||||
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);
|
||||
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetComboBoxOnKey(WidgetT *w, int32_t key);
|
||||
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetDropdownOnKey(WidgetT *w, int32_t key);
|
||||
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetImageButtonOnKey(WidgetT *w, int32_t key);
|
||||
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);
|
||||
void widgetListBoxOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetRadioOnMouse(WidgetT *hit);
|
||||
void widgetSliderOnMouse(WidgetT *hit, int32_t vx, int32_t vy);
|
||||
void widgetTabControlOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetTextInputOnMouse(WidgetT *hit, WidgetT *root, int32_t vx);
|
||||
void widgetListBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetRadioOnKey(WidgetT *w, int32_t key);
|
||||
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetSliderOnKey(WidgetT *w, int32_t key);
|
||||
void widgetSliderOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetTabControlOnKey(WidgetT *w, int32_t key);
|
||||
void widgetTabControlOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetTextEditOnKey(WidgetT *w, int32_t key, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff);
|
||||
void widgetTextInputOnKey(WidgetT *w, int32_t key);
|
||||
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetTreeViewOnKey(WidgetT *w, int32_t key);
|
||||
void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy);
|
||||
void widgetTreeViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
|
||||
#endif // WIDGET_INTERNAL_H
|
||||
|
|
|
|||
|
|
@ -19,6 +19,25 @@ WidgetT *wgtLabel(WidgetT *parent, const char *text) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetLabelGetText
|
||||
// ============================================================
|
||||
|
||||
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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -90,76 +90,11 @@ void widgetCalcMinSizeBox(WidgetT *w, const BitmapFontT *font) {
|
|||
void widgetCalcMinSizeTree(WidgetT *w, const BitmapFontT *font) {
|
||||
if (widgetIsBoxContainer(w->type)) {
|
||||
widgetCalcMinSizeBox(w, font);
|
||||
} else if (w->type == WidgetTabControlE) {
|
||||
widgetTabControlCalcMinSize(w, font);
|
||||
} else if (w->type == WidgetTreeViewE) {
|
||||
widgetTreeViewCalcMinSize(w, font);
|
||||
} else if (w->wclass && w->wclass->calcMinSize) {
|
||||
w->wclass->calcMinSize(w, font);
|
||||
} else {
|
||||
// Leaf widgets
|
||||
switch (w->type) {
|
||||
case WidgetLabelE:
|
||||
widgetLabelCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetButtonE:
|
||||
widgetButtonCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetCheckboxE:
|
||||
widgetCheckboxCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetRadioE:
|
||||
widgetRadioCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetTextInputE:
|
||||
widgetTextInputCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetSpacerE:
|
||||
widgetSpacerCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetDropdownE:
|
||||
widgetDropdownCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetImageE:
|
||||
widgetImageCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetListBoxE:
|
||||
widgetListBoxCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetImageButtonE:
|
||||
widgetImageButtonCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetCanvasE:
|
||||
widgetCanvasCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetComboBoxE:
|
||||
widgetComboBoxCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetProgressBarE:
|
||||
widgetProgressBarCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetSliderE:
|
||||
widgetSliderCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetAnsiTermE:
|
||||
widgetAnsiTermCalcMinSize(w, font);
|
||||
break;
|
||||
case WidgetSeparatorE:
|
||||
if (w->as.separator.vertical) {
|
||||
w->calcMinW = SEPARATOR_THICKNESS;
|
||||
w->calcMinH = 0;
|
||||
} else {
|
||||
w->calcMinW = 0;
|
||||
w->calcMinH = SEPARATOR_THICKNESS;
|
||||
}
|
||||
break;
|
||||
case WidgetTreeItemE:
|
||||
w->calcMinW = 0;
|
||||
w->calcMinH = 0;
|
||||
break;
|
||||
default:
|
||||
w->calcMinW = 0;
|
||||
w->calcMinH = 0;
|
||||
break;
|
||||
}
|
||||
w->calcMinW = 0;
|
||||
w->calcMinH = 0;
|
||||
}
|
||||
|
||||
// Apply size hints (override calculated minimum)
|
||||
|
|
@ -339,10 +274,8 @@ void widgetLayoutBox(WidgetT *w, const BitmapFontT *font) {
|
|||
void widgetLayoutChildren(WidgetT *w, const BitmapFontT *font) {
|
||||
if (widgetIsBoxContainer(w->type)) {
|
||||
widgetLayoutBox(w, font);
|
||||
} else if (w->type == WidgetTabControlE) {
|
||||
widgetTabControlLayout(w, font);
|
||||
} else if (w->type == WidgetTreeViewE) {
|
||||
widgetTreeViewLayout(w, font);
|
||||
} else if (w->wclass && w->wclass->layout) {
|
||||
w->wclass->layout(w, font);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,107 +49,21 @@ void widgetPaintOne(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFo
|
|||
return;
|
||||
}
|
||||
|
||||
switch (w->type) {
|
||||
case WidgetVBoxE:
|
||||
case WidgetHBoxE:
|
||||
// Containers are transparent — just paint children
|
||||
break;
|
||||
|
||||
case WidgetFrameE:
|
||||
widgetFramePaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetImageE:
|
||||
widgetImagePaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetImageButtonE:
|
||||
widgetImageButtonPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetCanvasE:
|
||||
widgetCanvasPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetLabelE:
|
||||
widgetLabelPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetButtonE:
|
||||
widgetButtonPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetCheckboxE:
|
||||
widgetCheckboxPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetRadioE:
|
||||
widgetRadioPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetTextInputE:
|
||||
widgetTextInputPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetSpacerE:
|
||||
// Invisible — draws nothing
|
||||
break;
|
||||
|
||||
case WidgetSeparatorE:
|
||||
widgetSeparatorPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetDropdownE:
|
||||
widgetDropdownPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetComboBoxE:
|
||||
widgetComboBoxPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetListBoxE:
|
||||
widgetListBoxPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetProgressBarE:
|
||||
widgetProgressBarPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetSliderE:
|
||||
widgetSliderPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetTabControlE:
|
||||
widgetTabControlPaint(w, d, ops, font, colors);
|
||||
if (sDebugLayout) {
|
||||
debugContainerBorder(w, d, ops);
|
||||
}
|
||||
return; // handles its own children
|
||||
|
||||
case WidgetStatusBarE:
|
||||
widgetStatusBarPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetToolbarE:
|
||||
widgetToolbarPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
case WidgetTreeViewE:
|
||||
widgetTreeViewPaint(w, d, ops, font, colors);
|
||||
if (sDebugLayout) {
|
||||
debugContainerBorder(w, d, ops);
|
||||
}
|
||||
return; // handles its own children
|
||||
|
||||
case WidgetAnsiTermE:
|
||||
widgetAnsiTermPaint(w, d, ops, font, colors);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
// Paint this widget via vtable
|
||||
if (w->wclass && w->wclass->paint) {
|
||||
w->wclass->paint(w, d, ops, font, colors);
|
||||
}
|
||||
|
||||
// Paint children (TabControl and TreeView return early above)
|
||||
// Widgets that paint their own children return early
|
||||
if (w->wclass && (w->wclass->flags & WCLASS_PAINTS_CHILDREN)) {
|
||||
if (sDebugLayout) {
|
||||
debugContainerBorder(w, d, ops);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Paint children
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
widgetPaintOne(c, d, ops, font, colors);
|
||||
}
|
||||
|
|
@ -184,10 +98,8 @@ void widgetPaintOverlays(WidgetT *root, DisplayT *d, const BlitOpsT *ops, const
|
|||
return;
|
||||
}
|
||||
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
widgetDropdownPaintPopup(sOpenPopup, d, ops, font, colors);
|
||||
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
||||
widgetComboBoxPaintPopup(sOpenPopup, d, ops, font, colors);
|
||||
if (sOpenPopup->wclass && sOpenPopup->wclass->paintOverlay) {
|
||||
sOpenPopup->wclass->paintOverlay(sOpenPopup, d, ops, font, colors);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,19 +119,8 @@ void wgtDestroy(WidgetT *w) {
|
|||
|
||||
widgetDestroyChildren(w);
|
||||
|
||||
if (w->type == WidgetTextInputE) {
|
||||
free(w->as.textInput.buf);
|
||||
} else if (w->type == WidgetTextAreaE) {
|
||||
free(w->as.textArea.buf);
|
||||
} else if (w->type == WidgetComboBoxE) {
|
||||
free(w->as.comboBox.buf);
|
||||
} else if (w->type == WidgetImageE) {
|
||||
free(w->as.image.data);
|
||||
} else if (w->type == WidgetCanvasE) {
|
||||
free(w->as.canvas.data);
|
||||
} else if (w->type == WidgetAnsiTermE) {
|
||||
free(w->as.ansiTerm.cells);
|
||||
free(w->as.ansiTerm.scrollback);
|
||||
if (w->wclass && w->wclass->destroy) {
|
||||
w->wclass->destroy(w);
|
||||
}
|
||||
|
||||
// Clear static references
|
||||
|
|
@ -278,21 +179,11 @@ const char *wgtGetText(const WidgetT *w) {
|
|||
return "";
|
||||
}
|
||||
|
||||
switch (w->type) {
|
||||
case WidgetLabelE: return w->as.label.text;
|
||||
case WidgetButtonE: return w->as.button.text;
|
||||
case WidgetCheckboxE: return w->as.checkbox.text;
|
||||
case WidgetRadioE: return w->as.radio.text;
|
||||
case WidgetTextInputE: return w->as.textInput.buf ? w->as.textInput.buf : "";
|
||||
case WidgetComboBoxE: return w->as.comboBox.buf ? w->as.comboBox.buf : "";
|
||||
case WidgetTreeItemE: return w->as.treeItem.text ? w->as.treeItem.text : "";
|
||||
case WidgetDropdownE:
|
||||
if (w->as.dropdown.selectedIdx >= 0 && w->as.dropdown.selectedIdx < w->as.dropdown.itemCount) {
|
||||
return w->as.dropdown.items[w->as.dropdown.selectedIdx];
|
||||
}
|
||||
return "";
|
||||
default: return "";
|
||||
if (w->wclass && w->wclass->getText) {
|
||||
return w->wclass->getText(w);
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -397,53 +288,8 @@ void wgtSetText(WidgetT *w, const char *text) {
|
|||
return;
|
||||
}
|
||||
|
||||
switch (w->type) {
|
||||
case WidgetLabelE:
|
||||
w->as.label.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
break;
|
||||
|
||||
case WidgetButtonE:
|
||||
w->as.button.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
break;
|
||||
|
||||
case WidgetCheckboxE:
|
||||
w->as.checkbox.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
break;
|
||||
|
||||
case WidgetRadioE:
|
||||
w->as.radio.text = text;
|
||||
w->accelKey = accelParse(text);
|
||||
break;
|
||||
|
||||
case WidgetTextInputE:
|
||||
if (w->as.textInput.buf) {
|
||||
strncpy(w->as.textInput.buf, text, w->as.textInput.bufSize - 1);
|
||||
w->as.textInput.buf[w->as.textInput.bufSize - 1] = '\0';
|
||||
w->as.textInput.len = (int32_t)strlen(w->as.textInput.buf);
|
||||
w->as.textInput.cursorPos = w->as.textInput.len;
|
||||
w->as.textInput.scrollOff = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case WidgetComboBoxE:
|
||||
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;
|
||||
}
|
||||
break;
|
||||
|
||||
case WidgetTreeItemE:
|
||||
w->as.treeItem.text = text;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (w->wclass && w->wclass->setText) {
|
||||
w->wclass->setText(w, text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,6 +45,25 @@ WidgetT *wgtRadioGroup(WidgetT *parent) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -56,16 +75,88 @@ void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetRadioOnKey(WidgetT *w, int32_t key) {
|
||||
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->onChange) {
|
||||
w->parent->onChange(w->parent);
|
||||
}
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
} else if (key == (0x50 | 0x100) || key == (0x4D | 0x100)) {
|
||||
// Down or Right — next radio in group
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||
WidgetT *next = NULL;
|
||||
|
||||
for (WidgetT *s = w->nextSibling; s; s = s->nextSibling) {
|
||||
if (s->type == WidgetRadioE && s->visible && s->enabled) {
|
||||
next = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next) {
|
||||
w->focused = false;
|
||||
next->focused = true;
|
||||
next->parent->as.radioGroup.selectedIdx = next->as.radio.index;
|
||||
|
||||
if (next->parent->onChange) {
|
||||
next->parent->onChange(next->parent);
|
||||
}
|
||||
|
||||
wgtInvalidate(next);
|
||||
}
|
||||
}
|
||||
} else if (key == (0x48 | 0x100) || key == (0x4B | 0x100)) {
|
||||
// Up or Left — previous radio in group
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||
WidgetT *prev = NULL;
|
||||
|
||||
for (WidgetT *s = w->parent->firstChild; s && s != w; s = s->nextSibling) {
|
||||
if (s->type == WidgetRadioE && s->visible && s->enabled) {
|
||||
prev = s;
|
||||
}
|
||||
}
|
||||
|
||||
if (prev) {
|
||||
w->focused = false;
|
||||
prev->focused = true;
|
||||
prev->parent->as.radioGroup.selectedIdx = prev->as.radio.index;
|
||||
|
||||
if (prev->parent->onChange) {
|
||||
prev->parent->onChange(prev->parent);
|
||||
}
|
||||
|
||||
wgtInvalidate(prev);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetRadioOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetRadioOnMouse(WidgetT *hit) {
|
||||
if (hit->parent && hit->parent->type == WidgetRadioGroupE) {
|
||||
hit->parent->as.radioGroup.selectedIdx = hit->as.radio.index;
|
||||
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)root;
|
||||
(void)vx;
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
|
||||
if (hit->parent->onChange) {
|
||||
hit->parent->onChange(hit->parent);
|
||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||
w->parent->as.radioGroup.selectedIdx = w->as.radio.index;
|
||||
|
||||
if (w->parent->onChange) {
|
||||
w->parent->onChange(w->parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,23 @@ WidgetT *wgtVSeparator(WidgetT *parent) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSeparatorCalcMinSize
|
||||
// ============================================================
|
||||
|
||||
void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||
(void)font;
|
||||
|
||||
if (w->as.separator.vertical) {
|
||||
w->calcMinW = SEPARATOR_THICKNESS;
|
||||
w->calcMinH = 0;
|
||||
} else {
|
||||
w->calcMinW = 0;
|
||||
w->calcMinH = SEPARATOR_THICKNESS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSeparatorPaint
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -73,11 +73,61 @@ void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSliderOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetSliderOnKey(WidgetT *w, int32_t key) {
|
||||
int32_t step = 1;
|
||||
int32_t range = w->as.slider.maxValue - w->as.slider.minValue;
|
||||
|
||||
if (range > 100) {
|
||||
step = range / 100;
|
||||
}
|
||||
|
||||
if (w->as.slider.vertical) {
|
||||
if (key == (0x48 | 0x100)) {
|
||||
w->as.slider.value -= step;
|
||||
} else if (key == (0x50 | 0x100)) {
|
||||
w->as.slider.value += step;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.minValue;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.maxValue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (key == (0x4B | 0x100)) {
|
||||
w->as.slider.value -= step;
|
||||
} else if (key == (0x4D | 0x100)) {
|
||||
w->as.slider.value += step;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.minValue;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
w->as.slider.value = w->as.slider.maxValue;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
w->as.slider.value = clampInt(w->as.slider.value, w->as.slider.minValue, w->as.slider.maxValue);
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetSliderOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetSliderOnMouse(WidgetT *hit, int32_t vx, int32_t vy) {
|
||||
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;
|
||||
|
||||
if (range <= 0) {
|
||||
|
|
|
|||
|
|
@ -127,11 +127,65 @@ void widgetTabControlLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTabControlOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetTabControlOnKey(WidgetT *w, int32_t key) {
|
||||
int32_t tabCount = 0;
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (c->type == WidgetTabPageE) {
|
||||
tabCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (tabCount <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t active = w->as.tabControl.activeTab;
|
||||
|
||||
if (key == (0x4D | 0x100)) {
|
||||
active = (active + 1) % tabCount;
|
||||
} else if (key == (0x4B | 0x100)) {
|
||||
active = (active - 1 + tabCount) % tabCount;
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
active = 0;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
active = tabCount - 1;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active != w->as.tabControl.activeTab) {
|
||||
if (sOpenPopup) {
|
||||
if (sOpenPopup->type == WidgetDropdownE) {
|
||||
sOpenPopup->as.dropdown.open = false;
|
||||
} else if (sOpenPopup->type == WidgetComboBoxE) {
|
||||
sOpenPopup->as.comboBox.open = false;
|
||||
}
|
||||
|
||||
sOpenPopup = NULL;
|
||||
}
|
||||
|
||||
w->as.tabControl.activeTab = active;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTabControlOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetTabControlOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
hit->focused = true;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t tabH = font->charHeight + TAB_PAD_V * 2;
|
||||
|
|
|
|||
|
|
@ -24,6 +24,15 @@ WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextAreaDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetTextAreaDestroy(WidgetT *w) {
|
||||
free(w->as.textArea.buf);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtTextInput
|
||||
// ============================================================
|
||||
|
|
@ -47,6 +56,39 @@ WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextInputDestroy
|
||||
// ============================================================
|
||||
|
||||
void widgetTextInputDestroy(WidgetT *w) {
|
||||
free(w->as.textInput.buf);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextInputGetText
|
||||
// ============================================================
|
||||
|
||||
const char *widgetTextInputGetText(const WidgetT *w) {
|
||||
return w->as.textInput.buf ? w->as.textInput.buf : "";
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextInputSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetTextInputSetText(WidgetT *w, const char *text) {
|
||||
if (w->as.textInput.buf) {
|
||||
strncpy(w->as.textInput.buf, text, w->as.textInput.bufSize - 1);
|
||||
w->as.textInput.buf[w->as.textInput.bufSize - 1] = '\0';
|
||||
w->as.textInput.len = (int32_t)strlen(w->as.textInput.buf);
|
||||
w->as.textInput.cursorPos = w->as.textInput.len;
|
||||
w->as.textInput.scrollOff = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextInputCalcMinSize
|
||||
// ============================================================
|
||||
|
|
@ -61,24 +103,125 @@ void widgetTextInputCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
|||
// widgetTextInputOnMouse
|
||||
// ============================================================
|
||||
|
||||
void widgetTextInputOnMouse(WidgetT *hit, WidgetT *root, int32_t vx) {
|
||||
hit->focused = true;
|
||||
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
(void)vy;
|
||||
w->focused = true;
|
||||
|
||||
// Place cursor at click position
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t relX = vx - hit->x - TEXT_INPUT_PAD;
|
||||
int32_t charPos = relX / font->charWidth + hit->as.textInput.scrollOff;
|
||||
int32_t relX = vx - w->x - TEXT_INPUT_PAD;
|
||||
int32_t charPos = relX / font->charWidth + w->as.textInput.scrollOff;
|
||||
|
||||
if (charPos < 0) {
|
||||
charPos = 0;
|
||||
}
|
||||
|
||||
if (charPos > hit->as.textInput.len) {
|
||||
charPos = hit->as.textInput.len;
|
||||
if (charPos > w->as.textInput.len) {
|
||||
charPos = w->as.textInput.len;
|
||||
}
|
||||
|
||||
hit->as.textInput.cursorPos = charPos;
|
||||
w->as.textInput.cursorPos = charPos;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextEditOnKey — shared text editing logic
|
||||
// ============================================================
|
||||
|
||||
void widgetTextEditOnKey(WidgetT *w, int32_t key, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff) {
|
||||
if (key >= 32 && key < 127) {
|
||||
// Printable character
|
||||
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 (*pCursor > 0) {
|
||||
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 (*pCursor > 0) {
|
||||
(*pCursor)--;
|
||||
}
|
||||
} else if (key == (0x4D | 0x100)) {
|
||||
// Right arrow
|
||||
if (*pCursor < *pLen) {
|
||||
(*pCursor)++;
|
||||
}
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home
|
||||
*pCursor = 0;
|
||||
} else if (key == (0x4F | 0x100)) {
|
||||
// End
|
||||
*pCursor = *pLen;
|
||||
} else if (key == (0x53 | 0x100)) {
|
||||
// Delete
|
||||
if (*pCursor < *pLen) {
|
||||
int32_t pos = *pCursor;
|
||||
|
||||
memmove(buf + pos, buf + pos + 1, *pLen - pos);
|
||||
(*pLen)--;
|
||||
|
||||
if (w->onChange) {
|
||||
w->onChange(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust scroll offset to keep cursor visible
|
||||
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
int32_t fieldW = w->w;
|
||||
|
||||
if (w->type == WidgetComboBoxE) {
|
||||
fieldW -= DROPDOWN_BTN_WIDTH;
|
||||
}
|
||||
|
||||
int32_t visibleChars = (fieldW - TEXT_INPUT_PAD * 2) / font->charWidth;
|
||||
|
||||
if (*pCursor < *pScrollOff) {
|
||||
*pScrollOff = *pCursor;
|
||||
}
|
||||
|
||||
if (*pCursor >= *pScrollOff + visibleChars) {
|
||||
*pScrollOff = *pCursor - visibleChars + 1;
|
||||
}
|
||||
|
||||
wgtInvalidate(w);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTextInputOnKey
|
||||
// ============================================================
|
||||
|
||||
void widgetTextInputOnKey(WidgetT *w, int32_t key) {
|
||||
if (!w->as.textInput.buf) {
|
||||
return;
|
||||
}
|
||||
|
||||
widgetTextEditOnKey(w, key, w->as.textInput.buf, w->as.textInput.bufSize,
|
||||
&w->as.textInput.len, &w->as.textInput.cursorPos,
|
||||
&w->as.textInput.scrollOff);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -548,6 +548,24 @@ WidgetT *wgtTreeItem(WidgetT *parent, const char *text) {
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTreeItemGetText
|
||||
// ============================================================
|
||||
|
||||
const char *widgetTreeItemGetText(const WidgetT *w) {
|
||||
return w->as.treeItem.text ? w->as.treeItem.text : "";
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// widgetTreeItemSetText
|
||||
// ============================================================
|
||||
|
||||
void widgetTreeItemSetText(WidgetT *w, const char *text) {
|
||||
w->as.treeItem.text = text;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtTreeItemIsExpanded
|
||||
// ============================================================
|
||||
|
|
@ -791,6 +809,7 @@ void widgetTreeViewLayout(WidgetT *w, const BitmapFontT *font) {
|
|||
// ============================================================
|
||||
|
||||
void widgetTreeViewOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||
hit->focused = true;
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue