Files
Crosspoint/src/components/themes/BaseTheme.h
T

255 lines
12 KiB
C++

#pragma once
#include <cstddef>
#include <cstdint>
#include <functional>
#include <string>
#include <vector>
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<std::string(int index)>& rowTitle,
const std::function<std::string(int index)>& rowSubtitle = nullptr,
const std::function<UIIcon(int index)>& rowIcon = nullptr,
const std::function<std::string(int index)>& rowValue = nullptr, bool highlightValue = false,
const std::function<bool(int index)>& 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<TabInfo>& tabs,
bool selected) const;
virtual void drawRecentBookCover(GfxRenderer& renderer, Rect rect, const std::vector<RecentBook>& recentBooks,
const int selectorIndex, bool& coverRendered, bool& coverBufferStored,
bool& bufferRestored, std::function<bool()> storeCoverBuffer) const;
virtual void drawButtonMenu(GfxRenderer& renderer, Rect rect, int buttonCount, int selectedIndex,
const std::function<std::string(int index)>& buttonLabel,
const std::function<UIIcon(int index)>& 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);
};