/* * 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 "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; }