kpmpgsmkii/client/src/gui/updown.c

377 lines
12 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 "updown.h"
#include "timer.h"
// 32 bit ranges from -2,147,483,648 to 2,147,483,647.
// Maximum character cells needed is 11 (10 digits + negative sign).
#define UPDOWN_MAX_DIGITS 11
// These are used in several places, so we made them global and just update as needed. See updownSizesRecalculate.
static uint16_t _labelWidth = 0;
static uint16_t _valueWidth = 0;
static uint16_t _arrowWidth = 0;
static uint16_t _arrowStart = 0;
static uint16_t _halfFont = 0;
static void updownDel(WidgetT **widget);
static void updownFocusEvent(WidgetT *widget, uint8_t focused);
static void updownMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event);
static void updownKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt);
static void updownPaint(WidgetT *widget, uint8_t enabled, RectT pos);
static void updownSizesRecalculate(UpdownT *updown);
static void updownVisibleSet(UpdownT *updown);
static void updownDel(WidgetT **widget) {
UpdownT *u = (UpdownT *)*widget;
if (u->title) free(u->title);
}
static void updownFocusEvent(WidgetT *widget, uint8_t focused) {
// Make sure cursor disappears when we lose focus.
if (!focused) {
GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
}
}
WidgetT *updownInit(WidgetT *widget, int32_t min, int32_t max, int32_t step, char *title) {
UpdownT *u = (UpdownT *)widget;
u->base.delMethod = updownDel;
u->base.focusMethod = updownFocusEvent;
u->base.paintMethod = updownPaint;
u->base.keyboardEventMethod = updownKeyboardEvent;
u->base.mouseEventMethod = updownMouseEvent;
u->maximum = INT32_MAX;
u->minimum = INT32_MIN;
u->title = NULL;
u->value = 0;
updownTitleSet(u, title); // Needs to be set first so updownSizesRecalculate works.
updownMinimumSet(u, min);
updownMaximumSet(u, max);
updownStepSet(u, step);
return widget;
}
static void updownKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) {
UpdownT *u = (UpdownT *)widget;
int32_t temp;
static uint8_t willBeNegative = 0;
(void)scancode;
(void)shift;
(void)control;
(void)alt;
if (extended) {
switch (ascii) {
case 71: // HOME
u->value = u->minimum;
willBeNegative = 0;
break;
case 79: // END
u->value = u->maximum;
willBeNegative = 0;
break;
case 73: // PAGEUP
u->value += u->step;
if (u->value > u->maximum) u->value = u->maximum;
willBeNegative = 0;
break;
case 81: // PAGEDOWN
u->value -= u->step;
if (u->value < u->minimum) u->value = u->minimum;
willBeNegative = 0;
break;
case 72: // UP
if (u->value < u->maximum) u->value++;
willBeNegative = 0;
break;
case 80: // DOWN
if (u->value > u->minimum) u->value--;
willBeNegative = 0;
break;
case 83: // DELETE
u->value = (int)(u->value * 0.1);
willBeNegative = 0;
break;
} // switch
} else { // extended
switch (ascii) {
case 8: // BACKSPACE
u->value = (int)(u->value * 0.1);
willBeNegative = 0;
break;
case '-': // Leading Minus
if (u->value == 0) willBeNegative = 1;
break;
default: // Number keys
if (ascii >= '0' && ascii <= '9') {
temp = u->value * 10 + (ascii - '0');
if (willBeNegative) u->value = -u->value;
if (temp <= u->maximum && temp >= u->minimum) u->value = temp;
}
willBeNegative = 0;
break;
} // switch
} // extended
}
void updownMaximumSet(UpdownT *updown, int32_t maximum) {
if (maximum > updown->minimum) {
updown->maximum = maximum;
updownVisibleSet(updown);
}
}
void updownMinimumSet(UpdownT *updown, int32_t minimum) {
if (minimum < updown->maximum) {
updown->minimum = minimum;
updownVisibleSet(updown);
}
}
static void updownMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) {
UpdownT *u = (UpdownT *)widget;
uint16_t o;
(void)x;
(void)y;
(void)mouse;
if (event == MOUSE_EVENT_LEFT_UP) {
updownSizesRecalculate(u);
_arrowStart += 1 + _guiMetric[METRIC_UPDOWN_HORIZONTAL_PADDING]; // Left of up arrow
o = 1 + _guiMetric[METRIC_UPDOWN_VERTICAL_PADDING]; // Top of arrows
// Did they click the up arrow?
if (x >= _arrowStart && x <= _arrowStart + fontHeightGet(_guiFont) && y >= o && y <= o + fontHeightGet(_guiFont)) {
// Can we increment?
if (u->value < u->maximum) {
// Clamp.
if (u->maximum - u->value < u->step) {
u->value = u->maximum;
} else {
u->value += u->step;
}
GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
}
}
// Did they click the down arrow?
_arrowStart += fontHeightGet(_guiFont) + 1; // Left of down arrow
if (x >= _arrowStart && x <= _arrowStart + fontHeightGet(_guiFont) && y >= o && y <= o + fontHeightGet(_guiFont)) {
// Can we decrement?
if (u->value > u->minimum) {
// Clamp.
if (u->value - u->minimum < u->step) {
u->value = u->minimum;
} else {
u->value -= u->step;
}
GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
}
}
}
}
UpdownT *updownNew(uint16_t x, uint16_t y, int32_t min, int32_t max, int32_t step, char *title) {
UpdownT *updown = (UpdownT *)malloc(sizeof(UpdownT));
WidgetT *widget = NULL;
uint16_t h = fontHeightGet(_guiFont) + 4 + (_guiMetric[METRIC_UPDOWN_VERTICAL_PADDING] * 2);
if (!updown) return NULL;
// Width is set in SetVisible.
widget = widgetInit(W(updown), MAGIC_UPDOWN, x, y, min, h, 0, 0, 0, 0);
if (!widget) {
free(updown);
return NULL;
}
updown = (UpdownT *)updownInit((WidgetT *)updown, min, max, step, title);
return updown;
}
static void updownPaint(WidgetT *widget, uint8_t enabled, RectT pos) {
UpdownT *u = (UpdownT *)widget;
uint8_t i;
uint16_t textX;
uint16_t textY;
uint16_t o;
ColorT color;
char draw[UPDOWN_MAX_DIGITS + 1];
char cursor[2] = { 0xb1, 0 };
if (GUI_GET_FLAG(widget, WIDGET_FLAG_DIRTY) || guiFocusGet() == widget) {
updownSizesRecalculate(u);
_arrowStart += pos.x; // This method expects the start in screen space, not widget space.
// Draw title.
fontRender(_guiFont, u->title, _guiColor[COLOR_UPDOWN_TEXT], _guiColor[COLOR_WINDOW_BACKGROUND], pos.x, pos.y + 2 + _guiMetric[METRIC_UPDOWN_VERTICAL_PADDING]);
// Draw outline of text.
surfaceHighlightFrameDraw( pos.x + _labelWidth, pos.y, pos.x + _labelWidth + _valueWidth + 2, pos.y + pos.h, _guiColor[COLOR_UPDOWN_SHADOW], _guiColor[COLOR_UPDOWN_HIGHLIGHT]);
surfaceRectangleDraw( pos.x + _labelWidth + 1, pos.y + 1, pos.x + _labelWidth + _valueWidth + 1, pos.y + pos.h - 1, _guiColor[COLOR_WINDOW_BACKGROUND]);
// Draw text background.
surfaceRectangleFilledDraw(pos.x + _labelWidth + 2, pos.y + 2, pos.x + _labelWidth + _valueWidth, pos.y + pos.h - 2, _guiColor[COLOR_UPDOWN_BACKGROUND]);
// Draw arrows outline
surfaceHighlightFrameDraw(_arrowStart, pos.y, _arrowStart + _arrowWidth + 1, pos.y + pos.h, _guiColor[COLOR_UPDOWN_SHADOW], _guiColor[COLOR_UPDOWN_HIGHLIGHT]);
// Draw arrows background
surfaceRectangleFilledDraw(_arrowStart + 1, pos.y + 1, _arrowStart + _arrowWidth, pos.y + pos.h - 1, _guiColor[COLOR_UPDOWN_ARROWS_BACKGROUND]);
// Draw up arrow
_arrowStart += _halfFont + 1 + _guiMetric[METRIC_UPDOWN_HORIZONTAL_PADDING]; // Center of up arrow
o = pos.y + 1 + _guiMetric[METRIC_UPDOWN_VERTICAL_PADDING]; // Top of up arrow
color = u->value < u->maximum ? _guiColor[COLOR_UPDOWN_ARROWS_ACTIVE] : _guiColor[COLOR_UPDOWN_ARROWS_INACTIVE];
for (i=0; i<=fontHeightGet(_guiFont); i++) {
surfaceLineDraw(_arrowStart - i * 0.5, o + i, _arrowStart + i * 0.5, o + i, color);
}
surfaceLineDraw(_arrowStart, o, _arrowStart + _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_SHADOW]);
surfaceLineDraw(_arrowStart - _halfFont, o + fontHeightGet(_guiFont), _arrowStart + _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_SHADOW]);
surfaceLineDraw(_arrowStart, o, _arrowStart - _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_HIGHLIGHT]);
// Draw down arrow
_arrowStart += fontHeightGet(_guiFont) + 1; // Center of down arrow
o += fontHeightGet(_guiFont); // Bottom of down arrow
color = u->value > u->minimum ? _guiColor[COLOR_UPDOWN_ARROWS_ACTIVE] : _guiColor[COLOR_UPDOWN_ARROWS_INACTIVE];
for (i=0; i<=fontHeightGet(_guiFont); i++) {
surfaceLineDraw(_arrowStart - i * 0.5, o - i, _arrowStart + i * 0.5, o - i, color);
}
surfaceLineDraw(_arrowStart, o, _arrowStart + _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_SHADOW]);
surfaceLineDraw(_arrowStart - _halfFont, o - fontHeightGet(_guiFont), _arrowStart + _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_HIGHLIGHT]);
surfaceLineDraw(_arrowStart, o, _arrowStart - _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_HIGHLIGHT]);
// Where's the text display start?
textX = pos.x + _labelWidth + 2 + _guiMetric[METRIC_UPDOWN_HORIZONTAL_PADDING];
textY = pos.y + 2 + _guiMetric[METRIC_UPDOWN_VERTICAL_PADDING];
// Draw value.
snprintf(draw, UPDOWN_MAX_DIGITS, "%ld", (long)u->value); // Weird typecasting prevents warnings in both DOS and Linux (32 and 64 bit)
textX += (u->visible - strlen(draw)) * fontWidthGet(_guiFont);
fontRender(_guiFont, draw, _guiColor[COLOR_UPDOWN_TEXT], _guiColor[COLOR_UPDOWN_BACKGROUND], textX, textY);
// Draw cursor.
if (guiFocusGet() == widget && timerQuarterSecondOn) {
textX += (strlen(draw) - 1) * fontWidthGet(_guiFont);
fontRender(_guiFont, cursor, _guiColor[COLOR_TEXTBOX_TEXT], _guiColor[COLOR_TEXTBOX_BACKGROUND], textX, textY);
}
GUI_CLEAR_FLAG(widget, WIDGET_FLAG_DIRTY);
}
}
static void updownSizesRecalculate(UpdownT *updown) {
// Label [ 0] ^v
// ====== - labelWidth, including space between label and text box.
// === - valueWidth (digits, padding, does not include border).
// === - arrowWidth (arrows, padding, 1px between arrows, and border).
// = - arrowStart (start of box surrounding arrows, including border).
// Set global stuff that isn't actually part of the widget state.
_halfFont = fontHeightGet(_guiFont) * 0.5;
_labelWidth = (strlen(updown->title) * fontWidthGet(_guiFont)) + _guiMetric[METRIC_UPDOWN_PADDING];
_valueWidth = (updown->visible * fontWidthGet(_guiFont)) + (_guiMetric[METRIC_UPDOWN_HORIZONTAL_PADDING] * 2);
_arrowWidth = (fontHeightGet(_guiFont) * 2) + 1 + 2 + (_guiMetric[METRIC_UPDOWN_HORIZONTAL_PADDING] * 2); // Arrow width = font height
_arrowStart = _labelWidth + _valueWidth + 2 + _guiMetric[METRIC_UPDOWN_ARROW_PADDING];
}
void updownStepSet(UpdownT *updown, int32_t step) {
updown->step = step;
}
void updownTitleSet(UpdownT *updown, char *title) {
if (updown->title) free(updown->title);
updown->title = strdup(title);
updownVisibleSet(updown);
GUI_SET_FLAG((WidgetT *)updown, WIDGET_FLAG_DIRTY);
}
int32_t updownValueGet(UpdownT *updown) {
return updown->value;
}
void updownValueSet(UpdownT *updown, int32_t value) {
if (value >= updown->minimum && value <= updown->maximum) {
updown->value = value;
GUI_SET_FLAG((WidgetT *)updown, WIDGET_FLAG_DIRTY);
}
}
static void updownVisibleSet(UpdownT *updown) {
char digits[UPDOWN_MAX_DIGITS + 1];
int8_t maxWidth;
int8_t minWidth;
// Calculate how many characters we need to be able to display.
maxWidth = snprintf(digits, UPDOWN_MAX_DIGITS, "%ld", (long)updown->maximum); // Weird typecasting prevents warnings in both DOS and Linux (32 and 64 bit)
minWidth = snprintf(digits, UPDOWN_MAX_DIGITS, "%ld", (long)updown->minimum); // Weird typecasting prevents warnings in both DOS and Linux (32 and 64 bit)
updown->visible = maxWidth > minWidth ? maxWidth : minWidth;
// Calculate width of the widget.
updownSizesRecalculate(updown);
updown->base.pos.w = _arrowStart + _arrowWidth + 1;
}