Spinbox added.
This commit is contained in:
parent
d525798836
commit
911cd1d123
6 changed files with 567 additions and 2 deletions
|
|
@ -35,6 +35,7 @@ WSRCS = widgets/widgetAnsiTerm.c \
|
||||||
widgets/widgetSeparator.c \
|
widgets/widgetSeparator.c \
|
||||||
widgets/widgetSlider.c \
|
widgets/widgetSlider.c \
|
||||||
widgets/widgetSpacer.c \
|
widgets/widgetSpacer.c \
|
||||||
|
widgets/widgetSpinner.c \
|
||||||
widgets/widgetStatusBar.c \
|
widgets/widgetStatusBar.c \
|
||||||
widgets/widgetTabControl.c \
|
widgets/widgetTabControl.c \
|
||||||
widgets/widgetTextInput.c \
|
widgets/widgetTextInput.c \
|
||||||
|
|
@ -102,6 +103,7 @@ $(WOBJDIR)/widgetRadio.o: widgets/widgetRadio.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetSeparator.o: widgets/widgetSeparator.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetSeparator.o: widgets/widgetSeparator.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetSlider.o: widgets/widgetSlider.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetSlider.o: widgets/widgetSlider.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetSpacer.o: widgets/widgetSpacer.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetSpacer.o: widgets/widgetSpacer.c $(WIDGET_DEPS)
|
||||||
|
$(WOBJDIR)/widgetSpinner.o: widgets/widgetSpinner.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetStatusBar.o: widgets/widgetStatusBar.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetStatusBar.o: widgets/widgetStatusBar.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetTabControl.o: widgets/widgetTabControl.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetTabControl.o: widgets/widgetTabControl.c $(WIDGET_DEPS)
|
||||||
$(WOBJDIR)/widgetTextInput.o: widgets/widgetTextInput.c $(WIDGET_DEPS)
|
$(WOBJDIR)/widgetTextInput.o: widgets/widgetTextInput.c $(WIDGET_DEPS)
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,8 @@ typedef enum {
|
||||||
WidgetImageButtonE,
|
WidgetImageButtonE,
|
||||||
WidgetCanvasE,
|
WidgetCanvasE,
|
||||||
WidgetAnsiTermE,
|
WidgetAnsiTermE,
|
||||||
WidgetListViewE
|
WidgetListViewE,
|
||||||
|
WidgetSpinnerE
|
||||||
} WidgetTypeE;
|
} WidgetTypeE;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -423,6 +424,23 @@ typedef struct WidgetT {
|
||||||
uint8_t *selBits;
|
uint8_t *selBits;
|
||||||
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
|
void (*onHeaderClick)(struct WidgetT *w, int32_t col, ListViewSortE dir);
|
||||||
} listView;
|
} 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;
|
||||||
} as;
|
} as;
|
||||||
} WidgetT;
|
} WidgetT;
|
||||||
|
|
||||||
|
|
@ -505,6 +523,16 @@ WidgetT *wgtSlider(WidgetT *parent, int32_t minVal, int32_t maxVal);
|
||||||
void wgtSliderSetValue(WidgetT *w, int32_t value);
|
void wgtSliderSetValue(WidgetT *w, int32_t value);
|
||||||
int32_t wgtSliderGetValue(const WidgetT *w);
|
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
|
// TabControl
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
|
||||||
|
|
@ -370,6 +370,19 @@ static const WidgetClassT sClassAnsiTerm = {
|
||||||
.setText = NULL
|
.setText = NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const WidgetClassT sClassSpinner = {
|
||||||
|
.flags = WCLASS_FOCUSABLE,
|
||||||
|
.paint = widgetSpinnerPaint,
|
||||||
|
.paintOverlay = NULL,
|
||||||
|
.calcMinSize = widgetSpinnerCalcMinSize,
|
||||||
|
.layout = NULL,
|
||||||
|
.onMouse = widgetSpinnerOnMouse,
|
||||||
|
.onKey = widgetSpinnerOnKey,
|
||||||
|
.destroy = NULL,
|
||||||
|
.getText = widgetSpinnerGetText,
|
||||||
|
.setText = widgetSpinnerSetText
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Class table — indexed by WidgetTypeE
|
// Class table — indexed by WidgetTypeE
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -402,5 +415,6 @@ const WidgetClassT *widgetClassTable[] = {
|
||||||
[WidgetImageButtonE] = &sClassImageButton,
|
[WidgetImageButtonE] = &sClassImageButton,
|
||||||
[WidgetCanvasE] = &sClassCanvas,
|
[WidgetCanvasE] = &sClassCanvas,
|
||||||
[WidgetAnsiTermE] = &sClassAnsiTerm,
|
[WidgetAnsiTermE] = &sClassAnsiTerm,
|
||||||
[WidgetListViewE] = &sClassListView
|
[WidgetListViewE] = &sClassListView,
|
||||||
|
[WidgetSpinnerE] = &sClassSpinner
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ void widgetProgressBarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const
|
||||||
void widgetRadioPaint(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 widgetSeparatorPaint(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 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 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 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 widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||||
|
|
@ -197,6 +198,7 @@ void widgetProgressBarCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetSliderCalcMinSize(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 widgetSpacerCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetTabControlCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetTabControlCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetTextAreaCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetTextAreaCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
|
|
@ -277,6 +279,10 @@ void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetRadioOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod);
|
void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetSliderOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
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 widgetTabControlOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetTabControlOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetTabControlOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod);
|
void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
|
|
|
||||||
508
dvx/widgets/widgetSpinner.c
Normal file
508
dvx/widgets/widgetSpinner.c
Normal file
|
|
@ -0,0 +1,508 @@
|
||||||
|
// widgetSpinner.c — Spinner (numeric up/down) widget
|
||||||
|
|
||||||
|
#include "widgetInternal.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#define SPINNER_BTN_W 14
|
||||||
|
#define SPINNER_BORDER 2
|
||||||
|
#define SPINNER_PAD 3
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Prototypes
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void spinnerClampAndFormat(WidgetT *w);
|
||||||
|
static void spinnerCommitEdit(WidgetT *w);
|
||||||
|
static void spinnerFormat(WidgetT *w);
|
||||||
|
static void spinnerStartEdit(WidgetT *w);
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// spinnerClampAndFormat
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void spinnerClampAndFormat(WidgetT *w) {
|
||||||
|
if (w->as.spinner.value < w->as.spinner.minValue) {
|
||||||
|
w->as.spinner.value = w->as.spinner.minValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w->as.spinner.value > w->as.spinner.maxValue) {
|
||||||
|
w->as.spinner.value = w->as.spinner.maxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
spinnerFormat(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// spinnerCommitEdit
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void spinnerCommitEdit(WidgetT *w) {
|
||||||
|
if (!w->as.spinner.editing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.spinner.editing = false;
|
||||||
|
w->as.spinner.buf[w->as.spinner.len] = '\0';
|
||||||
|
|
||||||
|
int32_t val = (int32_t)strtol(w->as.spinner.buf, NULL, 10);
|
||||||
|
w->as.spinner.value = val;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// spinnerFormat
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// spinnerStartEdit
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void spinnerStartEdit(WidgetT *w) {
|
||||||
|
if (!w->as.spinner.editing) {
|
||||||
|
w->as.spinner.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetSpinnerCalcMinSize
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetSpinnerCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
|
w->calcMinW = font->charWidth * 6 + SPINNER_PAD * 2 + SPINNER_BORDER * 2 + SPINNER_BTN_W;
|
||||||
|
w->calcMinH = font->charHeight + SPINNER_PAD * 2 + SPINNER_BORDER * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetSpinnerGetText
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const char *widgetSpinnerGetText(const WidgetT *w) {
|
||||||
|
return w->as.spinner.buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetSpinnerOnKey
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetSpinnerOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
int32_t step = w->as.spinner.step;
|
||||||
|
|
||||||
|
// Up arrow — increment
|
||||||
|
if (key == (0x48 | 0x100)) {
|
||||||
|
spinnerCommitEdit(w);
|
||||||
|
w->as.spinner.value += step;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down arrow — decrement
|
||||||
|
if (key == (0x50 | 0x100)) {
|
||||||
|
spinnerCommitEdit(w);
|
||||||
|
w->as.spinner.value -= step;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page Up — increment by step * 10
|
||||||
|
if (key == (0x49 | 0x100)) {
|
||||||
|
spinnerCommitEdit(w);
|
||||||
|
w->as.spinner.value += step * 10;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page Down — decrement by step * 10
|
||||||
|
if (key == (0x51 | 0x100)) {
|
||||||
|
spinnerCommitEdit(w);
|
||||||
|
w->as.spinner.value -= step * 10;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter — commit edit
|
||||||
|
if (key == '\r' || key == '\n') {
|
||||||
|
if (w->as.spinner.editing) {
|
||||||
|
spinnerCommitEdit(w);
|
||||||
|
|
||||||
|
if (w->onChange) {
|
||||||
|
w->onChange(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape — cancel edit, revert to current value
|
||||||
|
if (key == 27) {
|
||||||
|
if (w->as.spinner.editing) {
|
||||||
|
w->as.spinner.editing = false;
|
||||||
|
spinnerFormat(w);
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter: only allow digits, minus, and control keys through to text editor
|
||||||
|
bool isDigit = (key >= '0' && key <= '9');
|
||||||
|
bool isMinus = (key == '-');
|
||||||
|
bool isControl = (key < 0x20) || (key & 0x100);
|
||||||
|
|
||||||
|
if (!isDigit && !isMinus && !isControl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minus only at position 0 (and only if min is negative)
|
||||||
|
if (isMinus && (w->as.spinner.cursorPos != 0 || w->as.spinner.minValue >= 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enter edit mode on first text-modifying key
|
||||||
|
if (isDigit || isMinus || key == 8 || key == 127 || key == (0x53 | 0x100)) {
|
||||||
|
spinnerStartEdit(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
wgtInvalidatePaint(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetSpinnerOnMouse
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
|
hit->focused = true;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (vy < midY) {
|
||||||
|
hit->as.spinner.value += hit->as.spinner.step;
|
||||||
|
} else {
|
||||||
|
hit->as.spinner.value -= hit->as.spinner.step;
|
||||||
|
}
|
||||||
|
|
||||||
|
spinnerClampAndFormat(hit);
|
||||||
|
|
||||||
|
if (hit->onChange) {
|
||||||
|
hit->onChange(hit);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Click on text area
|
||||||
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
|
const BitmapFontT *font = &ctx->font;
|
||||||
|
int32_t textX = hit->x + SPINNER_BORDER + SPINNER_PAD;
|
||||||
|
int32_t relX = vx - textX + hit->as.spinner.scrollOff * font->charWidth;
|
||||||
|
int32_t pos = relX / font->charWidth;
|
||||||
|
|
||||||
|
if (pos < 0) {
|
||||||
|
pos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos > hit->as.spinner.len) {
|
||||||
|
pos = hit->as.spinner.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t clicks = multiClickDetect(vx, vy);
|
||||||
|
|
||||||
|
if (clicks >= 2) {
|
||||||
|
// Double/triple-click: select all text
|
||||||
|
hit->as.spinner.selStart = 0;
|
||||||
|
hit->as.spinner.selEnd = hit->as.spinner.len;
|
||||||
|
hit->as.spinner.cursorPos = hit->as.spinner.len;
|
||||||
|
} else {
|
||||||
|
// Single click: place cursor
|
||||||
|
hit->as.spinner.cursorPos = pos;
|
||||||
|
hit->as.spinner.selStart = -1;
|
||||||
|
hit->as.spinner.selEnd = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
spinnerStartEdit(hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtInvalidatePaint(hit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetSpinnerPaint
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
|
||||||
|
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
|
||||||
|
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
|
||||||
|
|
||||||
|
int32_t btnW = SPINNER_BTN_W;
|
||||||
|
int32_t btnX = w->x + w->w - SPINNER_BORDER - btnW;
|
||||||
|
|
||||||
|
// Sunken border around entire widget
|
||||||
|
BevelStyleT bevel;
|
||||||
|
bevel.highlight = colors->windowShadow;
|
||||||
|
bevel.shadow = colors->windowHighlight;
|
||||||
|
bevel.face = bg;
|
||||||
|
bevel.width = SPINNER_BORDER;
|
||||||
|
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
|
||||||
|
|
||||||
|
// Text area
|
||||||
|
int32_t textX = w->x + SPINNER_BORDER + SPINNER_PAD;
|
||||||
|
int32_t textY = w->y + (w->h - font->charHeight) / 2;
|
||||||
|
int32_t textW = btnX - (w->x + SPINNER_BORDER) - SPINNER_PAD * 2;
|
||||||
|
int32_t maxChars = textW / font->charWidth;
|
||||||
|
|
||||||
|
if (maxChars < 0) {
|
||||||
|
maxChars = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t off = w->as.spinner.scrollOff;
|
||||||
|
int32_t len = w->as.spinner.len - off;
|
||||||
|
|
||||||
|
if (len > maxChars) {
|
||||||
|
len = maxChars;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (len < 0) {
|
||||||
|
len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection range
|
||||||
|
int32_t selLo = -1;
|
||||||
|
int32_t selHi = -1;
|
||||||
|
|
||||||
|
if (w->as.spinner.selStart >= 0 && w->as.spinner.selEnd >= 0 && w->as.spinner.selStart != w->as.spinner.selEnd) {
|
||||||
|
selLo = w->as.spinner.selStart < w->as.spinner.selEnd ? w->as.spinner.selStart : w->as.spinner.selEnd;
|
||||||
|
selHi = w->as.spinner.selStart > w->as.spinner.selEnd ? w->as.spinner.selStart : w->as.spinner.selEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw text with selection highlighting (3-run approach like textInput)
|
||||||
|
int32_t visSelLo = selLo - off;
|
||||||
|
int32_t visSelHi = selHi - off;
|
||||||
|
|
||||||
|
if (visSelLo < 0) {
|
||||||
|
visSelLo = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (visSelHi > len) {
|
||||||
|
visSelHi = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selLo >= 0 && visSelLo < visSelHi) {
|
||||||
|
// Before selection
|
||||||
|
if (visSelLo > 0) {
|
||||||
|
drawTextN(d, ops, font, textX, textY, &w->as.spinner.buf[off], visSelLo, fg, bg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection
|
||||||
|
drawTextN(d, ops, font, textX + visSelLo * font->charWidth, textY, &w->as.spinner.buf[off + visSelLo], visSelHi - visSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
||||||
|
|
||||||
|
// After selection
|
||||||
|
if (visSelHi < len) {
|
||||||
|
drawTextN(d, ops, font, textX + visSelHi * font->charWidth, textY, &w->as.spinner.buf[off + visSelHi], len - visSelHi, fg, bg, true);
|
||||||
|
}
|
||||||
|
} else if (len > 0) {
|
||||||
|
drawTextN(d, ops, font, textX, textY, &w->as.spinner.buf[off], len, fg, bg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cursor
|
||||||
|
if (w->focused) {
|
||||||
|
int32_t curX = textX + (w->as.spinner.cursorPos - off) * font->charWidth;
|
||||||
|
|
||||||
|
if (curX >= w->x + SPINNER_BORDER && curX < btnX - SPINNER_PAD) {
|
||||||
|
drawVLine(d, ops, curX, textY, font->charHeight, fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
// Up arrow triangle
|
||||||
|
{
|
||||||
|
int32_t cx = btnX + btnW / 2;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Down button (bottom half)
|
||||||
|
drawBevel(d, ops, btnX, w->y + btnTopH, btnW + SPINNER_BORDER, btnBotH, &btnBevel);
|
||||||
|
|
||||||
|
// Down arrow triangle
|
||||||
|
{
|
||||||
|
int32_t cx = btnX + btnW / 2;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Focus rect around entire widget
|
||||||
|
if (w->focused) {
|
||||||
|
drawFocusRect(d, ops, w->x + 1, w->y + 1, w->w - 2, w->h - 2, fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetSpinnerSetText
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetSpinnerSetText(WidgetT *w, const char *text) {
|
||||||
|
int32_t val = (int32_t)strtol(text, NULL, 10);
|
||||||
|
w->as.spinner.value = val;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtSpinner
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (!w || w->type != WidgetSpinnerE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return w->as.spinner.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtSpinnerSetRange
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtSpinnerSetRange(WidgetT *w, int32_t minVal, int32_t maxVal) {
|
||||||
|
if (!w || w->type != WidgetSpinnerE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.spinner.minValue = minVal;
|
||||||
|
w->as.spinner.maxValue = maxVal;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtSpinnerSetStep
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtSpinnerSetStep(WidgetT *w, int32_t step) {
|
||||||
|
if (!w || w->type != WidgetSpinnerE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.spinner.step = step > 0 ? step : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// wgtSpinnerSetValue
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void wgtSpinnerSetValue(WidgetT *w, int32_t value) {
|
||||||
|
if (!w || w->type != WidgetSpinnerE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.spinner.value = value;
|
||||||
|
spinnerClampAndFormat(w);
|
||||||
|
}
|
||||||
|
|
@ -424,6 +424,13 @@ static void setupControlsWindow(AppContextT *ctx) {
|
||||||
wgtLabel(page1, "&Volume:");
|
wgtLabel(page1, "&Volume:");
|
||||||
wgtSlider(page1, 0, 100);
|
wgtSlider(page1, 0, 100);
|
||||||
|
|
||||||
|
WidgetT *spinRow = wgtHBox(page1);
|
||||||
|
spinRow->maxH = wgtPixels(30);
|
||||||
|
wgtLabel(spinRow, "&Quantity:");
|
||||||
|
WidgetT *spin = wgtSpinner(spinRow, 0, 999, 1);
|
||||||
|
wgtSpinnerSetValue(spin, 42);
|
||||||
|
spin->weight = 50;
|
||||||
|
|
||||||
// --- Tab 2: Tree ---
|
// --- Tab 2: Tree ---
|
||||||
WidgetT *page2 = wgtTabPage(tabs, "&Tree");
|
WidgetT *page2 = wgtTabPage(tabs, "&Tree");
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue