kpmpgsmkii/client/src/gui/textbox.c
2022-03-05 19:06:31 -06:00

348 lines
10 KiB
C

/*
* 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 <https://www.gnu.org/licenses/>.
*
*/
#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; x<strlen(t->value); 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; x<strlen(t->value); 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);
}