diff --git a/lib/GfxRenderer/GfxRenderer.cpp b/lib/GfxRenderer/GfxRenderer.cpp index ff23793..725c435 100644 --- a/lib/GfxRenderer/GfxRenderer.cpp +++ b/lib/GfxRenderer/GfxRenderer.cpp @@ -4,6 +4,37 @@ void GfxRenderer::insertFont(const int fontId, EpdFontFamily font) { fontMap.insert({fontId, font}); } +void GfxRenderer::rotateCoordinates(const int x, const int y, int* rotatedX, int* rotatedY) const { + switch (orientation) { + case Portrait: { + // Logical portrait (480x800) → panel (800x480) + // Rotation: 90 degrees clockwise + *rotatedX = y; + *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; + break; + } + case LandscapeClockwise: { + // Logical landscape (800x480) rotated 180 degrees (swap top/bottom and left/right) + *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - x; + *rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - y; + break; + } + case PortraitInverted: { + // Logical portrait (480x800) → panel (800x480) + // Rotation: 90 degrees counter-clockwise + *rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - y; + *rotatedY = x; + break; + } + case LandscapeCounterClockwise: { + // Logical landscape (800x480) aligned with panel orientation + *rotatedX = x; + *rotatedY = y; + break; + } + } +} + void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { uint8_t* frameBuffer = einkDisplay.getFrameBuffer(); @@ -15,28 +46,7 @@ void GfxRenderer::drawPixel(const int x, const int y, const bool state) const { int rotatedX = 0; int rotatedY = 0; - - switch (orientation) { - case Orientation::Portrait: { - // Logical portrait (480x800) → panel (800x480) - // Rotation: 90 degrees clockwise - rotatedX = y; - rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x; - break; - } - case Orientation::LandscapeNormal: { - // Logical landscape (800x480) aligned with panel orientation - rotatedX = x; - rotatedY = y; - break; - } - case Orientation::LandscapeFlipped: { - // Logical landscape (800x480) rotated 180° (swap top/bottom and left/right) - rotatedX = EInkDisplay::DISPLAY_WIDTH - 1 - x; - rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - y; - break; - } - } + rotateCoordinates(x, y, &rotatedX, &rotatedY); // Bounds checking against physical panel dimensions if (rotatedX < 0 || rotatedX >= EInkDisplay::DISPLAY_WIDTH || rotatedY < 0 || @@ -135,17 +145,11 @@ void GfxRenderer::fillRect(const int x, const int y, const int width, const int } void GfxRenderer::drawImage(const uint8_t bitmap[], const int x, const int y, const int width, const int height) const { - switch (orientation) { - case Orientation::Portrait: - // Flip X and Y for portrait mode - einkDisplay.drawImage(bitmap, y, x, height, width); - break; - case Orientation::LandscapeNormal: - case Orientation::LandscapeFlipped: - // Native landscape coordinates - einkDisplay.drawImage(bitmap, x, y, width, height); - break; - } + // TODO: Rotate bits + int rotatedX = 0; + int rotatedY = 0; + rotateCoordinates(x, y, &rotatedX, &rotatedY); + einkDisplay.drawImage(bitmap, rotatedX, rotatedY, width, height); } void GfxRenderer::drawBitmap(const Bitmap& bitmap, const int x, const int y, const int maxWidth, @@ -234,38 +238,15 @@ void GfxRenderer::displayBuffer(const EInkDisplay::RefreshMode refreshMode) cons einkDisplay.displayBuffer(refreshMode); } -void GfxRenderer::displayWindow(const int x, const int y, const int width, const int height) const { - switch (orientation) { - case Orientation::Portrait: { - // Rotate coordinates from portrait (480x800) to landscape (800x480) - // Rotation: 90 degrees clockwise - // Portrait coordinates: (x, y) with dimensions (width, height) - // Landscape coordinates: (rotatedX, rotatedY) with dimensions (rotatedWidth, rotatedHeight) - - const int rotatedX = y; - const int rotatedY = EInkDisplay::DISPLAY_HEIGHT - 1 - x - width + 1; - const int rotatedWidth = height; - const int rotatedHeight = width; - - einkDisplay.displayWindow(rotatedX, rotatedY, rotatedWidth, rotatedHeight); - break; - } - case Orientation::LandscapeNormal: - case Orientation::LandscapeFlipped: - // Native landscape coordinates - einkDisplay.displayWindow(x, y, width, height); - break; - } -} - // Note: Internal driver treats screen in command orientation; this library exposes a logical orientation int GfxRenderer::getScreenWidth() const { switch (orientation) { - case Orientation::Portrait: + case Portrait: + case PortraitInverted: // 480px wide in portrait logical coordinates return EInkDisplay::DISPLAY_HEIGHT; - case Orientation::LandscapeNormal: - case Orientation::LandscapeFlipped: + case LandscapeClockwise: + case LandscapeCounterClockwise: // 800px wide in landscape logical coordinates return EInkDisplay::DISPLAY_WIDTH; } @@ -274,11 +255,12 @@ int GfxRenderer::getScreenWidth() const { int GfxRenderer::getScreenHeight() const { switch (orientation) { - case Orientation::Portrait: + case Portrait: + case PortraitInverted: // 800px tall in portrait logical coordinates return EInkDisplay::DISPLAY_WIDTH; - case Orientation::LandscapeNormal: - case Orientation::LandscapeFlipped: + case LandscapeClockwise: + case LandscapeCounterClockwise: // 480px tall in landscape logical coordinates return EInkDisplay::DISPLAY_HEIGHT; } diff --git a/lib/GfxRenderer/GfxRenderer.h b/lib/GfxRenderer/GfxRenderer.h index dec65b7..a724e78 100644 --- a/lib/GfxRenderer/GfxRenderer.h +++ b/lib/GfxRenderer/GfxRenderer.h @@ -13,10 +13,11 @@ class GfxRenderer { enum RenderMode { BW, GRAYSCALE_LSB, GRAYSCALE_MSB }; // Logical screen orientation from the perspective of callers - enum class Orientation { - Portrait, // 480x800 logical coordinates (current default) - LandscapeNormal, // 800x480 logical coordinates, native panel orientation - LandscapeFlipped // 800x480 logical coordinates, rotated 180° (swap top/bottom) + enum Orientation { + Portrait, // 480x800 logical coordinates (current default) + LandscapeClockwise, // 800x480 logical coordinates, rotated 180° (swap top/bottom) + PortraitInverted, // 480x800 logical coordinates, inverted + LandscapeCounterClockwise // 800x480 logical coordinates, native panel orientation }; private: @@ -35,6 +36,7 @@ class GfxRenderer { void renderChar(const EpdFontFamily& fontFamily, uint32_t cp, int* x, const int* y, bool pixelState, EpdFontStyle style) const; void freeBwBufferChunks(); + void rotateCoordinates(int x, int y, int *rotatedX, int *rotatedY) const; public: explicit GfxRenderer(EInkDisplay& einkDisplay) : einkDisplay(einkDisplay), renderMode(BW) {} diff --git a/src/CrossPointSettings.cpp b/src/CrossPointSettings.cpp index 8cc030c..9328422 100644 --- a/src/CrossPointSettings.cpp +++ b/src/CrossPointSettings.cpp @@ -11,7 +11,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 = 6; +constexpr uint8_t SETTINGS_COUNT = 5; constexpr char SETTINGS_FILE[] = "/.crosspoint/settings.bin"; } // namespace @@ -30,8 +30,7 @@ bool CrossPointSettings::saveToFile() const { serialization::writePod(outputFile, extraParagraphSpacing); serialization::writePod(outputFile, shortPwrBtn); serialization::writePod(outputFile, statusBar); - serialization::writePod(outputFile, landscapeReading); - serialization::writePod(outputFile, landscapeFlipped); + serialization::writePod(outputFile, orientation); outputFile.close(); Serial.printf("[%lu] [CPS] Settings saved to file\n", millis()); @@ -66,9 +65,7 @@ bool CrossPointSettings::loadFromFile() { if (++settingsRead >= fileSettingsCount) break; serialization::readPod(inputFile, statusBar); if (++settingsRead >= fileSettingsCount) break; - serialization::readPod(inputFile, landscapeReading); - if (++settingsRead >= fileSettingsCount) break; - serialization::readPod(inputFile, landscapeFlipped); + serialization::readPod(inputFile, orientation); if (++settingsRead >= fileSettingsCount) break; } while (false); diff --git a/src/CrossPointSettings.h b/src/CrossPointSettings.h index 3279027..2b99664 100644 --- a/src/CrossPointSettings.h +++ b/src/CrossPointSettings.h @@ -21,6 +21,13 @@ class CrossPointSettings { // Status bar display type enum enum STATUS_BAR_MODE { NONE = 0, NO_PROGRESS = 1, FULL = 2 }; + enum ORIENTATION { + PORTRAIT = 0, // 480x800 logical coordinates (current default) + LANDSCAPE_CW = 1, // 800x480 logical coordinates, rotated 180° (swap top/bottom) + INVERTED = 2, // 480x800 logical coordinates, inverted + LANDSCAPE_CCW = 3 // 800x480 logical coordinates, native panel orientation + }; + // Sleep screen settings uint8_t sleepScreen = DARK; // Status bar settings @@ -30,10 +37,8 @@ class CrossPointSettings { // Duration of the power button press uint8_t shortPwrBtn = 0; // EPUB reading orientation settings - // 0 = portrait (default), 1 = landscape - uint8_t landscapeReading = 0; - // When in landscape mode: 0 = normal, 1 = flipped (swap top/bottom) - uint8_t landscapeFlipped = 0; + // 0 = portrait (default), 1 = landscape clockwise, 2 = inverted, 3 = landscape counter-clockwise + uint8_t orientation = PORTRAIT; ~CrossPointSettings() = default; diff --git a/src/activities/boot_sleep/BootActivity.cpp b/src/activities/boot_sleep/BootActivity.cpp index 616fbf8..a153088 100644 --- a/src/activities/boot_sleep/BootActivity.cpp +++ b/src/activities/boot_sleep/BootActivity.cpp @@ -12,7 +12,7 @@ void BootActivity::onEnter() { const auto pageHeight = renderer.getScreenHeight(); renderer.clearScreen(); - renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128); + renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128); renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "BOOTING"); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight - 30, CROSSPOINT_VERSION); diff --git a/src/activities/boot_sleep/SleepActivity.cpp b/src/activities/boot_sleep/SleepActivity.cpp index 4bc70f5..6ff348e 100644 --- a/src/activities/boot_sleep/SleepActivity.cpp +++ b/src/activities/boot_sleep/SleepActivity.cpp @@ -112,7 +112,7 @@ void SleepActivity::renderDefaultSleepScreen() const { const auto pageHeight = renderer.getScreenHeight(); renderer.clearScreen(); - renderer.drawImage(CrossLarge, (pageWidth - 128) / 2, (pageHeight - 128) / 2, 128, 128); + renderer.drawImage(CrossLarge, (pageWidth + 128) / 2, (pageHeight - 128) / 2, 128, 128); renderer.drawCenteredText(UI_FONT_ID, pageHeight / 2 + 70, "CrossPoint", true, BOLD); renderer.drawCenteredText(SMALL_FONT_ID, pageHeight / 2 + 95, "SLEEPING"); diff --git a/src/activities/reader/EpubReaderActivity.cpp b/src/activities/reader/EpubReaderActivity.cpp index 2e7cbbc..87dfe79 100644 --- a/src/activities/reader/EpubReaderActivity.cpp +++ b/src/activities/reader/EpubReaderActivity.cpp @@ -35,14 +35,21 @@ void EpubReaderActivity::onEnter() { } // Configure screen orientation based on settings - if (SETTINGS.landscapeReading) { - if (SETTINGS.landscapeFlipped) { - GfxRenderer::setOrientation(GfxRenderer::Orientation::LandscapeFlipped); - } else { - GfxRenderer::setOrientation(GfxRenderer::Orientation::LandscapeNormal); - } - } else { - GfxRenderer::setOrientation(GfxRenderer::Orientation::Portrait); + switch (SETTINGS.orientation) { + case CrossPointSettings::ORIENTATION::PORTRAIT: + renderer.setOrientation(GfxRenderer::Orientation::Portrait); + break; + case CrossPointSettings::ORIENTATION::LANDSCAPE_CW: + renderer.setOrientation(GfxRenderer::Orientation::LandscapeClockwise); + break; + case CrossPointSettings::ORIENTATION::INVERTED: + renderer.setOrientation(GfxRenderer::Orientation::PortraitInverted); + break; + case CrossPointSettings::ORIENTATION::LANDSCAPE_CCW: + renderer.setOrientation(GfxRenderer::Orientation::LandscapeCounterClockwise); + break; + default: + break; } renderingMutex = xSemaphoreCreateMutex(); diff --git a/src/activities/settings/SettingsActivity.cpp b/src/activities/settings/SettingsActivity.cpp index 5ba9946..aad20f3 100644 --- a/src/activities/settings/SettingsActivity.cpp +++ b/src/activities/settings/SettingsActivity.cpp @@ -16,8 +16,10 @@ const SettingInfo settingsList[settingsCount] = { {"Status Bar", SettingType::ENUM, &CrossPointSettings::statusBar, {"None", "No Progress", "Full"}}, {"Extra Paragraph Spacing", SettingType::TOGGLE, &CrossPointSettings::extraParagraphSpacing, {}}, {"Short Power Button Click", SettingType::TOGGLE, &CrossPointSettings::shortPwrBtn, {}}, - {"Landscape Reading", SettingType::TOGGLE, &CrossPointSettings::landscapeReading}, - {"Flip Landscape (swap top/bottom)", SettingType::TOGGLE, &CrossPointSettings::landscapeFlipped}, + {"Reading Orientation", + SettingType::ENUM, + &CrossPointSettings::orientation, + {"Portrait", "Landscape CW", "Inverted", "Landscape CCW"}}, {"Check for updates", SettingType::ACTION, nullptr, {}}, }; } // namespace