#pragma once #include #include #include #include #include class GfxRenderer; struct RecentBook; struct Rect { int x = 0; int y = 0; int width = 0; int height = 0; // Non-explicit zero-init default ctor so value-initialization (e.g. arrays of // structs embedding a Rect) does not route through an explicit constructor. // The parameterized ctor stays explicit to block implicit int->Rect. Rect() = default; explicit Rect(int x, int y = 0, int width = 0, int height = 0) : x(x), y(y), width(width), height(height) {} // Logical-coordinate point hit-test (used for touch target hit-testing). bool contains(int px, int py) const { return px >= x && px < x + width && py >= y && py < y + height; } }; struct TabInfo { const char* label; bool selected; }; struct ThemeMetrics { int batteryWidth; int batteryHeight; int topPadding; int batteryBarHeight; int headerHeight; int verticalSpacing; int previewPadding; int previewHeightPercent; int contentSidePadding; int listRowHeight; int listWithSubtitleRowHeight; int menuRowHeight; int menuSpacing; int tabSpacing; int tabBarHeight; int scrollBarWidth; int scrollBarRightOffset; int homeTopPadding; int homeCoverHeight; int homeCoverTileHeight; int homeRecentBooksCount; bool homeContinueReadingInMenu; int homeMenuTopOffset; int buttonHintsHeight; int sideButtonHintsWidth; int progressBarHeight; int progressBarMarginTop; int statusBarHorizontalMargin; int statusBarVerticalMargin; int keyboardKeyWidth; int keyboardKeyHeight; int keyboardKeySpacing; int keyboardBottomKeyHeight; int keyboardBottomKeySpacing; bool keyboardBottomAligned; bool keyboardCenteredText; int keyboardVerticalOffset; int keyboardTextFieldWidthPercent; int keyboardWidthPercent; int keyboardKeyCornerRadius; bool keyboardFillUnselected; bool keyboardOutlineAllUnselected; bool keyboardDrawSpecialOutlineWhenUnselected; int keyboardSecondaryLabelRightPadding; int keyboardSecondaryLabelTopPadding; int keyboardMinArrowHeadSize; float popupTopOffsetRatio; int popupMarginX; int popupMarginY; int popupFrameThickness; int popupCornerRadius; bool popupTextBold; bool popupTextInverted; int popupTextBaselineOffsetY; int popupProgressBarHeight; bool popupProgressDrawOutline; bool popupProgressClampPercent; bool popupProgressFillInverted; bool popupProgressOutlineInverted; int textFieldHorizontalPadding; int textFieldNormalThickness; int textFieldCursorThickness; int textFieldLineEndOffset; }; // Expected sizeof(ThemeMetrics), in bytes. scaleThemeMetrics() static_asserts // against this so a new/removed metric field can't slip through unclassified; // update it after adding a field (and decide there whether the field scales). inline constexpr unsigned THEME_METRICS_SIZEOF = 216; enum UIIcon { None = 0, Folder, Text, Image, Book, File, Settings, Transfer, Library, Wifi, Hotspot, Bookmark, Firmware }; enum class KeyboardKeyType { Normal, Shift, Mode, Space, Del, Ok, Disabled }; // Default theme implementation (Classic Theme) // Additional themes can inherit from this and override methods as needed namespace BaseMetrics { constexpr ThemeMetrics values = {.batteryWidth = 15, .batteryHeight = 12, .topPadding = 5, .batteryBarHeight = 20, .headerHeight = 45, .verticalSpacing = 10, .previewPadding = 12, .previewHeightPercent = 30, .contentSidePadding = 20, .listRowHeight = 30, .listWithSubtitleRowHeight = 50, .menuRowHeight = 45, .menuSpacing = 8, .tabSpacing = 10, .tabBarHeight = 50, .scrollBarWidth = 4, .scrollBarRightOffset = 5, .homeTopPadding = 40, .homeCoverHeight = 400, .homeCoverTileHeight = 400, .homeRecentBooksCount = 1, .homeContinueReadingInMenu = false, .homeMenuTopOffset = 10, .buttonHintsHeight = 40, .sideButtonHintsWidth = 30, .progressBarHeight = 16, .progressBarMarginTop = 1, .statusBarHorizontalMargin = 5, .statusBarVerticalMargin = 19, .keyboardKeyWidth = 22, .keyboardKeyHeight = 40, .keyboardKeySpacing = 0, .keyboardBottomKeyHeight = 35, .keyboardBottomKeySpacing = 5, .keyboardBottomAligned = true, .keyboardCenteredText = false, .keyboardVerticalOffset = -13, .keyboardTextFieldWidthPercent = 85, .keyboardWidthPercent = 90, .keyboardKeyCornerRadius = 0, .keyboardFillUnselected = false, .keyboardOutlineAllUnselected = false, .keyboardDrawSpecialOutlineWhenUnselected = true, .keyboardSecondaryLabelRightPadding = 1, .keyboardSecondaryLabelTopPadding = 0, .keyboardMinArrowHeadSize = 0, .popupTopOffsetRatio = 0.075f, .popupMarginX = 15, .popupMarginY = 15, .popupFrameThickness = 2, .popupCornerRadius = 0, .popupTextBold = true, .popupTextInverted = true, .popupTextBaselineOffsetY = -2, .popupProgressBarHeight = 4, .popupProgressDrawOutline = false, .popupProgressClampPercent = false, .popupProgressFillInverted = true, .popupProgressOutlineInverted = true, .textFieldHorizontalPadding = 6, .textFieldNormalThickness = 1, .textFieldCursorThickness = 3, .textFieldLineEndOffset = 0}; } class BaseTheme { public: virtual ~BaseTheme() = default; // Component drawing methods void drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total) const; void drawBatteryLeft(const GfxRenderer& renderer, Rect rect, bool showPercentage = true) const; // Left aligned (reader mode) void drawBatteryRight(const GfxRenderer& renderer, Rect rect, bool showPercentage = true) const; // Right aligned (UI headers) virtual void fillBatteryIcon(const GfxRenderer& renderer, Rect rect, uint16_t percentage) const; // Single switch for on-screen button hints, set by the CROSSPOINT_SHOW_BUTTON_HINTS // build flag (env; default on). Devices with no physical button row (touch + // rotary, e.g. M5Paper) set it to 0 in their env. static bool showButtonHints(); // Public, NON-virtual gate: returns early when hints are disabled, otherwise // dispatches to the per-theme *Impl below. Themes override the *Impl, never // these — so one switch hides hints across every theme. void drawButtonHints(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const; void drawSideButtonHints(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const; virtual void drawButtonHintsImpl(GfxRenderer& renderer, const char* btn1, const char* btn2, const char* btn3, const char* btn4) const; virtual void drawSideButtonHintsImpl(const GfxRenderer& renderer, const char* topBtn, const char* bottomBtn) const; virtual int getListPageItems(int contentHeight, bool hasSubtitle) const; 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& rowValue = nullptr, bool highlightValue = false, const std::function& rowDimmed = nullptr) const; virtual void drawHeader(const GfxRenderer& renderer, Rect rect, const char* title, const char* subtitle = nullptr) const; virtual void drawSubHeader(const GfxRenderer& renderer, Rect rect, const char* label, const char* rightLabel = nullptr) const; virtual void drawTabBar(const GfxRenderer& renderer, Rect rect, const std::vector& tabs, bool selected) const; virtual void drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector& recentBooks, const int selectorIndex, bool& coverRendered, bool& coverBufferStored, 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; virtual Rect drawPopup(const GfxRenderer& renderer, const char* message) const; virtual void fillPopupProgress(const GfxRenderer& renderer, const Rect& layout, const int progress) const; void drawStatusBar(GfxRenderer& renderer, const float bookProgress, const int currentPage, const int pageCount, std::string title, const int paddingBottom = 0, const int textYOffset = 0, const bool fillMargin = true) const; void drawHelpText(const GfxRenderer& renderer, Rect rect, const char* label) const; virtual void drawTextField(const GfxRenderer& renderer, Rect rect, const int textWidth, bool cursorMode = false, int contentStartX = 0, int contentWidth = 0) const; virtual void drawKeyboardKey(const GfxRenderer& renderer, Rect rect, const char* label, const bool isSelected, const char* secondaryLabel = nullptr, KeyboardKeyType keyType = KeyboardKeyType::Normal, bool inactiveSelection = false) const; virtual bool showsFileIcons() const { return false; } // Horizontal scroll offset (px to subtract from tab X positions) that keeps the // selected tab visible when the tab row is wider than availWidth — used by every // theme's drawTabBar so scaled-up tabs slide instead of running off-screen. // Returns 0 when the whole row fits. static int tabBarScrollOffset(int contentWidth, int selStart, int selWidth, int availWidth); // Shared constants and helpers for battery drawing (used by all themes) static constexpr int batteryPercentSpacing = 4; static void drawBatteryOutline(const GfxRenderer& renderer, int x, int y, int battWidth, int rectHeight); static void drawBatteryLightningBolt(const GfxRenderer& renderer, int boltX, int boltY); };