/* * 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 "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; xvalues[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; xvalues[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; istep; i++) listboxScrollUp(l); break; case 81: // PAGEDOWN for (i=0; istep; 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; iselected) { 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; xvalues[x]) == 0) { listbox->selected = x; GUI_SET_FLAG((WidgetT *)listbox, WIDGET_FLAG_DIRTY); break; } } } }