// 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); } } } }