From a8a89e35b87523549eec731912459725e1b8df1b Mon Sep 17 00:00:00 2001 From: CaptainFrito Date: Thu, 19 Feb 2026 17:38:09 +0700 Subject: [PATCH] feat: Lyra Icons (#725) /!\ This PR depends on https://github.com/crosspoint-reader/crosspoint-reader/pull/732 being merged first Also requires the https://github.com/open-x4-epaper/community-sdk/pull/18 PR Lyra theme icons on the home menu, in the file browser and on empty book covers ![IMG_8023 Medium](https://github.com/user-attachments/assets/ba7c1407-94d2-4353-80ff-d5b800c6ac5b) ![IMG_8024 Medium](https://github.com/user-attachments/assets/edb59e13-b1c9-4c86-bef3-c61cc8134e64) ![IMG_7958 Medium](https://github.com/user-attachments/assets/d3079ce1-95f0-43f4-bbc7-1f747cc70203) ![IMG_8033 Medium](https://github.com/user-attachments/assets/f3e2e03b-0fa8-47b7-8717-c0b71361b7a8) - Added a function to the open-x4-sdk renderer to draw transparent images - Added a scripts/convert_icon.py script to convert svg/png icons into a C array that can be directly imported into the project. Usage: ```bash python ./scripts/convert_icon.py 'path/to/icon.png' cover 32 32 ``` This will create a components/icons/cover.h file with a C array called CoverIcon, of size 32x32px. Lyra uses icons from https://lucide.dev/icons with a stroke width of 2px, that can be downloaded with any desired size on the site. > The file browser is noticeably slower with the addition of icons, and using an image buffer like on the home page doesn't help very much. Any suggestions to optimize this are welcome. --- While CrossPoint doesn't have restrictions on AI tools in contributing, please be transparent about their usage as it helps set the right context for reviewers. Did you use AI tools to help write this code? _**PARTIALLY**_ The icon conversion python script was generated by Copilot as I am not a python dev. --------- Co-authored-by: Dave Allie --- lib/GfxRenderer/GfxRenderer.cpp | 2 +- lib/hal/HalDisplay.cpp | 5 + lib/hal/HalDisplay.h | 2 + scripts/convert_icon.py | 80 ++++++++++++ src/activities/home/HomeActivity.cpp | 6 +- src/activities/home/MyLibraryActivity.cpp | 11 +- src/activities/home/RecentBooksActivity.cpp | 2 +- .../network/NetworkModeSelectionActivity.cpp | 3 +- src/components/UITheme.cpp | 23 ++++ src/components/UITheme.h | 3 + src/components/icons/book.h | 12 ++ src/components/icons/book24.h | 9 ++ src/components/icons/cog.h | 12 ++ src/components/icons/cover.h | 12 ++ src/components/icons/file24.h | 9 ++ src/components/icons/folder.h | 12 ++ src/components/icons/folder24.h | 9 ++ src/components/icons/hotspot.h | 12 ++ src/components/icons/image24.h | 9 ++ src/components/icons/library.h | 12 ++ src/components/icons/recent.h | 12 ++ src/components/icons/settings.h | 12 ++ src/components/icons/settings2.h | 12 ++ src/components/icons/text24.h | 9 ++ src/components/icons/transfer.h | 12 ++ src/components/icons/wifi.h | 12 ++ src/components/themes/BaseTheme.cpp | 6 +- src/components/themes/BaseTheme.h | 8 +- src/components/themes/lyra/LyraTheme.cpp | 114 +++++++++++++++--- src/components/themes/lyra/LyraTheme.h | 6 +- 30 files changed, 419 insertions(+), 29 deletions(-) create mode 100644 scripts/convert_icon.py create mode 100644 src/components/icons/book.h create mode 100644 src/components/icons/book24.h create mode 100644 src/components/icons/cog.h create mode 100644 src/components/icons/cover.h create mode 100644 src/components/icons/file24.h create mode 100644 src/components/icons/folder.h create mode 100644 src/components/icons/folder24.h create mode 100644 src/components/icons/hotspot.h create mode 100644 src/components/icons/image24.h create mode 100644 src/components/icons/library.h create mode 100644 src/components/icons/recent.h create mode 100644 src/components/icons/settings.h create mode 100644 src/components/icons/settings2.h create mode 100644 src/components/icons/text24.h create mode 100644 src/components/icons/transfer.h create mode 100644 src/components/icons/wifi.h diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index c08a9668..3defabf7 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -410,7 +410,7 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co } void GfxRenderer::drawIcon(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { - display.drawImage(bitmap, y, getScreenWidth() - width - x, height, width); + display.drawImageTransparent(bitmap, y, getScreenWidth() - width - x, height, width); } void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, const int maxHeight, diff --git a/lib/hal/HalDisplay.cpp b/lib/hal/HalDisplay.cpp index 4c0bdc27..6145eea7 100644 --- a/lib/hal/HalDisplay.cpp +++ b/lib/hal/HalDisplay.cpp @@ -16,6 +16,11 @@ void HalDisplay::drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uin einkDisplay.drawImage(imageData, x, y, w, h, fromProgmem); } +void HalDisplay::drawImageTransparent(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, + bool fromProgmem) const { + einkDisplay.drawImageTransparent(imageData, x, y, w, h, fromProgmem); +} + EInkDisplay::RefreshMode convertRefreshMode(HalDisplay::RefreshMode mode) { switch (mode) { case HalDisplay::FULL_REFRESH: diff --git a/lib/hal/HalDisplay.h b/lib/hal/HalDisplay.h index 547aa2df..97dd5143 100644 --- a/lib/hal/HalDisplay.h +++ b/lib/hal/HalDisplay.h @@ -30,6 +30,8 @@ class HalDisplay { void clearScreen(uint8_t color = 0xFF) const; void drawImage(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, bool fromProgmem = false) const; + void drawImageTransparent(const uint8_t* imageData, uint16_t x, uint16_t y, uint16_t w, uint16_t h, + bool fromProgmem = false) const; void displayBuffer(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false); void refreshDisplay(RefreshMode mode = RefreshMode::FAST_REFRESH, bool turnOffScreen = false); diff --git a/scripts/convert_icon.py b/scripts/convert_icon.py new file mode 100644 index 00000000..56ea7d4b --- /dev/null +++ b/scripts/convert_icon.py @@ -0,0 +1,80 @@ +import sys +import os +from PIL import Image +import cairosvg +import io + +threshold = 128 + +def svg_to_png_bytes(svg_path, width, height): + with open(svg_path, 'rb') as f: + svg_data = f.read() + png_bytes = cairosvg.svg2png(bytestring=svg_data, output_width=width, output_height=height) + return png_bytes + +def load_image(path, width, height): + ext = os.path.splitext(path)[1].lower() + if ext == '.svg': + png_bytes = svg_to_png_bytes(path, width, height) + img = Image.open(io.BytesIO(png_bytes)) + else: + img = Image.open(path) + img = img.convert('RGBA') + img = img.resize((width, height), Image.LANCZOS) + # Flatten alpha: paste on white background + background = Image.new('RGBA', img.size, (255, 255, 255, 255)) + background.paste(img, mask=img.split()[3]) + img = background + # Rotate 90 degrees counterclockwise + img = img.rotate(90, expand=True) + return img + +def image_to_c_array(img, array_name): + # Convert to grayscale, then threshold to get white=1, black=0 + # Convert to grayscale + img = img.convert('L') + width, height = img.size + pixels = list(img.getdata()) + packed = [] + for y in range(height): + for x in range(0, width, 8): + byte = 0 + for b in range(8): + if x + b < width: + v = pixels[y * width + x + b] + # 1 for white, 0 for black + bit = 1 if v >= threshold else 0 + byte |= (bit << (7 - b)) + packed.append(byte) + # Format as C array + c = f'#pragma once\n#include \n\n' + c += f'// size: {width}x{height}\n' + c += f'static const uint8_t {array_name}[] = {{\n ' + for i, v in enumerate(packed): + c += f'0x{v:02X}, ' + if (i + 1) % 16 == 0: + c += '\n ' + c = c.rstrip(', \n') + '\n};\n' + return c + +def main(): + if len(sys.argv) < 5: + print('Usage: python convert_image.py input.png output_name width height') + sys.exit(1) + input_path, output_name, width, height = sys.argv[1:5] + array_name = output_name.capitalize() + 'Icon' + width, height = int(width), int(height) + img = load_image(input_path, width, height) + c_array = image_to_c_array(img, array_name) + + # Always save to src/components/icons/[output_name].h relative to project root + project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + output_dir = os.path.join(project_root, 'src', 'components', 'icons') + os.makedirs(output_dir, exist_ok=True) + output_path = os.path.join(output_dir, f'{output_name}.h') + with open(output_path, 'w') as f: + f.write(c_array) + print(f'Wrote {output_path}') + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/src/activities/home/HomeActivity.cpp b/src/activities/home/HomeActivity.cpp index 90d1abb4..8adee199 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -246,9 +246,12 @@ void HomeActivity::render(Activity::RenderLock&&) { // Build menu items dynamically std::vector menuItems = {tr(STR_BROWSE_FILES), tr(STR_MENU_RECENT_BOOKS), tr(STR_FILE_TRANSFER), tr(STR_SETTINGS_TITLE)}; + std::vector menuIcons = {Folder, Recent, Transfer, Settings}; + if (hasOpdsUrl) { // Insert OPDS Browser after My Library menuItems.insert(menuItems.begin() + 2, tr(STR_OPDS_BROWSER)); + menuIcons.insert(menuIcons.begin() + 2, Library); } GUI.drawButtonMenu( @@ -257,7 +260,8 @@ void HomeActivity::render(Activity::RenderLock&&) { pageHeight - (metrics.headerHeight + metrics.homeTopPadding + metrics.verticalSpacing * 2 + metrics.buttonHintsHeight)}, static_cast(menuItems.size()), selectorIndex - recentBooks.size(), - [&menuItems](int index) { return std::string(menuItems[index]); }, nullptr); + [&menuItems](int index) { return std::string(menuItems[index]); }, + [&menuIcons](int index) { return menuIcons[index]; }); const auto labels = mappedInput.mapLabels("", tr(STR_SELECT), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); GUI.drawButtonHints(renderer, labels.btn1, labels.btn2, labels.btn3, labels.btn4); diff --git a/src/activities/home/MyLibraryActivity.cpp b/src/activities/home/MyLibraryActivity.cpp index a651799f..8b44d7d1 100644 --- a/src/activities/home/MyLibraryActivity.cpp +++ b/src/activities/home/MyLibraryActivity.cpp @@ -188,6 +188,14 @@ void MyLibraryActivity::loop() { }); } +std::string getFileName(std::string filename) { + if (filename.back() == '/') { + return filename.substr(0, filename.length() - 1); + } + const auto pos = filename.rfind('.'); + return filename.substr(0, pos); +} + void MyLibraryActivity::render(Activity::RenderLock&&) { renderer.clearScreen(); @@ -205,7 +213,8 @@ void MyLibraryActivity::render(Activity::RenderLock&&) { } else { GUI.drawList( renderer, Rect{0, contentTop, pageWidth, contentHeight}, files.size(), selectorIndex, - [this](int index) { return files[index]; }, nullptr, nullptr, nullptr); + [this](int index) { return getFileName(files[index]); }, nullptr, + [this](int index) { return UITheme::getFileIcon(files[index]); }); } // Help text diff --git a/src/activities/home/RecentBooksActivity.cpp b/src/activities/home/RecentBooksActivity.cpp index 05317214..2ea9ff01 100644 --- a/src/activities/home/RecentBooksActivity.cpp +++ b/src/activities/home/RecentBooksActivity.cpp @@ -102,7 +102,7 @@ void RecentBooksActivity::render(Activity::RenderLock&&) { GUI.drawList( renderer, Rect{0, contentTop, pageWidth, contentHeight}, recentBooks.size(), selectorIndex, [this](int index) { return recentBooks[index].title; }, [this](int index) { return recentBooks[index].author; }, - nullptr, nullptr); + [this](int index) { return UITheme::getFileIcon(recentBooks[index].path); }); } // Help text diff --git a/src/activities/network/NetworkModeSelectionActivity.cpp b/src/activities/network/NetworkModeSelectionActivity.cpp index e5214229..1b9304da 100644 --- a/src/activities/network/NetworkModeSelectionActivity.cpp +++ b/src/activities/network/NetworkModeSelectionActivity.cpp @@ -70,11 +70,12 @@ void NetworkModeSelectionActivity::render(Activity::RenderLock&&) { StrId::STR_CREATE_HOTSPOT}; static constexpr StrId menuDescs[MENU_ITEM_COUNT] = {StrId::STR_JOIN_DESC, StrId::STR_CALIBRE_DESC, StrId::STR_HOTSPOT_DESC}; + static constexpr UIIcon menuIcons[MENU_ITEM_COUNT] = {UIIcon::Wifi, UIIcon::Library, UIIcon::Hotspot}; GUI.drawList( renderer, Rect{0, contentTop, pageWidth, contentHeight}, static_cast(MENU_ITEM_COUNT), selectedIndex, [](int index) { return std::string(I18N.get(menuItems[index])); }, - [](int index) { return std::string(I18N.get(menuDescs[index])); }); + [](int index) { return std::string(I18N.get(menuDescs[index])); }, [](int index) { return menuIcons[index]; }); // Draw help text at bottom const auto labels = mappedInput.mapLabels(tr(STR_BACK), tr(STR_SELECT), "", ""); diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index b808dc75..5f40d17a 100644 --- a/src/components/UITheme.cpp +++ b/src/components/UITheme.cpp @@ -5,10 +5,16 @@ #include +#include "MappedInputManager.h" #include "RecentBooksStore.h" #include "components/themes/BaseTheme.h" #include "components/themes/lyra/Lyra3CoversTheme.h" #include "components/themes/lyra/LyraTheme.h" +#include "util/StringUtils.h" + +namespace { +constexpr int SKIP_PAGE_MS = 700; +} // namespace UITheme UITheme::instance; @@ -67,3 +73,20 @@ std::string UITheme::getCoverThumbPath(std::string coverBmpPath, int coverHeight } return coverBmpPath; } + +UIIcon UITheme::getFileIcon(std::string filename) { + if (filename.back() == '/') { + return Folder; + } + if (StringUtils::checkFileExtension(filename, ".epub") || StringUtils::checkFileExtension(filename, ".xtch") || + StringUtils::checkFileExtension(filename, ".xtc")) { + return Book; + } + if (StringUtils::checkFileExtension(filename, ".txt") || StringUtils::checkFileExtension(filename, ".md")) { + return Text; + } + if (StringUtils::checkFileExtension(filename, ".bmp")) { + return Image; + } + return File; +} \ No newline at end of file diff --git a/src/components/UITheme.h b/src/components/UITheme.h index b4ca3bf3..ac7176e7 100644 --- a/src/components/UITheme.h +++ b/src/components/UITheme.h @@ -7,6 +7,8 @@ #include "CrossPointSettings.h" #include "components/themes/BaseTheme.h" +class MappedInputManager; + class UITheme { // Static instance static UITheme instance; @@ -22,6 +24,7 @@ class UITheme { static int getNumberOfItemsPerPage(const GfxRenderer& renderer, bool hasHeader, bool hasTabBar, bool hasButtonHints, bool hasSubtitle); static std::string getCoverThumbPath(std::string coverBmpPath, int coverHeight); + static UIIcon getFileIcon(std::string filename); private: const ThemeMetrics* currentMetrics; diff --git a/src/components/icons/book.h b/src/components/icons/book.h new file mode 100644 index 00000000..928db130 --- /dev/null +++ b/src/components/icons/book.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t BookIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x07, 0xC0, 0x00, 0x00, 0x03, 0xCF, 0xFF, 0xF8, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, + 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, + 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, + 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, + 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xC3, 0xFF, 0xFC, 0x23, 0xE0, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/book24.h b/src/components/icons/book24.h new file mode 100644 index 00000000..c9f1db42 --- /dev/null +++ b/src/components/icons/book24.h @@ -0,0 +1,9 @@ +#pragma once +#include + +// size: 24x24 +static const uint8_t Book24Icon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0x80, 0x00, 0x01, 0x9F, 0xFF, 0x39, + 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, + 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, 0x9F, 0xFF, 0x39, + 0x9F, 0xFF, 0x39, 0xC0, 0x00, 0x03, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/cog.h b/src/components/icons/cog.h new file mode 100644 index 00000000..899d81a9 --- /dev/null +++ b/src/components/icons/cog.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t CogIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xBE, 0x7D, 0xFF, 0xFF, 0x1C, 0x38, + 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x82, 0x41, 0xFF, 0xFF, 0x0E, 0x70, 0xFF, 0xF6, 0x3E, 0x7C, 0x6F, 0xE0, 0x7E, + 0x7E, 0x07, 0xF0, 0xFE, 0x7F, 0x0F, 0xF8, 0xFE, 0x7F, 0x1F, 0xF9, 0xFE, 0x7F, 0x9F, 0xF9, 0xF8, 0x1F, 0x9F, 0xF3, + 0xF8, 0x1F, 0xCF, 0xC3, 0xF3, 0xCF, 0xC3, 0xC3, 0xF1, 0x8F, 0xC3, 0xF3, 0xE1, 0x87, 0xCF, 0xF9, 0xC0, 0x03, 0x9F, + 0xF9, 0x0E, 0x70, 0x9F, 0xF8, 0x3F, 0xFC, 0x1F, 0xF0, 0x7F, 0xFE, 0x0F, 0xE0, 0x7F, 0xFE, 0x07, 0xF6, 0x3F, 0xFC, + 0x6F, 0xFF, 0x0F, 0xF0, 0xFF, 0xFF, 0x83, 0xC1, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFF, 0x1C, 0x38, 0xFF, 0xFF, 0xBE, + 0x7D, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/cover.h b/src/components/icons/cover.h new file mode 100644 index 00000000..38eda9ff --- /dev/null +++ b/src/components/icons/cover.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t CoverIcon[] = { + 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0xFF, 0xFE, 0x1F, 0xE0, 0x00, 0x02, 0x1F, 0xE0, 0x00, 0x03, + 0x1F, 0xE0, 0x00, 0x03, 0x1F, 0xF0, 0x00, 0x01, 0x1F, 0xF0, 0x00, 0x01, 0x1F, 0xF0, 0x00, 0x01, 0x9F, 0xF8, 0x00, + 0x00, 0x9F, 0xF8, 0x00, 0x00, 0xDF, 0xFC, 0x00, 0x00, 0x6F, 0xFE, 0x00, 0x00, 0x3F, 0xFF, 0x00, 0x00, 0x1F, 0xFF, + 0x80, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x80, 0x00, 0x0F, 0xFF, 0x00, 0x00, 0x1F, + 0xFE, 0x00, 0x00, 0x3F, 0xFC, 0x00, 0x00, 0x6F, 0xF8, 0x00, 0x00, 0xDF, 0xF8, 0x00, 0x00, 0x9F, 0xF0, 0x00, 0x01, + 0x9F, 0xF0, 0x00, 0x01, 0x1F, 0xE0, 0x00, 0x01, 0x1F, 0xE0, 0x00, 0x03, 0x1F, 0xE0, 0x00, 0x02, 0x1F, 0xE0, 0x00, + 0x02, 0x1F, 0xFF, 0xFF, 0xFE, 0x1F, 0xFF, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x1F}; diff --git a/src/components/icons/file24.h b/src/components/icons/file24.h new file mode 100644 index 00000000..197097b3 --- /dev/null +++ b/src/components/icons/file24.h @@ -0,0 +1,9 @@ +#pragma once +#include + +// size: 24x24 +static const uint8_t File24Icon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x03, 0xF8, 0x00, 0x03, 0xF0, 0x7F, 0xF1, + 0xE2, 0x7F, 0xF9, 0xC6, 0x7F, 0xF9, 0x8E, 0x7F, 0xF9, 0x80, 0x7F, 0xF9, 0x80, 0xFF, 0xF9, 0x9F, 0xFF, 0xF9, + 0x9F, 0xFF, 0xF9, 0x9F, 0xFF, 0xF9, 0x9F, 0xFF, 0xF9, 0x9F, 0xFF, 0xF9, 0x9F, 0xFF, 0xF9, 0x9F, 0xFF, 0xF9, + 0x8F, 0xFF, 0xF1, 0xC0, 0x00, 0x01, 0xC0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/folder.h b/src/components/icons/folder.h new file mode 100644 index 00000000..b0997c45 --- /dev/null +++ b/src/components/icons/folder.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t FolderIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x1F, 0xFE, 0x00, 0x00, 0x1F, 0xFE, 0x3F, 0xFF, + 0x9F, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, + 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, + 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFE, 0x7F, 0xFF, 0xCF, 0xFC, 0x7F, 0xFF, 0xCF, 0xF0, 0xFF, 0xFF, 0xCF, + 0xE1, 0xFF, 0xFF, 0xCF, 0xE3, 0xFF, 0xFF, 0xCF, 0xE7, 0xFF, 0xFF, 0xCF, 0xE7, 0xFF, 0xFF, 0xCF, 0xE7, 0xFF, 0xFF, + 0xCF, 0xE7, 0xFF, 0xFF, 0xCF, 0xE7, 0xFF, 0xFF, 0xCF, 0xE7, 0xFF, 0xFF, 0x8F, 0xE7, 0xFF, 0xFF, 0x8F, 0xF0, 0x00, + 0x00, 0x1F, 0xF0, 0x00, 0x00, 0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/folder24.h b/src/components/icons/folder24.h new file mode 100644 index 00000000..20f47007 --- /dev/null +++ b/src/components/icons/folder24.h @@ -0,0 +1,9 @@ +#pragma once +#include + +// size: 24x24 +static const uint8_t Folder24Icon[] = { + 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x1F, 0xFC, 0x00, 0x0F, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, + 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, 0xF9, 0xFF, 0xE7, + 0xF9, 0xFF, 0xE7, 0xF3, 0xFF, 0xE7, 0xE7, 0xFF, 0xE7, 0xCF, 0xFF, 0xE7, 0xCF, 0xFF, 0xE7, 0xCF, 0xFF, 0xE7, + 0xCF, 0xFF, 0xE7, 0xCF, 0xFF, 0xE7, 0xCF, 0xFF, 0xE7, 0xE0, 0x00, 0x0F, 0xF0, 0x00, 0x1F, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/hotspot.h b/src/components/icons/hotspot.h new file mode 100644 index 00000000..81683325 --- /dev/null +++ b/src/components/icons/hotspot.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t HotspotIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0x80, 0x01, 0xFF, 0xFE, 0x0F, 0xF0, + 0x7F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFC, 0xFF, 0xFE, 0x3F, 0xFD, 0xF8, 0x1F, 0xBF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xC3, + 0xC3, 0xFF, 0xFF, 0xCF, 0xF1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, + 0xF9, 0x9F, 0xFF, 0xFF, 0xF3, 0xCF, 0xFF, 0xFF, 0xF3, 0xCF, 0xFF, 0xFF, 0xF9, 0x9F, 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, + 0xFF, 0xFE, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x8F, 0xF3, 0xFF, 0xFF, 0xC3, 0xC3, 0xFF, 0xFF, 0xE0, 0x07, + 0xFF, 0xFD, 0xF8, 0x1F, 0xBF, 0xFC, 0x7F, 0xFF, 0x3F, 0xFC, 0x3F, 0xFC, 0x3F, 0xFE, 0x0F, 0xF0, 0x7F, 0xFF, 0x80, + 0x01, 0xFF, 0xFF, 0xE0, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/image24.h b/src/components/icons/image24.h new file mode 100644 index 00000000..78303804 --- /dev/null +++ b/src/components/icons/image24.h @@ -0,0 +1,9 @@ +#pragma once +#include + +// size: 24x24 +static const uint8_t Image24Icon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x0F, 0xE0, 0x00, 0x07, 0xCF, 0xF1, 0xF3, 0xCF, 0xE3, 0xF3, + 0xCF, 0xE7, 0xF3, 0xCF, 0xEF, 0xF3, 0xCF, 0xE7, 0xF3, 0xCF, 0xE3, 0xF3, 0xCF, 0xF1, 0xF3, 0xCF, 0xF8, 0xF3, + 0xCF, 0x3C, 0x73, 0xCE, 0x1E, 0x33, 0xCC, 0xCF, 0x13, 0xCC, 0xCF, 0x83, 0xCE, 0x1F, 0xC3, 0xCF, 0x3F, 0xE3, + 0xCF, 0xFF, 0xF3, 0xCF, 0xFF, 0xF3, 0xE0, 0x00, 0x07, 0xF0, 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/library.h b/src/components/icons/library.h new file mode 100644 index 00000000..ab79e61c --- /dev/null +++ b/src/components/icons/library.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t LibraryIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, + 0x1F, 0xFF, 0xFF, 0xF0, 0x0F, 0xFF, 0xFF, 0xC1, 0xCF, 0xFF, 0xFE, 0x07, 0xE7, 0xFF, 0xF0, 0x3F, 0xE7, 0xFF, 0x81, + 0xFF, 0x87, 0xFC, 0x0F, 0xFC, 0x0F, 0xF0, 0x3F, 0xF0, 0x3F, 0xE1, 0xFF, 0x81, 0xFF, 0xE7, 0xFC, 0x0F, 0xFF, 0xE7, + 0xE0, 0x7F, 0xFF, 0xF3, 0x83, 0xFF, 0xFF, 0xF0, 0x00, 0x00, 0x1F, 0xE0, 0x00, 0x00, 0x07, 0xE7, 0xFF, 0xFF, 0xE7, + 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xE0, 0x00, 0x00, 0x07, 0xE0, 0x00, 0x00, + 0x07, 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xE7, 0xFF, 0xFF, 0xE7, 0xE0, 0x00, 0x00, 0x07, 0xF0, 0x00, + 0x00, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/recent.h b/src/components/icons/recent.h new file mode 100644 index 00000000..fd61b95b --- /dev/null +++ b/src/components/icons/recent.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t RecentIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0x00, 0x00, + 0x07, 0xC0, 0x00, 0x00, 0x03, 0xCF, 0xFF, 0xF8, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, + 0xFC, 0xF3, 0xC0, 0x03, 0xFC, 0xF3, 0xC0, 0x03, 0xFC, 0xF3, 0xCF, 0xC7, 0xFC, 0xF3, 0xCF, 0x8F, 0xFC, 0xF3, 0xCF, + 0x1F, 0xFC, 0xF3, 0xCF, 0x8F, 0xFC, 0xF3, 0xCF, 0x87, 0xFC, 0xF3, 0xCF, 0xC3, 0xFC, 0xF3, 0xC0, 0x03, 0xFC, 0xF3, + 0xC0, 0x03, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xCF, 0xFF, 0xFC, + 0xF3, 0xCF, 0xFF, 0xFC, 0xF3, 0xC3, 0xFF, 0xFC, 0x23, 0xE0, 0x00, 0x00, 0x07, 0xF8, 0x00, 0x00, 0x1F, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/settings.h b/src/components/icons/settings.h new file mode 100644 index 00000000..3c924be2 --- /dev/null +++ b/src/components/icons/settings.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t SettingsIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x87, 0xE1, 0xFF, 0xFF, 0x03, 0xC0, + 0xFF, 0xFE, 0x30, 0x8C, 0x7F, 0xFE, 0x78, 0x1E, 0x7F, 0xFE, 0x7C, 0x7E, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, + 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFC, 0x7C, 0x3E, 0x3F, 0xF0, 0xF0, 0x0F, 0x07, 0xC1, 0xF3, 0xCF, 0x83, 0xC7, + 0xE7, 0xE7, 0xE3, 0xCF, 0xE7, 0xE7, 0xF3, 0xCF, 0xE7, 0xE7, 0xF3, 0xC7, 0xE7, 0xE7, 0xE3, 0xC1, 0xF3, 0xCF, 0x83, + 0xE0, 0xF0, 0x0F, 0x0F, 0xFC, 0x7C, 0x3E, 0x3F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, + 0x7F, 0xFE, 0x7E, 0x3E, 0x7F, 0xFE, 0x78, 0x1E, 0x7F, 0xFE, 0x31, 0x0C, 0x7F, 0xFF, 0x03, 0xC0, 0xFF, 0xFF, 0x87, + 0xE1, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/settings2.h b/src/components/icons/settings2.h new file mode 100644 index 00000000..3aee5511 --- /dev/null +++ b/src/components/icons/settings2.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t Settings2Icon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, + 0xFF, 0xFF, 0xFF, 0xE0, 0x3F, 0xFF, 0x3F, 0xE3, 0x1F, 0xFF, 0x3F, 0xCF, 0x9F, 0xFF, 0x3F, 0xCF, 0xCF, 0xFF, 0x3F, + 0xCF, 0xCF, 0xFF, 0x3F, 0xCF, 0x8F, 0xFF, 0x3F, 0xC7, 0x9F, 0xFF, 0x3F, 0xE0, 0x1F, 0xFF, 0x3F, 0xF0, 0x7F, 0xFF, + 0x3F, 0xFC, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFF, 0x3F, 0xFC, 0xFF, 0xFE, 0x0F, 0xFC, 0xFF, + 0xF8, 0x07, 0xFC, 0xFF, 0xF9, 0xE3, 0xFC, 0xFF, 0xF1, 0xF3, 0xFC, 0xFF, 0xF3, 0xF3, 0xFC, 0xFF, 0xF3, 0xF3, 0xFC, + 0xFF, 0xF9, 0xF3, 0xFC, 0xFF, 0xF8, 0xC7, 0xFC, 0xFF, 0xFC, 0x07, 0xFF, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/text24.h b/src/components/icons/text24.h new file mode 100644 index 00000000..9020ec05 --- /dev/null +++ b/src/components/icons/text24.h @@ -0,0 +1,9 @@ +#pragma once +#include + +// size: 24x24 +static const uint8_t Text24Icon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x07, 0xF8, 0x00, 0x03, 0xF0, 0x7F, 0xF9, + 0xE2, 0x7F, 0xF9, 0xC6, 0x73, 0x39, 0xCE, 0x73, 0x39, 0x80, 0x73, 0x39, 0x80, 0xF3, 0x39, 0x9F, 0xF3, 0x39, + 0x9F, 0xF3, 0x39, 0x9F, 0x33, 0x39, 0x9F, 0x33, 0x39, 0x9F, 0x33, 0x39, 0x9F, 0x33, 0x39, 0x9F, 0xFF, 0xF9, + 0x9F, 0xFF, 0xF9, 0xC0, 0x00, 0x03, 0xE0, 0x00, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/transfer.h b/src/components/icons/transfer.h new file mode 100644 index 00000000..fd28566d --- /dev/null +++ b/src/components/icons/transfer.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t TransferIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x3F, 0xFF, 0xFF, 0xFC, 0x3F, 0xFF, 0xFF, 0xF8, 0x3F, + 0xFF, 0xFF, 0xF8, 0x1F, 0xFF, 0xFF, 0xF0, 0x1F, 0xFF, 0xFF, 0xF0, 0x4F, 0xFF, 0xFF, 0xE2, 0x4F, 0xFF, 0xFF, 0xE2, + 0x47, 0xFF, 0xFF, 0xC6, 0x67, 0xFF, 0xFF, 0xC6, 0x63, 0xFF, 0xFF, 0xCE, 0x73, 0xFF, 0xFF, 0x8E, 0x71, 0xFF, 0xFF, + 0x9E, 0x79, 0xFF, 0xFF, 0x1E, 0x78, 0xFF, 0xFF, 0x3E, 0x7C, 0xFF, 0xFE, 0x3E, 0x7C, 0x7F, 0xFE, 0x3E, 0x7E, 0x7F, + 0xFC, 0x7E, 0x7E, 0x3F, 0xFC, 0x7E, 0x7F, 0x3F, 0xF8, 0xFE, 0x7F, 0x3F, 0xF8, 0xFE, 0x7F, 0x9F, 0xF1, 0xFC, 0x1F, + 0x9F, 0xF1, 0xE0, 0x07, 0xCF, 0xE3, 0x83, 0x80, 0xCF, 0xE0, 0x0F, 0xF0, 0x07, 0xE0, 0x7F, 0xFC, 0x07, 0xF3, 0xFF, + 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/icons/wifi.h b/src/components/icons/wifi.h new file mode 100644 index 00000000..d84f5aed --- /dev/null +++ b/src/components/icons/wifi.h @@ -0,0 +1,12 @@ +#pragma once +#include + +// size: 32x32 +static const uint8_t WifiIcon[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0xC7, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, + 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0x1F, 0x3F, 0xFF, 0xFE, 0x3E, 0x3F, 0xFF, 0xFE, 0x3C, 0x7F, 0xFF, 0xFE, 0x7C, + 0x7F, 0xFF, 0xFC, 0x78, 0xFD, 0xFF, 0xFC, 0x78, 0xF1, 0xFF, 0xFC, 0xF9, 0xF1, 0xFF, 0xFC, 0xF1, 0xE3, 0xFF, 0xFC, + 0xF1, 0xE3, 0xFF, 0xFC, 0xF1, 0xE7, 0xDF, 0xFC, 0xF3, 0xE7, 0xDF, 0xFC, 0xF1, 0xE7, 0xFF, 0xFC, 0xF9, 0xE3, 0xFF, + 0xFC, 0xF9, 0xF3, 0xFF, 0xFC, 0xF9, 0xF1, 0xFF, 0xFC, 0x7C, 0xFB, 0xFF, 0xFE, 0x7C, 0xFF, 0xFF, 0xFE, 0x7C, 0x7F, + 0xFF, 0xFF, 0x3E, 0x3F, 0xFF, 0xFF, 0x3F, 0x3F, 0xFF, 0xFF, 0x1F, 0xFF, 0xFF, 0xFF, 0x8F, 0xFF, 0xFF, 0xFF, 0xCF, + 0xFF, 0xFF, 0xFF, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; diff --git a/src/components/themes/BaseTheme.cpp b/src/components/themes/BaseTheme.cpp index abaf9026..2bee8141 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -188,7 +188,7 @@ void BaseTheme::drawSideButtonHints(const GfxRenderer& renderer, const char* top void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, const std::function& rowTitle, const std::function& rowSubtitle, - const std::function& rowIcon, + const std::function& rowIcon, const std::function& rowValue, bool highlightValue) const { int rowHeight = (rowSubtitle != nullptr) ? BaseMetrics::values.listWithSubtitleRowHeight : BaseMetrics::values.listRowHeight; @@ -657,7 +657,7 @@ void BaseTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: void BaseTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, const std::function& buttonLabel, - const std::function& rowIcon) const { + const std::function& rowIcon) const { for (int i = 0; i < buttonCount; ++i) { const int tileY = BaseMetrics::values.verticalSpacing + rect.y + static_cast(i) * (BaseMetrics::values.menuRowHeight + BaseMetrics::values.menuSpacing); @@ -749,4 +749,4 @@ void BaseTheme::drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const ch renderer.drawText(UI_10_FONT_ID, textX + itemWidth, rect.y, "]"); } renderer.drawText(UI_10_FONT_ID, textX, rect.y, label); -} \ No newline at end of file +} diff --git a/src/components/themes/BaseTheme.h b/src/components/themes/BaseTheme.h index 07a7951c..7bc89f74 100644 --- a/src/components/themes/BaseTheme.h +++ b/src/components/themes/BaseTheme.h @@ -61,6 +61,8 @@ struct ThemeMetrics { bool keyboardCenteredText; }; +enum UIIcon { Folder, Text, Image, Book, File, Recent, Settings, Transfer, Library, Wifi, Hotspot }; + // Default theme implementation (Classic Theme) // Additional themes can inherit from this and override methods as needed @@ -111,7 +113,7 @@ class BaseTheme { virtual void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, const std::function& rowTitle, const std::function& rowSubtitle = nullptr, - const std::function& rowIcon = nullptr, + const std::function& rowIcon = nullptr, const std::function& rowValue = nullptr, bool highlightValue = false) const; virtual void drawHeader(const GfxRenderer& renderer, Rect rect, const char* title, @@ -125,11 +127,11 @@ class BaseTheme { bool& bufferRestored, std::function storeCoverBuffer) const; virtual void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, const std::function& buttonLabel, - const std::function& rowIcon) const; + const std::function& rowIcon) const; virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const; virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; virtual void drawReadingProgressBar(const GfxRenderer& renderer, const size_t bookProgress) const; virtual void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const; virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth) const; virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected) const; -}; \ No newline at end of file +}; diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index 7a5edc71..94f6f2f2 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -14,8 +14,21 @@ #include "CrossPointSettings.h" #include "RecentBooksStore.h" #include "components/UITheme.h" +#include "components/icons/book.h" +#include "components/icons/book24.h" +#include "components/icons/cover.h" +#include "components/icons/file24.h" +#include "components/icons/folder.h" +#include "components/icons/folder24.h" +#include "components/icons/hotspot.h" +#include "components/icons/image24.h" +#include "components/icons/library.h" +#include "components/icons/recent.h" +#include "components/icons/settings2.h" +#include "components/icons/text24.h" +#include "components/icons/transfer.h" +#include "components/icons/wifi.h" #include "fontIds.h" -#include "util/StringUtils.h" // Internal constants namespace { @@ -27,6 +40,50 @@ constexpr int popupMarginX = 16; constexpr int popupMarginY = 12; constexpr int maxSubtitleWidth = 100; constexpr int maxListValueWidth = 200; +constexpr int mainMenuIconSize = 32; +constexpr int listIconSize = 24; +constexpr int mainMenuColumns = 2; + +const uint8_t* iconForName(UIIcon icon, int size) { + if (size == 24) { + switch (icon) { + case UIIcon::Folder: + return Folder24Icon; + case UIIcon::Text: + return Text24Icon; + case UIIcon::Image: + return Image24Icon; + case UIIcon::Book: + return Book24Icon; + case UIIcon::File: + return File24Icon; + default: + return nullptr; + } + } else if (size == 32) { + switch (icon) { + case UIIcon::Folder: + return FolderIcon; + case UIIcon::Book: + return BookIcon; + case UIIcon::Recent: + return RecentIcon; + case UIIcon::Settings: + return Settings2Icon; + case UIIcon::Transfer: + return TransferIcon; + case UIIcon::Library: + return LibraryIcon; + case UIIcon::Wifi: + return WifiIcon; + case UIIcon::Hotspot: + return HotspotIcon; + default: + return nullptr; + } + } + return nullptr; +} } // namespace void LyraTheme::drawBatteryLeft(const GfxRenderer& renderer, Rect rect, const bool showPercentage) const { @@ -147,7 +204,7 @@ void LyraTheme::drawHeader(const GfxRenderer& renderer, Rect rect, const char* t renderer.drawText(UI_12_FONT_ID, rect.x + LyraMetrics::values.contentSidePadding, rect.y + LyraMetrics::values.batteryBarHeight + 3, truncatedTitle.c_str(), true, EpdFontFamily::BOLD); - renderer.drawLine(rect.x, rect.y + rect.height - 3, rect.x + rect.width, rect.y + rect.height - 3, 3, true); + renderer.drawLine(rect.x, rect.y + rect.height - 3, rect.x + rect.width - 1, rect.y + rect.height - 3, 3, true); } if (subtitle) { @@ -175,7 +232,7 @@ void LyraTheme::drawSubHeader(const GfxRenderer& renderer, Rect rect, const char UI_10_FONT_ID, label, rect.width - LyraMetrics::values.contentSidePadding - rightSpace, EpdFontFamily::REGULAR); renderer.drawText(UI_10_FONT_ID, currentX, rect.y + 6, truncatedLabel.c_str(), true, EpdFontFamily::REGULAR); - renderer.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width, rect.y + rect.height - 1, true); + renderer.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1, true); } void LyraTheme::drawTabBar(const GfxRenderer& renderer, Rect rect, const std::vector& tabs, @@ -207,13 +264,13 @@ void LyraTheme::drawTabBar(const GfxRenderer& renderer, Rect rect, const std::ve currentX += textWidth + LyraMetrics::values.tabSpacing + 2 * hPaddingInSelection; } - renderer.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width, rect.y + rect.height - 1, true); + renderer.drawLine(rect.x, rect.y + rect.height - 1, rect.x + rect.width - 1, rect.y + rect.height - 1, true); } void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, const std::function& rowTitle, const std::function& rowSubtitle, - const std::function& rowIcon, + const std::function& rowIcon, const std::function& rowValue, bool highlightValue) const { int rowHeight = (rowSubtitle != nullptr) ? LyraMetrics::values.listWithSubtitleRowHeight : LyraMetrics::values.listRowHeight; @@ -243,10 +300,21 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, Color::LightGray); } + int textX = rect.x + LyraMetrics::values.contentSidePadding + hPaddingInSelection; + int textWidth = contentWidth - LyraMetrics::values.contentSidePadding * 2 - hPaddingInSelection * 2; + int iconSize; + if (rowIcon != nullptr) { + iconSize = (rowSubtitle != nullptr) ? mainMenuIconSize : listIconSize; + textX += iconSize + hPaddingInSelection; + textWidth -= iconSize + hPaddingInSelection; + } + // Draw all items const auto pageStartIndex = selectedIndex / pageItems * pageItems; + int iconY = (rowSubtitle != nullptr) ? 16 : 10; for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) { const int itemY = rect.y + (i % pageItems) * rowHeight; + int rowTextWidth = textWidth; // Draw name int valueWidth = 0; @@ -255,19 +323,27 @@ void LyraTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, valueText = rowValue(i); valueText = renderer.truncatedText(UI_10_FONT_ID, valueText.c_str(), maxListValueWidth); valueWidth = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str()) + hPaddingInSelection; + rowTextWidth -= valueWidth; } - int textWidth = contentWidth - LyraMetrics::values.contentSidePadding * 2 - hPaddingInSelection * 2 - valueWidth; + auto itemName = rowTitle(i); - auto item = renderer.truncatedText(UI_10_FONT_ID, itemName.c_str(), textWidth); - renderer.drawText(UI_10_FONT_ID, rect.x + LyraMetrics::values.contentSidePadding + hPaddingInSelection * 2, - itemY + 6, item.c_str(), true); + auto item = renderer.truncatedText(UI_10_FONT_ID, itemName.c_str(), rowTextWidth); + renderer.drawText(UI_10_FONT_ID, textX, itemY + 7, item.c_str(), true); + + if (rowIcon != nullptr) { + UIIcon icon = rowIcon(i); + const uint8_t* iconBitmap = iconForName(icon, iconSize); + if (iconBitmap != nullptr) { + renderer.drawIcon(iconBitmap, rect.x + LyraMetrics::values.contentSidePadding + hPaddingInSelection, + itemY + iconY, iconSize, iconSize); + } + } if (rowSubtitle != nullptr) { // Draw subtitle std::string subtitleText = rowSubtitle(i); - auto subtitle = renderer.truncatedText(SMALL_FONT_ID, subtitleText.c_str(), textWidth); - renderer.drawText(SMALL_FONT_ID, rect.x + LyraMetrics::values.contentSidePadding + hPaddingInSelection * 2, - itemY + 30, subtitle.c_str(), true); + auto subtitle = renderer.truncatedText(SMALL_FONT_ID, subtitleText.c_str(), rowTextWidth); + renderer.drawText(SMALL_FONT_ID, textX, itemY + 30, subtitle.c_str(), true); } // Draw value @@ -557,7 +633,7 @@ void LyraTheme::drawEmptyRecents(const GfxRenderer& renderer, const Rect rect) c void LyraTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, const std::function& buttonLabel, - const std::function& rowIcon) const { + const std::function& rowIcon) const { for (int i = 0; i < buttonCount; ++i) { int tileWidth = rect.width - LyraMetrics::values.contentSidePadding * 2; Rect tileRect = Rect{rect.x + LyraMetrics::values.contentSidePadding, @@ -572,11 +648,19 @@ void LyraTheme::drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount std::string labelStr = buttonLabel(i); const char* label = labelStr.c_str(); - const int textX = tileRect.x + 16; + int textX = tileRect.x + 16; const int lineHeight = renderer.getLineHeight(UI_12_FONT_ID); const int textY = tileRect.y + (LyraMetrics::values.menuRowHeight - lineHeight) / 2; - // Invert text when the tile is selected, to contrast with the filled background + if (rowIcon != nullptr) { + UIIcon icon = rowIcon(i); + const uint8_t* iconBitmap = iconForName(icon, mainMenuIconSize); + if (iconBitmap != nullptr) { + renderer.drawIcon(iconBitmap, textX, textY + 3, mainMenuIconSize, mainMenuIconSize); + textX += mainMenuIconSize + hPaddingInSelection + 2; + } + } + renderer.drawText(UI_12_FONT_ID, textX, textY, label, true); } } diff --git a/src/components/themes/lyra/LyraTheme.h b/src/components/themes/lyra/LyraTheme.h index ae48fb89..0f1f3bae 100644 --- a/src/components/themes/lyra/LyraTheme.h +++ b/src/components/themes/lyra/LyraTheme.h @@ -50,14 +50,14 @@ class LyraTheme : public BaseTheme { void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex, const std::function& rowTitle, const std::function& rowSubtitle, - const std::function& rowIcon, - const std::function& rowValue, bool highlightValue) const override; + const std::function& rowIcon, const std::function& rowValue, + bool highlightValue) const override; void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const override; void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const override; void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex, const std::function& buttonLabel, - const std::function& rowIcon) const override; + const std::function& rowIcon) const override; void drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector& recentBooks, const int selectorIndex, bool& coverRendered, bool& coverBufferStored, bool& bufferRestored, std::function storeCoverBuffer) const override;