Much improved text editing. More widget demos.
This commit is contained in:
parent
505b95ff8c
commit
fd41390085
25 changed files with 2558 additions and 218 deletions
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*.bmp filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.BMP filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <ctype.h>
|
#include <ctype.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <dpmi.h>
|
#include <dpmi.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#define DBLCLICK_THRESHOLD (CLOCKS_PER_SEC / 2)
|
#define DBLCLICK_THRESHOLD (CLOCKS_PER_SEC / 2)
|
||||||
#define ICON_REFRESH_INTERVAL 8
|
#define ICON_REFRESH_INTERVAL 8
|
||||||
|
|
@ -741,6 +742,9 @@ const BitmapFontT *dvxGetFont(const AppContextT *ctx) {
|
||||||
int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
int32_t dvxInit(AppContextT *ctx, int32_t requestedW, int32_t requestedH, int32_t preferredBpp) {
|
||||||
memset(ctx, 0, sizeof(*ctx));
|
memset(ctx, 0, sizeof(*ctx));
|
||||||
|
|
||||||
|
// Disable Ctrl+C/Break termination
|
||||||
|
signal(SIGINT, SIG_IGN);
|
||||||
|
|
||||||
// Initialize video
|
// Initialize video
|
||||||
if (videoInit(&ctx->display, requestedW, requestedH, preferredBpp) != 0) {
|
if (videoInit(&ctx->display, requestedW, requestedH, preferredBpp) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
|
|
@ -1935,7 +1939,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
if (termFocused) {
|
if (termFocused) {
|
||||||
// Terminal has focus — send Tab to it
|
// Terminal has focus — send Tab to it
|
||||||
if (win->onKey) {
|
if (win->onKey) {
|
||||||
win->onKey(win, ascii ? ascii : (scancode | 0x100), 0);
|
win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags);
|
||||||
}
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1994,7 +1998,7 @@ static void pollKeyboard(AppContextT *ctx) {
|
||||||
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
WindowT *win = ctx->stack.windows[ctx->stack.focusedIdx];
|
||||||
|
|
||||||
if (win->onKey) {
|
if (win->onKey) {
|
||||||
win->onKey(win, ascii ? ascii : (scancode | 0x100), 0);
|
win->onKey(win, ascii ? ascii : (scancode | 0x100), shiftFlags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,6 +182,11 @@ typedef struct WidgetT {
|
||||||
int32_t len;
|
int32_t len;
|
||||||
int32_t cursorPos;
|
int32_t cursorPos;
|
||||||
int32_t scrollOff;
|
int32_t scrollOff;
|
||||||
|
int32_t selStart; // selection anchor (-1 = none)
|
||||||
|
int32_t selEnd; // selection end (-1 = none)
|
||||||
|
char *undoBuf;
|
||||||
|
int32_t undoLen;
|
||||||
|
int32_t undoCursor;
|
||||||
} textInput;
|
} textInput;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -191,6 +196,13 @@ typedef struct WidgetT {
|
||||||
int32_t cursorRow;
|
int32_t cursorRow;
|
||||||
int32_t cursorCol;
|
int32_t cursorCol;
|
||||||
int32_t scrollRow;
|
int32_t scrollRow;
|
||||||
|
int32_t scrollCol;
|
||||||
|
int32_t desiredCol; // sticky column for up/down movement
|
||||||
|
int32_t selAnchor; // selection anchor byte offset (-1 = none)
|
||||||
|
int32_t selCursor; // selection cursor byte offset (-1 = none)
|
||||||
|
char *undoBuf;
|
||||||
|
int32_t undoLen;
|
||||||
|
int32_t undoCursor; // byte offset at time of snapshot
|
||||||
} textArea;
|
} textArea;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|
@ -225,6 +237,11 @@ typedef struct WidgetT {
|
||||||
int32_t len;
|
int32_t len;
|
||||||
int32_t cursorPos;
|
int32_t cursorPos;
|
||||||
int32_t scrollOff;
|
int32_t scrollOff;
|
||||||
|
int32_t selStart; // selection anchor (-1 = none)
|
||||||
|
int32_t selEnd; // selection end (-1 = none)
|
||||||
|
char *undoBuf;
|
||||||
|
int32_t undoLen;
|
||||||
|
int32_t undoCursor;
|
||||||
const char **items;
|
const char **items;
|
||||||
int32_t itemCount;
|
int32_t itemCount;
|
||||||
int32_t selectedIdx;
|
int32_t selectedIdx;
|
||||||
|
|
@ -330,6 +347,12 @@ typedef struct WidgetT {
|
||||||
// Cached packed palette (avoids packColor per repaint)
|
// Cached packed palette (avoids packColor per repaint)
|
||||||
uint32_t packedPalette[16];
|
uint32_t packedPalette[16];
|
||||||
bool paletteValid;
|
bool paletteValid;
|
||||||
|
// Selection (line indices in scrollback+screen space)
|
||||||
|
int32_t selStartLine;
|
||||||
|
int32_t selStartCol;
|
||||||
|
int32_t selEndLine;
|
||||||
|
int32_t selEndCol;
|
||||||
|
bool selecting;
|
||||||
// Communications interface (all NULL = disconnected)
|
// Communications interface (all NULL = disconnected)
|
||||||
void *commCtx;
|
void *commCtx;
|
||||||
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
|
int32_t (*commRead)(void *ctx, uint8_t *buf, int32_t maxLen);
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ static const int32_t sAnsiToCga[8] = { 0, 4, 2, 6, 1, 5, 3, 7 };
|
||||||
|
|
||||||
static void ansiTermAddToScrollback(WidgetT *w, int32_t screenRow);
|
static void ansiTermAddToScrollback(WidgetT *w, int32_t screenRow);
|
||||||
static void ansiTermBuildPalette(WidgetT *w, const DisplayT *d);
|
static void ansiTermBuildPalette(WidgetT *w, const DisplayT *d);
|
||||||
|
static void ansiTermClearSelection(WidgetT *w);
|
||||||
|
static void ansiTermCopySelection(WidgetT *w);
|
||||||
static void ansiTermDeleteLines(WidgetT *w, int32_t count);
|
static void ansiTermDeleteLines(WidgetT *w, int32_t count);
|
||||||
static void ansiTermDirtyRange(WidgetT *w, int32_t startCell, int32_t count);
|
static void ansiTermDirtyRange(WidgetT *w, int32_t startCell, int32_t count);
|
||||||
static void ansiTermDirtyRow(WidgetT *w, int32_t row);
|
static void ansiTermDirtyRow(WidgetT *w, int32_t row);
|
||||||
|
|
@ -69,13 +71,17 @@ static void ansiTermEraseDisplay(WidgetT *w, int32_t mode);
|
||||||
static void ansiTermEraseLine(WidgetT *w, int32_t mode);
|
static void ansiTermEraseLine(WidgetT *w, int32_t mode);
|
||||||
static void ansiTermFillCells(WidgetT *w, int32_t start, int32_t count);
|
static void ansiTermFillCells(WidgetT *w, int32_t start, int32_t count);
|
||||||
static const uint8_t *ansiTermGetLine(WidgetT *w, int32_t lineIndex);
|
static const uint8_t *ansiTermGetLine(WidgetT *w, int32_t lineIndex);
|
||||||
|
static bool ansiTermHasSelection(const WidgetT *w);
|
||||||
static void ansiTermInsertLines(WidgetT *w, int32_t count);
|
static void ansiTermInsertLines(WidgetT *w, int32_t count);
|
||||||
static void ansiTermNewline(WidgetT *w);
|
static void ansiTermNewline(WidgetT *w);
|
||||||
|
static void ansiTermPaintSelRow(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t screenRow, int32_t baseX, int32_t baseY);
|
||||||
|
static void ansiTermPasteToComm(WidgetT *w);
|
||||||
static void ansiTermProcessByte(WidgetT *w, uint8_t ch);
|
static void ansiTermProcessByte(WidgetT *w, uint8_t ch);
|
||||||
static void ansiTermProcessSgr(WidgetT *w);
|
static void ansiTermProcessSgr(WidgetT *w);
|
||||||
static void ansiTermPutChar(WidgetT *w, uint8_t ch);
|
static void ansiTermPutChar(WidgetT *w, uint8_t ch);
|
||||||
static void ansiTermScrollDown(WidgetT *w);
|
static void ansiTermScrollDown(WidgetT *w);
|
||||||
static void ansiTermScrollUp(WidgetT *w);
|
static void ansiTermScrollUp(WidgetT *w);
|
||||||
|
static void ansiTermSelectionRange(const WidgetT *w, int32_t *startLine, int32_t *startCol, int32_t *endLine, int32_t *endCol);
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -126,6 +132,77 @@ static void ansiTermBuildPalette(WidgetT *w, const DisplayT *d) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermClearSelection
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void ansiTermClearSelection(WidgetT *w) {
|
||||||
|
if (ansiTermHasSelection(w)) {
|
||||||
|
w->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.ansiTerm.selStartLine = -1;
|
||||||
|
w->as.ansiTerm.selStartCol = -1;
|
||||||
|
w->as.ansiTerm.selEndLine = -1;
|
||||||
|
w->as.ansiTerm.selEndCol = -1;
|
||||||
|
w->as.ansiTerm.selecting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermCopySelection
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void ansiTermCopySelection(WidgetT *w) {
|
||||||
|
if (!ansiTermHasSelection(w)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t sLine;
|
||||||
|
int32_t sCol;
|
||||||
|
int32_t eLine;
|
||||||
|
int32_t eCol;
|
||||||
|
ansiTermSelectionRange(w, &sLine, &sCol, &eLine, &eCol);
|
||||||
|
|
||||||
|
int32_t cols = w->as.ansiTerm.cols;
|
||||||
|
|
||||||
|
// Build text from selected cells (strip trailing spaces per line)
|
||||||
|
char buf[4096];
|
||||||
|
int32_t pos = 0;
|
||||||
|
|
||||||
|
for (int32_t line = sLine; line <= eLine && pos < 4095; line++) {
|
||||||
|
const uint8_t *lineData = ansiTermGetLine(w, line);
|
||||||
|
|
||||||
|
int32_t colStart = (line == sLine) ? sCol : 0;
|
||||||
|
int32_t colEnd = (line == eLine) ? eCol : cols;
|
||||||
|
|
||||||
|
// Find last non-space character in this line's selection
|
||||||
|
int32_t lastNonSpace = colStart - 1;
|
||||||
|
|
||||||
|
for (int32_t c = colStart; c < colEnd; c++) {
|
||||||
|
if (lineData[c * 2] != ' ') {
|
||||||
|
lastNonSpace = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t c = colStart; c <= lastNonSpace && pos < 4095; c++) {
|
||||||
|
buf[pos++] = (char)lineData[c * 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add newline between lines (not after last)
|
||||||
|
if (line < eLine && pos < 4095) {
|
||||||
|
buf[pos++] = '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buf[pos] = '\0';
|
||||||
|
|
||||||
|
if (pos > 0) {
|
||||||
|
clipboardCopy(buf, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ansiTermDirtyRange
|
// ansiTermDirtyRange
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -643,6 +720,24 @@ static const uint8_t *ansiTermGetLine(WidgetT *w, int32_t lineIndex) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermHasSelection
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static bool ansiTermHasSelection(const WidgetT *w) {
|
||||||
|
if (w->as.ansiTerm.selStartLine < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (w->as.ansiTerm.selStartLine == w->as.ansiTerm.selEndLine &&
|
||||||
|
w->as.ansiTerm.selStartCol == w->as.ansiTerm.selEndCol) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ansiTermInsertLines
|
// ansiTermInsertLines
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -866,6 +961,35 @@ static void ansiTermProcessSgr(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermPasteToComm
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static void ansiTermPasteToComm(WidgetT *w) {
|
||||||
|
if (!w->as.ansiTerm.commWrite) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t clipLen;
|
||||||
|
const char *clip = clipboardGet(&clipLen);
|
||||||
|
|
||||||
|
if (clipLen <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transmit clipboard contents, converting \n to \r for the terminal
|
||||||
|
for (int32_t i = 0; i < clipLen; i++) {
|
||||||
|
uint8_t ch = (uint8_t)clip[i];
|
||||||
|
|
||||||
|
if (ch == '\n') {
|
||||||
|
ch = '\r';
|
||||||
|
}
|
||||||
|
|
||||||
|
w->as.ansiTerm.commWrite(w->as.ansiTerm.commCtx, &ch, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ansiTermPutChar
|
// ansiTermPutChar
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -967,6 +1091,32 @@ static void ansiTermScrollUp(WidgetT *w) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermSelectionRange
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Return selection start/end in normalized order (start <= end).
|
||||||
|
|
||||||
|
static void ansiTermSelectionRange(const WidgetT *w, int32_t *startLine, int32_t *startCol, int32_t *endLine, int32_t *endCol) {
|
||||||
|
int32_t sl = w->as.ansiTerm.selStartLine;
|
||||||
|
int32_t sc = w->as.ansiTerm.selStartCol;
|
||||||
|
int32_t el = w->as.ansiTerm.selEndLine;
|
||||||
|
int32_t ec = w->as.ansiTerm.selEndCol;
|
||||||
|
|
||||||
|
if (sl > el || (sl == el && sc > ec)) {
|
||||||
|
*startLine = el;
|
||||||
|
*startCol = ec;
|
||||||
|
*endLine = sl;
|
||||||
|
*endCol = sc;
|
||||||
|
} else {
|
||||||
|
*startLine = sl;
|
||||||
|
*startCol = sc;
|
||||||
|
*endLine = el;
|
||||||
|
*endCol = ec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// wgtAnsiTerm
|
// wgtAnsiTerm
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1031,6 +1181,11 @@ WidgetT *wgtAnsiTerm(WidgetT *parent, int32_t cols, int32_t rows) {
|
||||||
w->as.ansiTerm.commCtx = NULL;
|
w->as.ansiTerm.commCtx = NULL;
|
||||||
w->as.ansiTerm.commRead = NULL;
|
w->as.ansiTerm.commRead = NULL;
|
||||||
w->as.ansiTerm.commWrite = NULL;
|
w->as.ansiTerm.commWrite = NULL;
|
||||||
|
w->as.ansiTerm.selStartLine = -1;
|
||||||
|
w->as.ansiTerm.selStartCol = -1;
|
||||||
|
w->as.ansiTerm.selEndLine = -1;
|
||||||
|
w->as.ansiTerm.selEndCol = -1;
|
||||||
|
w->as.ansiTerm.selecting = false;
|
||||||
w->as.ansiTerm.blinkVisible = true;
|
w->as.ansiTerm.blinkVisible = true;
|
||||||
w->as.ansiTerm.blinkTime = clock();
|
w->as.ansiTerm.blinkTime = clock();
|
||||||
w->as.ansiTerm.cursorOn = true;
|
w->as.ansiTerm.cursorOn = true;
|
||||||
|
|
@ -1267,6 +1422,7 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH) {
|
||||||
}
|
}
|
||||||
|
|
||||||
drawTermRow(&cd, ops, font, baseX, baseY + row * cellH, cols, lineData, palette, w->as.ansiTerm.blinkVisible, curCol2);
|
drawTermRow(&cd, ops, font, baseX, baseY + row * cellH, cols, lineData, palette, w->as.ansiTerm.blinkVisible, curCol2);
|
||||||
|
ansiTermPaintSelRow(w, &cd, ops, font, row, baseX, baseY);
|
||||||
|
|
||||||
if (row < minRow) { minRow = row; }
|
if (row < minRow) { minRow = row; }
|
||||||
if (row > maxRow) { maxRow = row; }
|
if (row > maxRow) { maxRow = row; }
|
||||||
|
|
@ -1308,6 +1464,8 @@ void wgtAnsiTermWrite(WidgetT *w, const uint8_t *data, int32_t len) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ansiTermClearSelection(w);
|
||||||
|
|
||||||
for (int32_t i = 0; i < len; i++) {
|
for (int32_t i = 0; i < len; i++) {
|
||||||
ansiTermProcessByte(w, data[i]);
|
ansiTermProcessByte(w, data[i]);
|
||||||
}
|
}
|
||||||
|
|
@ -1334,6 +1492,54 @@ void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// ansiTermPaintSelRow
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Overlay selection highlighting for a single terminal row.
|
||||||
|
// Inverts fg/bg for selected cells.
|
||||||
|
|
||||||
|
static void ansiTermPaintSelRow(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t screenRow, int32_t baseX, int32_t baseY) {
|
||||||
|
if (!ansiTermHasSelection(w)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t sLine;
|
||||||
|
int32_t sCol;
|
||||||
|
int32_t eLine;
|
||||||
|
int32_t eCol;
|
||||||
|
ansiTermSelectionRange(w, &sLine, &sCol, &eLine, &eCol);
|
||||||
|
|
||||||
|
int32_t cols = w->as.ansiTerm.cols;
|
||||||
|
int32_t lineIndex = w->as.ansiTerm.scrollPos + screenRow;
|
||||||
|
|
||||||
|
if (lineIndex < sLine || lineIndex > eLine) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t colStart = (lineIndex == sLine) ? sCol : 0;
|
||||||
|
int32_t colEnd = (lineIndex == eLine) ? eCol : cols;
|
||||||
|
|
||||||
|
if (colStart >= colEnd) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t *lineData = ansiTermGetLine(w, lineIndex);
|
||||||
|
const uint32_t *palette = w->as.ansiTerm.packedPalette;
|
||||||
|
int32_t cellH = font->charHeight;
|
||||||
|
int32_t cellW = font->charWidth;
|
||||||
|
|
||||||
|
for (int32_t col = colStart; col < colEnd; col++) {
|
||||||
|
uint8_t ch = lineData[col * 2];
|
||||||
|
uint8_t attr = lineData[col * 2 + 1];
|
||||||
|
uint32_t fg = palette[(attr >> 4) & 0x07]; // swap: bg becomes fg
|
||||||
|
uint32_t bg = palette[attr & 0x0F]; // swap: fg becomes bg
|
||||||
|
|
||||||
|
drawChar(d, ops, font, baseX + col * cellW, baseY + screenRow * cellH, ch, fg, bg, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetAnsiTermOnKey
|
// widgetAnsiTermOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1341,11 +1547,35 @@ void widgetAnsiTermCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// Translate keyboard input to ANSI escape sequences and send
|
// Translate keyboard input to ANSI escape sequences and send
|
||||||
// via the comm interface. Does nothing if commWrite is NULL.
|
// via the comm interface. Does nothing if commWrite is NULL.
|
||||||
|
|
||||||
void widgetAnsiTermOnKey(WidgetT *w, int32_t key) {
|
void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
// Ctrl+C: copy if selection exists, otherwise send ^C
|
||||||
|
if (key == 0x03 && (mod & KEY_MOD_CTRL)) {
|
||||||
|
if (ansiTermHasSelection(w)) {
|
||||||
|
ansiTermCopySelection(w);
|
||||||
|
ansiTermClearSelection(w);
|
||||||
|
wgtInvalidate(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No selection — fall through to send ^C to terminal
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ctrl+V: paste from clipboard to terminal
|
||||||
|
if (key == 0x16 && (mod & KEY_MOD_CTRL)) {
|
||||||
|
ansiTermPasteToComm(w);
|
||||||
|
wgtInvalidate(w);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!w->as.ansiTerm.commWrite) {
|
if (!w->as.ansiTerm.commWrite) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any keypress clears selection
|
||||||
|
if (ansiTermHasSelection(w)) {
|
||||||
|
ansiTermClearSelection(w);
|
||||||
|
}
|
||||||
|
|
||||||
uint8_t buf[8];
|
uint8_t buf[8];
|
||||||
int32_t len = 0;
|
int32_t len = 0;
|
||||||
|
|
||||||
|
|
@ -1405,6 +1635,10 @@ void widgetAnsiTermOnKey(WidgetT *w, int32_t key) {
|
||||||
// Delete → ESC[3~
|
// Delete → ESC[3~
|
||||||
buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~';
|
buf[0] = 0x1B; buf[1] = '['; buf[2] = '3'; buf[3] = '~';
|
||||||
len = 4;
|
len = 4;
|
||||||
|
} else if (key >= 1 && key < 32) {
|
||||||
|
// Control characters (^A=1, ^B=2, ^C=3, etc.)
|
||||||
|
buf[0] = (uint8_t)key;
|
||||||
|
len = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (len > 0) {
|
if (len > 0) {
|
||||||
|
|
@ -1425,14 +1659,8 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
AppContextT *actx = (AppContextT *)root->userData;
|
AppContextT *actx = (AppContextT *)root->userData;
|
||||||
const BitmapFontT *font = &actx->font;
|
const BitmapFontT *font = &actx->font;
|
||||||
hit->focused = true;
|
hit->focused = true;
|
||||||
|
clearOtherSelections(hit);
|
||||||
|
|
||||||
int32_t sbCount = hit->as.ansiTerm.scrollbackCount;
|
|
||||||
|
|
||||||
if (sbCount == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scrollbar geometry
|
|
||||||
int32_t cols = hit->as.ansiTerm.cols;
|
int32_t cols = hit->as.ansiTerm.cols;
|
||||||
int32_t rows = hit->as.ansiTerm.rows;
|
int32_t rows = hit->as.ansiTerm.rows;
|
||||||
int32_t sbX = hit->x + ANSI_BORDER + cols * font->charWidth;
|
int32_t sbX = hit->x + ANSI_BORDER + cols * font->charWidth;
|
||||||
|
|
@ -1440,7 +1668,78 @@ void widgetAnsiTermOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy)
|
||||||
int32_t sbH = rows * font->charHeight;
|
int32_t sbH = rows * font->charHeight;
|
||||||
int32_t arrowH = ANSI_SB_W;
|
int32_t arrowH = ANSI_SB_W;
|
||||||
|
|
||||||
if (vx < sbX || vx >= sbX + ANSI_SB_W) {
|
// Click in text area — start selection
|
||||||
|
if (vx < sbX) {
|
||||||
|
int32_t baseX = hit->x + ANSI_BORDER;
|
||||||
|
int32_t baseY = hit->y + ANSI_BORDER;
|
||||||
|
int32_t clickRow = (vy - baseY) / font->charHeight;
|
||||||
|
int32_t clickCol = (vx - baseX) / font->charWidth;
|
||||||
|
|
||||||
|
if (clickRow < 0) {
|
||||||
|
clickRow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickRow >= rows) {
|
||||||
|
clickRow = rows - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickCol < 0) {
|
||||||
|
clickCol = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickCol >= cols) {
|
||||||
|
clickCol = cols - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t lineIndex = hit->as.ansiTerm.scrollPos + clickRow;
|
||||||
|
|
||||||
|
int32_t clicks = multiClickDetect(vx, vy);
|
||||||
|
|
||||||
|
if (clicks >= 3) {
|
||||||
|
// Triple-click: select entire line
|
||||||
|
hit->as.ansiTerm.selStartLine = lineIndex;
|
||||||
|
hit->as.ansiTerm.selStartCol = 0;
|
||||||
|
hit->as.ansiTerm.selEndLine = lineIndex;
|
||||||
|
hit->as.ansiTerm.selEndCol = cols;
|
||||||
|
hit->as.ansiTerm.selecting = false;
|
||||||
|
sDragTextSelect = NULL;
|
||||||
|
} else if (clicks == 2) {
|
||||||
|
// Double-click: select word
|
||||||
|
const uint8_t *lineData = ansiTermGetLine(hit, lineIndex);
|
||||||
|
int32_t ws = clickCol;
|
||||||
|
int32_t we = clickCol;
|
||||||
|
|
||||||
|
while (ws > 0 && isWordChar((char)lineData[(ws - 1) * 2])) {
|
||||||
|
ws--;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (we < cols && isWordChar((char)lineData[we * 2])) {
|
||||||
|
we++;
|
||||||
|
}
|
||||||
|
|
||||||
|
hit->as.ansiTerm.selStartLine = lineIndex;
|
||||||
|
hit->as.ansiTerm.selStartCol = ws;
|
||||||
|
hit->as.ansiTerm.selEndLine = lineIndex;
|
||||||
|
hit->as.ansiTerm.selEndCol = we;
|
||||||
|
hit->as.ansiTerm.selecting = false;
|
||||||
|
sDragTextSelect = NULL;
|
||||||
|
} else {
|
||||||
|
// Single click: start selection anchor
|
||||||
|
hit->as.ansiTerm.selStartLine = lineIndex;
|
||||||
|
hit->as.ansiTerm.selStartCol = clickCol;
|
||||||
|
hit->as.ansiTerm.selEndLine = lineIndex;
|
||||||
|
hit->as.ansiTerm.selEndCol = clickCol;
|
||||||
|
hit->as.ansiTerm.selecting = true;
|
||||||
|
sDragTextSelect = hit;
|
||||||
|
}
|
||||||
|
|
||||||
|
hit->as.ansiTerm.dirtyRows = 0xFFFFFFFF;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t sbCount = hit->as.ansiTerm.scrollbackCount;
|
||||||
|
|
||||||
|
if (sbCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1536,6 +1835,7 @@ void widgetAnsiTermPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
}
|
}
|
||||||
|
|
||||||
drawTermRow(d, ops, font, baseX, baseY + row * cellH, cols, lineData, palette, w->as.ansiTerm.blinkVisible, curCol);
|
drawTermRow(d, ops, font, baseX, baseY + row * cellH, cols, lineData, palette, w->as.ansiTerm.blinkVisible, curCol);
|
||||||
|
ansiTermPaintSelRow(w, d, ops, font, row, baseX, baseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw scrollbar
|
// Draw scrollbar
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,9 @@ void widgetButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetButtonOnKey
|
// widgetButtonOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetButtonOnKey(WidgetT *w, int32_t key) {
|
void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
if (key == ' ' || key == 0x0D) {
|
if (key == ' ' || key == 0x0D) {
|
||||||
w->as.button.pressed = true;
|
w->as.button.pressed = true;
|
||||||
sKeyPressedBtn = w;
|
sKeyPressedBtn = w;
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,9 @@ void widgetCheckboxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetCheckboxOnKey
|
// widgetCheckboxOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetCheckboxOnKey(WidgetT *w, int32_t key) {
|
void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
if (key == ' ' || key == 0x0D) {
|
if (key == ' ' || key == 0x0D) {
|
||||||
w->as.checkbox.checked = !w->as.checkbox.checked;
|
w->as.checkbox.checked = !w->as.checkbox.checked;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -111,16 +111,16 @@ static const WidgetClassT sClassTextInput = {
|
||||||
};
|
};
|
||||||
|
|
||||||
static const WidgetClassT sClassTextArea = {
|
static const WidgetClassT sClassTextArea = {
|
||||||
.flags = 0,
|
.flags = WCLASS_FOCUSABLE,
|
||||||
.paint = NULL,
|
.paint = widgetTextAreaPaint,
|
||||||
.paintOverlay = NULL,
|
.paintOverlay = NULL,
|
||||||
.calcMinSize = NULL,
|
.calcMinSize = widgetTextAreaCalcMinSize,
|
||||||
.layout = NULL,
|
.layout = NULL,
|
||||||
.onMouse = NULL,
|
.onMouse = widgetTextAreaOnMouse,
|
||||||
.onKey = NULL,
|
.onKey = widgetTextAreaOnKey,
|
||||||
.destroy = widgetTextAreaDestroy,
|
.destroy = widgetTextAreaDestroy,
|
||||||
.getText = NULL,
|
.getText = widgetTextAreaGetText,
|
||||||
.setText = NULL
|
.setText = widgetTextAreaSetText
|
||||||
};
|
};
|
||||||
|
|
||||||
static const WidgetClassT sClassListBox = {
|
static const WidgetClassT sClassListBox = {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,9 @@ WidgetT *wgtComboBox(WidgetT *parent, int32_t maxLen) {
|
||||||
w->as.comboBox.buf[0] = '\0';
|
w->as.comboBox.buf[0] = '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
w->as.comboBox.undoBuf = (char *)malloc(bufSize);
|
||||||
|
w->as.comboBox.selStart = -1;
|
||||||
|
w->as.comboBox.selEnd = -1;
|
||||||
w->as.comboBox.selectedIdx = -1;
|
w->as.comboBox.selectedIdx = -1;
|
||||||
w->weight = 100;
|
w->weight = 100;
|
||||||
}
|
}
|
||||||
|
|
@ -76,39 +79,8 @@ void wgtComboBoxSetSelected(WidgetT *w, int32_t idx) {
|
||||||
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
||||||
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
||||||
w->as.comboBox.scrollOff = 0;
|
w->as.comboBox.scrollOff = 0;
|
||||||
}
|
w->as.comboBox.selStart = -1;
|
||||||
}
|
w->as.comboBox.selEnd = -1;
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -133,11 +105,30 @@ void widgetComboBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetComboBoxDestroy
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void widgetComboBoxDestroy(WidgetT *w) {
|
||||||
|
free(w->as.comboBox.buf);
|
||||||
|
free(w->as.comboBox.undoBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// widgetComboBoxGetText
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
const char *widgetComboBoxGetText(const WidgetT *w) {
|
||||||
|
return w->as.comboBox.buf ? w->as.comboBox.buf : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// widgetComboBoxOnKey
|
// widgetComboBoxOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetComboBoxOnKey(WidgetT *w, int32_t key) {
|
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
if (w->as.comboBox.open) {
|
if (w->as.comboBox.open) {
|
||||||
if (key == (0x48 | 0x100)) {
|
if (key == (0x48 | 0x100)) {
|
||||||
if (w->as.comboBox.hoverIdx > 0) {
|
if (w->as.comboBox.hoverIdx > 0) {
|
||||||
|
|
@ -177,6 +168,8 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key) {
|
||||||
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
w->as.comboBox.len = (int32_t)strlen(w->as.comboBox.buf);
|
||||||
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
||||||
w->as.comboBox.scrollOff = 0;
|
w->as.comboBox.scrollOff = 0;
|
||||||
|
w->as.comboBox.selStart = -1;
|
||||||
|
w->as.comboBox.selEnd = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
w->as.comboBox.open = false;
|
w->as.comboBox.open = false;
|
||||||
|
|
@ -214,9 +207,14 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
widgetTextEditOnKey(w, key, w->as.comboBox.buf, w->as.comboBox.bufSize,
|
clearOtherSelections(w);
|
||||||
|
|
||||||
|
widgetTextEditOnKey(w, key, mod, w->as.comboBox.buf, w->as.comboBox.bufSize,
|
||||||
&w->as.comboBox.len, &w->as.comboBox.cursorPos,
|
&w->as.comboBox.len, &w->as.comboBox.cursorPos,
|
||||||
&w->as.comboBox.scrollOff);
|
&w->as.comboBox.scrollOff,
|
||||||
|
&w->as.comboBox.selStart, &w->as.comboBox.selEnd,
|
||||||
|
w->as.comboBox.undoBuf, &w->as.comboBox.undoLen,
|
||||||
|
&w->as.comboBox.undoCursor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -225,7 +223,6 @@ void widgetComboBoxOnKey(WidgetT *w, int32_t key) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
(void)vy;
|
|
||||||
w->focused = true;
|
w->focused = true;
|
||||||
|
|
||||||
// Check if click is on the button area
|
// Check if click is on the button area
|
||||||
|
|
@ -243,6 +240,8 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
sOpenPopup = w->as.comboBox.open ? w : NULL;
|
sOpenPopup = w->as.comboBox.open ? w : NULL;
|
||||||
} else {
|
} else {
|
||||||
// Text area click — focus for editing
|
// Text area click — focus for editing
|
||||||
|
clearOtherSelections(w);
|
||||||
|
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
const BitmapFontT *font = &ctx->font;
|
const BitmapFontT *font = &ctx->font;
|
||||||
int32_t relX = vx - w->x - TEXT_INPUT_PAD;
|
int32_t relX = vx - w->x - TEXT_INPUT_PAD;
|
||||||
|
|
@ -256,7 +255,34 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
charPos = w->as.comboBox.len;
|
charPos = w->as.comboBox.len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32_t clicks = multiClickDetect(vx, vy);
|
||||||
|
|
||||||
|
if (clicks >= 3) {
|
||||||
|
// Triple-click: select all (single line)
|
||||||
|
w->as.comboBox.selStart = 0;
|
||||||
|
w->as.comboBox.selEnd = w->as.comboBox.len;
|
||||||
|
w->as.comboBox.cursorPos = w->as.comboBox.len;
|
||||||
|
sDragTextSelect = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clicks == 2 && w->as.comboBox.buf) {
|
||||||
|
// Double-click: select word
|
||||||
|
int32_t ws = wordStart(w->as.comboBox.buf, charPos);
|
||||||
|
int32_t we = wordEnd(w->as.comboBox.buf, w->as.comboBox.len, charPos);
|
||||||
|
|
||||||
|
w->as.comboBox.selStart = ws;
|
||||||
|
w->as.comboBox.selEnd = we;
|
||||||
|
w->as.comboBox.cursorPos = we;
|
||||||
|
sDragTextSelect = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single click: place cursor + start drag-select
|
||||||
w->as.comboBox.cursorPos = charPos;
|
w->as.comboBox.cursorPos = charPos;
|
||||||
|
w->as.comboBox.selStart = charPos;
|
||||||
|
w->as.comboBox.selEnd = charPos;
|
||||||
|
sDragTextSelect = w;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -290,9 +316,27 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
len = maxChars;
|
len = maxChars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Selection range
|
||||||
|
int32_t selLo = -1;
|
||||||
|
int32_t selHi = -1;
|
||||||
|
|
||||||
|
if (w->as.comboBox.selStart >= 0 && w->as.comboBox.selEnd >= 0 && w->as.comboBox.selStart != w->as.comboBox.selEnd) {
|
||||||
|
selLo = w->as.comboBox.selStart < w->as.comboBox.selEnd ? w->as.comboBox.selStart : w->as.comboBox.selEnd;
|
||||||
|
selHi = w->as.comboBox.selStart < w->as.comboBox.selEnd ? w->as.comboBox.selEnd : w->as.comboBox.selStart;
|
||||||
|
}
|
||||||
|
|
||||||
for (int32_t i = 0; i < len; i++) {
|
for (int32_t i = 0; i < len; i++) {
|
||||||
|
int32_t charIdx = off + i;
|
||||||
|
uint32_t cfgc = fg;
|
||||||
|
uint32_t cbgc = bg;
|
||||||
|
|
||||||
|
if (selLo >= 0 && charIdx >= selLo && charIdx < selHi) {
|
||||||
|
cfgc = colors->menuHighlightFg;
|
||||||
|
cbgc = colors->menuHighlightBg;
|
||||||
|
}
|
||||||
|
|
||||||
drawChar(d, ops, font, textX + i * font->charWidth, textY,
|
drawChar(d, ops, font, textX + i * font->charWidth, textY,
|
||||||
w->as.comboBox.buf[off + i], fg, bg, true);
|
w->as.comboBox.buf[charIdx], cfgc, cbgc, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw cursor
|
// Draw cursor
|
||||||
|
|
@ -370,3 +414,20 @@ void widgetComboBoxPaintPopup(WidgetT *w, DisplayT *d, const BlitOpsT *ops, cons
|
||||||
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, idx == hoverIdx);
|
drawText(d, ops, font, textX, iy, items[idx], ifg, ibg, idx == hoverIdx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// 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;
|
||||||
|
w->as.comboBox.selStart = -1;
|
||||||
|
w->as.comboBox.selEnd = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ WidgetT *sOpenPopup = NULL;
|
||||||
WidgetT *sPressedButton = NULL;
|
WidgetT *sPressedButton = NULL;
|
||||||
WidgetT *sDragSlider = NULL;
|
WidgetT *sDragSlider = NULL;
|
||||||
WidgetT *sDrawingCanvas = NULL;
|
WidgetT *sDrawingCanvas = NULL;
|
||||||
|
WidgetT *sDragTextSelect = NULL;
|
||||||
int32_t sDragOffset = 0;
|
int32_t sDragOffset = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,9 @@ void widgetDropdownCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetDropdownOnKey
|
// widgetDropdownOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetDropdownOnKey(WidgetT *w, int32_t key) {
|
void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
if (w->as.dropdown.open) {
|
if (w->as.dropdown.open) {
|
||||||
// Popup is open — navigate items
|
// Popup is open — navigate items
|
||||||
if (key == (0x48 | 0x100)) {
|
if (key == (0x48 | 0x100)) {
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,6 @@ void widgetManageScrollbars(WindowT *win, AppContextT *ctx) {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
(void)mod;
|
|
||||||
WidgetT *root = win->widgetRoot;
|
WidgetT *root = win->widgetRoot;
|
||||||
|
|
||||||
if (!root) {
|
if (!root) {
|
||||||
|
|
@ -143,7 +142,7 @@ void widgetOnKey(WindowT *win, int32_t key, int32_t mod) {
|
||||||
|
|
||||||
// Dispatch to per-widget onKey handler via vtable
|
// Dispatch to per-widget onKey handler via vtable
|
||||||
if (focus->wclass && focus->wclass->onKey) {
|
if (focus->wclass && focus->wclass->onKey) {
|
||||||
focus->wclass->onKey(focus, key);
|
focus->wclass->onKey(focus, key, mod);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -171,6 +170,23 @@ void widgetOnMouse(WindowT *win, int32_t x, int32_t y, int32_t buttons) {
|
||||||
sOpenPopup = NULL;
|
sOpenPopup = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle text drag-select release
|
||||||
|
if (sDragTextSelect && !(buttons & 1)) {
|
||||||
|
sDragTextSelect = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle text drag-select (mouse move while pressed)
|
||||||
|
if (sDragTextSelect && (buttons & 1)) {
|
||||||
|
int32_t scrollX = win->hScroll ? win->hScroll->value : 0;
|
||||||
|
int32_t scrollY = win->vScroll ? win->vScroll->value : 0;
|
||||||
|
int32_t vx = x + scrollX;
|
||||||
|
int32_t vy = y + scrollY;
|
||||||
|
widgetTextDragUpdate(sDragTextSelect, root, vx, vy);
|
||||||
|
wgtInvalidate(root);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle canvas drawing release
|
// Handle canvas drawing release
|
||||||
if (sDrawingCanvas && !(buttons & 1)) {
|
if (sDrawingCanvas && !(buttons & 1)) {
|
||||||
sDrawingCanvas->as.canvas.lastX = -1;
|
sDrawingCanvas->as.canvas.lastX = -1;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,9 @@ void widgetImageButtonCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetImageButtonOnKey
|
// widgetImageButtonOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetImageButtonOnKey(WidgetT *w, int32_t key) {
|
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
if (key == ' ' || key == 0x0D) {
|
if (key == ' ' || key == 0x0D) {
|
||||||
w->as.imageButton.pressed = true;
|
w->as.imageButton.pressed = true;
|
||||||
sKeyPressedBtn = w;
|
sKeyPressedBtn = w;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ typedef struct WidgetClassT {
|
||||||
void (*calcMinSize)(WidgetT *w, const BitmapFontT *font);
|
void (*calcMinSize)(WidgetT *w, const BitmapFontT *font);
|
||||||
void (*layout)(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 (*onMouse)(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void (*onKey)(WidgetT *w, int32_t key);
|
void (*onKey)(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void (*destroy)(WidgetT *w);
|
void (*destroy)(WidgetT *w);
|
||||||
const char *(*getText)(const WidgetT *w);
|
const char *(*getText)(const WidgetT *w);
|
||||||
void (*setText)(WidgetT *w, const char *text);
|
void (*setText)(WidgetT *w, const char *text);
|
||||||
|
|
@ -41,6 +41,11 @@ extern const WidgetClassT *widgetClassTable[];
|
||||||
// Constants
|
// Constants
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
// Modifier flags (BIOS INT 16h shift state bits)
|
||||||
|
#define KEY_MOD_SHIFT 0x03
|
||||||
|
#define KEY_MOD_CTRL 0x04
|
||||||
|
#define KEY_MOD_ALT 0x08
|
||||||
|
|
||||||
#define DEFAULT_SPACING 4
|
#define DEFAULT_SPACING 4
|
||||||
#define DEFAULT_PADDING 4
|
#define DEFAULT_PADDING 4
|
||||||
#define SEPARATOR_THICKNESS 2
|
#define SEPARATOR_THICKNESS 2
|
||||||
|
|
@ -87,6 +92,7 @@ extern WidgetT *sOpenPopup;
|
||||||
extern WidgetT *sPressedButton;
|
extern WidgetT *sPressedButton;
|
||||||
extern WidgetT *sDragSlider;
|
extern WidgetT *sDragSlider;
|
||||||
extern WidgetT *sDrawingCanvas;
|
extern WidgetT *sDrawingCanvas;
|
||||||
|
extern WidgetT *sDragTextSelect;
|
||||||
extern int32_t sDragOffset;
|
extern int32_t sDragOffset;
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -160,6 +166,7 @@ void widgetSeparatorPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
|
||||||
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 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 widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||||
void widgetToolbarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
void widgetToolbarPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||||
void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
void widgetTreeViewPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors);
|
||||||
|
|
@ -184,6 +191,7 @@ void widgetSeparatorCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetSliderCalcMinSize(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 widgetTextInputCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetTextInputCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font);
|
||||||
|
|
||||||
|
|
@ -209,6 +217,8 @@ const char *widgetLabelGetText(const WidgetT *w);
|
||||||
void widgetLabelSetText(WidgetT *w, const char *text);
|
void widgetLabelSetText(WidgetT *w, const char *text);
|
||||||
const char *widgetRadioGetText(const WidgetT *w);
|
const char *widgetRadioGetText(const WidgetT *w);
|
||||||
void widgetRadioSetText(WidgetT *w, const char *text);
|
void widgetRadioSetText(WidgetT *w, const char *text);
|
||||||
|
const char *widgetTextAreaGetText(const WidgetT *w);
|
||||||
|
void widgetTextAreaSetText(WidgetT *w, const char *text);
|
||||||
const char *widgetTextInputGetText(const WidgetT *w);
|
const char *widgetTextInputGetText(const WidgetT *w);
|
||||||
void widgetTextInputSetText(WidgetT *w, const char *text);
|
void widgetTextInputSetText(WidgetT *w, const char *text);
|
||||||
const char *widgetTreeItemGetText(const WidgetT *w);
|
const char *widgetTreeItemGetText(const WidgetT *w);
|
||||||
|
|
@ -230,32 +240,42 @@ void widgetTextInputDestroy(WidgetT *w);
|
||||||
// Per-widget mouse/key functions
|
// Per-widget mouse/key functions
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetAnsiTermOnKey(WidgetT *w, int32_t key);
|
void clearOtherSelections(WidgetT *except);
|
||||||
|
void clipboardCopy(const char *text, int32_t len);
|
||||||
|
const char *clipboardGet(int32_t *outLen);
|
||||||
|
bool isWordChar(char c);
|
||||||
|
int32_t multiClickDetect(int32_t vx, int32_t vy);
|
||||||
|
void widgetAnsiTermOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetAnsiTermOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetAnsiTermOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetButtonOnKey(WidgetT *w, int32_t key);
|
void widgetButtonOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
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 widgetCanvasOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetCheckboxOnKey(WidgetT *w, int32_t key);
|
void widgetCheckboxOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetCheckboxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetComboBoxOnKey(WidgetT *w, int32_t key);
|
void widgetComboBoxOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetDropdownOnKey(WidgetT *w, int32_t key);
|
void widgetDropdownOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetDropdownOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetImageButtonOnKey(WidgetT *w, int32_t key);
|
void widgetImageButtonOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetImageButtonOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
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 widgetImageOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetListBoxOnKey(WidgetT *w, int32_t key);
|
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetListBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetListBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetRadioOnKey(WidgetT *w, int32_t key);
|
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);
|
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 widgetTabControlOnKey(WidgetT *w, int32_t key);
|
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 widgetTextEditOnKey(WidgetT *w, int32_t key, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff);
|
void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetTextInputOnKey(WidgetT *w, int32_t key);
|
void widgetTextAreaOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
|
void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
|
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
||||||
|
void widgetTextInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
void widgetTreeViewOnKey(WidgetT *w, int32_t key);
|
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
||||||
|
int32_t wordStart(const char *buf, int32_t pos);
|
||||||
|
void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||||
void widgetTreeViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
void widgetTreeViewOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
|
|
||||||
#endif // WIDGET_INTERNAL_H
|
#endif // WIDGET_INTERNAL_H
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,9 @@ void widgetListBoxCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetListBoxOnKey
|
// widgetListBoxOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetListBoxOnKey(WidgetT *w, int32_t key) {
|
void widgetListBoxOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
if (!w || w->type != WidgetListBoxE || w->as.listBox.itemCount == 0) {
|
if (!w || w->type != WidgetListBoxE || w->as.listBox.itemCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,9 @@ void widgetRadioCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetRadioOnKey
|
// widgetRadioOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetRadioOnKey(WidgetT *w, int32_t key) {
|
void widgetRadioOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
if (key == ' ' || key == 0x0D) {
|
if (key == ' ' || key == 0x0D) {
|
||||||
// Select this radio
|
// Select this radio
|
||||||
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
if (w->parent && w->parent->type == WidgetRadioGroupE) {
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,9 @@ void widgetSliderCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetSliderOnKey
|
// widgetSliderOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetSliderOnKey(WidgetT *w, int32_t key) {
|
void widgetSliderOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
int32_t step = 1;
|
int32_t step = 1;
|
||||||
int32_t range = w->as.slider.maxValue - w->as.slider.minValue;
|
int32_t range = w->as.slider.maxValue - w->as.slider.minValue;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,9 @@ void widgetTabControlLayout(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetTabControlOnKey
|
// widgetTabControlOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetTabControlOnKey(WidgetT *w, int32_t key) {
|
void widgetTabControlOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
int32_t tabCount = 0;
|
int32_t tabCount = 0;
|
||||||
|
|
||||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -649,7 +649,9 @@ void widgetTreeViewCalcMinSize(WidgetT *w, const BitmapFontT *font) {
|
||||||
// widgetTreeViewOnKey
|
// widgetTreeViewOnKey
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
void widgetTreeViewOnKey(WidgetT *w, int32_t key) {
|
void widgetTreeViewOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
||||||
|
(void)mod;
|
||||||
|
|
||||||
if (!w || w->type != WidgetTreeViewE) {
|
if (!w || w->type != WidgetTreeViewE) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
150
dvxdemo/demo.c
150
dvxdemo/demo.c
|
|
@ -4,7 +4,11 @@
|
||||||
#include "dvxWidget.h"
|
#include "dvxWidget.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "thirdparty/stb_image.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Menu command IDs
|
// Menu command IDs
|
||||||
|
|
@ -25,6 +29,7 @@
|
||||||
// Prototypes
|
// Prototypes
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
|
static uint8_t *loadBmpPixels(AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch);
|
||||||
static void onCloseCb(WindowT *win);
|
static void onCloseCb(WindowT *win);
|
||||||
static void onCloseMainCb(WindowT *win);
|
static void onCloseMainCb(WindowT *win);
|
||||||
static void onMenuCb(WindowT *win, int32_t menuId);
|
static void onMenuCb(WindowT *win, int32_t menuId);
|
||||||
|
|
@ -45,6 +50,54 @@ static void setupWidgetDemo(AppContextT *ctx);
|
||||||
static AppContextT *sCtx = NULL;
|
static AppContextT *sCtx = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// loadBmpPixels — load a BMP/PNG file into display-format pixels
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
static uint8_t *loadBmpPixels(AppContextT *ctx, const char *path, int32_t *outW, int32_t *outH, int32_t *outPitch) {
|
||||||
|
int imgW;
|
||||||
|
int imgH;
|
||||||
|
int channels;
|
||||||
|
uint8_t *rgb = stbi_load(path, &imgW, &imgH, &channels, 3);
|
||||||
|
|
||||||
|
if (!rgb) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DisplayT *d = dvxGetDisplay(ctx);
|
||||||
|
int32_t bpp = d->format.bytesPerPixel;
|
||||||
|
int32_t pitch = imgW * bpp;
|
||||||
|
uint8_t *data = (uint8_t *)malloc(pitch * imgH);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
stbi_image_free(rgb);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int32_t y = 0; y < imgH; y++) {
|
||||||
|
for (int32_t x = 0; x < imgW; x++) {
|
||||||
|
uint8_t *src = rgb + (y * imgW + x) * 3;
|
||||||
|
uint32_t color = packColor(d, src[0], src[1], src[2]);
|
||||||
|
uint8_t *dst = data + y * pitch + x * bpp;
|
||||||
|
|
||||||
|
if (bpp == 1) {
|
||||||
|
*dst = (uint8_t)color;
|
||||||
|
} else if (bpp == 2) {
|
||||||
|
*(uint16_t *)dst = (uint16_t)color;
|
||||||
|
} else {
|
||||||
|
*(uint32_t *)dst = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stbi_image_free(rgb);
|
||||||
|
*outW = imgW;
|
||||||
|
*outH = imgH;
|
||||||
|
*outPitch = pitch;
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// onCloseCb
|
// onCloseCb
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -308,7 +361,7 @@ static const char *colorItems[] = {"Red", "Green", "Blue", "Yellow", "Cyan", "Ma
|
||||||
static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"};
|
static const char *sizeItems[] = {"Small", "Medium", "Large", "Extra Large"};
|
||||||
|
|
||||||
static void setupControlsWindow(AppContextT *ctx) {
|
static void setupControlsWindow(AppContextT *ctx) {
|
||||||
WindowT *win = dvxCreateWindow(ctx, "Advanced Widgets", 380, 50, 320, 400, true);
|
WindowT *win = dvxCreateWindow(ctx, "Advanced Widgets", 380, 50, 360, 440, true);
|
||||||
|
|
||||||
if (!win) {
|
if (!win) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -366,18 +419,79 @@ static void setupControlsWindow(AppContextT *ctx) {
|
||||||
wgtTreeItem(config, "settings.ini");
|
wgtTreeItem(config, "settings.ini");
|
||||||
wgtTreeItem(config, "palette.dat");
|
wgtTreeItem(config, "palette.dat");
|
||||||
|
|
||||||
// --- Tab 3: Toolbar ---
|
// --- Tab 3: Toolbar (ImageButtons + VSeparator) ---
|
||||||
WidgetT *page3 = wgtTabPage(tabs, "Tool&bar");
|
WidgetT *page3 = wgtTabPage(tabs, "Tool&bar");
|
||||||
|
|
||||||
WidgetT *tb = wgtToolbar(page3);
|
WidgetT *tb = wgtToolbar(page3);
|
||||||
WidgetT *btnNew = wgtButton(tb, "&New");
|
|
||||||
WidgetT *btnOpen = wgtButton(tb, "&Open");
|
|
||||||
WidgetT *btnSave = wgtButton(tb, "&Save");
|
|
||||||
btnNew->onClick = onToolbarClick;
|
|
||||||
btnOpen->onClick = onToolbarClick;
|
|
||||||
btnSave->onClick = onToolbarClick;
|
|
||||||
|
|
||||||
wgtLabel(page3, "Toolbar with buttons above.");
|
int32_t imgW;
|
||||||
|
int32_t imgH;
|
||||||
|
int32_t imgPitch;
|
||||||
|
|
||||||
|
uint8_t *newData = loadBmpPixels(ctx, "new.bmp", &imgW, &imgH, &imgPitch);
|
||||||
|
if (newData) {
|
||||||
|
WidgetT *btnNew = wgtImageButton(tb, newData, imgW, imgH, imgPitch);
|
||||||
|
strncpy(btnNew->name, "New", MAX_WIDGET_NAME);
|
||||||
|
btnNew->onClick = onToolbarClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *openData = loadBmpPixels(ctx, "open.bmp", &imgW, &imgH, &imgPitch);
|
||||||
|
if (openData) {
|
||||||
|
WidgetT *btnOpen = wgtImageButton(tb, openData, imgW, imgH, imgPitch);
|
||||||
|
strncpy(btnOpen->name, "Open", MAX_WIDGET_NAME);
|
||||||
|
btnOpen->onClick = onToolbarClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t *saveData = loadBmpPixels(ctx, "save.bmp", &imgW, &imgH, &imgPitch);
|
||||||
|
if (saveData) {
|
||||||
|
WidgetT *btnSave = wgtImageButton(tb, saveData, imgW, imgH, imgPitch);
|
||||||
|
strncpy(btnSave->name, "Save", MAX_WIDGET_NAME);
|
||||||
|
btnSave->onClick = onToolbarClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
wgtVSeparator(tb);
|
||||||
|
WidgetT *btnHelp = wgtButton(tb, "&Help");
|
||||||
|
btnHelp->onClick = onToolbarClick;
|
||||||
|
|
||||||
|
wgtLabel(page3, "ImageButtons with VSeparator.");
|
||||||
|
|
||||||
|
// --- Tab 4: Media (Image, ImageFromFile) ---
|
||||||
|
WidgetT *page4 = wgtTabPage(tabs, "&Media");
|
||||||
|
|
||||||
|
wgtLabel(page4, "ImageFromFile (sample.bmp):");
|
||||||
|
wgtImageFromFile(page4, "sample.bmp");
|
||||||
|
|
||||||
|
wgtHSeparator(page4);
|
||||||
|
|
||||||
|
wgtLabel(page4, "Image (logo.bmp):");
|
||||||
|
WidgetT *imgRow = wgtHBox(page4);
|
||||||
|
uint8_t *logoData = loadBmpPixels(ctx, "logo.bmp", &imgW, &imgH, &imgPitch);
|
||||||
|
if (logoData) {
|
||||||
|
wgtImage(imgRow, logoData, imgW, imgH, imgPitch);
|
||||||
|
}
|
||||||
|
wgtVSeparator(imgRow);
|
||||||
|
wgtLabel(imgRow, "32x32 DV/X logo");
|
||||||
|
|
||||||
|
// --- Tab 5: Editor (TextArea, Canvas) ---
|
||||||
|
WidgetT *page5 = wgtTabPage(tabs, "&Editor");
|
||||||
|
|
||||||
|
wgtLabel(page5, "TextArea:");
|
||||||
|
WidgetT *ta = wgtTextArea(page5, 512);
|
||||||
|
ta->weight = 100;
|
||||||
|
wgtSetText(ta, "Multi-line text editor.\n\nFeatures:\n- Word wrap\n- Selection\n- Copy/Paste\n- Undo (Ctrl+Z)");
|
||||||
|
|
||||||
|
wgtHSeparator(page5);
|
||||||
|
|
||||||
|
wgtLabel(page5, "Canvas (draw with mouse):");
|
||||||
|
const DisplayT *d = dvxGetDisplay(ctx);
|
||||||
|
WidgetT *cv = wgtCanvas(page5, 280, 80);
|
||||||
|
wgtCanvasSetPenColor(cv, packColor(d, 200, 0, 0));
|
||||||
|
wgtCanvasDrawRect(cv, 5, 5, 50, 35);
|
||||||
|
wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 200));
|
||||||
|
wgtCanvasFillCircle(cv, 150, 40, 25);
|
||||||
|
wgtCanvasSetPenColor(cv, packColor(d, 0, 150, 0));
|
||||||
|
wgtCanvasDrawLine(cv, 70, 5, 130, 70);
|
||||||
|
wgtCanvasSetPenColor(cv, packColor(d, 0, 0, 0));
|
||||||
|
|
||||||
// Status bar at bottom (outside tabs)
|
// Status bar at bottom (outside tabs)
|
||||||
WidgetT *sb = wgtStatusBar(root);
|
WidgetT *sb = wgtStatusBar(root);
|
||||||
|
|
@ -630,7 +744,23 @@ static void setupWidgetDemo(AppContextT *ctx) {
|
||||||
// main
|
// main
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
int main(void) {
|
int main(int argc, char **argv) {
|
||||||
|
(void)argc;
|
||||||
|
|
||||||
|
// Change to executable's directory so relative BMP paths work
|
||||||
|
char exeDir[260];
|
||||||
|
strncpy(exeDir, argv[0], sizeof(exeDir) - 1);
|
||||||
|
exeDir[sizeof(exeDir) - 1] = '\0';
|
||||||
|
char *lastSep = strrchr(exeDir, '/');
|
||||||
|
char *lastBs = strrchr(exeDir, '\\');
|
||||||
|
if (lastBs > lastSep) {
|
||||||
|
lastSep = lastBs;
|
||||||
|
}
|
||||||
|
if (lastSep) {
|
||||||
|
*lastSep = '\0';
|
||||||
|
chdir(exeDir);
|
||||||
|
}
|
||||||
|
|
||||||
AppContextT ctx;
|
AppContextT ctx;
|
||||||
|
|
||||||
printf("DV/X GUI Demo\n");
|
printf("DV/X GUI Demo\n");
|
||||||
|
|
|
||||||
BIN
dvxdemo/logo.bmp
(Stored with Git LFS)
Normal file
BIN
dvxdemo/logo.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
dvxdemo/new.bmp
(Stored with Git LFS)
Normal file
BIN
dvxdemo/new.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
dvxdemo/open.bmp
(Stored with Git LFS)
Normal file
BIN
dvxdemo/open.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
dvxdemo/sample.bmp
(Stored with Git LFS)
Normal file
BIN
dvxdemo/sample.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
dvxdemo/save.bmp
(Stored with Git LFS)
Normal file
BIN
dvxdemo/save.bmp
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue