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");