From fdcd71e94df76614591a1fdc5f8cf50facb128f9 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 ## Summary 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) ## Additional Context - 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. --- ### AI Usage 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 +- .../themes/lyra/Lyra3CoversTheme.cpp | 13 +- src/components/themes/lyra/LyraTheme.cpp | 121 +++++++++++++++--- src/components/themes/lyra/LyraTheme.h | 6 +- 31 files changed, 434 insertions(+), 34 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 998abb74..802c3357 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -419,7 +419,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 0fafdbb5..240c8925 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 238832b0..25e34423 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 cd8bcdf1..d4e11509 100644 --- a/src/activities/home/HomeActivity.cpp +++ b/src/activities/home/HomeActivity.cpp @@ -229,9 +229,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( @@ -240,7 +243,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 8c76ba14..af300802 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), tr(STR_DIR_UP), tr(STR_DIR_DOWN)); diff --git a/src/components/UITheme.cpp b/src/components/UITheme.cpp index e1e8c506..1779be3a 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 08a3ede9..dc3e55f0 100644 --- a/src/components/UITheme.h +++ b/src/components/UITheme.h @@ -5,6 +5,8 @@ #include "CrossPointSettings.h" #include "components/themes/BaseTheme.h" +class MappedInputManager; + class UITheme { // Static instance static UITheme instance; @@ -20,6 +22,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 f195037e..950b79f3 100644 --- a/src/components/themes/BaseTheme.cpp +++ b/src/components/themes/BaseTheme.cpp @@ -186,7 +186,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; @@ -635,7 +635,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); @@ -727,4 +727,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/Lyra3CoversTheme.cpp b/src/components/themes/lyra/Lyra3CoversTheme.cpp index 50506280..54c745b6 100644 --- a/src/components/themes/lyra/Lyra3CoversTheme.cpp +++ b/src/components/themes/lyra/Lyra3CoversTheme.cpp @@ -6,11 +6,10 @@ #include #include -#include "Battery.h" #include "RecentBooksStore.h" #include "components/UITheme.h" +#include "components/icons/cover.h" #include "fontIds.h" -#include "util/StringUtils.h" // Internal constants namespace { @@ -67,8 +66,14 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con } if (!hasCover) { + // Render empty cover renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, - tileWidth - 2 * hPaddingInSelection, Lyra3CoversMetrics::values.homeCoverHeight); + tileWidth - 2 * hPaddingInSelection, Lyra3CoversMetrics::values.homeCoverHeight, true); + renderer.fillRect(tileX + hPaddingInSelection, + tileY + hPaddingInSelection + (Lyra3CoversMetrics::values.homeCoverHeight / 3), + tileWidth - 2 * hPaddingInSelection, 2 * Lyra3CoversMetrics::values.homeCoverHeight / 3, + true); + renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); } } @@ -101,4 +106,4 @@ void Lyra3CoversTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, con } else { drawEmptyRecents(renderer, rect); } -} \ No newline at end of file +} diff --git a/src/components/themes/lyra/LyraTheme.cpp b/src/components/themes/lyra/LyraTheme.cpp index a89fef9f..38a8aff2 100644 --- a/src/components/themes/lyra/LyraTheme.cpp +++ b/src/components/themes/lyra/LyraTheme.cpp @@ -10,8 +10,21 @@ #include "Battery.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 { @@ -23,7 +36,51 @@ 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; int coverWidth = 0; + +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 { @@ -124,7 +181,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) { @@ -152,7 +209,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, @@ -184,13 +241,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; @@ -220,10 +277,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; @@ -232,19 +300,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 @@ -373,8 +449,13 @@ void LyraTheme::drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std: } if (!hasCover) { + // Render empty cover renderer.drawRect(tileX + hPaddingInSelection, tileY + hPaddingInSelection, coverWidth, - LyraMetrics::values.homeCoverHeight); + LyraMetrics::values.homeCoverHeight, true); + renderer.fillRect(tileX + hPaddingInSelection, + tileY + hPaddingInSelection + (LyraMetrics::values.homeCoverHeight / 3), coverWidth, + 2 * LyraMetrics::values.homeCoverHeight / 3, true); + renderer.drawIcon(CoverIcon, tileX + hPaddingInSelection + 24, tileY + hPaddingInSelection + 24, 32, 32); } coverBufferStored = storeCoverBuffer(); @@ -421,7 +502,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, @@ -436,11 +517,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 5e3f545a..3fc451a8 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;