mirror of
https://github.com/daveallie/crosspoint-reader.git
synced 2026-02-06 07:37:37 +03:00
Merge 2d328b6a54 into 6d68466891
This commit is contained in:
commit
27d6ee75bf
@ -131,6 +131,12 @@ void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const bool state) con
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawLine(int x1, int y1, int x2, int y2, const int lineWidth, const bool state) const {
|
||||
for (int i = 0; i < lineWidth; i++) {
|
||||
drawLine(x1, y1 + i, x2, y2 + i, state);
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||
drawLine(x, y, x + width - 1, y, state);
|
||||
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
||||
@ -138,6 +144,121 @@ void GfxRenderer::drawRect(const int x, const int y, const int width, const int
|
||||
drawLine(x, y, x, y + height - 1, state);
|
||||
}
|
||||
|
||||
// Border is inside the rectangle
|
||||
void GfxRenderer::drawRect(const int x, const int y, const int width, const int height, const int lineWidth,
|
||||
const bool state) const {
|
||||
for (int i = 0; i < lineWidth; i++) {
|
||||
drawLine(x + i, y + i, x + width - i, y + i, state);
|
||||
drawLine(x + width - i, y + i, x + width - i, y + height - i, state);
|
||||
drawLine(x + width - i, y + height - i, x + i, y + height - i, state);
|
||||
drawLine(x + i, y + height - i, x + i, y + i, state);
|
||||
}
|
||||
}
|
||||
|
||||
void GfxRenderer::drawArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir,
|
||||
const int lineWidth, const bool state) const {
|
||||
const int stroke = std::min(lineWidth, maxRadius);
|
||||
const int innerRadius = std::max(maxRadius - stroke, 0);
|
||||
const int outerRadiusSq = maxRadius * maxRadius;
|
||||
const int innerRadiusSq = innerRadius * innerRadius;
|
||||
for (int dy = 0; dy <= maxRadius; ++dy) {
|
||||
for (int dx = 0; dx <= maxRadius; ++dx) {
|
||||
const int distSq = dx * dx + dy * dy;
|
||||
if (distSq > outerRadiusSq || distSq < innerRadiusSq) {
|
||||
continue;
|
||||
}
|
||||
const int px = cx + xDir * dx;
|
||||
const int py = cy + yDir * dy;
|
||||
drawPixel(px, py, state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Use Bayer matrix 4x4 dithering to fill the rectangle with a grey level - 0 white to 15 black
|
||||
void GfxRenderer::fillRectGrey(const int x, const int y, const int width, const int height, const int greyLevel) const {
|
||||
static constexpr uint8_t bayer4x4[4][4] = {
|
||||
{0, 8, 2, 10},
|
||||
{12, 4, 14, 6},
|
||||
{3, 11, 1, 9},
|
||||
{15, 7, 13, 5},
|
||||
};
|
||||
static constexpr int matrixSize = 4;
|
||||
static constexpr int matrixLevels = matrixSize * matrixSize;
|
||||
|
||||
const int normalizedGrey = (greyLevel * 255) / (matrixLevels - 1);
|
||||
const int clampedGrey = std::max(0, std::min(normalizedGrey, 255));
|
||||
const int threshold = (clampedGrey * (matrixLevels + 1)) / 256;
|
||||
|
||||
for (int dy = 0; dy < height; ++dy) {
|
||||
const int screenY = y + dy;
|
||||
const int matrixY = screenY & (matrixSize - 1);
|
||||
for (int dx = 0; dx < width; ++dx) {
|
||||
const int screenX = x + dx;
|
||||
const int matrixX = screenX & (matrixSize - 1);
|
||||
const uint8_t patternValue = bayer4x4[matrixY][matrixX];
|
||||
const bool black = patternValue < threshold;
|
||||
drawPixel(screenX, screenY, black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Color -1 white, 0 clear, 1 black
|
||||
void GfxRenderer::fillArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir,
|
||||
const int insideColor, const int outsideColor) const {
|
||||
const int radiusSq = maxRadius * maxRadius;
|
||||
for (int dy = 0; dy <= maxRadius; ++dy) {
|
||||
for (int dx = 0; dx <= maxRadius; ++dx) {
|
||||
const int distSq = dx * dx + dy * dy;
|
||||
const int px = cx + xDir * dx;
|
||||
const int py = cy + yDir * dy;
|
||||
if (distSq > radiusSq) {
|
||||
if (outsideColor != 0) {
|
||||
drawPixel(px, py, outsideColor == 1);
|
||||
}
|
||||
} else {
|
||||
if (insideColor != 0) {
|
||||
drawPixel(px, py, insideColor == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Border is inside the rectangle, rounded corners
|
||||
void GfxRenderer::drawRoundedRect(const int x, const int y, const int width, const int height, const int lineWidth,
|
||||
const int cornerRadius, const bool state) const {
|
||||
if (lineWidth <= 0 || width <= 0 || height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int maxRadius = std::min({cornerRadius, width / 2, height / 2});
|
||||
if (maxRadius <= 0) {
|
||||
drawRect(x, y, width, height, lineWidth, state);
|
||||
return;
|
||||
}
|
||||
|
||||
const int stroke = std::min(lineWidth, maxRadius);
|
||||
const int right = x + width - 1;
|
||||
const int bottom = y + height - 1;
|
||||
|
||||
const int horizontalWidth = width - 2 * maxRadius;
|
||||
if (horizontalWidth > 0) {
|
||||
fillRect(x + maxRadius, y, horizontalWidth, stroke, state);
|
||||
fillRect(x + maxRadius, bottom - stroke + 1, horizontalWidth, stroke, state);
|
||||
}
|
||||
|
||||
const int verticalHeight = height - 2 * maxRadius;
|
||||
if (verticalHeight > 0) {
|
||||
fillRect(x, y + maxRadius, stroke, verticalHeight, state);
|
||||
fillRect(right - stroke + 1, y + maxRadius, stroke, verticalHeight, state);
|
||||
}
|
||||
|
||||
drawArc(maxRadius, x + maxRadius, y + maxRadius, -1, -1, lineWidth, state); // TL
|
||||
drawArc(maxRadius, right - maxRadius, y + maxRadius, 1, -1, lineWidth, state); // TR
|
||||
drawArc(maxRadius, right - maxRadius, bottom - maxRadius, 1, 1, lineWidth, state); // BR
|
||||
drawArc(maxRadius, x + maxRadius, bottom - maxRadius, -1, 1, lineWidth, state); // BL
|
||||
}
|
||||
|
||||
void GfxRenderer::fillRect(const int x, const int y, const int width, const int height, const bool state) const {
|
||||
for (int fillY = y; fillY < y + height; fillY++) {
|
||||
drawLine(x, fillY, x + width - 1, fillY, state);
|
||||
@ -152,6 +273,10 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co
|
||||
einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height);
|
||||
}
|
||||
|
||||
void GfxRenderer::drawIcon(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const {
|
||||
einkDisplay.drawImage(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,
|
||||
const float cropX, const float cropY) const {
|
||||
// For 1-bit bitmaps, use optimized 1-bit rendering path (no crop support for 1-bit)
|
||||
|
||||
@ -63,9 +63,20 @@ class GfxRenderer {
|
||||
// Drawing
|
||||
void drawPixel(int x, int y, bool state = true) const;
|
||||
void drawLine(int x1, int y1, int x2, int y2, bool state = true) const;
|
||||
void drawLine(int x1, int y1, int x2, int y2, const int lineWidth, const bool state) const;
|
||||
void drawArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir, const int lineWidth,
|
||||
const bool state) const;
|
||||
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||
void drawRect(const int x, const int y, const int width, const int height, const int lineWidth,
|
||||
const bool state) const;
|
||||
void drawRoundedRect(const int x, const int y, const int width, const int height, const int lineWidth,
|
||||
const int cornerRadius, const bool state) const;
|
||||
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||
void fillRectGrey(const int x, const int y, const int width, const int height, const int greyLevel) const;
|
||||
void fillArc(const int maxRadius, const int cx, const int cy, const int xDir, const int yDir, const int insideColor,
|
||||
const int outsideColor) const;
|
||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||
void drawIcon(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const;
|
||||
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight, float cropX = 0,
|
||||
float cropY = 0) const;
|
||||
void drawBitmap1Bit(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit bd4e6707503ab9c97d13ee0d8f8c69e9ff03cd12
|
||||
Subproject commit fe766f15cb9ff1ef8214210a8667037df4c60b81
|
||||
@ -14,7 +14,7 @@ CrossPointSettings CrossPointSettings::instance;
|
||||
namespace {
|
||||
constexpr uint8_t SETTINGS_FILE_VERSION = 1;
|
||||
// Increment this when adding new persisted settings fields
|
||||
constexpr uint8_t SETTINGS_COUNT = 20;
|
||||
constexpr uint8_t SETTINGS_COUNT = 21;
|
||||
constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin";
|
||||
} // namespace
|
||||
|
||||
@ -49,6 +49,7 @@ bool CrossPointSettings::saveToFile() const {
|
||||
serialization::writePod(outputFile, hideBatteryPercentage);
|
||||
serialization::writePod(outputFile, longPressChapterSkip);
|
||||
serialization::writePod(outputFile, hyphenationEnabled);
|
||||
serialization::writePod(outputFile, uiTheme);
|
||||
outputFile.close();
|
||||
|
||||
Serial.printf("[%lu] [CPS] Settings saved to file\n", millis());
|
||||
@ -120,6 +121,8 @@ bool CrossPointSettings::loadFromFile() {
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, hyphenationEnabled);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
serialization::readPod(inputFile, uiTheme);
|
||||
if (++settingsRead >= fileSettingsCount) break;
|
||||
} while (false);
|
||||
|
||||
inputFile.close();
|
||||
|
||||
@ -58,6 +58,9 @@ class CrossPointSettings {
|
||||
// Hide battery percentage
|
||||
enum HIDE_BATTERY_PERCENTAGE { HIDE_NEVER = 0, HIDE_READER = 1, HIDE_ALWAYS = 2 };
|
||||
|
||||
// UI Theme
|
||||
enum UI_THEME { CLASSIC = 0, ROUNDED = 1 };
|
||||
|
||||
// Sleep screen settings
|
||||
uint8_t sleepScreen = DARK;
|
||||
// Sleep screen cover mode settings
|
||||
@ -94,6 +97,8 @@ class CrossPointSettings {
|
||||
uint8_t hideBatteryPercentage = HIDE_NEVER;
|
||||
// Long-press chapter skip on side buttons
|
||||
uint8_t longPressChapterSkip = 1;
|
||||
// UI Theme
|
||||
uint8_t uiTheme = CLASSIC;
|
||||
|
||||
~CrossPointSettings() = default;
|
||||
|
||||
|
||||
@ -1,66 +0,0 @@
|
||||
#include "ScreenComponents.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
void ScreenComponents::drawBattery(const GfxRenderer& renderer, const int left, const int top,
|
||||
const bool showPercentage) {
|
||||
// Left aligned battery icon and percentage
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : "";
|
||||
renderer.drawText(SMALL_FONT_ID, left + 20, top, percentageText.c_str());
|
||||
|
||||
// 1 column on left, 2 columns on right, 5 columns of battery body
|
||||
constexpr int batteryWidth = 15;
|
||||
constexpr int batteryHeight = 12;
|
||||
const int x = left;
|
||||
const int y = top + 6;
|
||||
|
||||
// Top line
|
||||
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y);
|
||||
// Bottom line
|
||||
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1);
|
||||
// Left line
|
||||
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2);
|
||||
// Battery end
|
||||
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2);
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + 3);
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4);
|
||||
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5);
|
||||
|
||||
// The +1 is to round up, so that we always fill at least one pixel
|
||||
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
|
||||
if (filledWidth > batteryWidth - 5) {
|
||||
filledWidth = batteryWidth - 5; // Ensure we don't overflow
|
||||
}
|
||||
|
||||
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
|
||||
}
|
||||
|
||||
void ScreenComponents::drawProgressBar(const GfxRenderer& renderer, const int x, const int y, const int width,
|
||||
const int height, const size_t current, const size_t total) {
|
||||
if (total == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use 64-bit arithmetic to avoid overflow for large files
|
||||
const int percent = static_cast<int>((static_cast<uint64_t>(current) * 100) / total);
|
||||
|
||||
// Draw outline
|
||||
renderer.drawRect(x, y, width, height);
|
||||
|
||||
// Draw filled portion
|
||||
const int fillWidth = (width - 4) * percent / 100;
|
||||
if (fillWidth > 0) {
|
||||
renderer.fillRect(x + 2, y + 2, fillWidth, height - 4);
|
||||
}
|
||||
|
||||
// Draw percentage text centered below bar
|
||||
const std::string percentText = std::to_string(percent) + "%";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, y + height + 15, percentText.c_str());
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
class GfxRenderer;
|
||||
|
||||
class ScreenComponents {
|
||||
public:
|
||||
static void drawBattery(const GfxRenderer& renderer, int left, int top, bool showPercentage = true);
|
||||
|
||||
/**
|
||||
* Draw a progress bar with percentage text.
|
||||
* @param renderer The graphics renderer
|
||||
* @param x Left position of the bar
|
||||
* @param y Top position of the bar
|
||||
* @param width Width of the bar
|
||||
* @param height Height of the bar
|
||||
* @param current Current progress value
|
||||
* @param total Total value for 100% progress
|
||||
*/
|
||||
static void drawProgressBar(const GfxRenderer& renderer, int x, int y, int width, int height, size_t current,
|
||||
size_t total);
|
||||
};
|
||||
@ -6,8 +6,8 @@
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "ScreenComponents.h"
|
||||
#include "activities/network/WifiSelectionActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "network/HttpDownloader.h"
|
||||
#include "util/StringUtils.h"
|
||||
@ -205,7 +205,7 @@ void OpdsBookBrowserActivity::render() const {
|
||||
constexpr int barHeight = 20;
|
||||
constexpr int barX = 50;
|
||||
const int barY = pageHeight / 2 + 20;
|
||||
ScreenComponents::drawProgressBar(renderer, barX, barY, barWidth, barHeight, downloadProgress, downloadTotal);
|
||||
UITheme::drawProgressBar(renderer, Rect{barX, barY, barWidth, barHeight}, downloadProgress, downloadTotal);
|
||||
}
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "ScreenComponents.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "util/StringUtils.h"
|
||||
|
||||
@ -550,7 +550,7 @@ void HomeActivity::render() {
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const auto percentageText = showBatteryPercentage ? std::to_string(percentage) + "%" : "";
|
||||
const auto batteryX = pageWidth - 25 - renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||
ScreenComponents::drawBattery(renderer, batteryX, 10, showBatteryPercentage);
|
||||
UITheme::drawBattery(renderer, Rect{batteryX, 10}, showBatteryPercentage);
|
||||
|
||||
renderer.displayBuffer();
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
#include <cstring>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "ScreenComponents.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "util/StringUtils.h"
|
||||
|
||||
@ -711,7 +711,7 @@ void CalibreWirelessActivity::render() const {
|
||||
constexpr int barHeight = 20;
|
||||
constexpr int barX = 50;
|
||||
const int barY = statusY + 20;
|
||||
ScreenComponents::drawProgressBar(renderer, barX, barY, barWidth, barHeight, bytesReceived, currentFileSize);
|
||||
UITheme::drawProgressBar(renderer, Rect{barX, barY, barWidth, barHeight}, bytesReceived, currentFileSize);
|
||||
}
|
||||
|
||||
// Draw error if present
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
#include "CrossPointState.h"
|
||||
#include "EpubReaderChapterSelectionActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "ScreenComponents.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
@ -455,7 +455,7 @@ void EpubReaderActivity::renderStatusBar(const int orientedMarginRight, const in
|
||||
}
|
||||
|
||||
if (showBattery) {
|
||||
ScreenComponents::drawBattery(renderer, orientedMarginLeft + 1, textY, showBatteryPercentage);
|
||||
UITheme::drawBattery(renderer, Rect{orientedMarginLeft + 1, textY}, showBatteryPercentage);
|
||||
}
|
||||
|
||||
if (showChapterTitle) {
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <SDCardManager.h>
|
||||
|
||||
#include "MappedInputManager.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
#include "util/StringUtils.h"
|
||||
|
||||
@ -180,23 +181,21 @@ void FileSelectionActivity::render() const {
|
||||
renderer.clearScreen();
|
||||
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Books", true, EpdFontFamily::BOLD);
|
||||
|
||||
auto folderName = basepath == "/" ? "SD card" : basepath.substr(basepath.rfind('/') + 1).c_str();
|
||||
UITheme::drawFullscreenWindowFrame(renderer, folderName);
|
||||
Rect contentRect = UITheme::getWindowContentFrame(renderer);
|
||||
|
||||
// Help text
|
||||
const auto labels = mappedInput.mapLabels("« Home", "Open", "", "");
|
||||
renderer.drawButtonHints(UI_10_FONT_ID, labels.btn1, labels.btn2, labels.btn3, labels.btn4);
|
||||
|
||||
if (files.empty()) {
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 60, "No books found");
|
||||
renderer.displayBuffer();
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pageStartIndex = selectorIndex / PAGE_ITEMS * PAGE_ITEMS;
|
||||
renderer.fillRect(0, 60 + (selectorIndex % PAGE_ITEMS) * 30 - 2, pageWidth - 1, 30);
|
||||
for (size_t i = pageStartIndex; i < files.size() && i < pageStartIndex + PAGE_ITEMS; i++) {
|
||||
auto item = renderer.truncatedText(UI_10_FONT_ID, files[i].c_str(), renderer.getScreenWidth() - 40);
|
||||
renderer.drawText(UI_10_FONT_ID, 20, 60 + (i % PAGE_ITEMS) * 30, item.c_str(), i != selectorIndex);
|
||||
renderer.drawText(UI_10_FONT_ID, contentRect.x + 20, contentRect.y + 20, "No books found");
|
||||
} else {
|
||||
UITheme::drawList(
|
||||
renderer, contentRect, files.size(), selectorIndex, [this](int index) { return files[index]; }, false, nullptr,
|
||||
false, nullptr);
|
||||
}
|
||||
|
||||
renderer.displayBuffer();
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
#include "CrossPointSettings.h"
|
||||
#include "CrossPointState.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "ScreenComponents.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
@ -517,7 +517,7 @@ void TxtReaderActivity::renderStatusBar(const int orientedMarginRight, const int
|
||||
}
|
||||
|
||||
if (showBattery) {
|
||||
ScreenComponents::drawBattery(renderer, orientedMarginLeft, textY);
|
||||
UITheme::drawBattery(renderer, Rect{orientedMarginLeft, textY});
|
||||
}
|
||||
|
||||
if (showTitle) {
|
||||
|
||||
@ -10,11 +10,12 @@
|
||||
#include "KOReaderSettingsActivity.h"
|
||||
#include "MappedInputManager.h"
|
||||
#include "OtaUpdateActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
// Define the static settings list
|
||||
namespace {
|
||||
constexpr int settingsCount = 22;
|
||||
constexpr int settingsCount = 23;
|
||||
const SettingInfo settingsList[settingsCount] = {
|
||||
// Should match with SLEEP_SCREEN_MODE
|
||||
SettingInfo::Enum("Sleep Screen", &CrossPointSettings::sleepScreen, {"Dark", "Light", "Custom", "Cover", "None"}),
|
||||
@ -27,7 +28,7 @@ const SettingInfo settingsList[settingsCount] = {
|
||||
SettingInfo::Enum("Reading Orientation", &CrossPointSettings::orientation,
|
||||
{"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}),
|
||||
SettingInfo::Enum("Front Button Layout", &CrossPointSettings::frontButtonLayout,
|
||||
{"Bck, Cnfrm, Lft, Rght", "Lft, Rght, Bck, Cnfrm", "Lft, Bck, Cnfrm, Rght"}),
|
||||
{"Bck, OK, L, R", "L, R, Bck, OK", "L, Bck, OK, R"}),
|
||||
SettingInfo::Enum("Side Button Layout (reader)", &CrossPointSettings::sideButtonLayout,
|
||||
{"Prev, Next", "Next, Prev"}),
|
||||
SettingInfo::Toggle("Long-press Chapter Skip", &CrossPointSettings::longPressChapterSkip),
|
||||
@ -43,6 +44,7 @@ const SettingInfo settingsList[settingsCount] = {
|
||||
{"1 min", "5 min", "10 min", "15 min", "30 min"}),
|
||||
SettingInfo::Enum("Refresh Frequency", &CrossPointSettings::refreshFrequency,
|
||||
{"1 page", "5 pages", "10 pages", "15 pages", "30 pages"}),
|
||||
SettingInfo::Enum("UI Theme", &CrossPointSettings::uiTheme, {"Classic", "Rounded"}),
|
||||
SettingInfo::Action("KOReader Sync"),
|
||||
SettingInfo::Action("Calibre Settings"),
|
||||
SettingInfo::Action("Check for updates")};
|
||||
@ -82,6 +84,8 @@ void SettingsActivity::onExit() {
|
||||
}
|
||||
vSemaphoreDelete(renderingMutex);
|
||||
renderingMutex = nullptr;
|
||||
|
||||
UITheme::initialize(); // Re-apply theme in case it was changed
|
||||
}
|
||||
|
||||
void SettingsActivity::loop() {
|
||||
@ -193,36 +197,26 @@ void SettingsActivity::render() const {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
|
||||
// Draw header
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, 15, "Settings", true, EpdFontFamily::BOLD);
|
||||
UITheme::drawFullscreenWindowFrame(renderer, "Settings");
|
||||
Rect contentRect = UITheme::getWindowContentFrame(renderer);
|
||||
|
||||
// Draw selection highlight
|
||||
renderer.fillRect(0, 60 + selectedSettingIndex * 30 - 2, pageWidth - 1, 30);
|
||||
|
||||
// Draw all settings
|
||||
for (int i = 0; i < settingsCount; i++) {
|
||||
const int settingY = 60 + i * 30; // 30 pixels between settings
|
||||
const bool isSelected = (i == selectedSettingIndex);
|
||||
|
||||
// Draw setting name
|
||||
renderer.drawText(UI_10_FONT_ID, 20, settingY, settingsList[i].name, !isSelected);
|
||||
|
||||
// Draw value based on setting type
|
||||
std::string valueText;
|
||||
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = value ? "ON" : "OFF";
|
||||
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = settingsList[i].enumValues[value];
|
||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||
}
|
||||
if (!valueText.empty()) {
|
||||
const auto width = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, pageWidth - 20 - width, settingY, valueText.c_str(), !isSelected);
|
||||
}
|
||||
}
|
||||
UITheme::drawList(
|
||||
renderer, contentRect, settingsCount, selectedSettingIndex,
|
||||
[](int index) { return std::string(settingsList[index].name); }, false, nullptr, true,
|
||||
[this](int i) {
|
||||
const auto& setting = settingsList[i];
|
||||
std::string valueText = "";
|
||||
if (settingsList[i].type == SettingType::TOGGLE && settingsList[i].valuePtr != nullptr) {
|
||||
const bool value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = value ? "ON" : "OFF";
|
||||
} else if (settingsList[i].type == SettingType::ENUM && settingsList[i].valuePtr != nullptr) {
|
||||
const uint8_t value = SETTINGS.*(settingsList[i].valuePtr);
|
||||
valueText = settingsList[i].enumValues[value];
|
||||
} else if (settingsList[i].type == SettingType::VALUE && settingsList[i].valuePtr != nullptr) {
|
||||
valueText = std::to_string(SETTINGS.*(settingsList[i].valuePtr));
|
||||
}
|
||||
return valueText;
|
||||
});
|
||||
|
||||
// Draw version text above button hints
|
||||
renderer.drawText(SMALL_FONT_ID, pageWidth - 20 - renderer.getTextWidth(SMALL_FONT_ID, CROSSPOINT_VERSION),
|
||||
|
||||
70
src/components/UITheme.cpp
Normal file
70
src/components/UITheme.cpp
Normal file
@ -0,0 +1,70 @@
|
||||
#include "UITheme.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "components/themes/BaseTheme.h"
|
||||
#include "components/themes/rounded/RoundedTheme.h"
|
||||
|
||||
std::unique_ptr<BaseTheme> currentTheme = nullptr;
|
||||
|
||||
// Initialize theme based on settings
|
||||
void UITheme::initialize() {
|
||||
auto themeType = static_cast<CrossPointSettings::UI_THEME>(SETTINGS.uiTheme);
|
||||
setTheme(themeType);
|
||||
}
|
||||
|
||||
void UITheme::setTheme(CrossPointSettings::UI_THEME type) {
|
||||
switch (type) {
|
||||
case CrossPointSettings::UI_THEME::CLASSIC:
|
||||
Serial.printf("[%lu] [UI] Using Classic theme\n", millis());
|
||||
currentTheme = std::unique_ptr<BaseTheme>(new BaseTheme());
|
||||
break;
|
||||
case CrossPointSettings::UI_THEME::ROUNDED:
|
||||
Serial.printf("[%lu] [UI] Using Rounded theme\n", millis());
|
||||
currentTheme = std::unique_ptr<BaseTheme>(new RoundedTheme());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Forward all component methods to the current theme
|
||||
void UITheme::drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total) {
|
||||
if (currentTheme != nullptr) {
|
||||
currentTheme->drawProgressBar(renderer, rect, current, total);
|
||||
}
|
||||
}
|
||||
|
||||
void UITheme::drawBattery(const GfxRenderer& renderer, Rect rect, bool showPercentage) {
|
||||
if (currentTheme != nullptr) {
|
||||
currentTheme->drawBattery(renderer, rect, showPercentage);
|
||||
}
|
||||
}
|
||||
|
||||
void UITheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
|
||||
const std::function<std::string(int index)>& rowTitle, bool hasIcon,
|
||||
const std::function<std::string(int index)>& rowIcon, bool hasValue,
|
||||
const std::function<std::string(int index)>& rowValue) {
|
||||
if (currentTheme != nullptr) {
|
||||
currentTheme->drawList(renderer, rect, itemCount, selectedIndex, rowTitle, hasIcon, rowIcon, hasValue, rowValue);
|
||||
}
|
||||
}
|
||||
|
||||
void UITheme::drawWindowFrame(GfxRenderer& renderer, Rect rect, bool isPopup, const char* title) {
|
||||
if (currentTheme != nullptr) {
|
||||
currentTheme->drawWindowFrame(renderer, rect, isPopup, title);
|
||||
}
|
||||
}
|
||||
|
||||
void UITheme::drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title) {
|
||||
if (currentTheme != nullptr) {
|
||||
currentTheme->drawFullscreenWindowFrame(renderer, title);
|
||||
}
|
||||
}
|
||||
|
||||
Rect UITheme::getWindowContentFrame(GfxRenderer& renderer) {
|
||||
if (currentTheme != nullptr) {
|
||||
return currentTheme->getWindowContentFrame(renderer);
|
||||
}
|
||||
return Rect{};
|
||||
}
|
||||
35
src/components/UITheme.h
Normal file
35
src/components/UITheme.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "CrossPointSettings.h"
|
||||
|
||||
class GfxRenderer;
|
||||
|
||||
struct Rect {
|
||||
int x;
|
||||
int y;
|
||||
int width;
|
||||
int height;
|
||||
|
||||
// Constructor for explicit initialization
|
||||
explicit Rect(int x = 0, int y = 0, int width = 0, int height = 0) : x(x), y(y), width(width), height(height) {}
|
||||
};
|
||||
|
||||
class UITheme {
|
||||
public:
|
||||
static void initialize();
|
||||
static void setTheme(CrossPointSettings::UI_THEME type);
|
||||
|
||||
static Rect getWindowContentFrame(GfxRenderer& renderer);
|
||||
|
||||
static void drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total);
|
||||
static void drawBattery(const GfxRenderer& renderer, Rect rect, bool showPercentage = true);
|
||||
static void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
|
||||
const std::function<std::string(int index)>& rowTitle, bool hasIcon,
|
||||
const std::function<std::string(int index)>& rowIcon, bool hasValue,
|
||||
const std::function<std::string(int index)>& rowValue);
|
||||
|
||||
static void drawWindowFrame(GfxRenderer& renderer, Rect rect, bool isPopup, const char* title);
|
||||
static void drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title);
|
||||
};
|
||||
111
src/components/themes/BaseTheme.cpp
Normal file
111
src/components/themes/BaseTheme.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
#include "BaseTheme.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
constexpr int rowHeight = 30;
|
||||
constexpr int pageItems = 23;
|
||||
} // namespace
|
||||
|
||||
void BaseTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) {
|
||||
// Left aligned battery icon and percentage
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : "";
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + 20, rect.y, percentageText.c_str());
|
||||
// 1 column on left, 2 columns on right, 5 columns of battery body
|
||||
constexpr int batteryWidth = 15;
|
||||
constexpr int batteryHeight = 12;
|
||||
const int x = rect.x;
|
||||
const int y = rect.y + 6;
|
||||
|
||||
// Top line
|
||||
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y);
|
||||
// Bottom line
|
||||
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1);
|
||||
// Left line
|
||||
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2);
|
||||
// Battery end
|
||||
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2);
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + 3);
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4);
|
||||
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5);
|
||||
|
||||
// The +1 is to round up, so that we always fill at least one pixel
|
||||
int filledWidth = percentage * (batteryWidth - 5) / 100 + 1;
|
||||
if (filledWidth > batteryWidth - 5) {
|
||||
filledWidth = batteryWidth - 5; // Ensure we don't overflow
|
||||
}
|
||||
|
||||
renderer.fillRect(x + 2, y + 2, filledWidth, batteryHeight - 4);
|
||||
}
|
||||
|
||||
void BaseTheme::drawProgressBar(const GfxRenderer& renderer, Rect rect, const size_t current, const size_t total) {
|
||||
if (total == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use 64-bit arithmetic to avoid overflow for large files
|
||||
const int percent = static_cast<int>((static_cast<uint64_t>(current) * 100) / total);
|
||||
|
||||
// Draw outline
|
||||
renderer.drawRect(rect.x, rect.y, rect.width, rect.height);
|
||||
|
||||
// Draw filled portion
|
||||
const int fillWidth = (rect.width - 4) * percent / 100;
|
||||
if (fillWidth > 0) {
|
||||
renderer.fillRect(rect.x + 2, rect.y + 2, fillWidth, rect.height - 4);
|
||||
}
|
||||
|
||||
// Draw percentage text centered below bar
|
||||
const std::string percentText = std::to_string(percent) + "%";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, rect.y + rect.height + 15, percentText.c_str());
|
||||
}
|
||||
|
||||
void BaseTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
|
||||
const std::function<std::string(int index)>& rowTitle, bool hasIcon,
|
||||
const std::function<std::string(int index)>& rowIcon, bool hasValue,
|
||||
const std::function<std::string(int index)>& rowValue) {
|
||||
// Draw selection
|
||||
renderer.fillRect(0, rect.y + selectedIndex % pageItems * rowHeight - 2, rect.width - 1, rowHeight);
|
||||
// Draw all items
|
||||
const auto pageStartIndex = selectedIndex / pageItems * pageItems;
|
||||
for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) {
|
||||
const int itemY = rect.y + (i % pageItems) * rowHeight;
|
||||
|
||||
// Draw name
|
||||
auto itemName = rowTitle(i);
|
||||
auto item = renderer.truncatedText(UI_10_FONT_ID, itemName.c_str(),
|
||||
rect.width - (hasValue ? 100 : 40)); // TODO truncate according to value width?
|
||||
renderer.drawText(UI_10_FONT_ID, 20, itemY, item.c_str(), i != selectedIndex);
|
||||
|
||||
if (hasValue) {
|
||||
// Draw value
|
||||
std::string valueText = rowValue(i);
|
||||
const auto textWidth = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, rect.width - 20 - textWidth, itemY, valueText.c_str(), i != selectedIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect BaseTheme::getWindowContentFrame(GfxRenderer& renderer) {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
return Rect{0, 60, pageWidth, pageHeight - 120};
|
||||
}
|
||||
|
||||
void BaseTheme::drawWindowFrame(GfxRenderer& renderer, Rect rect, bool isPopup, const char* title) {
|
||||
if (title) {
|
||||
renderer.drawCenteredText(UI_12_FONT_ID, rect.y, title, true, EpdFontFamily::BOLD);
|
||||
}
|
||||
}
|
||||
|
||||
void BaseTheme::drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title) {
|
||||
BaseTheme::drawWindowFrame(renderer, Rect{0, 15, renderer.getScreenWidth(), renderer.getScreenHeight()}, false,
|
||||
title);
|
||||
}
|
||||
31
src/components/themes/BaseTheme.h
Normal file
31
src/components/themes/BaseTheme.h
Normal file
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
#include "components/UITheme.h"
|
||||
|
||||
class GfxRenderer;
|
||||
|
||||
// Default theme implementation (Classic Theme)
|
||||
// Additional themes can inherit from this and override methods as needed
|
||||
|
||||
class BaseTheme {
|
||||
public:
|
||||
virtual ~BaseTheme() = default;
|
||||
|
||||
// Property getters
|
||||
virtual Rect getWindowContentFrame(GfxRenderer& renderer);
|
||||
|
||||
// Component drawing methods
|
||||
virtual void drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total);
|
||||
virtual void drawBattery(const GfxRenderer& renderer, Rect rect, bool showPercentage = true);
|
||||
virtual void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
|
||||
const std::function<std::string(int index)>& rowTitle, bool hasIcon,
|
||||
const std::function<std::string(int index)>& rowIcon, bool hasValue,
|
||||
const std::function<std::string(int index)>& rowValue);
|
||||
|
||||
virtual void drawWindowFrame(GfxRenderer& renderer, Rect rect, bool isPopup, const char* title);
|
||||
virtual void drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title);
|
||||
};
|
||||
160
src/components/themes/rounded/RoundedTheme.cpp
Normal file
160
src/components/themes/rounded/RoundedTheme.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
#include "RoundedTheme.h"
|
||||
|
||||
#include <GfxRenderer.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "Battery.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
namespace {
|
||||
constexpr int rowHeight = 64;
|
||||
constexpr int pageItems = 9;
|
||||
constexpr int windowCornerRadius = 16;
|
||||
constexpr int windowBorderWidth = 2;
|
||||
constexpr int fullscreenWindowMargin = 20;
|
||||
constexpr int windowHeaderHeight = 50;
|
||||
constexpr int statusBarHeight = 50;
|
||||
constexpr int buttonHintsHeight = 50;
|
||||
} // namespace
|
||||
|
||||
void RoundedTheme::drawBattery(const GfxRenderer& renderer, Rect rect, const bool showPercentage) {
|
||||
// Left aligned battery icon and percentage
|
||||
const uint16_t percentage = battery.readPercentage();
|
||||
const auto percentageText = showPercentage ? std::to_string(percentage) + "%" : "";
|
||||
renderer.drawText(SMALL_FONT_ID, rect.x + 20, rect.y, percentageText.c_str());
|
||||
|
||||
// 1 column on left, 2 columns on right, 5 columns of battery body
|
||||
constexpr int batteryWidth = 15;
|
||||
constexpr int batteryHeight = 12;
|
||||
const int x = rect.x;
|
||||
const int y = rect.y + 6;
|
||||
|
||||
// Top line
|
||||
renderer.drawLine(x + 1, y, x + batteryWidth - 3, y);
|
||||
// Bottom line
|
||||
renderer.drawLine(x + 1, y + batteryHeight - 1, x + batteryWidth - 3, y + batteryHeight - 1);
|
||||
// Left line
|
||||
renderer.drawLine(x, y + 1, x, y + batteryHeight - 2);
|
||||
// Battery end
|
||||
renderer.drawLine(x + batteryWidth - 2, y + 1, x + batteryWidth - 2, y + batteryHeight - 2);
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + 3);
|
||||
renderer.drawPixel(x + batteryWidth - 1, y + batteryHeight - 4);
|
||||
renderer.drawLine(x + batteryWidth - 0, y + 4, x + batteryWidth - 0, y + batteryHeight - 5);
|
||||
|
||||
// Draw bars
|
||||
if (percentage > 10) {
|
||||
renderer.fillRect(x + 2, y + 2, 3, batteryHeight - 4);
|
||||
}
|
||||
if (percentage > 40) {
|
||||
renderer.fillRect(x + 6, y + 2, 3, batteryHeight - 4);
|
||||
}
|
||||
if (percentage > 70) {
|
||||
renderer.fillRect(x + 10, y + 2, 2, batteryHeight - 4);
|
||||
}
|
||||
}
|
||||
|
||||
void RoundedTheme::drawProgressBar(const GfxRenderer& renderer, Rect rect, const size_t current, const size_t total) {
|
||||
if (total == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use 64-bit arithmetic to avoid overflow for large files
|
||||
const int percent = static_cast<int>((static_cast<uint64_t>(current) * 100) / total);
|
||||
|
||||
// Draw filled portion
|
||||
const int fillWidth = (rect.width - 4) * percent / 100;
|
||||
if (fillWidth > 0) {
|
||||
renderer.fillRect(rect.x + 2, rect.y + 2, fillWidth, rect.height - 4);
|
||||
}
|
||||
|
||||
// Draw percentage text centered below bar
|
||||
const std::string percentText = std::to_string(percent) + "%";
|
||||
renderer.drawCenteredText(UI_10_FONT_ID, rect.y + rect.height + 15, percentText.c_str());
|
||||
}
|
||||
|
||||
void RoundedTheme::drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
|
||||
const std::function<std::string(int index)>& rowTitle, bool hasIcon,
|
||||
const std::function<std::string(int index)>& rowIcon, bool hasValue,
|
||||
const std::function<std::string(int index)>& rowValue) {
|
||||
const int totalPages = (itemCount + pageItems - 1) / pageItems;
|
||||
if (totalPages > 1) {
|
||||
// Draw scroll bar
|
||||
const int scrollBarHeight = (rect.height * pageItems) / itemCount;
|
||||
const int currentPage = selectedIndex / pageItems;
|
||||
const int scrollBarY = rect.y + ((rect.height - scrollBarHeight) * currentPage) / (totalPages - 1);
|
||||
renderer.fillRectGrey(rect.x + rect.width, rect.y, 4, rect.height, 5);
|
||||
renderer.fillRect(rect.x + rect.width, scrollBarY, 4, scrollBarHeight, true);
|
||||
}
|
||||
|
||||
// Draw selection
|
||||
renderer.fillRectGrey(rect.x, rect.y + selectedIndex % pageItems * rowHeight - 2, rect.width - 1, rowHeight, 3);
|
||||
// Draw all items
|
||||
const auto pageStartIndex = selectedIndex / pageItems * pageItems;
|
||||
for (int i = pageStartIndex; i < itemCount && i < pageStartIndex + pageItems; i++) {
|
||||
const int itemY = rect.y + (i % pageItems) * rowHeight;
|
||||
|
||||
// Draw name
|
||||
auto itemName = rowTitle(i);
|
||||
auto item = renderer.truncatedText(UI_10_FONT_ID, itemName.c_str(),
|
||||
rect.width - (hasValue ? 100 : 40)); // TODO truncate according to value width?
|
||||
renderer.drawText(UI_10_FONT_ID, rect.x + 20, itemY + 20, item.c_str(), true);
|
||||
|
||||
if (hasValue) {
|
||||
// Draw value
|
||||
std::string valueText = rowValue(i);
|
||||
const auto textWidth = renderer.getTextWidth(UI_10_FONT_ID, valueText.c_str());
|
||||
renderer.drawText(UI_10_FONT_ID, rect.x + rect.width - 20 - textWidth, itemY + 20, valueText.c_str(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rect RoundedTheme::getWindowContentFrame(GfxRenderer& renderer) {
|
||||
const auto pageWidth = renderer.getScreenWidth();
|
||||
const auto pageHeight = renderer.getScreenHeight();
|
||||
return Rect{35, 125, pageWidth - 70, pageHeight - 165 - buttonHintsHeight};
|
||||
}
|
||||
|
||||
void RoundedTheme::drawWindowFrame(GfxRenderer& renderer, Rect rect, bool isPopup, const char* title) {
|
||||
const int windowWidth = renderer.getScreenWidth() - 2 * rect.x;
|
||||
|
||||
if (title) { // Header background
|
||||
renderer.fillRectGrey(rect.x, rect.y, windowWidth, windowHeaderHeight, 5);
|
||||
renderer.fillArc(windowCornerRadius, rect.x + windowCornerRadius, rect.y + windowCornerRadius, -1, -1, 0,
|
||||
-1); // TL
|
||||
renderer.fillArc(windowCornerRadius, windowWidth + rect.x - windowCornerRadius, rect.y + windowCornerRadius, 1, -1,
|
||||
0, -1); // TR
|
||||
}
|
||||
|
||||
renderer.drawRoundedRect(rect.x, rect.y, windowWidth, rect.height, windowBorderWidth, windowCornerRadius, true);
|
||||
|
||||
if (!isPopup) {
|
||||
renderer.drawLine(windowWidth + rect.x, rect.y + windowCornerRadius + 2, windowWidth + rect.x,
|
||||
rect.y + rect.height - windowCornerRadius, windowBorderWidth, true);
|
||||
renderer.drawLine(rect.x + windowCornerRadius + 2, rect.y + rect.height, windowWidth + rect.x - windowCornerRadius,
|
||||
rect.y + rect.height, windowBorderWidth, true);
|
||||
renderer.drawArc(windowCornerRadius + windowBorderWidth, windowWidth + rect.x - 1 - windowCornerRadius,
|
||||
rect.y + rect.height - 1 - windowCornerRadius, 1, 1, windowBorderWidth, true);
|
||||
renderer.drawPixel(rect.x + windowCornerRadius + 1, rect.y + rect.height, true);
|
||||
}
|
||||
|
||||
if (title) { // Header
|
||||
const int titleWidth = renderer.getTextWidth(UI_12_FONT_ID, title);
|
||||
const int titleX = (renderer.getScreenWidth() - titleWidth) / 2;
|
||||
const int titleY = rect.y + 10;
|
||||
renderer.drawText(UI_12_FONT_ID, titleX, titleY, title, true, EpdFontFamily::REGULAR);
|
||||
renderer.drawLine(rect.x, rect.y + windowHeaderHeight, windowWidth + rect.x, rect.y + windowHeaderHeight,
|
||||
windowBorderWidth, true);
|
||||
}
|
||||
}
|
||||
|
||||
void RoundedTheme::drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title) {
|
||||
// drawStatusBar(renderer);
|
||||
RoundedTheme::drawWindowFrame(
|
||||
renderer,
|
||||
Rect{fullscreenWindowMargin, statusBarHeight, 0,
|
||||
renderer.getScreenHeight() - fullscreenWindowMargin - statusBarHeight - buttonHintsHeight},
|
||||
false, title);
|
||||
RoundedTheme::drawBattery(renderer, Rect{fullscreenWindowMargin, 18}, false);
|
||||
}
|
||||
25
src/components/themes/rounded/RoundedTheme.h
Normal file
25
src/components/themes/rounded/RoundedTheme.h
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#include "components/themes/BaseTheme.h"
|
||||
|
||||
class GfxRenderer;
|
||||
|
||||
class RoundedTheme : public BaseTheme {
|
||||
public:
|
||||
// Property getters
|
||||
Rect getWindowContentFrame(GfxRenderer& renderer) override;
|
||||
|
||||
// Component drawing methods
|
||||
void drawProgressBar(const GfxRenderer& renderer, Rect rect, size_t current, size_t total) override;
|
||||
void drawBattery(const GfxRenderer& renderer, Rect rect, bool showPercentage = true) override;
|
||||
void drawList(const GfxRenderer& renderer, Rect rect, int itemCount, int selectedIndex,
|
||||
const std::function<std::string(int index)>& rowTitle, bool hasIcon,
|
||||
const std::function<std::string(int index)>& rowIcon, bool hasValue,
|
||||
const std::function<std::string(int index)>& rowValue) override;
|
||||
|
||||
void drawWindowFrame(GfxRenderer& renderer, Rect rect, bool isPopup, const char* title) override;
|
||||
void drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title) override;
|
||||
};
|
||||
@ -22,6 +22,7 @@
|
||||
#include "activities/reader/ReaderActivity.h"
|
||||
#include "activities/settings/SettingsActivity.h"
|
||||
#include "activities/util/FullScreenMessageActivity.h"
|
||||
#include "components/UITheme.h"
|
||||
#include "fontIds.h"
|
||||
|
||||
#define SPI_FQ 40000000
|
||||
@ -273,6 +274,7 @@ void setup() {
|
||||
}
|
||||
|
||||
inputManager.begin();
|
||||
|
||||
// Initialize pins
|
||||
pinMode(BAT_GPIO0, INPUT);
|
||||
|
||||
@ -291,6 +293,7 @@ void setup() {
|
||||
|
||||
SETTINGS.loadFromFile();
|
||||
KOREADER_STORE.loadFromFile();
|
||||
UITheme::initialize();
|
||||
|
||||
// verify power button press duration after we've read settings.
|
||||
verifyWakeupLongPress();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user