kpmpgsmkii/client/src/gui/listbox.c
2021-11-07 18:45:50 -06:00

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;
}
}
}
}