From 83ea7cc2af6b75d062c3f4dca18a3f7403be8975 Mon Sep 17 00:00:00 2001 From: nakst <> Date: Sun, 14 Nov 2021 17:17:28 +0000 Subject: [PATCH] create list sample application --- apps/file_manager/main.cpp | 2 +- apps/samples/list.cpp | 35 +++++++++++++++++++++++++++++++++++ apps/samples/list.ini | 5 +++++ apps/system_monitor.cpp | 14 +++++++------- desktop/desktop.cpp | 21 +++++++++++++-------- desktop/gui.cpp | 5 ++--- desktop/list_view.cpp | 11 +---------- desktop/os.header | 15 +++++++-------- desktop/text.cpp | 11 +++++++++++ desktop/theme.cpp | 30 ++++++++++++++---------------- res/Theme.dat | Bin 30488 -> 30980 bytes 11 files changed, 96 insertions(+), 53 deletions(-) create mode 100644 apps/samples/list.cpp create mode 100644 apps/samples/list.ini diff --git a/apps/file_manager/main.cpp b/apps/file_manager/main.cpp index c4b71f2..792d338 100644 --- a/apps/file_manager/main.cpp +++ b/apps/file_manager/main.cpp @@ -47,7 +47,7 @@ EsListViewColumn folderOutputColumns[] = { #define COLUMN_TYPE (1) { INTERFACE_STRING(FileManagerColumnType), ES_LIST_VIEW_COLUMN_HAS_MENU }, #define COLUMN_SIZE (2) - { INTERFACE_STRING(FileManagerColumnSize), ES_LIST_VIEW_COLUMN_HAS_MENU | ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED }, + { INTERFACE_STRING(FileManagerColumnSize), ES_LIST_VIEW_COLUMN_HAS_MENU | ES_TEXT_H_RIGHT }, }; #define LOAD_FOLDER_BACK (1) diff --git a/apps/samples/list.cpp b/apps/samples/list.cpp new file mode 100644 index 0000000..524dbd5 --- /dev/null +++ b/apps/samples/list.cpp @@ -0,0 +1,35 @@ +#include + +EsListViewColumn columns[] = { + // Title Flags Initial width + { "Name", -1, ES_FLAGS_DEFAULT, 150 }, + { "Age", -1, ES_TEXT_H_RIGHT, 100 }, + { "Favorite color", -1, ES_DRAW_CONTENT_RICH_TEXT, 150 }, +}; + +void AddPerson(EsListView *list, const char *name, int age, const char *favoriteColor) { + char ageString[16]; + EsStringFormat(ageString, sizeof(ageString), "%d%c", age, 0); + + EsListViewIndex index = EsListViewFixedItemInsert(list, name); + EsListViewFixedItemAddString(list, index, ageString); + EsListViewFixedItemAddString(list, index, favoriteColor); +} + +void _start() { + _init(); + + while (true) { + EsMessage *message = EsMessageReceive(); + + if (message->type == ES_MSG_INSTANCE_CREATE) { + EsInstance *instance = EsInstanceCreate(message, "List", -1); + EsPanel *wrapper = EsPanelCreate(instance->window, ES_CELL_FILL, ES_STYLE_PANEL_WINDOW_DIVIDER); + EsListView *list = EsListViewCreate(wrapper, ES_CELL_FILL | ES_LIST_VIEW_COLUMNS | ES_LIST_VIEW_FIXED_ITEMS); + EsListViewSetColumns(list, columns, sizeof(columns) / sizeof(columns[0])); + AddPerson(list, "Alice", 20, "\a#e00]Red"); + AddPerson(list, "Bob", 30, "\a#080]Green"); + AddPerson(list, "Cameron", 40, "\a#00f]Blue"); + } + } +} diff --git a/apps/samples/list.ini b/apps/samples/list.ini new file mode 100644 index 0000000..9e3236f --- /dev/null +++ b/apps/samples/list.ini @@ -0,0 +1,5 @@ +[general] +name=List + +[build] +source=apps/samples/list.cpp diff --git a/apps/system_monitor.cpp b/apps/system_monitor.cpp index 1530822..2698322 100644 --- a/apps/system_monitor.cpp +++ b/apps/system_monitor.cpp @@ -28,16 +28,16 @@ struct Instance : EsInstance { EsListViewColumn listViewProcessesColumns[] = { { "Name", -1, 0, 150 }, - { "PID", -1, ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 120 }, - { "Memory", -1, ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 120 }, - { "CPU", -1, ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 120 }, - { "Handles", -1, ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 120 }, - { "Threads", -1, ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 120 }, + { "PID", -1, ES_TEXT_H_RIGHT, 120 }, + { "Memory", -1, ES_TEXT_H_RIGHT, 120 }, + { "CPU", -1, ES_TEXT_H_RIGHT, 120 }, + { "Handles", -1, ES_TEXT_H_RIGHT, 120 }, + { "Threads", -1, ES_TEXT_H_RIGHT, 120 }, }; EsListViewColumn listViewContextSwitchesColumns[] = { - { "Time stamp (ms)", -1, ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, - { "CPU", -1, ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED, 150 }, + { "Time stamp (ms)", -1, ES_TEXT_H_RIGHT, 150 }, + { "CPU", -1, ES_TEXT_H_RIGHT, 150 }, { "Process", -1, 0, 150 }, { "Thread", -1, 0, 150 }, { "Count", -1, 0, 150 }, diff --git a/desktop/desktop.cpp b/desktop/desktop.cpp index 79fe1cc..648a3c9 100644 --- a/desktop/desktop.cpp +++ b/desktop/desktop.cpp @@ -193,6 +193,15 @@ const EsStyle styleButtonGroupContainer = { }, }; +const EsStyle styleClockButton = { + .inherit = ES_STYLE_TASK_BAR_EXTRA, + + .metrics = { + .mask = ES_THEME_METRICS_TEXT_FIGURES, + .textFigures = ES_TEXT_FIGURE_TABULAR, + }, +}; + struct { Array installedApplications; Array allApplicationInstances; @@ -371,10 +380,6 @@ int ReorderListLayout(ReorderList *list, int additionalRightMargin, bool clampDr size_t childCount = list->items.Length(); - if (!childCount) { - return 0; - } - int totalWidth = 0; uintptr_t nonExitingChildCount = 0; @@ -394,8 +399,8 @@ int ReorderListLayout(ReorderList *list, int additionalRightMargin, bool clampDr widthClamped = true; } - int targetWidth = totalWidth / nonExitingChildCount; - int extraWidth = totalWidth % nonExitingChildCount; + int targetWidth = nonExitingChildCount ? totalWidth / nonExitingChildCount : -1; + int extraWidth = nonExitingChildCount ? totalWidth % nonExitingChildCount : 0; list->targetWidth = targetWidth; list->extraWidth = extraWidth; @@ -575,7 +580,7 @@ void WindowTabDestroy(WindowTab *tab) { if (container->taskBarButton) { container->taskBarButton->exiting = true; container->taskBarButton->containerWindow = nullptr; - EsElementRelayout(&desktop.taskBar); + EsElementRelayout(container->taskBarButton->parent); // The button is destroyed by ReorderItemAnimate, once the exit animation completes. } @@ -2652,7 +2657,7 @@ void DesktopSetup() { desktop.tasksButton = EsButtonCreate(panel, ES_ELEMENT_HIDDEN, ES_STYLE_TASK_BAR_BUTTON); desktop.tasksButton->messageUser = TaskBarTasksButtonMessage; - EsButton *clockButton = EsButtonCreate(panel, ES_BUTTON_TABULAR | ES_BUTTON_COMPACT, ES_STYLE_TASK_BAR_EXTRA); + EsButton *clockButton = EsButtonCreate(panel, ES_BUTTON_COMPACT, &styleClockButton); clockButton->cName = "current time"; EsThreadCreate(TaskBarClockUpdateThread, nullptr, clockButton); diff --git a/desktop/gui.cpp b/desktop/gui.cpp index 66206d0..cd564f3 100644 --- a/desktop/gui.cpp +++ b/desktop/gui.cpp @@ -3920,8 +3920,7 @@ int ProcessButtonMessage(EsElement *element, EsMessage *message) { EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height), button->label, button->labelBytes, button->iconID, - ((button->flags & ES_BUTTON_DROPDOWN) ? ES_DRAW_CONTENT_MARKER_DOWN_ARROW : 0) - | ((button->flags & ES_BUTTON_TABULAR) ? ES_DRAW_CONTENT_TABULAR : 0)); + ((button->flags & ES_BUTTON_DROPDOWN) ? ES_DRAW_CONTENT_MARKER_DOWN_ARROW : 0)); } else if (message->type == ES_MSG_PAINT_ICON) { if (button->imageDisplay) { EsRectangle imageSize = ES_RECT_2S(button->imageDisplay->width, button->imageDisplay->height); @@ -4233,7 +4232,7 @@ int ProcessMenuItemMessage(EsElement *element, EsMessage *message) { EsDrawContent(message->painter, element, ES_RECT_2S(message->painter->width, message->painter->height), - (const char *) _buffer, buffer.position, 0, ES_DRAW_CONTENT_CHANGE_ALIGNMENT | ES_TEXT_H_RIGHT | ES_TEXT_V_CENTER); + (const char *) _buffer, buffer.position, 0, ES_TEXT_H_RIGHT); } else if (message->type == ES_MSG_GET_WIDTH) { uint8_t _buffer[64]; EsBuffer buffer = { .out = _buffer, .bytes = sizeof(_buffer) }; diff --git a/desktop/list_view.cpp b/desktop/list_view.cpp index 171d604..4154872 100644 --- a/desktop/list_view.cpp +++ b/desktop/list_view.cpp @@ -1193,18 +1193,9 @@ struct EsListView : EsElement { && ES_HANDLED == EsMessageSend(this, &m)) { bool useSelectedCellStyle = (item->element->customStyleState & THEME_STATE_SELECTED) && (flags & ES_LIST_VIEW_CHOICE_SELECT); UIStyle *style = useSelectedCellStyle ? selectedCellStyle : i ? secondaryCellStyle : primaryCellStyle; - - uint8_t previousTextAlign = style->textAlign; - - if (columns[i].flags & ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED) { - style->textAlign ^= ES_TEXT_H_RIGHT | ES_TEXT_H_LEFT; - } - style->PaintText(message->painter, element, bounds, (char *) _buffer, buffer.position, m.getContent.icon, - (columns[i].flags & ES_LIST_VIEW_COLUMN_TABULAR) ? ES_DRAW_CONTENT_TABULAR : ES_FLAGS_DEFAULT, - i ? nullptr : &selection); - style->textAlign = previousTextAlign; + columns[i].flags, i ? nullptr : &selection); } bounds.l += columns[i].width * theming.scale + secondaryCellStyle->gapMajor; diff --git a/desktop/os.header b/desktop/os.header index 991764c..c0402b7 100644 --- a/desktop/os.header +++ b/desktop/os.header @@ -379,13 +379,15 @@ define ES_TEXT_PLAN_CLIP_UNBREAKABLE_LINES (1 << 11) define ES_TEXT_PLAN_NO_FONT_SUBSTITUTION (1 << 12) // ...plus alignment flags. -define ES_DRAW_CONTENT_CHANGE_ALIGNMENT (1 << 8) +define ES_DRAW_CONTENT_TABULAR (1 << 8) define ES_DRAW_CONTENT_MARKER_DOWN_ARROW (1 << 9) define ES_DRAW_CONTENT_MARKER_UP_ARROW (1 << 10) -define ES_DRAW_CONTENT_TABULAR (1 << 11) define ES_DRAW_CONTENT_RICH_TEXT (1 << 12) // ...plus alignment flags, if CHANGE_ALIGNMENT set. +define ES_LIST_VIEW_COLUMN_HAS_MENU (1 << 16) // The header can be clicked to open a menu. +// ...plus draw content flags and alignment flags. + define ES_FILE_READ_SHARED (0x1) // Read-only. The file can still be opened for writing. define ES_FILE_READ (0x2) // Read-only. The file will not openable for writing. This will fail if the file is already opened for writing. define ES_FILE_WRITE_SHARED (0x4) // Read-write. The file can still be opened for writing. This will fail if the file is already opened for exclusive writing. @@ -514,7 +516,6 @@ define ES_BUTTON_CHECKBOX (1 << 10) define ES_BUTTON_RADIOBOX (1 << 11) define ES_BUTTON_CANCEL (1 << 12) define ES_BUTTON_PUSH (1 << 13) -define ES_BUTTON_TABULAR (1 << 14) // Use tabular text figures. define ES_MENU_ITEM_CHECKED (ES_CHECK_CHECKED) define ES_COLOR_WELL_HAS_OPACITY (1 << 0) @@ -542,10 +543,6 @@ define ES_LIST_VIEW_GROUP_HAS_FOOTER (1 << 1) // The last item in the group is define ES_LIST_VIEW_GROUP_INDENT (1 << 2) // Indent the group's items (excluding the header and footer). define ES_LIST_VIEW_GROUP_COLLAPSABLE (1 << 3) // The group can be collapsed. -define ES_LIST_VIEW_COLUMN_RIGHT_ALIGNED (1 << 0) // The column's contents is right-aligned. -define ES_LIST_VIEW_COLUMN_HAS_MENU (1 << 1) // The header can be clicked to open a menu. -define ES_LIST_VIEW_COLUMN_TABULAR (1 << 2) // Use tabular text figures (so that digits are aligned). - define ES_MENU_AT_CURSOR (1 << 0) define ES_MENU_MAXIMUM_HEIGHT (1 << 1) @@ -627,6 +624,7 @@ define ES_THEME_METRICS_FONT_WEIGHT (1 << 20) define ES_THEME_METRICS_ICON_SIZE (1 << 21) define ES_THEME_METRICS_IS_ITALIC (1 << 22) define ES_THEME_METRICS_LAYOUT_VERTICAL (1 << 23) +define ES_THEME_METRICS_TEXT_FIGURES (1 << 24) define ES_WINDOW_MOVE_MAXIMIZED (1 << 0) define ES_WINDOW_MOVE_ADJUST_TO_FIT_SCREEN (1 << 1) @@ -1331,7 +1329,8 @@ struct EsThemeMetrics { int32_t maximumWidth, maximumHeight; int32_t gapMajor, gapMinor, gapWrap; uint32_t textColor, selectedBackground, selectedText, iconColor; - int textAlign, textSize, fontFamily, fontWeight, iconSize; + int32_t textAlign, textSize, fontFamily, fontWeight, iconSize; + uint8_t textFigures; bool isItalic, layoutVertical; }; diff --git a/desktop/text.cpp b/desktop/text.cpp index 67f459d..fe3d721 100644 --- a/desktop/text.cpp +++ b/desktop/text.cpp @@ -2686,6 +2686,17 @@ void EsRichTextParse(const char *inString, ptrdiff_t inStringBytes, textRun->style.decorations |= ES_TEXT_DECORATION_UNDERLINE; } else if (c == '2' /* secondary color */) { textRun->style.color = GetConstantNumber("textSecondary"); + } else if (c == '#' /* custom color */) { + char string[9]; + size_t bytes = 0; + + while (bytes < sizeof(string)) { + if (i >= inStringBytes || inString[i] == ']') break; + string[bytes++] = inString[i++]; + } + + textRun->style.color = EsColorParse(string, bytes); + goto parsedFormat; } } diff --git a/desktop/theme.cpp b/desktop/theme.cpp index 8d15f53..919f132 100644 --- a/desktop/theme.cpp +++ b/desktop/theme.cpp @@ -207,6 +207,7 @@ typedef struct ThemeMetrics { uint32_t textColor, selectedBackground, selectedText, iconColor; int8_t textAlign, fontWeight; int16_t textSize, iconSize; + uint8_t textFigures; bool isItalic, layoutVertical; } ThemeMetrics; @@ -1200,24 +1201,16 @@ typedef struct ThemeAnimation { struct UIStyle { intptr_t referenceCount; - uint32_t observedStyleStateMask; - bool IsStateChangeObserved(uint16_t state1, uint16_t state2); - // General information. uint8_t textAlign; - uint16_t textSize; - uint32_t textColor; EsFont font; - EsRectangle insets, borders; uint16_t preferredWidth, preferredHeight; int16_t gapMajor, gapMinor, gapWrap; - + uint32_t observedStyleStateMask; EsRectangle paintOutsets, opaqueInsets; - float scale; - EsThemeAppearance *appearance; // An optional, custom appearance provided by the application. // Data. @@ -1242,7 +1235,7 @@ struct UIStyle { // Misc. bool IsRegionCompletelyOpaque(EsRectangle region, int width, int height); - + bool IsStateChangeObserved(uint16_t state1, uint16_t state2); inline void GetTextStyle(EsTextStyle *style); }; @@ -1326,8 +1319,6 @@ bool ThemeInitialise() { } void ThemeStyleCopyInlineMetrics(UIStyle *style) { - style->textSize = style->metrics->textSize; - style->textColor = style->metrics->textColor; style->font.family = style->metrics->fontFamily; style->font.weight = style->metrics->fontWeight; style->font.italic = style->metrics->isItalic; @@ -1547,6 +1538,7 @@ void ThemeStylePrepare(UIStyle *style, UIStyleKey key) { if (customMetrics->mask & ES_THEME_METRICS_GAP_MINOR) style->metrics->gapMinor = customMetrics->gapMinor; if (customMetrics->mask & ES_THEME_METRICS_GAP_WRAP) style->metrics->gapWrap = customMetrics->gapWrap; if (customMetrics->mask & ES_THEME_METRICS_TEXT_COLOR) style->metrics->textColor = customMetrics->textColor; + if (customMetrics->mask & ES_THEME_METRICS_TEXT_FIGURES) style->metrics->textFigures = customMetrics->textFigures; if (customMetrics->mask & ES_THEME_METRICS_SELECTED_BACKGROUND) style->metrics->selectedBackground = customMetrics->selectedBackground; if (customMetrics->mask & ES_THEME_METRICS_SELECTED_TEXT) style->metrics->selectedText = customMetrics->selectedText; if (customMetrics->mask & ES_THEME_METRICS_ICON_COLOR) style->metrics->iconColor = customMetrics->iconColor; @@ -1933,13 +1925,18 @@ void UIStyle::PaintText(EsPainter *painter, EsElement *element, EsRectangle rect if (textBytes) { EsTextPlanProperties properties = {}; - properties.flags = (flags & ES_DRAW_CONTENT_CHANGE_ALIGNMENT) ? (flags & 0xFF) : textAlign; + properties.flags = textAlign; + + if (flags & ES_TEXT_H_LEFT) properties.flags = (properties.flags & ~(ES_TEXT_H_CENTER | ES_TEXT_H_RIGHT)) | ES_TEXT_H_LEFT; + if (flags & ES_TEXT_H_CENTER) properties.flags = (properties.flags & ~(ES_TEXT_H_LEFT | ES_TEXT_H_RIGHT)) | ES_TEXT_H_CENTER; + if (flags & ES_TEXT_H_RIGHT) properties.flags = (properties.flags & ~(ES_TEXT_H_LEFT | ES_TEXT_H_CENTER)) | ES_TEXT_H_RIGHT; + if (flags & ES_TEXT_V_TOP) properties.flags = (properties.flags & ~(ES_TEXT_V_CENTER | ES_TEXT_V_BOTTOM)) | ES_TEXT_V_TOP; + if (flags & ES_TEXT_V_CENTER) properties.flags = (properties.flags & ~(ES_TEXT_V_TOP | ES_TEXT_V_BOTTOM)) | ES_TEXT_V_CENTER; + if (flags & ES_TEXT_V_BOTTOM) properties.flags = (properties.flags & ~(ES_TEXT_V_TOP | ES_TEXT_V_CENTER)) | ES_TEXT_V_BOTTOM; EsTextRun textRun[2] = {}; textRun[1].offset = textBytes; - textRun[0].style.font = font; - textRun[0].style.size = textSize; - textRun[0].style.color = textColor; + GetTextStyle(&textRun[0].style); if (flags & ES_DRAW_CONTENT_TABULAR) { textRun[0].style.figures = ES_TEXT_FIGURE_TABULAR; @@ -2038,6 +2035,7 @@ inline void UIStyle::GetTextStyle(EsTextStyle *style) { style->font = font; style->size = metrics->textSize; style->color = metrics->textColor; + style->figures = metrics->textFigures; } bool UIStyle::IsStateChangeObserved(uint16_t state1, uint16_t state2) { diff --git a/res/Theme.dat b/res/Theme.dat index b675896fcf724083b8c94e9256bcd9695b3ba6f5..563c8856ca8ff08971b2b139afbe88168bf10307 100644 GIT binary patch literal 30980 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 literal 30488 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