diff --git a/apps/samples/list.cpp b/apps/samples/list.cpp index 4924d67..fee660b 100644 --- a/apps/samples/list.cpp +++ b/apps/samples/list.cpp @@ -63,7 +63,7 @@ void _start() { "Age", -1, // Its title string. ES_LIST_VIEW_COLUMN_HAS_MENU // Column header has a menu. | ES_TEXT_H_RIGHT // Align the text in the column to the right. - | ES_DRAW_CONTENT_TABULAR // Use the tabular digits style, so that digits between items line up. + | ES_DRAW_CONTENT_TABULAR // Use the tabular digits style, so that digits line up between rows. | ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS // We're storing integers in this column (the default is strings). | ES_LIST_VIEW_COLUMN_FIXED_SORT_SIZE, // The items in the column can be sorted by their size. 100); // Initial width. diff --git a/apps/system_monitor.cpp b/apps/system_monitor.cpp index f3d2f23..efe9eac 100644 --- a/apps/system_monitor.cpp +++ b/apps/system_monitor.cpp @@ -26,6 +26,13 @@ struct Instance : EsInstance { #define DISPLAY_GENERAL_LOG (3) #define DISPLAY_MEMORY (12) +#define PROCESSES_COLUMN_NAME (0) +#define PROCESSES_COLUMN_PID (1) +#define PROCESSES_COLUMN_MEMORY (2) +#define PROCESSES_COLUMN_CPU (3) +#define PROCESSES_COLUMN_HANDLES (4) +#define PROCESSES_COLUMN_THREADS (5) + const EsStyle styleMonospacedTextbox = { .inherit = ES_STYLE_TEXTBOX_NO_BORDER, @@ -156,7 +163,8 @@ void UpdateProcesses(Instance *instance) { for (uintptr_t i = 0; i < previous.Length(); i++) { if (!FindProcessByPID(processes, previous[i].data.pid)) { - EsListViewRemove(instance->listViewProcesses, 0, i, 1); + bool found = EsListViewFixedItemRemove(instance->listViewProcesses, previous[i].data.pid); + EsAssert(found); previous.Delete(i--); } } @@ -194,11 +202,21 @@ void UpdateProcesses(Instance *instance) { for (uintptr_t i = 0; i < processes.Length(); i++) { if (!FindProcessByPID(previous, processes[i].data.pid)) { - EsListViewInsert(instance->listViewProcesses, 0, i, 1); + EsListViewIndex index = EsListViewFixedItemInsert(instance->listViewProcesses, processes[i].data.pid); + EsAssert(index == (EsListViewIndex) i); } } - EsListViewInvalidateAll(instance->listViewProcesses); + for (uintptr_t i = 0; i < processes.Length(); i++) { + EsListViewFixedItemSetString (instance->listViewProcesses, i, PROCESSES_COLUMN_NAME, processes[i].data.name, processes[i].data.nameBytes); + EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_PID, processes[i].data.pid); + EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_MEMORY, processes[i].data.memoryUsage); + EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_CPU, processes[i].cpuUsage); + EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_HANDLES, processes[i].data.handleCount); + EsListViewFixedItemSetInteger(instance->listViewProcesses, i, PROCESSES_COLUMN_THREADS, processes[i].data.threadCount); + } + + EsListViewFixedItemSortAll(instance->listViewProcesses); EsCommandSetDisabled(&instance->commandTerminateProcess, selectedPID < 0 || !FindProcessByPID(processes, selectedPID)); EsTimerSet(REFRESH_INTERVAL, [] (EsGeneric context) { @@ -305,17 +323,7 @@ void UpdateDisplay(Instance *instance, int index) { #define GET_CONTENT(...) EsBufferFormat(message->getContent.buffer, __VA_ARGS__) int ListViewProcessesCallback(EsElement *element, EsMessage *message) { - if (message->type == ES_MSG_LIST_VIEW_GET_CONTENT) { - int column = message->getContent.columnID, index = message->getContent.index; - ProcessItem *item = &processes[index]; - if (column == 0) GET_CONTENT("%s", item->data.nameBytes, item->data.name); - else if (column == 1) { if (item->data.pid == -1) GET_CONTENT("n/a"); else GET_CONTENT("%d", item->data.pid); } - else if (column == 2) GET_CONTENT("%D", item->data.memoryUsage); - else if (column == 3) GET_CONTENT("%d%%", item->cpuUsage); - else if (column == 4) GET_CONTENT("%d", item->data.handleCount); - else if (column == 5) GET_CONTENT("%d", item->data.threadCount); - else EsAssert(false); - } else if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) { + if (message->type == ES_MSG_LIST_VIEW_IS_SELECTED) { message->selectItem.isSelected = processes[message->selectItem.index].data.pid == selectedPID; } else if (message->type == ES_MSG_LIST_VIEW_SELECT && message->selectItem.isSelected) { selectedPID = processes[message->selectItem.index].data.pid; @@ -370,16 +378,17 @@ void ProcessApplicationMessage(EsMessage *message) { instance->textboxGeneralLog = EsTextboxCreate(switcher, ES_TEXTBOX_MULTILINE | ES_CELL_FILL | ES_ELEMENT_DISABLED, &styleMonospacedTextbox); - instance->listViewProcesses = EsListViewCreate(switcher, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_SINGLE_SELECT); + instance->listViewProcesses = EsListViewCreate(switcher, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_SINGLE_SELECT | ES_LIST_VIEW_FIXED_ITEMS); instance->listViewProcesses->messageUser = ListViewProcessesCallback; - EsListViewRegisterColumn(instance->listViewProcesses, 0, "Name", -1, 0, 150); - EsListViewRegisterColumn(instance->listViewProcesses, 1, "PID", -1, ES_TEXT_H_RIGHT, 120); - EsListViewRegisterColumn(instance->listViewProcesses, 2, "Memory", -1, ES_TEXT_H_RIGHT, 120); - EsListViewRegisterColumn(instance->listViewProcesses, 3, "CPU", -1, ES_TEXT_H_RIGHT, 120); - EsListViewRegisterColumn(instance->listViewProcesses, 4, "Handles", -1, ES_TEXT_H_RIGHT, 120); - EsListViewRegisterColumn(instance->listViewProcesses, 5, "Threads", -1, ES_TEXT_H_RIGHT, 120); + EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_NAME, "Name", -1, ES_LIST_VIEW_COLUMN_HAS_MENU, 150); + uint32_t numericColumnFlags = ES_LIST_VIEW_COLUMN_HAS_MENU | ES_TEXT_H_RIGHT | ES_DRAW_CONTENT_TABULAR + | ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS | ES_LIST_VIEW_COLUMN_FIXED_SORT_SIZE; + EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_PID, "PID", -1, numericColumnFlags, 120); + EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_MEMORY, "Memory", -1, numericColumnFlags | ES_LIST_VIEW_COLUMN_FIXED_FORMAT_BYTES, 120); + EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_CPU, "CPU", -1, numericColumnFlags | ES_LIST_VIEW_COLUMN_FIXED_FORMAT_PERCENTAGE, 120); + EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_HANDLES, "Handles", -1, numericColumnFlags, 120); + EsListViewRegisterColumn(instance->listViewProcesses, PROCESSES_COLUMN_THREADS, "Threads", -1, numericColumnFlags, 120); EsListViewAddAllColumns(instance->listViewProcesses); - EsListViewInsertGroup(instance->listViewProcesses, 0); instance->panelMemoryStatistics = EsPanelCreate(switcher, ES_CELL_FILL | ES_PANEL_TABLE | ES_PANEL_HORIZONTAL | ES_PANEL_V_SCROLL_AUTO, &stylePanelMemoryStatistics); diff --git a/desktop/gui.cpp b/desktop/gui.cpp index b7a3e3f..13a0c09 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -11,12 +11,11 @@ // TODO Close menus within menus (bug). // TODO Keyboard navigation - menus; escape to restore default focus. // TODO Middle click panning. -// TODO Scrollbar middle click and zooming; scroll wheel. +// TODO Scrollbar middle click and zooming. // TODO Textboxes: date/time overlays, keyboard shortcut overlay, custom overlays. // TODO Breadcrumb bar overflow menu; keep hover after recreating UI. // TODO Textbox embedded objects. // TODO Closing windows in menu/access key mode. -// TODO Ignore ES_MSG_LAYOUT in panels if layout.sizeChanged is false? // Behaviour of activation clicks. --> Only ignore activation clicks from menus. // Behaviour of the scroll wheel with regards to focused/hovered elements --> Scroll the hovered element only. diff --git a/desktop/list_view.cpp b/desktop/list_view.cpp index 54b4da1..23fa175 100644 --- a/desktop/list_view.cpp +++ b/desktop/list_view.cpp @@ -61,6 +61,8 @@ struct ListViewColumn { size_t enumStringCount; }; +typedef void (*ListViewSortFunction)(EsListViewIndex *, size_t, ListViewColumn *); + int ListViewProcessItemMessage(EsElement *element, EsMessage *message); void ListViewSetSortAscending(EsMenu *menu, EsGeneric context); void ListViewSetSortDescending(EsMenu *menu, EsGeneric context); @@ -2582,6 +2584,42 @@ void EsListViewInvalidateContent(EsListView *view, EsListViewIndex group, EsList } } +#define LIST_VIEW_SORT_FUNCTION(_name, _line) \ + ES_MACRO_SORT(_name, EsListViewIndex, { \ + ListViewFixedItemData *left = (ListViewFixedItemData *) &context->items[*_left]; \ + ListViewFixedItemData *right = (ListViewFixedItemData *) &context->items[*_right]; \ + result = _line; \ + }, ListViewColumn *) + +LIST_VIEW_SORT_FUNCTION(ListViewSortByStringsAscending, EsStringCompare(left->s.string, left->s.bytes, right->s.string, right->s.bytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByStringsDescending, -EsStringCompare(left->s.string, left->s.bytes, right->s.string, right->s.bytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByEnumsAscending, EsStringCompare(context->enumStrings[left->i].string, context->enumStrings[left->i].stringBytes, + context->enumStrings[right->i].string, context->enumStrings[right->i].stringBytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByEnumsDescending, -EsStringCompare(context->enumStrings[left->i].string, context->enumStrings[left->i].stringBytes, + context->enumStrings[right->i].string, context->enumStrings[right->i].stringBytes)); +LIST_VIEW_SORT_FUNCTION(ListViewSortByIntegersAscending, left->i > right->i ? 1 : left->i == right->i ? 0 : -1); +LIST_VIEW_SORT_FUNCTION(ListViewSortByIntegersDescending, left->i < right->i ? 1 : left->i == right->i ? 0 : -1); +LIST_VIEW_SORT_FUNCTION(ListViewSortByDoublesAscending, left->d > right->d ? 1 : left->d == right->d ? 0 : -1); +LIST_VIEW_SORT_FUNCTION(ListViewSortByDoublesDescending, left->d < right->d ? 1 : left->d == right->d ? 0 : -1); + +ListViewSortFunction ListViewGetSortFunction(ListViewColumn *column, uint8_t direction) { + if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_STRINGS) { + return (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByStringsDescending : ListViewSortByStringsAscending); + } else if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS) { + if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_FORMAT_MASK) == ES_LIST_VIEW_COLUMN_FIXED_FORMAT_ENUM_STRING) { + return (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByEnumsDescending : ListViewSortByEnumsAscending); + } else { + return (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByIntegersDescending : ListViewSortByIntegersAscending); + } + } else if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_DOUBLES) { + return (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByDoublesDescending : ListViewSortByDoublesAscending); + } else { + EsAssert(false); + } + + return nullptr; +} + EsListViewIndex EsListViewFixedItemInsert(EsListView *view, EsGeneric data, EsListViewIndex index, uint32_t iconID) { EsAssert(view->flags & ES_LIST_VIEW_FIXED_ITEMS); @@ -2643,12 +2681,32 @@ void ListViewFixedItemSetInternal(EsListView *view, EsListViewIndex index, uint3 EsMemoryZero(&column->items[oldLength], (view->fixedItems.Length() - oldLength) * sizeof(column->items[0])); } + bool changed = false; + + if (dataType == ES_LIST_VIEW_COLUMN_FIXED_DATA_STRINGS) { + changed = EsStringCompareRaw(column->items[index].s.string, column->items[index].s.bytes, data.s.string, data.s.bytes); + } else if (dataType == ES_LIST_VIEW_COLUMN_FIXED_DATA_DOUBLES) { + changed = column->items[index].d != data.d; + } else if (dataType == ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS) { + changed = column->items[index].i != data.i; + } else { + EsAssert(false); + } + if (dataType == ES_LIST_VIEW_COLUMN_FIXED_DATA_STRINGS) { EsHeapFree(column->items[index].s.string); } column->items[index] = data; - EsListViewInvalidateContent(view, 0, index); + + if (changed) { + for (uintptr_t i = 0; i < view->fixedItemIndices.Length(); i++) { + if (view->fixedItemIndices[i] == index) { + EsListViewInvalidateContent(view, 0, i); + break; + } + } + } } void EsListViewFixedItemSetString(EsListView *view, EsListViewIndex index, uint32_t columnID, const char *string, ptrdiff_t stringBytes) { @@ -2765,29 +2823,11 @@ void EsListViewFixedItemSetEnumStringsForColumn(EsListView *view, uint32_t colum EsAssert(false); } -#define LIST_VIEW_SORT_FUNCTION(_name, _line) \ - ES_MACRO_SORT(_name, EsListViewIndex, { \ - ListViewFixedItemData *left = (ListViewFixedItemData *) &context->items[*_left]; \ - ListViewFixedItemData *right = (ListViewFixedItemData *) &context->items[*_right]; \ - result = _line; \ - }, ListViewColumn *) - -LIST_VIEW_SORT_FUNCTION(ListViewSortByStringsAscending, EsStringCompare(left->s.string, left->s.bytes, right->s.string, right->s.bytes)); -LIST_VIEW_SORT_FUNCTION(ListViewSortByStringsDescending, -EsStringCompare(left->s.string, left->s.bytes, right->s.string, right->s.bytes)); -LIST_VIEW_SORT_FUNCTION(ListViewSortByEnumsAscending, EsStringCompare(context->enumStrings[left->i].string, context->enumStrings[left->i].stringBytes, - context->enumStrings[right->i].string, context->enumStrings[right->i].stringBytes)); -LIST_VIEW_SORT_FUNCTION(ListViewSortByEnumsDescending, -EsStringCompare(context->enumStrings[left->i].string, context->enumStrings[left->i].stringBytes, - context->enumStrings[right->i].string, context->enumStrings[right->i].stringBytes)); -LIST_VIEW_SORT_FUNCTION(ListViewSortByIntegersAscending, left->i > right->i ? 1 : left->i == right->i ? 0 : -1); -LIST_VIEW_SORT_FUNCTION(ListViewSortByIntegersDescending, left->i < right->i ? 1 : left->i == right->i ? 0 : -1); -LIST_VIEW_SORT_FUNCTION(ListViewSortByDoublesAscending, left->d > right->d ? 1 : left->d == right->d ? 0 : -1); -LIST_VIEW_SORT_FUNCTION(ListViewSortByDoublesDescending, left->d < right->d ? 1 : left->d == right->d ? 0 : -1); - -void ListViewSetSortDirection(EsListView *view, uint32_t columnID, uint8_t direction) { +void EsListViewFixedItemSortAll(EsListView *view) { ListViewColumn *column = nullptr; for (uintptr_t i = 0; i < view->registeredColumns.Length(); i++) { - if (view->registeredColumns[i].id == columnID) { + if (view->registeredColumns[i].id == view->fixedItemSortColumnID) { column = &view->registeredColumns[i]; break; } @@ -2795,34 +2835,12 @@ void ListViewSetSortDirection(EsListView *view, uint32_t columnID, uint8_t direc EsAssert(column); - if (view->fixedItemSortColumnID == columnID && view->fixedItemSortDirection == direction) { - return; - } - - view->fixedItemSortColumnID = columnID; - view->fixedItemSortDirection = direction; - EsAssert(view->fixedItems.Length() == view->fixedItemIndices.Length()); - void (*sortFunction)(EsListViewIndex *, size_t, ListViewColumn *) = nullptr; - - if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_STRINGS) { - sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByStringsDescending : ListViewSortByStringsAscending); - } else if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_INTEGERS) { - if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_FORMAT_MASK) == ES_LIST_VIEW_COLUMN_FIXED_FORMAT_ENUM_STRING) { - sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByEnumsDescending : ListViewSortByEnumsAscending); - } else { - sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByIntegersDescending : ListViewSortByIntegersAscending); - } - } else if ((column->flags & ES_LIST_VIEW_COLUMN_FIXED_DATA_MASK) == ES_LIST_VIEW_COLUMN_FIXED_DATA_DOUBLES) { - sortFunction = (direction == LIST_SORT_DIRECTION_DESCENDING ? ListViewSortByDoublesDescending : ListViewSortByDoublesAscending); - } else { - EsAssert(false); - } - EsListViewIndex previousSelectionIndex = view->fixedItemSelection >= 0 && (uintptr_t) view->fixedItemSelection < view->fixedItemIndices.Length() ? view->fixedItemIndices[view->fixedItemSelection] : -1; + ListViewSortFunction sortFunction = ListViewGetSortFunction(column, view->fixedItemSortDirection); sortFunction(view->fixedItemIndices.array, view->fixedItems.Length(), column); EsListViewInvalidateAll(view); @@ -2838,6 +2856,14 @@ void ListViewSetSortDirection(EsListView *view, uint32_t columnID, uint8_t direc } } +void ListViewSetSortDirection(EsListView *view, uint32_t columnID, uint8_t direction) { + if (view->fixedItemSortColumnID != columnID || view->fixedItemSortDirection != direction) { + view->fixedItemSortColumnID = columnID; + view->fixedItemSortDirection = direction; + EsListViewFixedItemSortAll(view); + } +} + void ListViewSetSortAscending(EsMenu *menu, EsGeneric context) { ListViewSetSortDirection((EsListView *) menu->userData.p, context.u, LIST_SORT_DIRECTION_ASCENDING); } diff --git a/desktop/os.header b/desktop/os.header index d2afd77..ac5bf9b 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -2626,3 +2626,4 @@ function void EsListViewFixedItemSetInteger(EsListView *view, EsListViewIndex in function bool EsListViewFixedItemSelect(EsListView *view, EsGeneric data); // Returns false if the item was not found. function bool EsListViewFixedItemGetSelected(EsListView *view, EsGeneric *data); // Returns false if no item was selected. function void EsListViewFixedItemSetEnumStringsForColumn(EsListView *view, uint32_t columnID, const EsListViewEnumString *strings, size_t stringCount); +function void EsListViewFixedItemSortAll(EsListView *view); // Re-sort the list after inserting new items or modifying existing items. TODO EsListViewFixedItemSortSingleItem. diff --git a/util/api_table.ini b/util/api_table.ini index 0d69a08..e5f7614 100644 --- a/util/api_table.ini +++ b/util/api_table.ini @@ -142,6 +142,7 @@ EsMutexAcquire=140 EsMutexDestroy=141 EsMutexRelease=142 EsListViewAddAllColumns=143 +EsListViewFixedItemSortAll=144 EsSchedulerYield=145 EsSleep=146 EsSpinlockAcquire=147