377 lines
12 KiB
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;
|
|
}
|