/* * Kangaroo Punch MultiPlayer Game Server Mark II * Copyright (C) 2020-2021 Scott Duensing * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "textbox.h" #include "timer.h" static void textboxDel(WidgetT **widget); static void textboxFocusEvent(WidgetT *widget, uint8_t focused); static void textboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event); static void textboxKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt); static void textboxPaint(WidgetT *widget, uint8_t enabled, RectT pos); static void textboxDel(WidgetT **widget) { TextboxT *t = (TextboxT *)*widget; if (t->title) free(t->title); if (t->value) free(t->value); } static void textboxFocusEvent(WidgetT *widget, uint8_t focused) { // Make sure cursor disappears when we lose focus. if (!focused) { GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY); } } WidgetT *textboxInit(WidgetT *widget, char *title) { TextboxT *t = (TextboxT *)widget; t->base.delMethod = textboxDel; t->base.focusMethod = textboxFocusEvent; t->base.keyboardEventMethod = textboxKeyboardEvent; t->base.paintMethod = textboxPaint; t->base.mouseEventMethod = textboxMouseEvent; t->title = NULL; t->maxLength = 256; t->value = (char *)malloc(t->maxLength); t->caret = 0; t->offset = 0; t->password = 0; if (!t->value) return NULL; t->value[0] = 0; // Visible is set in textboxSetTitle textboxTitleSet(t, title); // We need to blink a cursor. GUI_SET_FLAG(widget, WIDGET_FLAG_ALWAYS_PAINT); return widget; } static void textboxKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) { TextboxT *t = (TextboxT *)widget; uint16_t x; uint16_t y; char *temp; (void)scancode; (void)shift; (void)control; (void)alt; if (extended) { switch (ascii) { case 71: // HOME t->caret = 0; t->offset = 0; break; case 79: // END // We cheat and just reset the value. That moves the offset and cursor. temp = strdup(t->value); textboxValueSet(t, temp); free(temp); break; case 75: // LEFT // Can we move left in the value? if (t->caret + t->offset > 0) { // Is the caret on the left edge of the box? if (t->caret == 0) { // Can we move the string offset? if (t->offset > 0) { t->offset--; } } else { // Move the caret left. t->caret--; } } break; case 77: // RIGHT // Can we move right in the value? if (t->caret + t->offset < strlen(t->value)) { // Is the caret on the right edge of the box? if (t->caret == t->visible - 1) { // Can we move the string offset? if (t->offset < strlen(t->value)) { t->offset++; } } else { // Move the caret right. t->caret++; } } break; case 83: // DELETE // Is there more data to the right in the value? if (t->caret + t->offset < strlen(t->value)) { // Delete character under caret. for (x = t->caret + t->offset; xvalue); x++) { t->value[x] = t->value[x+1]; } } break; } // switch } else { // extended switch (ascii) { case 8: // BACKSPACE // Can we delete left in the value? if (t->caret + t->offset > 0) { // Is the caret on the left edge of the box? if (t->caret == 0) { // Can we move the string offset? if (t->offset > 0) { t->offset--; } } else { // Move the caret left. t->caret--; } // Delete the character to the left of the caret. for (x = t->caret + t->offset; xvalue); x++) { t->value[x] = t->value[x+1]; } } break; default: // Other keys if (ascii >= 32 && ascii <= 126) { // Remember length so we can zero terminate this after the edit. y = strlen(t->value); // Insert character, if room. if (y < (size_t)t->maxLength - 1) { // Move existing characters over, if needed. if (t->caret + t->offset < y) { for (x=y + 1; x>t->caret + t->offset; x--) { t->value[x] = t->value[x-1]; } } // Place typed character at caret. t->value[t->caret + t->offset] = ascii; y++; // Fix zero termination. t->value[y] = 0; // Is the caret on the right edge of the box? if (t->caret == t->visible - 1) { // Can we move the string offset? if (t->offset < y) { t->offset++; } } else { // Move the caret right. t->caret++; } } } break; } // switch } // extended } void textboxLengthMaxSet(TextboxT *textbox, uint16_t length) { char *temp = strdup(textbox->value); free(textbox->value); textbox->maxLength = length + 1; textbox->value = (char *)malloc(textbox->maxLength); textboxValueSet(textbox, temp); free(temp); } static void textboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) { TextboxT *t = (TextboxT *)widget; RectT textArea; (void)x; (void)y; (void)mouse; // Allow dragging/positioning text cursor with mouse. if (event == MOUSE_EVENT_LEFT_HOLD) { // Where's the text display? textArea.x = (strlen(t->title) * fontWidthGet(__guiFont)) + __guiMetric[METRIC_TEXTBOX_PADDING] + 2 + __guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING]; textArea.y = 2 + __guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING]; textArea.w = textArea.x + t->visible * fontWidthGet(__guiFont); textArea.h = textArea.y + fontHeightGet(__guiFont); // Is the pointer over it? if (x >= textArea.x && x < textArea.w && y >= textArea.y && y < textArea.h) { // Move caret. t->caret = (x - textArea.x) / fontWidthGet(__guiFont); // Did we go too far? if (t->caret + t->offset > strlen(t->value)) { t->caret = strlen(t->value) - t->offset; } // Ensure we redraw. GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY); } } } TextboxT *textboxNew(uint16_t x, uint16_t y, uint16_t w, char *title) { TextboxT *textbox = (TextboxT *)malloc(sizeof(TextboxT)); WidgetT *widget = NULL; uint16_t h = fontHeightGet(__guiFont) + 4 + (__guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING] * 2); if (!textbox) return NULL; widget = widgetInit(W(textbox), MAGIC_TEXTBOX, x, y, w, h, 0, 0, 0, 0); if (!widget) { free(textbox); return NULL; } textbox = (TextboxT *)textboxInit((WidgetT *)textbox, title); return textbox; } static void textboxPaint(WidgetT *widget, uint8_t enabled, RectT pos) { TextboxT *t = (TextboxT *)widget; char *draw = NULL; uint16_t labelWidth; uint16_t valueWidth; uint16_t caretPos; uint16_t textX; uint16_t textY; char cursor[2] = { 0xb1, 0 }; if (GUI_GET_FLAG(widget, WIDGET_FLAG_DIRTY) || guiFocusGet() == widget) { labelWidth = (strlen(t->title) * fontWidthGet(__guiFont)) + __guiMetric[METRIC_TEXTBOX_PADDING]; valueWidth = (t->visible * fontWidthGet(__guiFont)) + (__guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING] * 2); // Draw title. fontRender(__guiFont, t->title, __guiColor[COLOR_TEXTBOX_TEXT], __guiColor[COLOR_WINDOW_BACKGROUND], pos.x, pos.y + 2 + __guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING]); // Draw outline of textbox. surfaceHighlightFrameDraw( pos.x + labelWidth, pos.y, pos.x + labelWidth + valueWidth + 2, pos.y + pos.h, __guiColor[COLOR_TEXTBOX_SHADOW], __guiColor[COLOR_TEXTBOX_HIGHLIGHT]); surfaceRectangleDraw( pos.x + labelWidth + 1, pos.y + 1, pos.x + labelWidth + valueWidth + 1, pos.y + pos.h - 1, __guiColor[COLOR_WINDOW_BACKGROUND]); // Draw background. surfaceRectangleFilledDraw(pos.x + labelWidth + 2, pos.y + 2, pos.x + labelWidth + valueWidth, pos.y + pos.h - 2, __guiColor[COLOR_TEXTBOX_BACKGROUND]); // Where's the text display start? textX = pos.x + labelWidth + 2 + __guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING]; textY = pos.y + 2 + __guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING]; // Draw value. draw = strdup(&t->value[t->offset]); if (strlen(draw) > t->visible) draw[t->visible] = 0; if (t->password != 0) memset(draw, t->password, strlen(draw)); fontRender(__guiFont, draw, __guiColor[COLOR_TEXTBOX_TEXT], __guiColor[COLOR_TEXTBOX_BACKGROUND], textX, textY); free(draw); // Draw cursor. if (guiFocusGet() == widget && guiTimerQuarterSecondOn()) { caretPos = textX + fontWidthGet(__guiFont) * t->caret; fontRender(__guiFont, cursor, __guiColor[COLOR_TEXTBOX_TEXT], __guiColor[COLOR_TEXTBOX_BACKGROUND], caretPos, textY); } GUI_CLEAR_FLAG(widget, WIDGET_FLAG_DIRTY); } } void textboxPasswordCharacterSet(TextboxT *textbox, char c) { textbox->password = c; } void textboxTitleSet(TextboxT *textbox, char *title) { if (textbox->title) free(textbox->title); textbox->title = strdup(title); // Figure out how many characters we have room to display. textbox->visible = (textbox->base.pos.w - ((strlen(title) * fontWidthGet(__guiFont)) + __guiMetric[METRIC_TEXTBOX_PADDING] + 4 + (__guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING] * 2))) / fontWidthGet(__guiFont); GUI_SET_FLAG((WidgetT *)textbox, WIDGET_FLAG_DIRTY); } char *textboxValueGet(TextboxT *textbox) { return textbox->value; } void textboxValueSet(TextboxT *textbox, char *value) { // Copy it & truncate if needed. strncpy(textbox->value, value, textbox->maxLength - 1); // Is this longer than the area we have to display it in? if (strlen(textbox->value) > (size_t)(textbox->visible - 1)) { textbox->offset = strlen(textbox->value) - (textbox->visible - 1); } // Set caret position. textbox->caret = strlen(&textbox->value[textbox->offset]); GUI_SET_FLAG((WidgetT *)textbox, WIDGET_FLAG_DIRTY); }