Grid pagination, window frame
This commit is contained in:
parent
c1f25e9c2a
commit
d3e16152e2
@ -80,11 +80,18 @@ void GfxRenderer::drawText(const int fontId, const int x, const int y, const cha
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GfxRenderer::drawTextInBox(const int fontId, const int x, const int y, const int w, const int h, const char* text, const bool black, const EpdFontStyle style) const {
|
void GfxRenderer::drawTextInBox(const int fontId, const int x, const int y, const int w, const int h, const char* text, const bool centered, const bool black, const EpdFontStyle style) const {
|
||||||
const int lineHeight = getLineHeight(fontId);
|
const int lineHeight = getLineHeight(fontId);
|
||||||
const int spaceWidth = getSpaceWidth(fontId);
|
const int spaceWidth = getSpaceWidth(fontId);
|
||||||
int xpos = x;
|
int xpos = x;
|
||||||
int ypos = y + lineHeight;
|
int ypos = y + lineHeight;
|
||||||
|
if (centered) {
|
||||||
|
int textWidth = getTextWidth(fontId, text, style);
|
||||||
|
if (textWidth < w) {
|
||||||
|
// Center if text on single line
|
||||||
|
xpos = x + (w - textWidth) / 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// cannot draw a NULL / empty string
|
// cannot draw a NULL / empty string
|
||||||
if (text == nullptr || *text == '\0') {
|
if (text == nullptr || *text == '\0') {
|
||||||
@ -117,6 +124,13 @@ void GfxRenderer::drawTextInBox(const int fontId, const int x, const int y, cons
|
|||||||
renderChar(font, '.', &dotX, &ypos, black, style);
|
renderChar(font, '.', &dotX, &ypos, black, style);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
// TODO center when more than one line
|
||||||
|
// if (centered) {
|
||||||
|
// int textWidth = getTextWidth(fontId, text, style);
|
||||||
|
// if (textWidth < w) {
|
||||||
|
// xpos = x + (w - textWidth) / 2;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
xpos = x;
|
xpos = x;
|
||||||
ypos += lineHeight;
|
ypos += lineHeight;
|
||||||
if (h > 0 && ypos - y > h) {
|
if (h > 0 && ypos - y > h) {
|
||||||
@ -155,6 +169,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 {
|
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, y, x + width - 1, y, state);
|
||||||
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
drawLine(x + width - 1, y, x + width - 1, y + height - 1, state);
|
||||||
@ -162,6 +182,68 @@ void GfxRenderer::drawRect(const int x, const int y, const int width, const int
|
|||||||
drawLine(x, y, x, y + height - 1, state);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 {
|
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++) {
|
for (int fillY = y; fillY < y + height; fillY++) {
|
||||||
drawLine(x, fillY, x + width - 1, fillY, state);
|
drawLine(x, fillY, x + width - 1, fillY, state);
|
||||||
@ -173,6 +255,10 @@ void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, co
|
|||||||
einkDisplay.drawImage(bitmap, y, x, height, width);
|
einkDisplay.drawImage(bitmap, y, x, height, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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,
|
void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth,
|
||||||
const int maxHeight) const {
|
const int maxHeight) const {
|
||||||
float scale = 1.0f;
|
float scale = 1.0f;
|
||||||
|
|||||||
@ -45,16 +45,21 @@ class GfxRenderer {
|
|||||||
// Drawing
|
// Drawing
|
||||||
void drawPixel(int x, int y, bool state = true) const;
|
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, bool state = true) const;
|
||||||
|
void drawLine(int x1, int y1, int x2, int y2, int lineWidth, bool state = true) const;
|
||||||
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
void drawRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
|
void drawRect(int x, int y, int width, int height, int lineWidth, bool state) const;
|
||||||
|
void drawArc(int maxRadius, int cx, int cy, int xDir, int yDir, int lineWidth, bool state) const;
|
||||||
|
void drawRoundedRect(int x, int y, int width, int height, int lineWidth, int cornerRadius, bool state) const;
|
||||||
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
void fillRect(int x, int y, int width, int height, bool state = true) const;
|
||||||
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
void drawImage(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||||
|
void drawIcon(const uint8_t bitmap[], int x, int y, int width, int height) const;
|
||||||
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
void drawBitmap(const Bitmap& bitmap, int x, int y, int maxWidth, int maxHeight) const;
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const;
|
int getTextWidth(int fontId, const char* text, EpdFontStyle style = REGULAR) const;
|
||||||
void drawCenteredText(int fontId, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
void drawCenteredText(int fontId, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||||
void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
void drawText(int fontId, int x, int y, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||||
void drawTextInBox(int fontId, int x, int y, int w, int h, const char* text, bool black = true, EpdFontStyle style = REGULAR) const;
|
void drawTextInBox(int fontId, int x, int y, int w, int h, const char* text, bool centered, bool black = true, EpdFontStyle style = REGULAR) const;
|
||||||
int getSpaceWidth(int fontId) const;
|
int getSpaceWidth(int fontId) const;
|
||||||
int getLineHeight(int fontId) const;
|
int getLineHeight(int fontId) const;
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
#include <GfxRenderer.h>
|
#include <GfxRenderer.h>
|
||||||
#include <SD.h>
|
#include <SD.h>
|
||||||
|
#include <InputManager.h>
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "../../images/FolderIcon.h"
|
#include "../../images/FolderIcon.h"
|
||||||
|
#include "../util/Window.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr int PAGE_ITEMS = 12;
|
constexpr int PAGE_ITEMS = 9;
|
||||||
constexpr int SKIP_PAGE_MS = 700;
|
constexpr int SKIP_PAGE_MS = 700;
|
||||||
constexpr int TILE_W = 135;
|
constexpr int TILE_W = 135;
|
||||||
constexpr int TILE_H = 200;
|
constexpr int TILE_H = 200;
|
||||||
@ -15,6 +17,8 @@ constexpr int TILE_PADDING = 5;
|
|||||||
constexpr int THUMB_W = 90;
|
constexpr int THUMB_W = 90;
|
||||||
constexpr int THUMB_H = 120;
|
constexpr int THUMB_H = 120;
|
||||||
constexpr int TILE_TEXT_H = 60;
|
constexpr int TILE_TEXT_H = 60;
|
||||||
|
constexpr int gridLeftOffset = 45;
|
||||||
|
constexpr int gridTopOffset = 125;
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
inline int min(const int a, const int b) { return a < b ? a : b; }
|
inline int min(const int a, const int b) { return a < b ? a : b; }
|
||||||
@ -37,6 +41,8 @@ void GridBrowserActivity::taskTrampoline(void* param) {
|
|||||||
void GridBrowserActivity::loadFiles() {
|
void GridBrowserActivity::loadFiles() {
|
||||||
files.clear();
|
files.clear();
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
|
previousSelectorIndex = -1;
|
||||||
|
page = 0;
|
||||||
auto root = SD.open(basepath.c_str());
|
auto root = SD.open(basepath.c_str());
|
||||||
for (File file = root.openNextFile(); file; file = root.openNextFile()) {
|
for (File file = root.openNextFile(); file; file = root.openNextFile()) {
|
||||||
const std::string filename = std::string(file.name());
|
const std::string filename = std::string(file.name());
|
||||||
@ -49,7 +55,7 @@ void GridBrowserActivity::loadFiles() {
|
|||||||
files.emplace_back(FileInfo{ filename, filename, F_DIRECTORY });
|
files.emplace_back(FileInfo{ filename, filename, F_DIRECTORY });
|
||||||
} else {
|
} else {
|
||||||
FileType type = F_FILE;
|
FileType type = F_FILE;
|
||||||
size_t dot = filename.find_last_of('.');
|
size_t dot = filename.find_first_of('.');
|
||||||
std::string basename = filename;
|
std::string basename = filename;
|
||||||
if (dot != std::string::npos) {
|
if (dot != std::string::npos) {
|
||||||
std::string ext = filename.substr(dot);
|
std::string ext = filename.substr(dot);
|
||||||
@ -58,7 +64,7 @@ void GridBrowserActivity::loadFiles() {
|
|||||||
for (char &c : ext) c = (char)tolower(c);
|
for (char &c : ext) c = (char)tolower(c);
|
||||||
if (ext == ".epub") {
|
if (ext == ".epub") {
|
||||||
type = F_EPUB;
|
type = F_EPUB;
|
||||||
} else if (ext == ".bmp") {
|
} else if (ext == ".thumb.bmp") {
|
||||||
type = F_BMP;
|
type = F_BMP;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,12 +84,13 @@ void GridBrowserActivity::onEnter() {
|
|||||||
Serial.printf("Enter grid\n");
|
Serial.printf("Enter grid\n");
|
||||||
renderingMutex = xSemaphoreCreateMutex();
|
renderingMutex = xSemaphoreCreateMutex();
|
||||||
|
|
||||||
basepath = "/Dev/Thumbs";
|
basepath = "/";
|
||||||
loadFiles();
|
loadFiles();
|
||||||
selectorIndex = 0;
|
selectorIndex = 0;
|
||||||
|
page = 0;
|
||||||
|
|
||||||
// Trigger first update
|
// Trigger first render
|
||||||
updateRequired = true;
|
renderRequired = true;
|
||||||
|
|
||||||
xTaskCreate(&GridBrowserActivity::taskTrampoline, "GridFileBrowserTask",
|
xTaskCreate(&GridBrowserActivity::taskTrampoline, "GridFileBrowserTask",
|
||||||
8192, // Stack size
|
8192, // Stack size
|
||||||
@ -111,6 +118,7 @@ void GridBrowserActivity::loop() {
|
|||||||
const bool prevReleased = inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
const bool prevReleased = inputManager.wasReleased(InputManager::BTN_UP) || inputManager.wasReleased(InputManager::BTN_LEFT);
|
||||||
const bool nextReleased = inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
|
const bool nextReleased = inputManager.wasReleased(InputManager::BTN_DOWN) || inputManager.wasReleased(InputManager::BTN_RIGHT);
|
||||||
const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS;
|
const bool skipPage = inputManager.getHeldTime() > SKIP_PAGE_MS;
|
||||||
|
const int selected = selectorIndex + page * PAGE_ITEMS;
|
||||||
|
|
||||||
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
if (inputManager.wasPressed(InputManager::BTN_CONFIRM)) {
|
||||||
if (files.empty()) {
|
if (files.empty()) {
|
||||||
@ -120,57 +128,77 @@ void GridBrowserActivity::loop() {
|
|||||||
if (basepath.back() != '/') {
|
if (basepath.back() != '/') {
|
||||||
basepath += "/";
|
basepath += "/";
|
||||||
}
|
}
|
||||||
if (files[selectorIndex].type == F_DIRECTORY) {
|
if (files[selected].type == F_DIRECTORY) {
|
||||||
// open subfolder
|
// open subfolder
|
||||||
basepath += files[selectorIndex].name;
|
basepath += files[selected].name;
|
||||||
loadFiles();
|
loadFiles();
|
||||||
updateRequired = true;
|
renderRequired = true;
|
||||||
} else {
|
} else {
|
||||||
onSelect(basepath + files[selectorIndex].name);
|
onSelect(basepath + files[selected].name);
|
||||||
}
|
}
|
||||||
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
} else if (inputManager.wasPressed(InputManager::BTN_BACK)) {
|
||||||
if (basepath != "/") {
|
if (basepath != "/") {
|
||||||
basepath = basepath.substr(0, basepath.rfind('/'));
|
basepath = basepath.substr(0, basepath.rfind('/'));
|
||||||
if (basepath.empty()) basepath = "/";
|
if (basepath.empty()) basepath = "/";
|
||||||
loadFiles();
|
loadFiles();
|
||||||
updateRequired = true;
|
renderRequired = true;
|
||||||
} else {
|
} else {
|
||||||
// At root level, go back home
|
// At root level, go back home
|
||||||
onGoHome();
|
onGoHome();
|
||||||
}
|
}
|
||||||
} else if (prevReleased) {
|
} else if (prevReleased) {
|
||||||
if (skipPage) {
|
previousSelectorIndex = selectorIndex;
|
||||||
selectorIndex = ((selectorIndex / PAGE_ITEMS - 1) * PAGE_ITEMS + files.size()) % files.size();
|
if (selectorIndex == 0 || skipPage) {
|
||||||
|
if (page > 0) {
|
||||||
|
page--;
|
||||||
|
selectorIndex = 0;
|
||||||
|
previousSelectorIndex = -1;
|
||||||
|
renderRequired = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectorIndex = (selectorIndex + files.size() - 1) % files.size();
|
selectorIndex--;
|
||||||
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
|
||||||
} else if (nextReleased) {
|
} else if (nextReleased) {
|
||||||
if (skipPage) {
|
previousSelectorIndex = selectorIndex;
|
||||||
selectorIndex = ((selectorIndex / PAGE_ITEMS + 1) * PAGE_ITEMS) % files.size();
|
if (selectorIndex == min(PAGE_ITEMS, files.size() - page * PAGE_ITEMS) - 1 || skipPage) {
|
||||||
|
if (page < files.size() / PAGE_ITEMS) {
|
||||||
|
page++;
|
||||||
|
selectorIndex = 0;
|
||||||
|
previousSelectorIndex = -1;
|
||||||
|
renderRequired = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
selectorIndex = (selectorIndex + 1) % files.size();
|
selectorIndex++;
|
||||||
|
updateRequired = true;
|
||||||
}
|
}
|
||||||
updateRequired = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GridBrowserActivity::displayTaskLoop() {
|
void GridBrowserActivity::displayTaskLoop() {
|
||||||
while (true) {
|
while (true) {
|
||||||
if (updateRequired) {
|
if (renderRequired) {
|
||||||
|
renderRequired = false;
|
||||||
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
|
render(true);
|
||||||
|
xSemaphoreGive(renderingMutex);
|
||||||
|
} else if (updateRequired) {
|
||||||
updateRequired = false;
|
updateRequired = false;
|
||||||
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
xSemaphoreTake(renderingMutex, portMAX_DELAY);
|
||||||
render();
|
// update(true);
|
||||||
|
render(false);
|
||||||
xSemaphoreGive(renderingMutex);
|
xSemaphoreGive(renderingMutex);
|
||||||
}
|
}
|
||||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GridBrowserActivity::render() const {
|
void GridBrowserActivity::render(bool clear) const {
|
||||||
const auto pageWidth = renderer.getScreenWidth();
|
if (clear) {
|
||||||
const auto pageHeight = renderer.getScreenHeight();
|
renderer.clearScreen();
|
||||||
renderer.clearScreen();
|
auto folderName = basepath == "/" ? "SD card" : basepath.substr(basepath.rfind('/') + 1).c_str();
|
||||||
|
drawFullscreenWindowFrame(renderer, folderName);
|
||||||
|
}
|
||||||
bool hasGeyscaleBitmaps = false;
|
bool hasGeyscaleBitmaps = false;
|
||||||
|
|
||||||
if (!files.empty()) {
|
if (!files.empty()) {
|
||||||
@ -184,20 +212,17 @@ void GridBrowserActivity::render() const {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const int16_t iconOffsetX = (TILE_W - FOLDERICON_WIDTH) / 2;
|
for (size_t i = 0; i < min(PAGE_ITEMS, files.size() - page * PAGE_ITEMS); i++) {
|
||||||
const int16_t iconOffsetY = (TILE_H - TILE_TEXT_H - FOLDERICON_HEIGHT) / 2;
|
const auto file = files[i + page * PAGE_ITEMS];
|
||||||
const int16_t thumbOffsetX = (TILE_W - THUMB_W) / 2;
|
|
||||||
const int16_t thumbOffsetY = (TILE_H - TILE_TEXT_H - THUMB_H) / 2;
|
|
||||||
for (size_t i = 0; i < min(PAGE_ITEMS, files.size()); i++) {
|
|
||||||
const auto file = files[i];
|
|
||||||
|
|
||||||
const int16_t tileX = 45 + i % 3 * TILE_W;
|
const int16_t tileX = gridLeftOffset + i % 3 * TILE_W;
|
||||||
const int16_t tileY = 115 + i / 3 * TILE_H;
|
const int16_t tileY = gridTopOffset + i / 3 * TILE_H;
|
||||||
|
|
||||||
if (pass == 0) {
|
if (pass == 0) {
|
||||||
Serial.printf("Rendering file %s at (%d, %d)\n", file.name.c_str(), tileX, tileY);
|
|
||||||
if (file.type == F_DIRECTORY) {
|
if (file.type == F_DIRECTORY) {
|
||||||
renderer.drawImage(FolderIcon, tileX + iconOffsetX, tileY + iconOffsetY, FOLDERICON_WIDTH, FOLDERICON_HEIGHT);
|
constexpr int iconOffsetX = (TILE_W - FOLDERICON_WIDTH) / 2;
|
||||||
|
constexpr int iconOffsetY = (TILE_H - TILE_TEXT_H - FOLDERICON_HEIGHT) / 2;
|
||||||
|
renderer.drawIcon(FolderIcon, tileX + iconOffsetX, tileY + iconOffsetY, FOLDERICON_WIDTH, FOLDERICON_HEIGHT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,19 +234,24 @@ void GridBrowserActivity::render() const {
|
|||||||
if (bitmap.hasGreyscale()) {
|
if (bitmap.hasGreyscale()) {
|
||||||
hasGeyscaleBitmaps = true;
|
hasGeyscaleBitmaps = true;
|
||||||
}
|
}
|
||||||
|
constexpr int thumbOffsetX = (TILE_W - THUMB_W) / 2;
|
||||||
|
constexpr int thumbOffsetY = (TILE_H - TILE_TEXT_H - THUMB_H) / 2;
|
||||||
renderer.drawBitmap(bitmap, tileX + thumbOffsetX, tileY + thumbOffsetY, THUMB_W, THUMB_H);
|
renderer.drawBitmap(bitmap, tileX + thumbOffsetX, tileY + thumbOffsetY, THUMB_W, THUMB_H);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pass == 0) {
|
if (pass == 0) {
|
||||||
renderer.drawTextInBox(UI_FONT_ID, tileX + TILE_PADDING, tileY + TILE_H - TILE_TEXT_H, TILE_W - 2 * TILE_PADDING, TILE_TEXT_H, file.basename.c_str(), 1); // i != selectorIndex
|
renderer.drawTextInBox(UI_FONT_ID, tileX + TILE_PADDING, tileY + TILE_H - TILE_TEXT_H, TILE_W - 2 * TILE_PADDING, TILE_TEXT_H, file.basename.c_str(), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pass == 0) {
|
if (pass == 0) {
|
||||||
renderer.displayBuffer(EInkDisplay::HALF_REFRESH);
|
update(false);
|
||||||
if (!hasGeyscaleBitmaps) {
|
renderer.displayBuffer();
|
||||||
|
if (hasGeyscaleBitmaps) {
|
||||||
|
renderer.storeBwBuffer();
|
||||||
|
} else {
|
||||||
// we can skip grayscale passes if no bitmaps use it
|
// we can skip grayscale passes if no bitmaps use it
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -231,7 +261,21 @@ void GridBrowserActivity::render() const {
|
|||||||
renderer.copyGrayscaleMsbBuffers();
|
renderer.copyGrayscaleMsbBuffers();
|
||||||
renderer.displayGrayBuffer();
|
renderer.displayGrayBuffer();
|
||||||
renderer.setRenderMode(GfxRenderer::BW);
|
renderer.setRenderMode(GfxRenderer::BW);
|
||||||
|
renderer.restoreBwBuffer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GridBrowserActivity::drawSelectionRectangle(int tileIndex, bool black) const {
|
||||||
|
renderer.drawRoundedRect(gridLeftOffset + tileIndex % 3 * TILE_W, gridTopOffset + tileIndex / 3 * TILE_H, TILE_W, TILE_H, 2, 5, black);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GridBrowserActivity::update(bool render) const {
|
||||||
|
// Redraw only changed tiles
|
||||||
|
// renderer.clearScreen();
|
||||||
|
if (previousSelectorIndex >= 0) {
|
||||||
|
drawSelectionRectangle(previousSelectorIndex, false);
|
||||||
|
}
|
||||||
|
drawSelectionRectangle(selectorIndex, true);
|
||||||
|
}
|
||||||
@ -29,14 +29,19 @@ class GridBrowserActivity final : public Activity {
|
|||||||
std::string basepath = "/";
|
std::string basepath = "/";
|
||||||
std::vector<FileInfo> files;
|
std::vector<FileInfo> files;
|
||||||
int selectorIndex = 0;
|
int selectorIndex = 0;
|
||||||
|
int previousSelectorIndex = -1;
|
||||||
|
int page;
|
||||||
bool updateRequired = false;
|
bool updateRequired = false;
|
||||||
|
bool renderRequired = false;
|
||||||
const std::function<void(const std::string&)> onSelect;
|
const std::function<void(const std::string&)> onSelect;
|
||||||
const std::function<void()> onGoHome;
|
const std::function<void()> onGoHome;
|
||||||
|
|
||||||
static void taskTrampoline(void* param);
|
static void taskTrampoline(void* param);
|
||||||
[[noreturn]] void displayTaskLoop();
|
[[noreturn]] void displayTaskLoop();
|
||||||
void render() const;
|
void render(bool clear) const;
|
||||||
|
void update(bool render) const;
|
||||||
void loadFiles();
|
void loadFiles();
|
||||||
|
void drawSelectionRectangle(int tileIndex, bool black) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GridBrowserActivity(GfxRenderer& renderer, InputManager& inputManager,
|
explicit GridBrowserActivity(GfxRenderer& renderer, InputManager& inputManager,
|
||||||
|
|||||||
71
src/activities/util/Window.cpp
Normal file
71
src/activities/util/Window.cpp
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
#include "./Window.h"
|
||||||
|
#include "Battery.h"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr int windowCornerRadius = 16;
|
||||||
|
constexpr int windowBorderWidth = 2;
|
||||||
|
constexpr int fullscreenWindowMargin = 20;
|
||||||
|
constexpr int windowHeaderHeight = 50;
|
||||||
|
constexpr int statusBarHeight = 50;
|
||||||
|
constexpr int batteryWidth = 15;
|
||||||
|
constexpr int batteryHeight = 10;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void drawWindowFrame(GfxRenderer& renderer, int xMargin, int y, int height, bool hasShadow, const char* title) {
|
||||||
|
const int windowWidth = GfxRenderer::getScreenWidth() - 2 * xMargin;
|
||||||
|
renderer.drawRoundedRect(xMargin, y, windowWidth, height, windowBorderWidth, windowCornerRadius, true);
|
||||||
|
|
||||||
|
if (hasShadow) {
|
||||||
|
renderer.drawLine(windowWidth + xMargin, y + windowCornerRadius + 2, windowWidth + xMargin, y + height - windowCornerRadius, windowBorderWidth, true);
|
||||||
|
renderer.drawLine(xMargin + windowCornerRadius + 2, y + height, windowWidth + xMargin - windowCornerRadius, y + height, windowBorderWidth, true);
|
||||||
|
renderer.drawArc(windowCornerRadius + windowBorderWidth, windowWidth + xMargin - 1 - windowCornerRadius, y + height - 1 - windowCornerRadius, 1, 1, windowBorderWidth, true);
|
||||||
|
renderer.drawPixel(xMargin + windowCornerRadius + 1, y + height, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) { // Header
|
||||||
|
const int titleWidth = renderer.getTextWidth(UI_FONT_ID, title);
|
||||||
|
const int titleX = (GfxRenderer::getScreenWidth() - titleWidth) / 2;
|
||||||
|
const int titleY = y + 10;
|
||||||
|
renderer.drawText(UI_FONT_ID, titleX, titleY, title, true, REGULAR);
|
||||||
|
renderer.drawLine(xMargin, y + windowHeaderHeight, windowWidth + xMargin, y + windowHeaderHeight, windowBorderWidth, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title) {
|
||||||
|
drawStatusBar(renderer);
|
||||||
|
drawWindowFrame(renderer, fullscreenWindowMargin, statusBarHeight, GfxRenderer::getScreenHeight() - fullscreenWindowMargin - statusBarHeight, true, title);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawStatusBar(GfxRenderer& renderer) {
|
||||||
|
constexpr auto textY = 18;
|
||||||
|
|
||||||
|
// Left aligned battery icon and percentage
|
||||||
|
const uint16_t percentage = battery.readPercentage();
|
||||||
|
const auto percentageText = std::to_string(percentage) + "%";
|
||||||
|
const auto percentageTextWidth = renderer.getTextWidth(SMALL_FONT_ID, percentageText.c_str());
|
||||||
|
renderer.drawText(SMALL_FONT_ID, fullscreenWindowMargin + batteryWidth + 5, textY, percentageText.c_str());
|
||||||
|
|
||||||
|
// 1 column on left, 2 columns on right, 5 columns of battery body
|
||||||
|
constexpr int x = fullscreenWindowMargin;
|
||||||
|
constexpr int y = textY + 5;
|
||||||
|
|
||||||
|
// Top line
|
||||||
|
renderer.drawLine(x, y, x + batteryWidth - 4, y);
|
||||||
|
// Bottom line
|
||||||
|
renderer.drawLine(x, y + batteryHeight - 1, x + batteryWidth - 4, y + batteryHeight - 1);
|
||||||
|
// Left line
|
||||||
|
renderer.drawLine(x, y, x, y + batteryHeight - 1);
|
||||||
|
// Battery end
|
||||||
|
renderer.drawLine(x + batteryWidth - 4, y, x + batteryWidth - 4, y + batteryHeight - 1);
|
||||||
|
renderer.drawLine(x + batteryWidth - 3, y + 2, x + batteryWidth - 1, y + 2);
|
||||||
|
renderer.drawLine(x + batteryWidth - 3, y + batteryHeight - 3, x + batteryWidth - 1, y + batteryHeight - 3);
|
||||||
|
renderer.drawLine(x + batteryWidth - 1, y + 2, x + batteryWidth - 1, y + batteryHeight - 3);
|
||||||
|
|
||||||
|
// 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 + 1, y + 1, filledWidth, batteryHeight - 2);
|
||||||
|
}
|
||||||
6
src/activities/util/Window.h
Normal file
6
src/activities/util/Window.h
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <GfxRenderer.h>
|
||||||
|
|
||||||
|
void drawWindowFrame(GfxRenderer& renderer, int xMargin, int y, int height, bool hasShadow, const char* title);
|
||||||
|
void drawFullscreenWindowFrame(GfxRenderer& renderer, const char* title);
|
||||||
|
void drawStatusBar(GfxRenderer& renderer);
|
||||||
Loading…
x
Reference in New Issue
Block a user