348 lines
10 KiB
C
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);
|
|
}
|