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);
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -370,49 +331,7 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
// duplicating the full editing logic (cursor movement, word selection,
|
||||
// clipboard, undo) three times.
|
||||
void widgetTextEditDragUpdateLine(int32_t vx, int32_t leftEdge, int32_t maxChars, const BitmapFontT *font, int32_t len, int32_t *pCursorPos, int32_t *pScrollOff, int32_t *pSelEnd);
|
||||
void widgetTextEditMouseClick(WidgetT *w, int32_t vx, int32_t vy, int32_t textLeftX, const BitmapFontT *font, const char *buf, int32_t len, int32_t scrollOff, int32_t *pCursorPos, int32_t *pSelStart, int32_t *pSelEnd, bool wordSelect, bool dragSelect);
|
||||
void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff, int32_t *pSelStart, int32_t *pSelEnd, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor);
|
||||
void widgetTextEditPaintLine(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors, int32_t textX, int32_t textY, const char *buf, int32_t visLen, int32_t scrollOff, int32_t cursorPos, int32_t selStart, int32_t selEnd, uint32_t fg, uint32_t bg, bool showCursor, int32_t cursorMinX, int32_t cursorMaxX);
|
||||
void widgetTextInputOnKey(WidgetT *w, int32_t key, int32_t mod);
|
||||
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy);
|
||||
int32_t wordEnd(const char *buf, int32_t len, int32_t pos);
|
||||
|
|
|
|||
|
|
@ -305,34 +305,8 @@ void widgetSpinnerOnMouse(WidgetT *hit, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
widgetTextEditMouseClick(hit, vx, vy, hit->x + SPINNER_BORDER + SPINNER_PAD, &ctx->font, hit->as.spinner.buf, hit->as.spinner.len, hit->as.spinner.scrollOff, &hit->as.spinner.cursorPos, &hit->as.spinner.selStart, &hit->as.spinner.selEnd, false, false);
|
||||
spinnerStartEdit(hit);
|
||||
}
|
||||
|
||||
|
|
@ -398,52 +372,7 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
|
|||
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 (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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
|
||||
// Up button (top half)
|
||||
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
|
||||
// ============================================================
|
||||
|
|
@ -1830,30 +1979,9 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
const BitmapFontT *font = &ctx->font;
|
||||
|
||||
if (w->type == WidgetTextInputE) {
|
||||
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
||||
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth;
|
||||
int32_t rightEdge = leftEdge + maxChars * font->charWidth;
|
||||
|
||||
// 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;
|
||||
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
||||
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth;
|
||||
widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.textInput.len, &w->as.textInput.cursorPos, &w->as.textInput.scrollOff, &w->as.textInput.selEnd);
|
||||
} else if (w->type == WidgetTextAreaE) {
|
||||
int32_t innerX = w->x + TEXTAREA_BORDER + TEXTAREA_PAD;
|
||||
int32_t innerY = w->y + TEXTAREA_BORDER;
|
||||
|
|
@ -1918,30 +2046,9 @@ void widgetTextDragUpdate(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
w->as.textArea.desiredCol = clickCol;
|
||||
w->as.textArea.selCursor = textAreaCursorToOff(w->as.textArea.buf, w->as.textArea.len, clickRow, clickCol);
|
||||
} else if (w->type == WidgetComboBoxE) {
|
||||
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
||||
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2 - 16) / font->charWidth;
|
||||
int32_t rightEdge = leftEdge + maxChars * font->charWidth;
|
||||
|
||||
// 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;
|
||||
int32_t leftEdge = w->x + TEXT_INPUT_PAD;
|
||||
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2 - 16) / font->charWidth;
|
||||
widgetTextEditDragUpdateLine(vx, leftEdge, maxChars, font, w->as.comboBox.len, &w->as.comboBox.cursorPos, &w->as.comboBox.scrollOff, &w->as.comboBox.selEnd);
|
||||
} else if (w->type == WidgetAnsiTermE) {
|
||||
int32_t baseX = w->x + 2; // ANSI_BORDER
|
||||
int32_t baseY = w->y + 2;
|
||||
|
|
@ -2360,46 +2467,7 @@ void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
|
|||
clearOtherSelections(w);
|
||||
|
||||
AppContextT *ctx = (AppContextT *)root->userData;
|
||||
const BitmapFontT *font = &ctx->font;
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2440,15 +2508,6 @@ void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
|
|||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Draw up to 3 runs: before selection, selection, after selection
|
||||
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);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue