Text editing cursors now blink.

This commit is contained in:
Scott Duensing 2026-03-17 23:59:16 -05:00
parent 786353fa08
commit e4b44d08c1
6 changed files with 46 additions and 8 deletions

View file

@ -1638,6 +1638,7 @@ bool dvxUpdate(AppContextT *ctx) {
dispatchEvents(ctx);
updateTooltip(ctx);
pollAnsiTermWidgets(ctx);
wgtUpdateCursorBlink();
ctx->frameCount++;

View file

@ -876,6 +876,11 @@ int32_t wgtAnsiTermRepaint(WidgetT *w, int32_t *outY, int32_t *outH);
// function call.
struct AppContextT *wgtGetContext(const WidgetT *w);
// Update text cursor blink state. Call once per frame from dvxUpdate.
// Toggles the cursor visibility at 250ms intervals, matching the ANSI
// terminal cursor rate.
void wgtUpdateCursorBlink(void);
// Mark a widget as needing both re-layout (measure + position) and
// repaint. Propagates upward to ancestors since a child's size change
// can affect parent layout. Use this after structural changes (adding/

View file

@ -404,8 +404,8 @@ void widgetComboBoxPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
drawTextN(d, ops, font, textX, textY, w->as.comboBox.buf + off, len, fg, bg, true);
}
// Draw cursor
if (w->focused && w->enabled && !w->as.comboBox.open) {
// 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 &&

View file

@ -173,6 +173,7 @@ static inline void drawTextAccelEmbossed(DisplayT *d, const BlitOpsT *ops, const
// is that these are truly global — but since the GUI is single-threaded
// and only one mouse can exist, this is safe.
extern bool sCursorBlinkOn; // text cursor blink phase (toggled by wgtUpdateCursorBlink)
extern bool sDebugLayout;
extern WidgetT *sClosedPopup; // popup that was just closed (prevents immediate reopen)
extern WidgetT *sFocusedWidget; // currently focused widget across all windows

View file

@ -436,8 +436,8 @@ void widgetSpinnerPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bitm
drawTextN(d, ops, font, textX, textY, &w->as.spinner.buf[off], len, fg, bg, true);
}
// Cursor
if (w->focused) {
// 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) {

View file

@ -71,6 +71,8 @@
#define TEXTAREA_MIN_COLS 20
#define CLIPBOARD_MAX 4096
#define DBLCLICK_TICKS (CLOCKS_PER_SEC / 2)
// Match the ANSI terminal cursor blink rate (CURSOR_MS in widgetAnsiTerm.c)
#define CURSOR_BLINK_MS 250
// ============================================================
// Prototypes
@ -104,6 +106,15 @@ static int32_t wordBoundaryRight(const char *buf, int32_t len, int32_t pos);
static char sClipboard[CLIPBOARD_MAX];
static int32_t sClipboardLen = 0;
// ============================================================
// Cursor blink state
// ============================================================
// Shared across all text-editing widgets (TextInput, TextArea,
// ComboBox, Spinner). Matches the ANSI terminal's 250ms rate.
bool sCursorBlinkOn = true;
static clock_t sCursorBlinkTime = 0;
void clipboardCopy(const char *text, int32_t len) {
if (!text || len <= 0) {
@ -129,6 +140,26 @@ const char *clipboardGet(int32_t *outLen) {
}
// ============================================================
// Cursor blink
// ============================================================
void wgtUpdateCursorBlink(void) {
clock_t now = clock();
clock_t interval = (clock_t)CURSOR_BLINK_MS * CLOCKS_PER_SEC / 1000;
if ((now - sCursorBlinkTime) >= interval) {
sCursorBlinkTime = now;
sCursorBlinkOn = !sCursorBlinkOn;
// Invalidate the focused widget so its cursor redraws
if (sFocusedWidget) {
wgtInvalidatePaint(sFocusedWidget);
}
}
}
// ============================================================
// Multi-click tracking
// ============================================================
@ -2105,8 +2136,8 @@ void widgetTextAreaPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bit
}
}
// Draw cursor
if (w->focused) {
// Draw cursor (blinks at same rate as terminal cursor)
if (w->focused && sCursorBlinkOn) {
int32_t curDrawCol = w->as.textArea.cursorCol - w->as.textArea.scrollCol;
int32_t curDrawRow = w->as.textArea.cursorRow - w->as.textArea.scrollRow;
@ -2457,8 +2488,8 @@ void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const Bi
drawTextN(d, ops, font, textX, textY, dispBuf, dispLen, fg, bg, true);
}
// Draw cursor
if (w->focused && w->enabled) {
// 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 &&