402 lines
13 KiB
C
402 lines
13 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 "listbox.h"
|
|
|
|
|
|
static uint16_t _halfFont = 0;
|
|
static uint8_t _visibleX = 0; // How many characters are visible in control
|
|
static uint8_t _visibleY = 0; // How many characters are visible in control
|
|
static uint16_t _valueWidth = 0;
|
|
static uint16_t _valueHeight = 0;
|
|
static uint16_t _valueTop = 0;
|
|
static uint16_t _valueBottom = 0;
|
|
static uint16_t _arrowWidth = 0;
|
|
static uint16_t _arrowStart = 0;
|
|
|
|
|
|
static void listboxDel(WidgetT **widget);
|
|
static void listboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event);
|
|
static void listboxKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt);
|
|
static void listboxPaint(WidgetT *widget, RectT pos);
|
|
static void listboxScrollDown(ListboxT *listbox);
|
|
static void listboxScrollUp(ListboxT *listbox);
|
|
static void listboxSizesRecalculate(ListboxT *listbox);
|
|
|
|
|
|
static void listboxDel(WidgetT **widget) {
|
|
ListboxT *l = (ListboxT *)*widget;
|
|
size_t len = arrlenu(l->values);
|
|
size_t x;
|
|
|
|
if (len > 0) {
|
|
for (x=0; x<len; x++) {
|
|
free(l->values[x]);
|
|
}
|
|
}
|
|
|
|
if (l->title) free(l->title);
|
|
free(l);
|
|
l = NULL;
|
|
}
|
|
|
|
|
|
uint16_t listboxIndexGet(ListboxT *listbox) {
|
|
return listbox->selected;
|
|
}
|
|
|
|
|
|
void listboxIndexSet(ListboxT *listbox, uint16_t index) {
|
|
if (index < arrlenu(listbox->values)) {
|
|
listbox->selected = index;
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
}
|
|
}
|
|
|
|
|
|
WidgetT *listboxInit(WidgetT *widget, char *title) {
|
|
ListboxT *l = (ListboxT *)widget;
|
|
|
|
l->base.delMethod = listboxDel;
|
|
l->base.keyboardEventMethod = listboxKeyboardEvent;
|
|
l->base.paintMethod = listboxPaint;
|
|
l->base.mouseEventMethod = listboxMouseEvent;
|
|
l->title = NULL;
|
|
l->values = NULL;
|
|
l->selected = 0;
|
|
l->step = 1;
|
|
l->offset = 0;
|
|
|
|
// Visibles are set in listboxSetTitle
|
|
listboxTitleSet(l, title);
|
|
|
|
return widget;
|
|
}
|
|
|
|
|
|
void listboxItemAdd(ListboxT *listbox, char *item) {
|
|
arrput(listbox->values, strdup(item));
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
}
|
|
|
|
|
|
void listboxItemRemove(ListboxT *listbox, char *item) {
|
|
size_t len = arrlenu(listbox->values);
|
|
size_t x;
|
|
|
|
if (len > 0) {
|
|
for (x=0; x<len; x++) {
|
|
if (strcmp(item, listbox->values[x]) == 0) {
|
|
arrdel(listbox->values, x);
|
|
if (listbox->selected > len || listbox->selected > 0) listbox->selected--;
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void listboxKeyboardEvent(WidgetT *widget, uint8_t ascii, uint8_t extended, uint8_t scancode, uint8_t shift, uint8_t control, uint8_t alt) {
|
|
|
|
ListboxT *l = (ListboxT *)widget;
|
|
int16_t len = arrlenu(l->values);
|
|
uint16_t i;
|
|
|
|
(void)scancode;
|
|
(void)shift;
|
|
(void)control;
|
|
(void)alt;
|
|
|
|
listboxSizesRecalculate(l);
|
|
|
|
if (extended) {
|
|
|
|
switch (ascii) {
|
|
case 71: // HOME
|
|
l->selected = 0;
|
|
l->offset = 0;
|
|
GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
|
|
break;
|
|
|
|
case 79: // END
|
|
// Are there more items than visible?
|
|
if (len > _visibleY - 1) {
|
|
// Yep. Scroll & set selected.
|
|
l->selected = _visibleY - 2;
|
|
l->offset = len - _visibleY + 1;
|
|
} else {
|
|
// No. Just set selected.
|
|
l->selected = len - 1;
|
|
l->offset = 0;
|
|
}
|
|
GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
|
|
break;
|
|
|
|
case 73: // PAGEUP
|
|
for (i=0; i<l->step; i++) listboxScrollUp(l);
|
|
break;
|
|
|
|
case 81: // PAGEDOWN
|
|
for (i=0; i<l->step; i++) listboxScrollDown(l);
|
|
break;
|
|
|
|
case 72: // UP
|
|
listboxScrollUp(l);
|
|
break;
|
|
|
|
case 80: // DOWN
|
|
listboxScrollDown(l);
|
|
break;
|
|
|
|
} // switch
|
|
|
|
} // extended
|
|
}
|
|
|
|
|
|
static void listboxMouseEvent(WidgetT *widget, MouseT *mouse, uint16_t x, uint16_t y, uint8_t event) {
|
|
ListboxT *l = (ListboxT *)widget;
|
|
uint16_t o;
|
|
uint16_t valueStart;
|
|
int16_t len;
|
|
|
|
(void)x;
|
|
(void)y;
|
|
(void)mouse;
|
|
|
|
if (event == MOUSE_EVENT_LEFT_UP) {
|
|
listboxSizesRecalculate(l);
|
|
|
|
len = arrlenu(l->values);
|
|
|
|
// Clicked item in listbox.
|
|
valueStart = 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING];
|
|
if (x > valueStart && x < valueStart + _valueWidth - 1 && y > _valueTop + 1 && y < _valueBottom - 1) {
|
|
o = (y - _valueTop) / fontHeightGet(_guiFont);
|
|
if (o < len) l->selected = o;
|
|
if (l->selected > _visibleY - 2) l->selected = _visibleY - 2; // Two because 1 is to correct _visibleY and the other 1 is because selected is zero based.
|
|
GUI_SET_FLAG(widget, WIDGET_FLAG_DIRTY);
|
|
}
|
|
|
|
_arrowStart += 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING]; // Left of up arrow
|
|
o = _valueTop + 1 + _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING]; // Top of arrows
|
|
|
|
// Did they click the up arrow?
|
|
if (x >= _arrowStart && x <= _arrowStart + fontHeightGet(_guiFont) && y >= o && y <= o + fontHeightGet(_guiFont)) {
|
|
listboxScrollUp(l);
|
|
}
|
|
|
|
// Did they click the down arrow?
|
|
o = _valueBottom - 1 - _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING]; // Bottom of down arrow
|
|
if (x >= _arrowStart && x <= _arrowStart + fontHeightGet(_guiFont) && y >= o - fontHeightGet(_guiFont) && y <= o) {
|
|
listboxScrollDown(l);
|
|
}
|
|
|
|
} // MOUSE_UP
|
|
}
|
|
|
|
|
|
ListboxT *listboxNew(uint16_t x, uint16_t y, uint16_t w, uint16_t h, char *title) {
|
|
ListboxT *listbox = (ListboxT *)malloc(sizeof(ListboxT));
|
|
WidgetT *widget = NULL;
|
|
|
|
if (!listbox) return NULL;
|
|
|
|
widget = widgetInit(W(listbox), MAGIC_LISTBOX, x, y, w, h, 0, 0, 0, 0);
|
|
if (!widget) {
|
|
free(listbox);
|
|
return NULL;
|
|
}
|
|
|
|
listbox = (ListboxT *)listboxInit((WidgetT *)listbox, title);
|
|
|
|
return listbox;
|
|
}
|
|
|
|
|
|
static void listboxPaint(WidgetT *widget, RectT pos) {
|
|
ListboxT *l = (ListboxT *)widget;
|
|
uint16_t items;
|
|
uint16_t o;
|
|
uint16_t i;
|
|
int16_t len;
|
|
ColorT color;
|
|
|
|
if (GUI_GET_FLAG(widget, WIDGET_FLAG_DIRTY)) {
|
|
listboxSizesRecalculate(l);
|
|
|
|
// Move a few things into screen space, not widget space.
|
|
_arrowStart += pos.x;
|
|
_valueTop += pos.y;
|
|
_valueBottom += pos.y;
|
|
|
|
len = arrlenu(l->values);
|
|
|
|
// How many items can we draw?
|
|
items = len - l->offset;
|
|
if (items > _visibleY - 1) items = _visibleY - 1;
|
|
|
|
// Draw title.
|
|
fontRender(_guiFont, l->title, _guiColor[COLOR_LISTBOX_TEXT], _guiColor[COLOR_WINDOW_BACKGROUND], pos.x, pos.y);
|
|
|
|
// Draw outline of listbox.
|
|
surfaceHighlightFrameDraw(pos.x, _valueTop, pos.x + _valueWidth, _valueBottom, _guiColor[COLOR_LISTBOX_SHADOW], _guiColor[COLOR_LISTBOX_HIGHLIGHT]);
|
|
|
|
// Draw background of listbox.
|
|
surfaceRectangleFilledDraw(pos.x + 1, _valueTop + 1, pos.x + _valueWidth - 1, _valueBottom - 1, _guiColor[COLOR_LISTBOX_BACKGROUND]);
|
|
|
|
// Draw listbox contents.
|
|
o = _valueTop + 1 + _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING];
|
|
for (i=0; i<items; i++) {
|
|
if (i == l->selected) {
|
|
surfaceRectangleFilledDraw(pos.x + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o, pos.x + _valueWidth - _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o + fontHeightGet(_guiFont) - 1, _guiColor[COLOR_LISTBOX_SELECTED_BACKGROUND]);
|
|
fontRender(_guiFont, l->values[l->offset + i], _guiColor[COLOR_LISTBOX_SELECTED_TEXT], _guiColor[COLOR_LISTBOX_SELECTED_BACKGROUND], pos.x + 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o);
|
|
} else {
|
|
fontRender(_guiFont, l->values[l->offset + i], _guiColor[COLOR_LISTBOX_TEXT], _guiColor[COLOR_LISTBOX_BACKGROUND], pos.x + 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING], o);
|
|
}
|
|
o += fontHeightGet(_guiFont);
|
|
}
|
|
|
|
// Draw outline of arrows.
|
|
surfaceHighlightFrameDraw(_arrowStart, _valueTop, _arrowStart + _arrowWidth, _valueBottom, _guiColor[COLOR_LISTBOX_SHADOW], _guiColor[COLOR_LISTBOX_HIGHLIGHT]);
|
|
|
|
// Draw background of arrows.
|
|
surfaceRectangleFilledDraw(_arrowStart + 1, _valueTop + 1, _arrowStart + _arrowWidth - 1, _valueBottom - 1, _guiColor[COLOR_LISTBOX_ARROWS_BACKGROUND]);
|
|
|
|
// Draw up arrow
|
|
_arrowStart += _halfFont + 1 + _guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING]; // Center of up arrow
|
|
o = _valueTop + 1 + _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING]; // Top of up arrow
|
|
color = l->offset + l->selected > 0 ? _guiColor[COLOR_LISTBOX_ARROWS_ACTIVE] : _guiColor[COLOR_LISTBOX_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_LISTBOX_SHADOW]);
|
|
surfaceLineDraw(_arrowStart - _halfFont, o + fontHeightGet(_guiFont), _arrowStart + _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_LISTBOX_SHADOW]);
|
|
surfaceLineDraw(_arrowStart, o, _arrowStart - _halfFont, o + fontHeightGet(_guiFont), _guiColor[COLOR_LISTBOX_HIGHLIGHT]);
|
|
|
|
// Draw down arrow
|
|
o = _valueBottom - 1 - _guiMetric[METRIC_LISTBOX_VERTICAL_PADDING]; // Bottom of down arrow
|
|
color = l->offset + l->selected < len - 1 ? _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]);
|
|
|
|
GUI_CLEAR_FLAG(widget, WIDGET_FLAG_DIRTY);
|
|
}
|
|
}
|
|
|
|
|
|
static void listboxScrollDown(ListboxT *listbox) {
|
|
int16_t len = arrlenu(listbox->values);
|
|
|
|
// Can we increment?
|
|
if (listbox->selected < _visibleY - 2) {
|
|
// Is it off the end of the list?
|
|
if (listbox->offset + listbox->selected < len - 1) {
|
|
listbox->selected++;
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
}
|
|
} else {
|
|
// Can we scroll the list down?
|
|
if (listbox->offset + listbox->selected < len - 1) {
|
|
listbox->offset++;
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void listboxScrollUp(ListboxT *listbox) {
|
|
// Can we decrement?
|
|
if (listbox->selected > 0) {
|
|
listbox->selected--;
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
} else {
|
|
// Can we scroll the list up?
|
|
if (listbox->offset > 0) {
|
|
listbox->offset--;
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void listboxSizesRecalculate(ListboxT *listbox) {
|
|
|
|
// Arrow box width is border + (hpadding*2) + fontHeight
|
|
// Label is drawn at x,y
|
|
// One line horizontal gap between label and border
|
|
// One line vertical gap between value and arrow box
|
|
// Value border is drawn at x+1, y+2 to x+w-2-arrowWidth, y+h-2
|
|
// Value is x+1+hpadding, y+2+vpadding to x+w-1-(hpadding*2)-arrowWidth, y+h-2-(vpadding*2)
|
|
// arrowStart is x+1+valueWidth
|
|
|
|
// Set global stuff that isn't actually part of the widget state.
|
|
_halfFont = fontHeightGet(_guiFont) * 0.5;
|
|
_arrowWidth = 2 + (_guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING] * 2) + fontHeightGet(_guiFont); // Arrow width = font height
|
|
_valueWidth = listbox->base.pos.w - 1 - 1 - (_guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING] * 2) - _arrowWidth;
|
|
_valueHeight = listbox->base.pos.h - 2 - (_guiMetric[METRIC_LISTBOX_VERTICAL_PADDING] * 2);
|
|
_valueTop = fontHeightGet(_guiFont) + 1;
|
|
_valueBottom = _valueHeight;
|
|
_arrowStart = 2 + _valueWidth;
|
|
|
|
// Figure out how many characters we have room to display.
|
|
_visibleX = (_valueWidth - 2 + (_guiMetric[METRIC_LISTBOX_HORIZONTAL_PADDING] * 2)) / fontWidthGet(_guiFont);
|
|
_visibleY = (_valueHeight - 2 + (_guiMetric[METRIC_LISTBOX_VERTICAL_PADDING] * 2)) / fontHeightGet(_guiFont);
|
|
|
|
}
|
|
|
|
|
|
void listboxStepSet(ListboxT *listbox, int32_t step) {
|
|
listbox->step = step;
|
|
}
|
|
|
|
|
|
void listboxTitleSet(ListboxT *listbox, char *title) {
|
|
if (listbox->title) free(listbox->title);
|
|
listbox->title = strdup(title);
|
|
listboxSizesRecalculate(listbox);
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
}
|
|
|
|
|
|
char *listboxValueGet(ListboxT *listbox) {
|
|
return listbox->values[listbox->selected];
|
|
}
|
|
|
|
|
|
void listboxValueSet(ListboxT *listbox, char *value) {
|
|
size_t len = arrlenu(listbox->values);
|
|
size_t x;
|
|
|
|
if (len > 0) {
|
|
for (x=0; x<len; x++) {
|
|
if (strcmp(value, listbox->values[x]) == 0) {
|
|
listbox->selected = x;
|
|
GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|