diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/LWidgets.c |
initial commit
Diffstat (limited to 'src/LWidgets.c')
-rw-r--r-- | src/LWidgets.c | 793 |
1 files changed, 793 insertions, 0 deletions
diff --git a/src/LWidgets.c b/src/LWidgets.c new file mode 100644 index 0000000..7a7da60 --- /dev/null +++ b/src/LWidgets.c @@ -0,0 +1,793 @@ +#include "LWidgets.h" +#ifndef CC_BUILD_WEB +#include "String.h" +#include "Gui.h" +#include "Drawer2D.h" +#include "Launcher.h" +#include "ExtMath.h" +#include "Window.h" +#include "Funcs.h" +#include "LWeb.h" +#include "Platform.h" +#include "LScreens.h" +#include "Input.h" +#include "Utils.h" +#include "LBackend.h" + +static int flagXOffset, flagYOffset; +static int oneX, twoX, fourX; +static int oneY, twoY, fourY; + +void LWidget_CalcOffsets(void) { + oneX = Display_ScaleX(1); + oneY = Display_ScaleY(1); + + if (oneX < 1) { oneX = 1; } + if (oneY < 1) { oneY = 1; } + + twoX = oneX * 2; fourX = oneX * 4; + twoY = oneY * 2; fourY = oneY * 4; + + flagXOffset = Display_ScaleX(2); + flagYOffset = Display_ScaleY(6); +} + +static void LWidget_DrawInsetBorder(struct Context2D* ctx, BitmapCol color, int insetX, int insetY, + int x, int y, int width, int height) { + Context2D_Clear(ctx, color, + x + insetX, y, + width - 2 * insetX, insetY); + Context2D_Clear(ctx, color, + x + insetX, y + height - insetY, + width - 2 * insetX, insetY); + Context2D_Clear(ctx, color, + x, y + insetY, + insetX, height - 2 * insetY); + Context2D_Clear(ctx, color, + x + width - insetX, y + insetY, + insetX, height - 2 * insetY); +} + +void LWidget_DrawBorder(struct Context2D* ctx, BitmapCol color, int borderX, int borderY, + int x, int y, int width, int height) { + Context2D_Clear(ctx, color, + x, y, + width, borderY); + Context2D_Clear(ctx, color, + x, y + height - borderY, + width, borderY); + Context2D_Clear(ctx, color, + x, y, + borderX, height); + Context2D_Clear(ctx, color, + x + width - borderX, y, + borderX, height); +} + + +/*########################################################################################################################* +*------------------------------------------------------ButtonWidget-------------------------------------------------------* +*#########################################################################################################################*/ +static void LButton_DrawBase(struct Context2D* ctx, int x, int y, int width, int height, cc_bool active) { + BitmapCol color = active ? Launcher_Theme.ButtonForeActiveColor + : Launcher_Theme.ButtonForeColor; + + if (Launcher_Theme.ClassicBackground) { + Gradient_Noise(ctx, color, 8, + x + oneX, y + oneY, + width - twoX, height - twoY); + } else { + Gradient_Vertical(ctx, BitmapColor_Offset(color, 8,8,8), BitmapColor_Offset(color, -8,-8,-8), + x + oneX, y + oneY, + width - twoX, height - twoY); + } +} + +static void LButton_DrawBorder(struct Context2D* ctx, int x, int y, int width, int height) { + BitmapCol backColor = Launcher_Theme.ButtonBorderColor; +#ifdef CC_BUILD_IOS + LWidget_DrawBorder(ctx, backColor, oneX, oneY, x, y, width, height); +#else + LWidget_DrawInsetBorder(ctx, backColor, oneX, oneY, x, y, width, height); +#endif +} + +static void LButton_DrawHighlight(struct Context2D* ctx, int x, int y, int width, int height, cc_bool active) { + BitmapCol activeColor = BitmapColor_RGB(189, 198, 255); + BitmapCol color = Launcher_Theme.ButtonHighlightColor; + + if (Launcher_Theme.ClassicBackground) { + if (active) color = activeColor; + + Context2D_Clear(ctx, color, + x + twoX, y + oneY, + width - fourX, oneY); + Context2D_Clear(ctx, color, + x + oneX, y + twoY, + oneX, height - fourY); + } else if (!active) { + Context2D_Clear(ctx, color, + x + twoX, y + oneY, + width - fourX, oneY); + } +} + +void LButton_DrawBackground(struct Context2D* ctx, int x, int y, int width, int height, cc_bool active) { + LButton_DrawBase( ctx, x, y, width, height, active); + LButton_DrawBorder( ctx, x, y, width, height); + LButton_DrawHighlight(ctx, x, y, width, height, active); +} + +static void LButton_Draw(void* widget) { + struct LButton* w = (struct LButton*)widget; + LBackend_ButtonDraw(w); +} + +static void LButton_Hover(void* w, int idx, cc_bool wasOver) { + /* only need to redraw when changing from unhovered to active */ + if (!wasOver) LBackend_MarkDirty(w); +} + +static void LButton_Unhover(void* w) { LBackend_MarkDirty(w); } +static void LButton_OnSelect(void* w, int idx, cc_bool wasSelected) { LBackend_MarkDirty(w); } +static void LButton_OnUnselect(void* w, int idx) { LBackend_MarkDirty(w); } + +static const struct LWidgetVTABLE lbutton_VTABLE = { + LButton_Draw, NULL, + NULL, NULL, /* Key */ + LButton_Hover, LButton_Unhover, /* Hover */ + LButton_OnSelect, LButton_OnUnselect /* Select */ +}; +void LButton_Add(void* screen, struct LButton* w, int width, int height, const char* text, + LWidgetFunc onClick, const struct LLayout* layouts) { + w->VTABLE = &lbutton_VTABLE; + w->type = LWIDGET_BUTTON; + w->OnClick = onClick; + w->layouts = layouts; + w->autoSelectable = true; + + LBackend_ButtonInit(w, width, height); + LButton_SetConst(w, text); + LScreen_AddWidget(screen, w); +} + +void LButton_SetConst(struct LButton* w, const char* text) { + w->text = String_FromReadonly(text); + LBackend_ButtonUpdate(w); +} + + +/*########################################################################################################################* +*-----------------------------------------------------CheckboxWidget------------------------------------------------------* +*#########################################################################################################################*/ +static void LCheckbox_Draw(void* widget) { + struct LCheckbox* w = (struct LCheckbox*)widget; + LBackend_CheckboxDraw(w); +} + +static const struct LWidgetVTABLE lcheckbox_VTABLE = { + LCheckbox_Draw, NULL, + NULL, NULL, /* Key */ + NULL, NULL, /* Hover */ + NULL, NULL /* Select */ +}; +void LCheckbox_Add(void* screen, struct LCheckbox* w, const char* text, + LCheckboxChanged onChanged, const struct LLayout* layouts) { + w->VTABLE = &lcheckbox_VTABLE; + w->type = LWIDGET_CHECKBOX; + w->layouts = layouts; + w->autoSelectable = true; + w->ValueChanged = onChanged; + + w->text = String_FromReadonly(text); + LBackend_CheckboxInit(w); + LScreen_AddWidget(screen, w); +} + +void LCheckbox_Set(struct LCheckbox* w, cc_bool value) { + w->value = value; + LBackend_CheckboxUpdate(w); +} + + +/*########################################################################################################################* +*------------------------------------------------------InputWidget--------------------------------------------------------* +*#########################################################################################################################*/ +void LInput_UNSAFE_GetText(struct LInput* w, cc_string* text) { + int i; + if (w->inputType != KEYBOARD_TYPE_PASSWORD) { *text = w->text; return; } + + for (i = 0; i < w->text.length; i++) { + String_Append(text, '*'); + } +} + +static void LInput_Draw(void* widget) { + struct LInput* w = (struct LInput*)widget; + LBackend_InputDraw(w); +} + +static void LInput_TickCaret(void* widget) { + struct LInput* w = (struct LInput*)widget; + LBackend_InputTick(w); +} + +static void LInput_Select(void* widget, int idx, cc_bool wasSelected) { + struct LInput* w = (struct LInput*)widget; + LBackend_InputSelect(w, idx, wasSelected); +} + +static void LInput_Unselect(void* widget, int idx) { + struct LInput* w = (struct LInput*)widget; + LBackend_InputUnselect(w); +} + +static void LInput_AdvanceCaretPos(struct LInput* w, cc_bool forwards) { + if (forwards && w->caretPos == -1) return; + if (!forwards && w->caretPos == 0) return; + if (w->caretPos == -1 && !forwards) /* caret after text */ + w->caretPos = w->text.length; + + w->caretPos += (forwards ? 1 : -1); + if (w->caretPos < 0 || w->caretPos >= w->text.length) w->caretPos = -1; + LBackend_InputUpdate(w); +} + +static void LInput_CopyFromClipboard(struct LInput* w) { + cc_string text; char textBuffer[2048]; + String_InitArray(text, textBuffer); + + Clipboard_GetText(&text); + String_UNSAFE_TrimStart(&text); + String_UNSAFE_TrimEnd(&text); + + if (w->ClipboardFilter) w->ClipboardFilter(&text); + LInput_AppendString(w, &text); +} + +/* If caret position is now beyond end of text, resets to -1 */ +static CC_INLINE void LInput_ClampCaret(struct LInput* w) { + if (w->caretPos >= w->text.length) w->caretPos = -1; +} + +/* Removes the character preceding the caret in the currently entered text */ +static void LInput_Backspace(struct LInput* w) { + if (!w->text.length || w->caretPos == 0) return; + + if (w->caretPos == -1) { + String_DeleteAt(&w->text, w->text.length - 1); + } else { + String_DeleteAt(&w->text, w->caretPos - 1); + w->caretPos--; + if (w->caretPos == -1) w->caretPos = 0; + } + + if (w->TextChanged) w->TextChanged(w); + LInput_ClampCaret(w); + LBackend_InputUpdate(w); +} + +/* Removes the character at the caret in the currently entered text */ +static void LInput_Delete(struct LInput* w) { + if (!w->text.length || w->caretPos == -1) return; + + String_DeleteAt(&w->text, w->caretPos); + if (w->caretPos == -1) w->caretPos = 0; + + if (w->TextChanged) w->TextChanged(w); + LInput_ClampCaret(w); + LBackend_InputUpdate(w); +} + +static cc_bool LInput_KeyDown(void* widget, int key, cc_bool was) { + struct LInput* w = (struct LInput*)widget; + if (key == CCKEY_BACKSPACE) { + LInput_Backspace(w); + } else if (key == CCKEY_DELETE) { + LInput_Delete(w); + } else if (key == INPUT_CLIPBOARD_COPY) { + if (w->text.length) Clipboard_SetText(&w->text); + } else if (key == INPUT_CLIPBOARD_PASTE) { + LInput_CopyFromClipboard(w); + } else if (Input_IsEscapeButton(key)) { + if (w->text.length) LInput_SetString(w, &String_Empty); + } else if (Input_IsLeftButton(key)) { + LInput_AdvanceCaretPos(w, false); + } else if (Input_IsRightButton(key)) { + LInput_AdvanceCaretPos(w, true); + } else { return false; } + + return true; +} + +static cc_bool LInput_CanAppend(struct LInput* w, char c) { + switch (w->inputType) { + case KEYBOARD_TYPE_PASSWORD: + return true; /* keyboard accepts all characters */ + case KEYBOARD_TYPE_INTEGER: + return c >= '0' && c <= '9'; + } + return c >= ' ' && c <= '~' && c != '&'; +} + +/* Appends a character to the currently entered text */ +static CC_NOINLINE cc_bool LInput_Append(struct LInput* w, char c) { + if (LInput_CanAppend(w, c) && w->text.length < w->text.capacity) { + if (w->caretPos == -1) { + String_Append(&w->text, c); + } else { + String_InsertAt(&w->text, w->caretPos, c); + w->caretPos++; + } + return true; + } + return false; +} + +static void LInput_KeyChar(void* widget, char c) { + struct LInput* w = (struct LInput*)widget; + cc_bool appended = LInput_Append(w, c); + + if (appended && w->TextChanged) w->TextChanged(w); + if (appended) LBackend_InputUpdate(w); +} + +static void LInput_TextChanged(void* widget, const cc_string* str) { + struct LInput* w = (struct LInput*)widget; + LInput_SetText(w, str); + if (w->TextChanged) w->TextChanged(w); +} + +static const struct LWidgetVTABLE linput_VTABLE = { + LInput_Draw, LInput_TickCaret, + LInput_KeyDown, LInput_KeyChar, /* Key */ + NULL, NULL, /* Hover */ + /* TODO: Don't redraw whole thing, just the outer border */ + LInput_Select, LInput_Unselect, /* Select */ + NULL, LInput_TextChanged /* TextChanged */ +}; +void LInput_Add(void* screen, struct LInput* w, int width, const char* hintText, + const struct LLayout* layouts) { + w->VTABLE = &linput_VTABLE; + w->type = LWIDGET_INPUT; + w->autoSelectable = true; + w->opaque = true; + w->layouts = layouts; + + /* Preserve existing input across Add calls */ + if (!w->text.buffer) { + String_InitArray(w->text, w->_textBuffer); + } + + w->hintText = hintText; + w->caretPos = -1; + LBackend_InputInit(w, width); + LScreen_AddWidget(screen, w); +} + +void LInput_SetText(struct LInput* w, const cc_string* text) { + String_Copy(&w->text, text); + LInput_ClampCaret(w); + LBackend_InputUpdate(w); +} + +void LInput_ClearText(struct LInput* w) { + w->text.length = 0; + w->caretPos = -1; + LBackend_InputUpdate(w); +} + +void LInput_AppendString(struct LInput* w, const cc_string* str) { + int i, appended = 0; + for (i = 0; i < str->length; i++) { + if (LInput_Append(w, str->buffer[i])) appended++; + } + + if (appended && w->TextChanged) w->TextChanged(w); + if (appended) LBackend_InputUpdate(w); +} + +void LInput_SetString(struct LInput* w, const cc_string* str) { + LInput_SetText(w, str); + if (w->TextChanged) w->TextChanged(w); +} + + +/*########################################################################################################################* +*------------------------------------------------------LabelWidget--------------------------------------------------------* +*#########################################################################################################################*/ +static void LLabel_Draw(void* widget) { + struct LLabel* w = (struct LLabel*)widget; + LBackend_LabelDraw(w); +} + +static const struct LWidgetVTABLE llabel_VTABLE = { + LLabel_Draw, NULL, + NULL, NULL, /* Key */ + NULL, NULL, /* Hover */ + NULL, NULL /* Select */ +}; +void LLabel_Add(void* screen, struct LLabel* w, const char* text, + const struct LLayout* layouts) { + w->VTABLE = &llabel_VTABLE; + w->type = LWIDGET_LABEL; + w->layouts = layouts; + + String_InitArray(w->text, w->_textBuffer); + LBackend_LabelInit(w); + LLabel_SetConst(w, text); + LScreen_AddWidget(screen, w); +} + +void LLabel_SetText(struct LLabel* w, const cc_string* text) { + String_Copy(&w->text, text); + LBackend_LabelUpdate(w); + LBackend_LayoutWidget((struct LWidget*)w); +} + +void LLabel_SetConst(struct LLabel* w, const char* text) { + cc_string str = String_FromReadonly(text); + LLabel_SetText(w, &str); +} + + +/*########################################################################################################################* +*-------------------------------------------------------LineWidget--------------------------------------------------------* +*#########################################################################################################################*/ +static void LLine_Draw(void* widget) { + struct LLine* w = (struct LLine*)widget; + LBackend_LineDraw(w); +} + +static const struct LWidgetVTABLE lline_VTABLE = { + LLine_Draw, NULL, + NULL, NULL, /* Key */ + NULL, NULL, /* Hover */ + NULL, NULL /* Select */ +}; +void LLine_Add(void* screen, struct LLine* w, int width, + const struct LLayout* layouts) { + w->VTABLE = &lline_VTABLE; + w->type = LWIDGET_LINE; + w->layouts = layouts; + + LBackend_LineInit(w, width); + LScreen_AddWidget(screen, w); +} + +#define CLASSIC_LINE_COLOR BitmapColor_RGB(128, 128, 128) +BitmapCol LLine_GetColor(void) { + return Launcher_Theme.ClassicBackground ? CLASSIC_LINE_COLOR : Launcher_Theme.ButtonBorderColor; +} + + +/*########################################################################################################################* +*------------------------------------------------------SliderWidget-------------------------------------------------------* +*#########################################################################################################################*/ +static void LSlider_Draw(void* widget) { + struct LSlider* w = (struct LSlider*)widget; + LBackend_SliderDraw(w); +} + +static const struct LWidgetVTABLE lslider_VTABLE = { + LSlider_Draw, NULL, + NULL, NULL, /* Key */ + NULL, NULL, /* Hover */ + NULL, NULL /* Select */ +}; +void LSlider_Add(void* screen, struct LSlider* w, int width, int height, BitmapCol color, + const struct LLayout* layouts) { + w->VTABLE = &lslider_VTABLE; + w->type = LWIDGET_SLIDER; + w->color = color; + w->opaque = true; + w->layouts = layouts; + + LBackend_SliderInit(w, width, height); + LScreen_AddWidget(screen, w); +} + +void LSlider_SetProgress(struct LSlider* w, int progress) { + if (progress == w->value) return; + w->value = progress; + LBackend_SliderUpdate(w); +} + + +/*########################################################################################################################* +*------------------------------------------------------TableWidget--------------------------------------------------------* +*#########################################################################################################################*/ +static void FlagColumn_Draw(struct ServerInfo* row, struct DrawTextArgs* args, struct LTableCell* cell, struct Context2D* ctx) { + struct Flag* flag = Flags_Get(row); + if (!flag) return; + Context2D_DrawPixels(ctx, cell->x + flagXOffset, cell->y + flagYOffset, &flag->bmp); +} + +static void NameColumn_Draw(struct ServerInfo* row, struct DrawTextArgs* args, struct LTableCell* cell, struct Context2D* ctx) { + args->text = row->name; +} +static int NameColumn_Sort(const struct ServerInfo* a, const struct ServerInfo* b) { + return String_Compare(&b->name, &a->name); +} + +static void PlayersColumn_Draw(struct ServerInfo* row, struct DrawTextArgs* args, struct LTableCell* cell, struct Context2D* ctx) { + String_Format2(&args->text, "%i/%i", &row->players, &row->maxPlayers); +} +static int PlayersColumn_Sort(const struct ServerInfo* a, const struct ServerInfo* b) { + return b->players - a->players; +} + +static void UptimeColumn_Draw(struct ServerInfo* row, struct DrawTextArgs* args, struct LTableCell* cell, struct Context2D* ctx) { + LTable_FormatUptime(&args->text, row->uptime); +} +static int UptimeColumn_Sort(const struct ServerInfo* a, const struct ServerInfo* b) { + return b->uptime - a->uptime; +} + +static void SoftwareColumn_Draw(struct ServerInfo* row, struct DrawTextArgs* args, struct LTableCell* cell, struct Context2D* ctx) { + /* last column, so adjust to fit size of table */ + int leftover = cell->table->width - cell->x; + cell->width = max(cell->width, leftover); + args->text = row->software; +} +static int SoftwareColumn_Sort(const struct ServerInfo* a, const struct ServerInfo* b) { + return String_Compare(&b->software, &a->software); +} + +static struct LTableColumn tableColumns[] = { + { "", 15, FlagColumn_Draw, NULL, false, false, false }, + { "Name", 328, NameColumn_Draw, NameColumn_Sort, true, false, true }, + { "Players", 73, PlayersColumn_Draw, PlayersColumn_Sort, true, true, true }, + { "Uptime", 73, UptimeColumn_Draw, UptimeColumn_Sort, true, true, true }, + { "Software", 143, SoftwareColumn_Draw, SoftwareColumn_Sort, false, true, true } +}; + + +void LTable_FormatUptime(cc_string* dst, int uptime) { + char unit = 's'; + + if (uptime >= SECS_PER_DAY * 7) { + uptime /= SECS_PER_DAY; unit = 'd'; + } else if (uptime >= SECS_PER_HOUR) { + uptime /= SECS_PER_HOUR; unit = 'h'; + } else if (uptime >= SECS_PER_MIN) { + uptime /= SECS_PER_MIN; unit = 'm'; + } + String_Format2(dst, "%i%r", &uptime, &unit); +} + +void LTable_GetScrollbarCoords(struct LTable* w, int* y, int* height) { + float scale; + if (!w->rowsCount) { *y = 0; *height = 0; return; } + + scale = w->height / (float)w->rowsCount; + *y = Math_Ceil(w->topRow * scale); + *height = Math_Ceil(w->visibleRows * scale); + *height = min(*y + *height, w->height) - *y; +} + +void LTable_ClampTopRow(struct LTable* w) { + if (w->topRow > w->rowsCount - w->visibleRows) { + w->topRow = w->rowsCount - w->visibleRows; + } + if (w->topRow < 0) w->topRow = 0; +} + +int LTable_GetSelectedIndex(struct LTable* w) { + struct ServerInfo* entry; + int row; + + for (row = 0; row < w->rowsCount; row++) { + entry = LTable_Get(row); + if (String_CaselessEquals(w->selectedHash, &entry->hash)) return row; + } + return -1; +} + +void LTable_SetSelectedTo(struct LTable* w, int index) { + if (!w->rowsCount) return; + if (index >= w->rowsCount) index = w->rowsCount - 1; + if (index < 0) index = 0; + + String_Copy(w->selectedHash, <able_Get(index)->hash); + LTable_ShowSelected(w); + w->OnSelectedChanged(); +} + +void LTable_RowClick(struct LTable* w, int row) { + cc_uint64 now; + LTable_SetSelectedTo(w, row); + now = Stopwatch_Measure(); + + /* double click on row to join */ + if (Stopwatch_ElapsedMS(w->_lastClick, now) < 1000 && row == w->_lastRow) { + Launcher_ConnectToServer(<able_Get(row)->hash); + } + + w->_lastRow = LTable_GetSelectedIndex(w); + w->_lastClick = now; +} + +cc_bool LTable_HandlesKey(int key) { + return Input_IsUpButton(key) || key == CCKEY_PAGEUP || + Input_IsDownButton(key) || key == CCKEY_PAGEDOWN; +} + +static cc_bool LTable_KeyDown(void* widget, int key, cc_bool was) { + struct LTable* w = (struct LTable*)widget; + int index = LTable_GetSelectedIndex(w); + + if (Input_IsUpButton(key)) { + index--; + } else if (Input_IsDownButton(key)) { + index++; + } else if (key == CCKEY_PAGEUP) { + index -= w->visibleRows; + } else if (key == CCKEY_PAGEDOWN) { + index += w->visibleRows; + } else { return false; } + + w->_lastRow = -1; + LTable_SetSelectedTo(w, index); + return true; +} + +static void LTable_MouseDown(void* widget, int idx, cc_bool wasOver) { + struct LTable* w = (struct LTable*)widget; + LBackend_TableMouseDown(w, idx); +} + +static void LTable_MouseMove(void* widget, int idx, cc_bool wasOver) { + struct LTable* w = (struct LTable*)widget; + LBackend_TableMouseMove(w, idx); +} + +static void LTable_MouseUp(void* widget, int idx) { + struct LTable* w = (struct LTable*)widget; + LBackend_TableMouseUp(w, idx); +} + +static void LTable_MouseWheel(void* widget, float delta) { + struct LTable* w = (struct LTable*)widget; + w->topRow -= Utils_AccumulateWheelDelta(&w->_wheelAcc, delta); + LTable_ClampTopRow(w); + LBackend_MarkDirty(w); + w->_lastRow = -1; +} + +static void LTable_Draw(void* widget) { + struct LTable* w = (struct LTable*)widget; + LBackend_TableDraw(w); +} + +static const struct LWidgetVTABLE ltable_VTABLE = { + LTable_Draw, NULL, + LTable_KeyDown, NULL, /* Key */ + LTable_MouseMove, NULL, /* Hover */ + LTable_MouseDown, LTable_MouseUp, /* Select */ + LTable_MouseWheel, /* Wheel */ +}; +void LTable_Add(void* screen, struct LTable* w, + const struct LLayout* layouts) { + int i; + w->VTABLE = <able_VTABLE; + w->type = LWIDGET_TABLE; + w->columns = tableColumns; + w->numColumns = Array_Elems(tableColumns); + w->sortingCol = -1; + w->opaque = true; + w->layouts = layouts; + + for (i = 0; i < w->numColumns; i++) { + w->columns[i].width = Display_ScaleX(w->columns[i].width); + } + LBackend_TableInit(w); + LScreen_AddWidget(screen, w); +} + +void LTable_Reset(struct LTable* w) { + LBackend_TableMouseUp(w, 0); + LBackend_TableReposition(w); + + w->topRow = 0; + w->rowsCount = 0; + w->_wheelAcc = 0.0f; + w->sortingCol = -1; +} + +static int ShouldShowServer(struct LTable* w, struct ServerInfo* server) { + return String_CaselessContains(&server->name, w->filter) + && (Launcher_ShowEmptyServers || server->players > 0); +} + +void LTable_ApplyFilter(struct LTable* w) { + int i, j, count; + + count = FetchServersTask.numServers; + for (i = 0, j = 0; i < count; i++) { + if (ShouldShowServer(w, Servers_Get(i))) { + FetchServersTask.servers[j++]._order = FetchServersTask.orders[i]; + } + } + + w->rowsCount = j; + for (; j < count; j++) { + FetchServersTask.servers[j]._order = -100000; + } + + w->_lastRow = -1; + LTable_ClampTopRow(w); + LBackend_TableUpdate(w); +} + +static int sortingCol; +static int LTable_SortOrder(const struct ServerInfo* a, const struct ServerInfo* b) { + int order; + if (sortingCol >= 0) { + order = tableColumns[sortingCol].SortOrder(a, b); + return tableColumns[sortingCol].invertSort ? -order : order; + } + + /* Default sort order. (most active server, then by highest uptime) */ + if (a->players != b->players) return a->players - b->players; + return a->uptime - b->uptime; +} + +static void LTable_QuickSort(int left, int right) { + cc_uint16* keys = FetchServersTask.orders; cc_uint16 key; + + while (left < right) { + int i = left, j = right; + struct ServerInfo* mid = Servers_Get((i + j) >> 1); + + /* partition the list */ + while (i <= j) { + while (LTable_SortOrder(mid, Servers_Get(i)) < 0) i++; + while (LTable_SortOrder(mid, Servers_Get(j)) > 0) j--; + QuickSort_Swap_Maybe(); + } + /* recurse into the smaller subset */ + QuickSort_Recurse(LTable_QuickSort) + } +} + +void LTable_Sort(struct LTable* w) { + sortingCol = w->sortingCol; + FetchServersTask_ResetOrder(); + + if (FetchServersTask.numServers) + LTable_QuickSort(0, FetchServersTask.numServers - 1); + + LTable_ApplyFilter(w); + LTable_ShowSelected(w); +} + +void LTable_ShowSelected(struct LTable* w) { + int i = LTable_GetSelectedIndex(w); + if (i == -1) return; + + if (i >= w->topRow + w->visibleRows) { + w->topRow = i - (w->visibleRows - 1); + } + if (i < w->topRow) w->topRow = i; + LTable_ClampTopRow(w); +} + +BitmapCol LTable_RowColor(int row, cc_bool selected, cc_bool featured) { + BitmapCol featSelColor = BitmapColor_RGB( 50, 53, 0); + BitmapCol featuredColor = BitmapColor_RGB(101, 107, 0); + BitmapCol selectedColor = BitmapColor_RGB( 40, 40, 40); + + if (featured) { + return selected ? featSelColor : featuredColor; + } else if (selected) { + return selectedColor; + } + + if (!Launcher_Theme.ClassicBackground) { + return BitmapColor_RGB(20, 20, 10); + } else { + return (row & 1) == 0 ? Launcher_Theme.BackgroundColor : 0; + } +} +#endif |