DVX_GUI/dvx/widgets/widgetTextInput.c

272 lines
7.8 KiB
C

// widgetTextInput.c — TextInput and TextArea widgets
#include "widgetInternal.h"
// ============================================================
// wgtTextArea
// ============================================================
WidgetT *wgtTextArea(WidgetT *parent, int32_t maxLen) {
WidgetT *w = widgetAlloc(parent, WidgetTextAreaE);
if (w) {
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
w->as.textArea.buf = (char *)malloc(bufSize);
w->as.textArea.bufSize = bufSize;
if (w->as.textArea.buf) {
w->as.textArea.buf[0] = '\0';
}
}
return w;
}
// ============================================================
// widgetTextAreaDestroy
// ============================================================
void widgetTextAreaDestroy(WidgetT *w) {
free(w->as.textArea.buf);
}
// ============================================================
// wgtTextInput
// ============================================================
WidgetT *wgtTextInput(WidgetT *parent, int32_t maxLen) {
WidgetT *w = widgetAlloc(parent, WidgetTextInputE);
if (w) {
int32_t bufSize = maxLen > 0 ? maxLen + 1 : 256;
w->as.textInput.buf = (char *)malloc(bufSize);
w->as.textInput.bufSize = bufSize;
if (w->as.textInput.buf) {
w->as.textInput.buf[0] = '\0';
}
w->weight = 100; // text inputs stretch by default
}
return w;
}
// ============================================================
// widgetTextInputDestroy
// ============================================================
void widgetTextInputDestroy(WidgetT *w) {
free(w->as.textInput.buf);
}
// ============================================================
// widgetTextInputGetText
// ============================================================
const char *widgetTextInputGetText(const WidgetT *w) {
return w->as.textInput.buf ? w->as.textInput.buf : "";
}
// ============================================================
// widgetTextInputSetText
// ============================================================
void widgetTextInputSetText(WidgetT *w, const char *text) {
if (w->as.textInput.buf) {
strncpy(w->as.textInput.buf, text, w->as.textInput.bufSize - 1);
w->as.textInput.buf[w->as.textInput.bufSize - 1] = '\0';
w->as.textInput.len = (int32_t)strlen(w->as.textInput.buf);
w->as.textInput.cursorPos = w->as.textInput.len;
w->as.textInput.scrollOff = 0;
}
}
// ============================================================
// widgetTextInputCalcMinSize
// ============================================================
void widgetTextInputCalcMinSize(WidgetT *w, const BitmapFontT *font) {
w->calcMinW = font->charWidth * 8 + TEXT_INPUT_PAD * 2;
w->calcMinH = font->charHeight + TEXT_INPUT_PAD * 2;
}
// ============================================================
// widgetTextInputOnMouse
// ============================================================
void widgetTextInputOnMouse(WidgetT *w, WidgetT *root, int32_t vx, int32_t vy) {
(void)vy;
w->focused = true;
// Place cursor at click position
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;
}
w->as.textInput.cursorPos = charPos;
}
// ============================================================
// widgetTextEditOnKey — shared text editing logic
// ============================================================
void widgetTextEditOnKey(WidgetT *w, int32_t key, char *buf, int32_t bufSize, int32_t *pLen, int32_t *pCursor, int32_t *pScrollOff) {
if (key >= 32 && key < 127) {
// Printable character
if (*pLen < bufSize - 1) {
int32_t pos = *pCursor;
memmove(buf + pos + 1, buf + pos, *pLen - pos + 1);
buf[pos] = (char)key;
(*pLen)++;
(*pCursor)++;
if (w->onChange) {
w->onChange(w);
}
}
} else if (key == 8) {
// Backspace
if (*pCursor > 0) {
int32_t pos = *pCursor;
memmove(buf + pos - 1, buf + pos, *pLen - pos + 1);
(*pLen)--;
(*pCursor)--;
if (w->onChange) {
w->onChange(w);
}
}
} else if (key == (0x4B | 0x100)) {
// Left arrow
if (*pCursor > 0) {
(*pCursor)--;
}
} else if (key == (0x4D | 0x100)) {
// Right arrow
if (*pCursor < *pLen) {
(*pCursor)++;
}
} else if (key == (0x47 | 0x100)) {
// Home
*pCursor = 0;
} else if (key == (0x4F | 0x100)) {
// End
*pCursor = *pLen;
} else if (key == (0x53 | 0x100)) {
// Delete
if (*pCursor < *pLen) {
int32_t pos = *pCursor;
memmove(buf + pos, buf + pos + 1, *pLen - pos);
(*pLen)--;
if (w->onChange) {
w->onChange(w);
}
}
}
// Adjust scroll offset to keep cursor visible
AppContextT *ctx = (AppContextT *)w->window->widgetRoot->userData;
const BitmapFontT *font = &ctx->font;
int32_t fieldW = w->w;
if (w->type == WidgetComboBoxE) {
fieldW -= DROPDOWN_BTN_WIDTH;
}
int32_t visibleChars = (fieldW - TEXT_INPUT_PAD * 2) / font->charWidth;
if (*pCursor < *pScrollOff) {
*pScrollOff = *pCursor;
}
if (*pCursor >= *pScrollOff + visibleChars) {
*pScrollOff = *pCursor - visibleChars + 1;
}
wgtInvalidate(w);
}
// ============================================================
// widgetTextInputOnKey
// ============================================================
void widgetTextInputOnKey(WidgetT *w, int32_t key) {
if (!w->as.textInput.buf) {
return;
}
widgetTextEditOnKey(w, key, w->as.textInput.buf, w->as.textInput.bufSize,
&w->as.textInput.len, &w->as.textInput.cursorPos,
&w->as.textInput.scrollOff);
}
// ============================================================
// widgetTextInputPaint
// ============================================================
void widgetTextInputPaint(WidgetT *w, DisplayT *d, const BlitOpsT *ops, const BitmapFontT *font, const ColorSchemeT *colors) {
uint32_t fg = w->fgColor ? w->fgColor : colors->contentFg;
uint32_t bg = w->bgColor ? w->bgColor : colors->contentBg;
// Sunken border
BevelStyleT bevel;
bevel.highlight = colors->windowShadow;
bevel.shadow = colors->windowHighlight;
bevel.face = bg;
bevel.width = 2;
drawBevel(d, ops, w->x, w->y, w->w, w->h, &bevel);
// Draw text
if (w->as.textInput.buf) {
int32_t textX = w->x + TEXT_INPUT_PAD;
int32_t textY = w->y + (w->h - font->charHeight) / 2;
int32_t maxChars = (w->w - TEXT_INPUT_PAD * 2) / font->charWidth;
int32_t off = w->as.textInput.scrollOff;
int32_t len = w->as.textInput.len - off;
if (len > maxChars) {
len = maxChars;
}
for (int32_t i = 0; i < len; i++) {
drawChar(d, ops, font, textX + i * font->charWidth, textY,
w->as.textInput.buf[off + i],
fg, bg, true);
}
// Draw cursor
if (w->focused) {
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);
}
}
}
}