272 lines
7.8 KiB
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);
|
|
}
|
|
}
|
|
}
|
|
}
|