diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 00e25cb2a..012034daa 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -222,6 +222,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.read((uint8_t *)&_prefs.invert_display, sizeof(_prefs.invert_display)); // 85 file.close(); } @@ -254,6 +255,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.write((uint8_t *)&_prefs.invert_display, sizeof(_prefs.invert_display)); // 85 file.close(); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 13c9f8842..b50e3fb69 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -25,4 +25,5 @@ struct NodePrefs { // persisted to file uint32_t ble_pin; uint8_t advert_loc_policy; uint8_t buzzer_quiet; + uint8_t invert_display; // 0 = normal (black on white), 1 = inverted (white on black) }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 59a1b2dee..9c84c0203 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -79,6 +79,7 @@ class HomeScreen : public UIScreen { RADIO, BLUETOOTH, ADVERT, + INVERT, #if ENV_INCLUDE_GPS == 1 GPS, #endif @@ -257,6 +258,12 @@ class HomeScreen : public UIScreen { display.setColor(DisplayDriver::GREEN); display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL); + } else if (_page == HomePage::INVERT) { + display.setColor(DisplayDriver::GREEN); + char buf[24]; + sprintf(buf, "Invert: %s", _node_prefs->invert_display ? "ON" : "OFF"); + display.drawTextCentered(display.width() / 2, 32, buf); + display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL); #if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); @@ -423,6 +430,10 @@ class HomeScreen : public UIScreen { return true; } #endif + if (c == KEY_ENTER && _page == HomePage::INVERT) { + _task->toggleInvertDisplay(); + return true; + } if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { _shutdown_init = true; // need to wait for button to be released return true; @@ -538,6 +549,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no _node_prefs = node_prefs; if (_display != NULL) { + _display->setInverted(_node_prefs->invert_display); // apply saved inversion state _display->turnOn(); } @@ -893,3 +905,15 @@ void UITask::toggleBuzzer() { _next_refresh = 0; // trigger refresh #endif } + +void UITask::toggleInvertDisplay() { + // Toggle display inversion + _node_prefs->invert_display = !_node_prefs->invert_display; + if (_display != NULL) { + _display->setInverted(_node_prefs->invert_display); + } + notify(UIEventType::ack); + showAlert(_node_prefs->invert_display ? "Invert: ON" : "Invert: OFF", 800); + the_mesh.savePrefs(); + _next_refresh = 0; // trigger immediate refresh +} diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 32d5f3a0d..a15ade3f4 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -77,6 +77,7 @@ class UITask : public AbstractUITask { void toggleBuzzer(); bool getGPSState(); void toggleGPS(); + void toggleInvertDisplay(); // from AbstractUITask diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index ec63c1912..47e9073b0 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -6,12 +6,15 @@ class DisplayDriver { int _w, _h; protected: + bool _inverted = false; // display inversion state DisplayDriver(int w, int h) { _w = w; _h = h; } public: enum Color { DARK=0, LIGHT, RED, GREEN, BLUE, YELLOW, ORANGE }; // on b/w screen, colors will be !=0 synonym of light int width() const { return _w; } int height() const { return _h; } + bool isInverted() const { return _inverted; } + virtual void setInverted(bool inv) { _inverted = inv; } virtual bool isOn() = 0; virtual void turnOn() = 0; diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index ad47754bf..992a77efe 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -56,15 +56,17 @@ void GxEPDDisplay::turnOff() { } void GxEPDDisplay::clear() { - display.fillScreen(GxEPD_WHITE); - display.setTextColor(GxEPD_BLACK); + display.fillScreen(_inverted ? GxEPD_BLACK : GxEPD_WHITE); + display.setTextColor(_inverted ? GxEPD_WHITE : GxEPD_BLACK); display_crc.reset(); } void GxEPDDisplay::startFrame(Color bkg) { - display.fillScreen(GxEPD_WHITE); - display.setTextColor(_curr_color = GxEPD_BLACK); + // use inverted background if inversion is enabled + display.fillScreen(_inverted ? GxEPD_BLACK : GxEPD_WHITE); + display.setTextColor(_curr_color = (_inverted ? GxEPD_WHITE : GxEPD_BLACK)); display_crc.reset(); + display_crc.update(_inverted); // include inversion state in CRC } void GxEPDDisplay::setTextSize(int sz) { @@ -87,11 +89,13 @@ void GxEPDDisplay::setTextSize(int sz) { void GxEPDDisplay::setColor(Color c) { display_crc.update (c); - // colours need to be inverted for epaper displays + // On e-paper: GxEPD_WHITE = white pixels, GxEPD_BLACK = black pixels + // Normal mode (white bg): DARK->white (bg), others->black (visible) + // Inverted mode (black bg): DARK->black (bg), others->white (visible) if (c == DARK) { - display.setTextColor(_curr_color = GxEPD_WHITE); + display.setTextColor(_curr_color = _inverted ? GxEPD_BLACK : GxEPD_WHITE); } else { - display.setTextColor(_curr_color = GxEPD_BLACK); + display.setTextColor(_curr_color = _inverted ? GxEPD_WHITE : GxEPD_BLACK); } } @@ -177,3 +181,9 @@ void GxEPDDisplay::endFrame() { last_display_crc_value = crc; } } + +void GxEPDDisplay::setInverted(bool inv) { + _inverted = inv; + // Invalidate CRC to force redraw on next frame + last_display_crc_value = 0; +} diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index 1a04cc246..bb0dcde1c 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -60,4 +60,5 @@ class GxEPDDisplay : public DisplayDriver { void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; uint16_t getTextWidth(const char* str) override; void endFrame() override; + void setInverted(bool inv) override; };