From 9acf435c52283cc5cf27946f5885794401a90d5a Mon Sep 17 00:00:00 2001 From: Scott Duensing Date: Sat, 30 Oct 2021 20:00:16 -0500 Subject: [PATCH] Basic UpDown widget added. --- client/client.pro | 2 + client/src/gui/gui.c | 11 ++ client/src/gui/gui.h | 13 +- client/src/gui/memory.h | 3 + client/src/gui/textbox.c | 2 +- client/src/gui/updown.c | 279 +++++++++++++++++++++++++++++++++++++++ client/src/gui/updown.h | 50 +++++++ client/src/main.c | 6 +- 8 files changed, 363 insertions(+), 3 deletions(-) create mode 100644 client/src/gui/updown.c create mode 100644 client/src/gui/updown.h diff --git a/client/client.pro b/client/client.pro index acc559f..e3ca7aa 100644 --- a/client/client.pro +++ b/client/client.pro @@ -44,6 +44,7 @@ INCLUDEPATH += \ HEADERS = \ $$LINUX_HEADERS \ + src/gui/updown.h \ src/thirdparty/stb_ds.h \ src/thirdparty/stb_image.h \ src/thirdparty/memwatch/memwatch.h \ @@ -73,6 +74,7 @@ HEADERS = \ SOURCES = \ $$LINUX_SOURCES \ + src/gui/updown.c \ src/thirdparty/memwatch/memwatch.c \ src/gui/memory.c \ src/gui/array.c \ diff --git a/client/src/gui/gui.c b/client/src/gui/gui.c index c65e3da..077045d 100644 --- a/client/src/gui/gui.c +++ b/client/src/gui/gui.c @@ -478,6 +478,10 @@ DesktopT *guiStartup(void) { _guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING] = 2; _guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING] = 2; _guiMetric[METRIC_TEXTBOX_PADDING] = 6; // Matches other label / widget padding. + _guiMetric[METRIC_UPDOWN_HORIZONTAL_PADDING] = 2; + _guiMetric[METRIC_UPDOWN_VERTICAL_PADDING] = 2; + _guiMetric[METRIC_UPDOWN_PADDING] = 6; // Matches other label / widget padding. + _guiMetric[METRIC_UPDOWN_ARROW_PADDING] = 2; _guiColor[COLOR_BUTTON_BACKGROUND] = vbeMakeColor(168, 168, 168); _guiColor[COLOR_BUTTON_HIGHLIGHT] = vbeMakeColor(248, 252, 248); @@ -510,6 +514,13 @@ DesktopT *guiStartup(void) { _guiColor[COLOR_TEXTBOX_SHADOW] = vbeMakeColor( 0, 0, 0); _guiColor[COLOR_TEXTBOX_TEXT] = vbeMakeColor( 0, 0, 0); _guiColor[COLOR_TEXTBOX_BACKGROUND] = vbeMakeColor(248, 252, 248); + _guiColor[COLOR_UPDOWN_HIGHLIGHT] = vbeMakeColor(248, 252, 248); + _guiColor[COLOR_UPDOWN_SHADOW] = vbeMakeColor( 0, 0, 0); + _guiColor[COLOR_UPDOWN_TEXT] = vbeMakeColor( 0, 0, 0); + _guiColor[COLOR_UPDOWN_BACKGROUND] = vbeMakeColor(248, 252, 248); + _guiColor[COLOR_UPDOWN_ARROWS_BACKGROUND] = vbeMakeColor(124, 126, 124); + _guiColor[COLOR_UPDOWN_ARROWS_ACTIVE] = vbeMakeColor(168, 168, 168); + _guiColor[COLOR_UPDOWN_ARROWS_INACTIVE] = vbeMakeColor( 80, 84, 80); _guiFont = fontLoad("vga8x14.dat"); diff --git a/client/src/gui/gui.h b/client/src/gui/gui.h index 67b483a..73ddbcd 100644 --- a/client/src/gui/gui.h +++ b/client/src/gui/gui.h @@ -49,7 +49,7 @@ enum MagicE { MAGIC_PICTURE, MAGIC_FRAME, MAGIC_TEXTBOX, - //MAGIC_UPDOWN, + MAGIC_UPDOWN, //MAGIC_LISTBOX, //MAGIC_TERMINAL, MAGIC_COUNT @@ -68,6 +68,10 @@ enum MetricE { METRIC_TEXTBOX_HORIZONTAL_PADDING, METRIC_TEXTBOX_VERTICAL_PADDING, METRIC_TEXTBOX_PADDING, + METRIC_UPDOWN_PADDING, + METRIC_UPDOWN_ARROW_PADDING, + METRIC_UPDOWN_HORIZONTAL_PADDING, + METRIC_UPDOWN_VERTICAL_PADDING, METRIC_COUNT }; @@ -104,6 +108,13 @@ enum ColorE { COLOR_TEXTBOX_SHADOW, COLOR_TEXTBOX_TEXT, COLOR_TEXTBOX_BACKGROUND, + COLOR_UPDOWN_HIGHLIGHT, + COLOR_UPDOWN_SHADOW, + COLOR_UPDOWN_TEXT, + COLOR_UPDOWN_BACKGROUND, + COLOR_UPDOWN_ARROWS_BACKGROUND, + COLOR_UPDOWN_ARROWS_ACTIVE, + COLOR_UPDOWN_ARROWS_INACTIVE, COLOR_COUNT }; diff --git a/client/src/gui/memory.h b/client/src/gui/memory.h index 8b938ea..5ee7428 100644 --- a/client/src/gui/memory.h +++ b/client/src/gui/memory.h @@ -24,8 +24,11 @@ #include "os.h" + +#ifdef __linux__ #define MEMWATCH #define MEMWATCH_STDIO +#endif #include "memwatch/memwatch.h" diff --git a/client/src/gui/textbox.c b/client/src/gui/textbox.c index 38a6020..118c26f 100644 --- a/client/src/gui/textbox.c +++ b/client/src/gui/textbox.c @@ -265,7 +265,7 @@ static void textboxPaint(WidgetT *widget, RectT pos) { textX = pos.x + labelWidth + 2 + _guiMetric[METRIC_TEXTBOX_HORIZONTAL_PADDING]; textY = pos.y + 2 + _guiMetric[METRIC_TEXTBOX_VERTICAL_PADDING]; - // Draw value. ***TODO*** This needs much more! Do it without strdup? + // Draw value. draw = strdup(&t->value[t->offset]); if (strlen(draw) > t->visible) draw[t->visible] = 0; fontRender(_guiFont, draw, _guiColor[COLOR_TEXTBOX_TEXT], _guiColor[COLOR_TEXTBOX_BACKGROUND], textX, textY); diff --git a/client/src/gui/updown.c b/client/src/gui/updown.c new file mode 100644 index 0000000..691cec2 --- /dev/null +++ b/client/src/gui/updown.c @@ -0,0 +1,279 @@ +/* + * 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" + + +// 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 updownMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event); +static void updownPaint(WidgetT *widget, RectT pos); +static void updownSetVisible(UpdownT *updown); +static void updownSizesRecalculate(UpdownT *updown); + + +static void updownDel(WidgetT **widget) { + UpdownT *u = (UpdownT *)*widget; + + if (u->title) free(u->title); + free(u); + u = NULL; +} + + +int32_t updownGetValue(UpdownT *updown) { + return updown->value; +} + + +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.paintMethod = updownPaint; + u->base.mouseEventMethod = updownMouseEvent; + u->maximum = INT32_MAX; + u->minimum = INT32_MIN; + u->title = NULL; + u->value = 0; + + updownSetTitle(u, title); // Needs to be set first so updownSizesRecalculate works. + updownSetMinimum(u, min); + updownSetMaximum(u, max); + updownSetStep(u, step); + + return widget; +} + + +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; + } + } + } + + // 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; + } + } + } + + // Ensure we redraw. + 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, 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]; + + if (GUI_GET_FLAG(widget, WIDGET_FLAG_DIRTY)) { + 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. + guiDrawHighlightFrame( pos.x + _labelWidth, pos.y, pos.x + _labelWidth + _valueWidth + 2, pos.y + pos.h, _guiColor[COLOR_UPDOWN_SHADOW], _guiColor[COLOR_UPDOWN_HIGHLIGHT]); + guiDrawRectangle( pos.x + _labelWidth + 1, pos.y + 1, pos.x + _labelWidth + _valueWidth + 1, pos.y + pos.h - 1, _guiColor[COLOR_WINDOW_BACKGROUND]); + + // Draw text background. + guiDrawRectangleFilled(pos.x + _labelWidth + 2, pos.y + 2, pos.x + _labelWidth + _valueWidth, pos.y + pos.h - 2, _guiColor[COLOR_UPDOWN_BACKGROUND]); + + // Draw arrows outline + guiDrawHighlightFrame(_arrowStart, pos.y, _arrowStart + _arrowWidth + 1, pos.y + pos.h, _guiColor[COLOR_UPDOWN_SHADOW], _guiColor[COLOR_UPDOWN_HIGHLIGHT]); + + // Draw arrows background + guiDrawRectangleFilled(_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++) { + guiDrawLine(_arrowStart - i * 0.5, o + i, _arrowStart + i * 0.5, o + i, color); + } + guiDrawLine(_arrowStart, o, _arrowStart + _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_SHADOW]); + guiDrawLine(_arrowStart - _halfFont, o + fontHeightGet(_guiFont), _arrowStart + _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_SHADOW]); + guiDrawLine(_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++) { + guiDrawLine(_arrowStart - i * 0.5, o - i, _arrowStart + i * 0.5, o - i, color); + } + guiDrawLine(_arrowStart, o, _arrowStart + _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_SHADOW]); + guiDrawLine(_arrowStart - _halfFont, o - fontHeightGet(_guiFont), _arrowStart + _halfFont, o - fontHeightGet(_guiFont), _guiColor[COLOR_UPDOWN_HIGHLIGHT]); + guiDrawLine(_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, "%d", u->value); + textX += (u->visible - strlen(draw)) * fontWidthGet(_guiFont); + fontRender(_guiFont, draw, _guiColor[COLOR_UPDOWN_TEXT], _guiColor[COLOR_UPDOWN_BACKGROUND], textX, textY); + + GUI_CLEAR_FLAG(widget, WIDGET_FLAG_DIRTY); + } +} + + +void updownSetMaximum(UpdownT *updown, int32_t maximum) { + if (maximum > updown->minimum) { + updown->maximum = maximum; + updownSetVisible(updown); + } +} + + +void updownSetMinimum(UpdownT *updown, int32_t minimum) { + if (minimum < updown->maximum) { + updown->minimum = minimum; + updownSetVisible(updown); + } +} + + +void updownSetStep(UpdownT *updown, int32_t step) { + updown->step = step; +} + + +void updownSetTitle(UpdownT *updown, char *title) { + if (updown->title) free(updown->title); + updown->title = strdup(title); + updownSetVisible(updown); + GUI_SET_FLAG((WidgetT *)updown, WIDGET_FLAG_DIRTY); +} + + +void updownSetValue(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 updownSetVisible(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, "%d", updown->maximum); + minWidth = snprintf(digits, UPDOWN_MAX_DIGITS, "%d", updown->minimum); + + updown->visible = maxWidth > minWidth ? maxWidth : minWidth; + + // Calculate width of the widget. + updownSizesRecalculate(updown); + updown->base.pos.w = _arrowStart + _arrowWidth + 1; +} + + +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]; +} diff --git a/client/src/gui/updown.h b/client/src/gui/updown.h new file mode 100644 index 0000000..84b7582 --- /dev/null +++ b/client/src/gui/updown.h @@ -0,0 +1,50 @@ +/* + * 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 . + * + */ + + +#ifndef UPDOWN_H +#define UPDOWN_H + + +#include "gui.h" +#include "widget.h" + + +typedef struct UpdownS { + WidgetT base; // Must be first in every widget + char *title; + int32_t value; + int32_t maximum; + int32_t minimum; + int32_t step; + uint8_t visible; // How many characters are visible in control +} UpdownT; + + +int32_t updownGetValue(UpdownT *updown); +WidgetT *updownInit(WidgetT *widget, int32_t min, int32_t max, int32_t step, char *title); +UpdownT *updownNew(uint16_t x, uint16_t y, int32_t min, int32_t max, int32_t step, char *title); +void updownSetValue(UpdownT *updown, int32_t value); +void updownSetMaximum(UpdownT *updown, int32_t maximum); +void updownSetMinimum(UpdownT *updown, int32_t minimum); +void updownSetStep(UpdownT *updown, int32_t step); +void updownSetTitle(UpdownT *updown, char *title); + + +#endif // UPDOWN_H diff --git a/client/src/main.c b/client/src/main.c index 4dd50a1..7de18fd 100644 --- a/client/src/main.c +++ b/client/src/main.c @@ -26,7 +26,7 @@ * - More widget states: Ghosted, hidden * - Move setup math for paint events inside the dirty check * - Methods that can change the width of a widget (such as setTitle) need to repaint the parent window as well - * - Focus events are firing for controls not in top level window + * - Metrics, colors, etc. should be defined in each widget and not in GUI * */ @@ -51,6 +51,7 @@ #include "picture.h" #include "frame.h" #include "textbox.h" +#include "updown.h" void buttonClick(WidgetT *widget) { @@ -141,6 +142,7 @@ void test(void *data) { FrameT *f1 = NULL; TextboxT *t1 = NULL; TextboxT *t2 = NULL; + UpdownT *u1 = NULL; (void)data; @@ -177,6 +179,8 @@ void test(void *data) { t2 = textboxNew(10, 85, 265, "Test Textbox"); textboxSetValue(t2, "Short string."); guiAttach(W(w2), W(t2)); + u1 = updownNew(10, 110, 0, 1024, 5, "UpDown"); + guiAttach(W(w2), W(u1)); // Window 3 f1 = frameNew(10, 5, 175, 125, "Test Frame");