More text editing code consolidated.
This commit is contained in:
parent
73a54a8b3a
commit
83860ac13d
4 changed files with 165 additions and 288 deletions
|
|
@ -291,46 +291,7 @@ void widgetComboBoxOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
clearOtherSelections(w);
|
clearOtherSelections(w);
|
||||||
|
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
const BitmapFontT *font = &ctx->font;
|
widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, w->as.comboBox.buf, w->as.comboBox.len, w->as.comboBox.scrollOff, &w->as.comboBox.cursorPos, &w->as.comboBox.selStart, &w->as.comboBox.selEnd, true, true);
|
||||||
int32_t relX = vx - w->x - TEXT_INPUT_PAD;
|
|
||||||
int32_t charPos = relX / font->charWidth + w->as.comboBox.scrollOff;
|
|
||||||
|
|
||||||
if (charPos < 0) {
|
|
||||||
charPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charPos > 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.selStart = charPos;
|
|
||||||
w->as.comboBox.selEnd = charPos;
|
|
||||||
sDragTextSelect = w;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,49 +331,7 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
||||||
len = maxChars;
|
len = maxChars;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection range
|
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, w->as.comboBox.buf + off, len, off, w->as.comboBox.cursorPos, w->as.comboBox.selStart, w->as.comboBox.selEnd, fg, bg, w->focused && w->enabled && !w->as.comboBox.open, w->x + TEXT_INPUT_PAD, w->x + textAreaW - TEXT_INPUT_PAD);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw up to 3 text runs: before selection (normal colors), selection
|
|
||||||
// (highlight colors), after selection (normal colors). This avoids
|
|
||||||
// drawing the entire line twice (once normal, once selected) which
|
|
||||||
// would be wasteful on slow hardware. The visible selection range
|
|
||||||
// is clamped to the scrolled viewport.
|
|
||||||
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) {
|
|
||||||
if (visSelLo > 0) {
|
|
||||||
drawTextN(d, ops, font, textX, textY, w->as.comboBox.buf + off, visSelLo, fg, bg, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
drawTextN(d, ops, font, textX + visSelLo * font->charWidth, textY, w->as.comboBox.buf + off + visSelLo, visSelHi - visSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
|
||||||
|
|
||||||
if (visSelHi < len) {
|
|
||||||
drawTextN(d, ops, font, textX + visSelHi * font->charWidth, textY, w->as.comboBox.buf + off + visSelHi, len - visSelHi, fg, bg, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
drawTextN(d, ops, font, textX, textY, w->as.comboBox.buf + off, len, fg, bg, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw cursor (blinks at same rate as terminal cursor)
|
|
||||||
if (w->focused && w->enabled && !w->as.comboBox.open && sCursorBlinkOn) {
|
|
||||||
int32_t cursorX = textX + (w->as.comboBox.cursorPos - off) * font->charWidth;
|
|
||||||
|
|
||||||
if (cursorX >= w->x + TEXT_INPUT_PAD &&
|
|
||||||
cursorX < w->x + textAreaW - TEXT_INPUT_PAD) {
|
|
||||||
drawVLine(d, ops, cursorX, textY, font->charHeight, fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop button
|
// Drop button
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,10 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||||
// (which store these fields in different union members). This avoids
|
// (which store these fields in different union members). This avoids
|
||||||
// duplicating the full editing logic (cursor movement, word selection,
|
// duplicating the full editing logic (cursor movement, word selection,
|
||||||
// clipboard, undo) three times.
|
// clipboard, undo) three times.
|
||||||
|
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
|
||||||
|
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
|
||||||
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
||||||
|
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
|
||||||
void widgetTextInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
void 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);
|
||||||
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
||||||
|
|
|
||||||
|
|
@ -306,33 +306,7 @@ void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
} else {
|
} else {
|
||||||
// Click on text area
|
// Click on text area
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
const BitmapFontT *font = &ctx->font;
|
widgetTextEditMouseClick(hit, vx, vy, hit->x + SPINNER_BORDER + SPINNER_PAD, &ctx->font, hit->as.spinner.buf, hit->as.spinner.len, hit->as.spinner.scrollOff, &hit->as.spinner.cursorPos, &hit->as.spinner.selStart, &hit->as.spinner.selEnd, false, false);
|
||||||
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);
|
spinnerStartEdit(hit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -398,52 +372,7 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
||||||
len = 0;
|
len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection range
|
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, &w->as.spinner.buf[off], len, off, w->as.spinner.cursorPos, w->as.spinner.selStart, w->as.spinner.selEnd, fg, bg, w->focused, w->x + SPINNER_BORDER, btnX - SPINNER_PAD);
|
||||||
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 (blinks at same rate as terminal cursor)
|
|
||||||
if (w->focused && sCursorBlinkOn) {
|
|
||||||
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)
|
// Up button (top half)
|
||||||
int32_t btnTopH = w->h / 2;
|
int32_t btnTopH = w->h / 2;
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,155 @@ void wgtUpdateCursorBlink(void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Shared single-line text editing: mouse click
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Computes cursor position from pixel coordinates, handles multi-click
|
||||||
|
// (double = word select, triple = select all), and optionally starts
|
||||||
|
// drag-select. Used by TextInput, ComboBox, and Spinner mouse handlers
|
||||||
|
// so click-to-cursor behavior is consistent across all text widgets.
|
||||||
|
//
|
||||||
|
// wordSelect: if true, double-click selects the word under the cursor;
|
||||||
|
// if false, double-click selects all (used by Spinner).
|
||||||
|
// dragSelect: if true, single-click registers the widget for drag-select
|
||||||
|
// tracking (TextInput/ComboBox); false for Spinner.
|
||||||
|
|
||||||
|
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect) {
|
||||||
|
int32_t relX = vx - textLeftX;
|
||||||
|
int32_t charPos = relX / font->charWidth + scrollOff;
|
||||||
|
|
||||||
|
if (charPos < 0) {
|
||||||
|
charPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charPos > len) {
|
||||||
|
charPos = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t clicks = multiClickDetect(vx, vy);
|
||||||
|
|
||||||
|
if (clicks >= 3) {
|
||||||
|
*pSelStart = 0;
|
||||||
|
*pSelEnd = len;
|
||||||
|
*pCursorPos = len;
|
||||||
|
sDragTextSelect = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clicks == 2) {
|
||||||
|
if (wordSelect && buf) {
|
||||||
|
int32_t ws = wordStart(buf, charPos);
|
||||||
|
int32_t we = wordEnd(buf, len, charPos);
|
||||||
|
*pSelStart = ws;
|
||||||
|
*pSelEnd = we;
|
||||||
|
*pCursorPos = we;
|
||||||
|
} else {
|
||||||
|
*pSelStart = 0;
|
||||||
|
*pSelEnd = len;
|
||||||
|
*pCursorPos = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
sDragTextSelect = NULL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single click: place cursor
|
||||||
|
*pCursorPos = charPos;
|
||||||
|
*pSelStart = charPos;
|
||||||
|
*pSelEnd = charPos;
|
||||||
|
sDragTextSelect = dragSelect ? w : NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Shared single-line text editing: drag update
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Called during mouse drag to extend the selection. Auto-scrolls
|
||||||
|
// when the mouse moves past the visible text edges. Used by
|
||||||
|
// widgetTextDragUpdate for TextInput and ComboBox.
|
||||||
|
|
||||||
|
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd) {
|
||||||
|
int32_t rightEdge = leftEdge + maxChars * font->charWidth;
|
||||||
|
|
||||||
|
if (vx < leftEdge && *pScrollOff > 0) {
|
||||||
|
(*pScrollOff)--;
|
||||||
|
} else if (vx >= rightEdge && *pScrollOff + maxChars < len) {
|
||||||
|
(*pScrollOff)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t relX = vx - leftEdge;
|
||||||
|
int32_t charPos = relX / font->charWidth + *pScrollOff;
|
||||||
|
|
||||||
|
if (charPos < 0) {
|
||||||
|
charPos = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charPos > len) {
|
||||||
|
charPos = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
*pCursorPos = charPos;
|
||||||
|
*pSelEnd = charPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Shared single-line text editing: paint
|
||||||
|
// ============================================================
|
||||||
|
//
|
||||||
|
// Renders a single line of text with optional selection highlighting
|
||||||
|
// and a blinking cursor. Draws up to 3 runs (before/during/after
|
||||||
|
// selection) to avoid overdraw. Used by TextInput, ComboBox, and
|
||||||
|
// Spinner paint functions so selection rendering is identical.
|
||||||
|
//
|
||||||
|
// buf points to the already-scrolled display text (buf + scrollOff),
|
||||||
|
// and visLen is the number of visible characters. For password mode,
|
||||||
|
// the caller passes a bullet-filled display buffer.
|
||||||
|
|
||||||
|
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX) {
|
||||||
|
// Normalize selection to low/high
|
||||||
|
int32_t selLo = -1;
|
||||||
|
int32_t selHi = -1;
|
||||||
|
|
||||||
|
if (selStart >= 0 && selEnd >= 0 && selStart != selEnd) {
|
||||||
|
selLo = selStart < selEnd ? selStart : selEnd;
|
||||||
|
selHi = selStart < selEnd ? selEnd : selStart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map selection to visible range
|
||||||
|
int32_t visSelLo = selLo - scrollOff;
|
||||||
|
int32_t visSelHi = selHi - scrollOff;
|
||||||
|
|
||||||
|
if (visSelLo < 0) { visSelLo = 0; }
|
||||||
|
if (visSelHi > visLen) { visSelHi = visLen; }
|
||||||
|
|
||||||
|
if (selLo >= 0 && visSelLo < visSelHi) {
|
||||||
|
if (visSelLo > 0) {
|
||||||
|
drawTextN(d, ops, font, textX, textY, buf, visSelLo, fg, bg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawTextN(d, ops, font, textX + visSelLo * font->charWidth, textY, buf + visSelLo, visSelHi - visSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
||||||
|
|
||||||
|
if (visSelHi < visLen) {
|
||||||
|
drawTextN(d, ops, font, textX + visSelHi * font->charWidth, textY, buf + visSelHi, visLen - visSelHi, fg, bg, true);
|
||||||
|
}
|
||||||
|
} else if (visLen > 0) {
|
||||||
|
drawTextN(d, ops, font, textX, textY, buf, visLen, fg, bg, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Blinking cursor
|
||||||
|
if (showCursor && sCursorBlinkOn) {
|
||||||
|
int32_t cursorX = textX + (cursorPos - scrollOff) * font->charWidth;
|
||||||
|
|
||||||
|
if (cursorX >= cursorMinX && cursorX < cursorMaxX) {
|
||||||
|
drawVLine(d, ops, cursorX, textY, font->charHeight, fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Multi-click tracking
|
// Multi-click tracking
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
@ -1832,28 +1981,7 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
if (w->type == WidgetTextInputE) {
|
if (w->type == WidgetTextInputE) {
|
||||||
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
||||||
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth;
|
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth;
|
||||||
int32_t rightEdge = leftEdge + maxChars * font->charWidth;
|
widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.textInput.len, &w->as.textInput.cursorPos, &w->as.textInput.scrollOff, &w->as.textInput.selEnd);
|
||||||
|
|
||||||
// Auto-scroll when mouse is past edges
|
|
||||||
if (vx < leftEdge && w->as.textInput.scrollOff > 0) {
|
|
||||||
w->as.textInput.scrollOff--;
|
|
||||||
} else if (vx >= rightEdge && w->as.textInput.scrollOff + maxChars < w->as.textInput.len) {
|
|
||||||
w->as.textInput.scrollOff++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t relX = vx - leftEdge;
|
|
||||||
int32_t charPos = relX / font->charWidth + w->as.textInput.scrollOff;
|
|
||||||
|
|
||||||
if (charPos < 0) {
|
|
||||||
charPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charPos > w->as.textInput.len) {
|
|
||||||
charPos = w->as.textInput.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.textInput.cursorPos = charPos;
|
|
||||||
w->as.textInput.selEnd = charPos;
|
|
||||||
} else if (w->type == WidgetTextAreaE) {
|
} else if (w->type == WidgetTextAreaE) {
|
||||||
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
||||||
int32_t innerY = w->y + TEXTAREA_BORDER;
|
int32_t innerY = w->y + TEXTAREA_BORDER;
|
||||||
|
|
@ -1920,28 +2048,7 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
} else if (w->type == WidgetComboBoxE) {
|
} else if (w->type == WidgetComboBoxE) {
|
||||||
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
||||||
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2 - 16) / font->charWidth;
|
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2 - 16) / font->charWidth;
|
||||||
int32_t rightEdge = leftEdge + maxChars * font->charWidth;
|
widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.comboBox.len, &w->as.comboBox.cursorPos, &w->as.comboBox.scrollOff, &w->as.comboBox.selEnd);
|
||||||
|
|
||||||
// Auto-scroll when mouse is past edges
|
|
||||||
if (vx < leftEdge && w->as.comboBox.scrollOff > 0) {
|
|
||||||
w->as.comboBox.scrollOff--;
|
|
||||||
} else if (vx >= rightEdge && w->as.comboBox.scrollOff + maxChars < w->as.comboBox.len) {
|
|
||||||
w->as.comboBox.scrollOff++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t relX = vx - leftEdge;
|
|
||||||
int32_t charPos = relX / font->charWidth + w->as.comboBox.scrollOff;
|
|
||||||
|
|
||||||
if (charPos < 0) {
|
|
||||||
charPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charPos > w->as.comboBox.len) {
|
|
||||||
charPos = w->as.comboBox.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
w->as.comboBox.cursorPos = charPos;
|
|
||||||
w->as.comboBox.selEnd = charPos;
|
|
||||||
} else if (w->type == WidgetAnsiTermE) {
|
} else if (w->type == WidgetAnsiTermE) {
|
||||||
int32_t baseX = w->x + 2; // ANSI_BORDER
|
int32_t baseX = w->x + 2; // ANSI_BORDER
|
||||||
int32_t baseY = w->y + 2;
|
int32_t baseY = w->y + 2;
|
||||||
|
|
@ -2360,46 +2467,7 @@ void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
||||||
clearOtherSelections(w);
|
clearOtherSelections(w);
|
||||||
|
|
||||||
AppContextT *ctx = (AppContextT *)root->userData;
|
AppContextT *ctx = (AppContextT *)root->userData;
|
||||||
const BitmapFontT *font = &ctx->font;
|
widgetTextEditMouseClick(w, vx, vy, w->x + TEXT_INPUT_PAD, &ctx->font, w->as.textInput.buf, w->as.textInput.len, w->as.textInput.scrollOff, &w->as.textInput.cursorPos, &w->as.textInput.selStart, &w->as.textInput.selEnd, true, true);
|
||||||
int32_t relX = vx - w->x - TEXT_INPUT_PAD;
|
|
||||||
int32_t charPos = relX / font->charWidth + w->as.textInput.scrollOff;
|
|
||||||
|
|
||||||
if (charPos < 0) {
|
|
||||||
charPos = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (charPos > w->as.textInput.len) {
|
|
||||||
charPos = w->as.textInput.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t clicks = multiClickDetect(vx, vy);
|
|
||||||
|
|
||||||
if (clicks >= 3) {
|
|
||||||
// Triple-click: select all (single line)
|
|
||||||
w->as.textInput.selStart = 0;
|
|
||||||
w->as.textInput.selEnd = w->as.textInput.len;
|
|
||||||
w->as.textInput.cursorPos = w->as.textInput.len;
|
|
||||||
sDragTextSelect = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clicks == 2 && w->as.textInput.buf) {
|
|
||||||
// Double-click: select word
|
|
||||||
int32_t ws = wordStart(w->as.textInput.buf, charPos);
|
|
||||||
int32_t we = wordEnd(w->as.textInput.buf, w->as.textInput.len, charPos);
|
|
||||||
|
|
||||||
w->as.textInput.selStart = ws;
|
|
||||||
w->as.textInput.selEnd = we;
|
|
||||||
w->as.textInput.cursorPos = we;
|
|
||||||
sDragTextSelect = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single click: place cursor + start drag-select
|
|
||||||
w->as.textInput.cursorPos = charPos;
|
|
||||||
w->as.textInput.selStart = charPos;
|
|
||||||
w->as.textInput.selEnd = charPos;
|
|
||||||
sDragTextSelect = w;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -2440,15 +2508,6 @@ void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
|
||||||
len = maxChars;
|
len = maxChars;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Selection range
|
|
||||||
int32_t selLo = -1;
|
|
||||||
int32_t selHi = -1;
|
|
||||||
|
|
||||||
if (w->as.textInput.selStart >= 0 && w->as.textInput.selEnd >= 0 && w->as.textInput.selStart != w->as.textInput.selEnd) {
|
|
||||||
selLo = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selStart : w->as.textInput.selEnd;
|
|
||||||
selHi = w->as.textInput.selStart < w->as.textInput.selEnd ? w->as.textInput.selEnd : w->as.textInput.selStart;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isPassword = (w->as.textInput.inputMode == InputPasswordE);
|
bool isPassword = (w->as.textInput.inputMode == InputPasswordE);
|
||||||
|
|
||||||
// Build display buffer (password masking)
|
// Build display buffer (password masking)
|
||||||
|
|
@ -2461,40 +2520,7 @@ void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
|
||||||
memcpy(dispBuf, w->as.textInput.buf + off, dispLen);
|
memcpy(dispBuf, w->as.textInput.buf + off, dispLen);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw up to 3 runs: before selection, selection, after selection
|
widgetTextEditPaintLine(d, ops, font, colors, textX, textY, dispBuf, dispLen, off, w->as.textInput.cursorPos, w->as.textInput.selStart, w->as.textInput.selEnd, fg, bg, w->focused && w->enabled, w->x + TEXT_INPUT_PAD, w->x + w->w - TEXT_INPUT_PAD);
|
||||||
int32_t visSelLo = selLo - off;
|
|
||||||
int32_t visSelHi = selHi - off;
|
|
||||||
|
|
||||||
if (visSelLo < 0) { visSelLo = 0; }
|
|
||||||
if (visSelHi > dispLen) { visSelHi = dispLen; }
|
|
||||||
|
|
||||||
if (selLo >= 0 && visSelLo < visSelHi) {
|
|
||||||
// Before selection
|
|
||||||
if (visSelLo > 0) {
|
|
||||||
drawTextN(d, ops, font, textX, textY, dispBuf, visSelLo, fg, bg, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selection
|
|
||||||
drawTextN(d, ops, font, textX + visSelLo * font->charWidth, textY, dispBuf + visSelLo, visSelHi - visSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
|
||||||
|
|
||||||
// After selection
|
|
||||||
if (visSelHi < dispLen) {
|
|
||||||
drawTextN(d, ops, font, textX + visSelHi * font->charWidth, textY, dispBuf + visSelHi, dispLen - visSelHi, fg, bg, true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// No selection — single run
|
|
||||||
drawTextN(d, ops, font, textX, textY, dispBuf, dispLen, fg, bg, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw cursor (blinks at same rate as terminal cursor)
|
|
||||||
if (w->focused && w->enabled && sCursorBlinkOn) {
|
|
||||||
int32_t cursorX = textX + (w->as.textInput.cursorPos - off) * font->charWidth;
|
|
||||||
|
|
||||||
if (cursorX >= w->x + TEXT_INPUT_PAD &&
|
|
||||||
cursorX < w->x + w->w - TEXT_INPUT_PAD) {
|
|
||||||
drawVLine(d, ops, cursorX, textY, font->charHeight, fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue