More optimizations. Word-at-a-time keyboard selection added.
This commit is contained in:
parent
e489f54ef0
commit
97530513b8
4 changed files with 466 additions and 108 deletions
154
dvx/dvxDraw.c
154
dvx/dvxDraw.c
|
|
@ -266,6 +266,160 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawTextN
|
||||
// ============================================================
|
||||
//
|
||||
// Renders exactly 'count' characters from a buffer in one pass.
|
||||
// Same idea as drawTermRow but for uniform fg/bg text runs.
|
||||
// Avoids per-character function call overhead, redundant clip
|
||||
// calculation, and spanFill startup costs.
|
||||
|
||||
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque) {
|
||||
if (count <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t cw = font->charWidth;
|
||||
int32_t ch = font->charHeight;
|
||||
int32_t bpp = ops->bytesPerPixel;
|
||||
int32_t pitch = d->pitch;
|
||||
|
||||
// Row-level clip: reject if entirely outside vertically
|
||||
int32_t clipX1 = d->clipX;
|
||||
int32_t clipX2 = d->clipX + d->clipW;
|
||||
int32_t clipY1 = d->clipY;
|
||||
int32_t clipY2 = d->clipY + d->clipH;
|
||||
|
||||
if (y + ch <= clipY1 || y >= clipY2) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t totalW = count * cw;
|
||||
|
||||
if (x + totalW <= clipX1 || x >= clipX2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Vertical clip for glyph scanlines
|
||||
int32_t rowStart = 0;
|
||||
int32_t rowEnd = ch;
|
||||
if (y < clipY1) { rowStart = clipY1 - y; }
|
||||
if (y + ch > clipY2) { rowEnd = clipY2 - y; }
|
||||
|
||||
// Horizontal clip: find first and last visible column (character index)
|
||||
int32_t firstChar = 0;
|
||||
int32_t lastChar = count;
|
||||
|
||||
if (x < clipX1) {
|
||||
firstChar = (clipX1 - x) / cw;
|
||||
}
|
||||
|
||||
if (x + totalW > clipX2) {
|
||||
lastChar = (clipX2 - x + cw - 1) / cw;
|
||||
if (lastChar > count) { lastChar = count; }
|
||||
}
|
||||
|
||||
// Per-pixel clip for partially visible edge characters
|
||||
int32_t edgeColStart = 0;
|
||||
|
||||
if (x + firstChar * cw < clipX1) {
|
||||
edgeColStart = clipX1 - (x + firstChar * cw);
|
||||
}
|
||||
|
||||
if (opaque) {
|
||||
// Opaque: fill background for the entire visible span once per scanline,
|
||||
// then overlay foreground glyph pixels
|
||||
int32_t fillX1 = x + firstChar * cw;
|
||||
int32_t fillX2 = x + lastChar * cw;
|
||||
|
||||
if (fillX1 < clipX1) { fillX1 = clipX1; }
|
||||
if (fillX2 > clipX2) { fillX2 = clipX2; }
|
||||
|
||||
int32_t fillW = fillX2 - fillX1;
|
||||
|
||||
if (fillW > 0) {
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t *dst = d->backBuf + (y + row) * pitch + fillX1 * bpp;
|
||||
ops->spanFill(dst, bg, fillW);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Render glyph foreground pixels
|
||||
for (int32_t ci = firstChar; ci < lastChar; ci++) {
|
||||
int32_t cx = x + ci * cw;
|
||||
|
||||
int32_t cStart = 0;
|
||||
int32_t cEnd = cw;
|
||||
|
||||
if (ci == firstChar) {
|
||||
cStart = edgeColStart;
|
||||
}
|
||||
|
||||
if (cx + cw > clipX2) {
|
||||
cEnd = clipX2 - cx;
|
||||
}
|
||||
|
||||
int32_t idx = (uint8_t)text[ci] - font->firstChar;
|
||||
const uint8_t *glyph = NULL;
|
||||
|
||||
if (idx >= 0 && idx < font->numChars) {
|
||||
glyph = font->glyphData + idx * ch;
|
||||
}
|
||||
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (bpp == 2) {
|
||||
uint16_t fg16 = (uint16_t)fg;
|
||||
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint16_t *dst = (uint16_t *)(d->backBuf + (y + row) * pitch + cx * 2);
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg16;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (bpp == 4) {
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint32_t *dst = (uint32_t *)(d->backBuf + (y + row) * pitch + cx * 4);
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uint8_t fg8 = (uint8_t)fg;
|
||||
|
||||
for (int32_t row = rowStart; row < rowEnd; row++) {
|
||||
uint8_t bits = glyph[row];
|
||||
if (bits == 0) { continue; }
|
||||
|
||||
uint8_t *dst = d->backBuf + (y + row) * pitch + cx;
|
||||
|
||||
for (int32_t p = cStart; p < cEnd; p++) {
|
||||
if (bits & sGlyphBit[p]) {
|
||||
dst[p] = fg8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// drawFocusRect
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -22,6 +22,11 @@ int32_t drawChar(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int3
|
|||
// Draw a null-terminated string
|
||||
void drawText(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, uint32_t fg, uint32_t bg, bool opaque);
|
||||
|
||||
// Draw exactly 'count' characters from a buffer (not null-terminated).
|
||||
// Much faster than calling drawChar per character: computes clip once,
|
||||
// fills background in bulk, then overlays glyph foreground pixels.
|
||||
void drawTextN(DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, int32_t x, int32_t y, const char *text, int32_t count, uint32_t fg, uint32_t bg, bool opaque);
|
||||
|
||||
// Measure text width in pixels
|
||||
int32_t textWidth(const BitmapFontT *font, const char *text);
|
||||
|
||||
|
|
|
|||
|
|
@ -340,18 +340,25 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
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++) {
|
||||
int32_t charIdx = off + i;
|
||||
uint32_t cfgc = fg;
|
||||
uint32_t cbgc = bg;
|
||||
// Draw up to 3 runs: before selection, selection, after selection
|
||||
int32_t visSelLo = selLo - off;
|
||||
int32_t visSelHi = selHi - off;
|
||||
|
||||
if (selLo >= 0 && charIdx >= selLo && charIdx < selHi) {
|
||||
cfgc = colors->menuHighlightFg;
|
||||
cbgc = colors->menuHighlightBg;
|
||||
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);
|
||||
}
|
||||
|
||||
drawChar(d, ops, font, textX + i * font->charWidth, textY,
|
||||
w->as.comboBox.buf[charIdx], cfgc, cbgc, 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
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ static int32_t textAreaMaxLineLen(const char *buf, int32_t len);
|
|||
static void textAreaOffToRowCol(const char *buf, int32_t off, int32_t *row, int32_t *col);
|
||||
static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor, int32_t *pSelStart, int32_t *pSelEnd);
|
||||
static void textEditSaveUndo(char *buf, int32_t len, int32_t cursor, char *undoBuf, int32_t *pUndoLen, int32_t *pUndoCursor, int32_t bufSize);
|
||||
static int32_t wordBoundaryLeft(const char *buf, int32_t pos);
|
||||
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
|
||||
|
||||
// ============================================================
|
||||
// Shared clipboard
|
||||
|
|
@ -122,37 +124,35 @@ int32_t wordEnd(const char *buf, int32_t len, int32_t pos) {
|
|||
// Clear selection on all text widgets except 'except'
|
||||
// ============================================================
|
||||
|
||||
static bool clearSelectionsInTree(WidgetT *root, WidgetT *except) {
|
||||
if (!root) {
|
||||
return false;
|
||||
}
|
||||
// Track the widget that last had an active selection so we can
|
||||
// clear it in O(1) instead of walking every widget in every window.
|
||||
static WidgetT *sLastSelectedWidget = NULL;
|
||||
|
||||
bool cleared = false;
|
||||
WidgetT *stack[64];
|
||||
int32_t top = 0;
|
||||
stack[top++] = root;
|
||||
|
||||
while (top > 0) {
|
||||
WidgetT *w = stack[--top];
|
||||
|
||||
if (w != except) {
|
||||
static bool clearSelectionOnWidget(WidgetT *w) {
|
||||
if (w->type == WidgetTextInputE) {
|
||||
if (w->as.textInput.selStart != w->as.textInput.selEnd) {
|
||||
cleared = true;
|
||||
w->as.textInput.selStart = -1;
|
||||
w->as.textInput.selEnd = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
w->as.textInput.selStart = -1;
|
||||
w->as.textInput.selEnd = -1;
|
||||
} else if (w->type == WidgetTextAreaE) {
|
||||
if (w->as.textArea.selAnchor != w->as.textArea.selCursor) {
|
||||
cleared = true;
|
||||
w->as.textArea.selAnchor = -1;
|
||||
w->as.textArea.selCursor = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
w->as.textArea.selAnchor = -1;
|
||||
w->as.textArea.selCursor = -1;
|
||||
} else if (w->type == WidgetComboBoxE) {
|
||||
if (w->as.comboBox.selStart != w->as.comboBox.selEnd) {
|
||||
cleared = true;
|
||||
w->as.comboBox.selStart = -1;
|
||||
w->as.comboBox.selEnd = -1;
|
||||
return true;
|
||||
}
|
||||
|
||||
w->as.comboBox.selStart = -1;
|
||||
|
|
@ -161,8 +161,13 @@ static bool clearSelectionsInTree(WidgetT *root, WidgetT *except) {
|
|||
if (w->as.ansiTerm.selStartLine >= 0 &&
|
||||
(w->as.ansiTerm.selStartLine != w->as.ansiTerm.selEndLine ||
|
||||
w->as.ansiTerm.selStartCol != w->as.ansiTerm.selEndCol)) {
|
||||
cleared = true;
|
||||
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;
|
||||
return true;
|
||||
}
|
||||
|
||||
w->as.ansiTerm.selStartLine = -1;
|
||||
|
|
@ -171,16 +176,8 @@ static bool clearSelectionsInTree(WidgetT *root, WidgetT *except) {
|
|||
w->as.ansiTerm.selEndCol = -1;
|
||||
w->as.ansiTerm.selecting = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (WidgetT *c = w->firstChild; c; c = c->nextSibling) {
|
||||
if (top < 64) {
|
||||
stack[top++] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cleared;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -189,23 +186,44 @@ void clearOtherSelections(WidgetT *except) {
|
|||
return;
|
||||
}
|
||||
|
||||
WidgetT *prev = sLastSelectedWidget;
|
||||
sLastSelectedWidget = except;
|
||||
|
||||
if (!prev || prev == except) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify the widget is still alive (its window still in the stack)
|
||||
WindowT *prevWin = prev->window;
|
||||
|
||||
if (!prevWin) {
|
||||
return;
|
||||
}
|
||||
|
||||
AppContextT *ctx = (AppContextT *)except->window->widgetRoot->userData;
|
||||
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
||||
WindowT *win = ctx->stack.windows[i];
|
||||
bool found = false;
|
||||
|
||||
if (win && win->widgetRoot) {
|
||||
if (clearSelectionsInTree(win->widgetRoot, except) && win != except->window) {
|
||||
RectT fullRect = {0, 0, win->contentW, win->contentH};
|
||||
widgetOnPaint(win, &fullRect);
|
||||
win->contentDirty = true;
|
||||
dvxInvalidateWindow(ctx, win);
|
||||
for (int32_t i = 0; i < ctx->stack.count; i++) {
|
||||
if (ctx->stack.windows[i] == prevWin) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (clearSelectionOnWidget(prev) && prevWin != except->window) {
|
||||
RectT fullRect = {0, 0, prevWin->contentW, prevWin->contentH};
|
||||
widgetOnPaint(prevWin, &fullRect);
|
||||
prevWin->contentDirty = true;
|
||||
dvxInvalidateWindow(ctx, prevWin);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,6 +275,58 @@ static void textEditDeleteSelection(char *buf, int32_t *pLen, int32_t *pCursor,
|
|||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wordBoundaryLeft
|
||||
// ============================================================
|
||||
//
|
||||
// From position pos, skip non-word chars left, then skip word chars left.
|
||||
// Returns the position at the start of the word (or 0).
|
||||
|
||||
static int32_t wordBoundaryLeft(const char *buf, int32_t pos) {
|
||||
if (pos <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Skip non-word characters
|
||||
while (pos > 0 && !isalnum((unsigned char)buf[pos - 1]) && buf[pos - 1] != '_') {
|
||||
pos--;
|
||||
}
|
||||
|
||||
// Skip word characters
|
||||
while (pos > 0 && (isalnum((unsigned char)buf[pos - 1]) || buf[pos - 1] == '_')) {
|
||||
pos--;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wordBoundaryRight
|
||||
// ============================================================
|
||||
//
|
||||
// From position pos, skip word chars right, then skip non-word chars right.
|
||||
// Returns the position at the end of the word (or len).
|
||||
|
||||
static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos) {
|
||||
if (pos >= len) {
|
||||
return len;
|
||||
}
|
||||
|
||||
// Skip word characters
|
||||
while (pos < len && (isalnum((unsigned char)buf[pos]) || buf[pos] == '_')) {
|
||||
pos++;
|
||||
}
|
||||
|
||||
// Skip non-word characters
|
||||
while (pos < len && !isalnum((unsigned char)buf[pos]) && buf[pos] != '_') {
|
||||
pos++;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// wgtTextArea
|
||||
// ============================================================
|
||||
|
|
@ -1217,6 +1287,32 @@ void widgetTextAreaOnKey(WidgetT *w, int32_t key, int32_t mod) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Left — word left
|
||||
if (key == (0x73 | 0x100)) {
|
||||
SEL_BEGIN();
|
||||
int32_t off = CUR_OFF();
|
||||
int32_t newOff = wordBoundaryLeft(buf, off);
|
||||
textAreaOffToRowCol(buf, newOff, pRow, pCol);
|
||||
w->as.textArea.desiredCol = *pCol;
|
||||
SEL_END();
|
||||
textAreaEnsureVisible(w, visRows, visCols);
|
||||
wgtInvalidatePaint(w);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Right — word right
|
||||
if (key == (0x74 | 0x100)) {
|
||||
SEL_BEGIN();
|
||||
int32_t off = CUR_OFF();
|
||||
int32_t newOff = wordBoundaryRight(buf, *pLen, off);
|
||||
textAreaOffToRowCol(buf, newOff, pRow, pCol);
|
||||
w->as.textArea.desiredCol = *pCol;
|
||||
SEL_END();
|
||||
textAreaEnsureVisible(w, visRows, visCols);
|
||||
wgtInvalidatePaint(w);
|
||||
return;
|
||||
}
|
||||
|
||||
// Up arrow
|
||||
if (key == (0x48 | 0x100)) {
|
||||
SEL_BEGIN();
|
||||
|
|
@ -1791,35 +1887,70 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
|
|||
}
|
||||
int32_t drawY = textY + i * font->charHeight;
|
||||
|
||||
for (int32_t j = 0; j < visCols; j++) {
|
||||
int32_t col = w->as.textArea.scrollCol + j;
|
||||
int32_t charOff = lineOff + col;
|
||||
int32_t drawX = textX + j * font->charWidth;
|
||||
// Visible range within line
|
||||
int32_t scrollCol = w->as.textArea.scrollCol;
|
||||
int32_t visStart = scrollCol;
|
||||
int32_t visEnd = scrollCol + visCols;
|
||||
int32_t textEnd = lineL; // chars in this line
|
||||
|
||||
uint32_t cfgc = fg;
|
||||
uint32_t cbgc = bg;
|
||||
// Clamp visible range to actual line content for text drawing
|
||||
int32_t drawStart = visStart < textEnd ? visStart : textEnd;
|
||||
int32_t drawEnd = visEnd < textEnd ? visEnd : textEnd;
|
||||
|
||||
// Determine selection intersection with this line
|
||||
int32_t lineSelLo = -1;
|
||||
int32_t lineSelHi = -1;
|
||||
|
||||
// Check selection — past end of line, test the newline byte
|
||||
// instead of lineOff+col (which aliases into subsequent lines)
|
||||
bool inSel = false;
|
||||
if (selLo >= 0) {
|
||||
if (col < lineL) {
|
||||
inSel = (charOff >= selLo && charOff < selHi);
|
||||
} else {
|
||||
// Selection range in column-space for this line
|
||||
if (selLo < lineOff + lineL + 1 && selHi > lineOff) {
|
||||
lineSelLo = selLo - lineOff;
|
||||
lineSelHi = selHi - lineOff;
|
||||
if (lineSelLo < 0) { lineSelLo = 0; }
|
||||
// selHi can extend past line (newline selected)
|
||||
}
|
||||
}
|
||||
|
||||
if (lineSelLo >= 0 && lineSelLo < lineSelHi) {
|
||||
// Clamp selection to visible columns for text runs
|
||||
int32_t vSelLo = lineSelLo < drawStart ? drawStart : lineSelLo;
|
||||
int32_t vSelHi = lineSelHi < drawEnd ? lineSelHi : drawEnd;
|
||||
|
||||
if (vSelLo > vSelHi) { vSelLo = vSelHi; }
|
||||
|
||||
// Before selection
|
||||
if (drawStart < vSelLo) {
|
||||
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, vSelLo - drawStart, fg, bg, true);
|
||||
}
|
||||
|
||||
// Selection (text portion)
|
||||
if (vSelLo < vSelHi) {
|
||||
drawTextN(d, ops, font, textX + (vSelLo - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelLo, vSelHi - vSelLo, colors->menuHighlightFg, colors->menuHighlightBg, true);
|
||||
}
|
||||
|
||||
// After selection
|
||||
if (vSelHi < drawEnd) {
|
||||
drawTextN(d, ops, font, textX + (vSelHi - scrollCol) * font->charWidth, drawY, buf + lineOff + vSelHi, drawEnd - vSelHi, fg, bg, true);
|
||||
}
|
||||
|
||||
// Past end of text: fill selected area with highlight bg
|
||||
int32_t nlOff = lineOff + lineL;
|
||||
inSel = (nlOff >= selLo && nlOff < selHi);
|
||||
}
|
||||
}
|
||||
bool pastEolSelected = (nlOff >= selLo && nlOff < selHi);
|
||||
|
||||
if (inSel) {
|
||||
cfgc = colors->menuHighlightFg;
|
||||
cbgc = colors->menuHighlightBg;
|
||||
}
|
||||
if (pastEolSelected && drawEnd < visEnd) {
|
||||
int32_t selPastStart = drawEnd < lineSelLo ? lineSelLo : drawEnd;
|
||||
int32_t selPastEnd = visEnd;
|
||||
|
||||
if (col < lineL) {
|
||||
drawChar(d, ops, font, drawX, drawY, buf[charOff], cfgc, cbgc, true);
|
||||
} else if (inSel) {
|
||||
rectFill(d, ops, drawX, drawY, font->charWidth, font->charHeight, cbgc);
|
||||
if (selPastStart < visStart) { selPastStart = visStart; }
|
||||
|
||||
if (selPastStart < selPastEnd) {
|
||||
rectFill(d, ops, textX + (selPastStart - scrollCol) * font->charWidth, drawY, (selPastEnd - selPastStart) * font->charWidth, font->charHeight, colors->menuHighlightBg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No selection on this line — single run
|
||||
if (drawStart < drawEnd) {
|
||||
drawTextN(d, ops, font, textX + (drawStart - scrollCol) * font->charWidth, drawY, buf + lineOff + drawStart, drawEnd - drawStart, fg, bg, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2127,24 +2258,39 @@ void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
|
|||
|
||||
bool isPassword = (w->as.textInput.inputMode == InputPasswordE);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
char displayCh = w->as.textInput.buf[charIdx];
|
||||
// Build display buffer (password masking)
|
||||
char dispBuf[256];
|
||||
int32_t dispLen = len > 255 ? 255 : len;
|
||||
|
||||
if (isPassword) {
|
||||
displayCh = '\xF9'; // CP437 bullet
|
||||
memset(dispBuf, '\xF9', dispLen); // CP437 bullet
|
||||
} else {
|
||||
memcpy(dispBuf, w->as.textInput.buf + off, dispLen);
|
||||
}
|
||||
|
||||
drawChar(d, ops, font, textX + i * font->charWidth, textY,
|
||||
displayCh, cfgc, cbgc, true);
|
||||
// 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
|
||||
|
|
@ -2427,6 +2573,52 @@ void widgetTextEditOnKey(WidgetT *w, int32_t key, int32_t mod, char *buf, int32_
|
|||
(*pCursor)++;
|
||||
}
|
||||
}
|
||||
} else if (key == (0x73 | 0x100)) {
|
||||
// Ctrl+Left — word left
|
||||
int32_t newPos = wordBoundaryLeft(buf, *pCursor);
|
||||
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
*pSelEnd = newPos;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
}
|
||||
} else if (key == (0x74 | 0x100)) {
|
||||
// Ctrl+Right — word right
|
||||
int32_t newPos = wordBoundaryRight(buf, *pLen, *pCursor);
|
||||
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
if (*pSelStart < 0) {
|
||||
*pSelStart = *pCursor;
|
||||
*pSelEnd = *pCursor;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
*pSelEnd = newPos;
|
||||
} else {
|
||||
if (pSelStart) {
|
||||
*pSelStart = -1;
|
||||
}
|
||||
|
||||
if (pSelEnd) {
|
||||
*pSelEnd = -1;
|
||||
}
|
||||
|
||||
*pCursor = newPos;
|
||||
}
|
||||
} else if (key == (0x47 | 0x100)) {
|
||||
// Home
|
||||
if (shift && pSelStart && pSelEnd) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue