diff --git a/docs/source/Plugin/P036.rst b/docs/source/Plugin/P036.rst index 6dd1183fc1..2be3e553ca 100644 --- a/docs/source/Plugin/P036.rst +++ b/docs/source/Plugin/P036.rst @@ -65,7 +65,17 @@ Device Settings .. image:: P036_ScrollOptions.png -* **Scroll**: Switching between pages can be "instant" or "scrolling". Please note that scrolling will need more resources of the ESP, which can have an effect on other active tasks of the node. +* **Scroll**: Switching between pages can be "instant", "scrolling" or a "ticker" band. Please note that scrolling will need more resources of the ESP, which can have an effect on other active tasks of the node. +For the ``Ticker`` there are some restrictions: +* Depending on the build used (NORMAL and CUSTOM) this option is available +* only one line is displaying the ticker string +* all line contents are parsed and combined to this ticker string, the parsing happens only at each ticker start (using the setting ``Interval``) +* the optional split token ``<|>`` is replaced by three spaces +* the gaps between the ticker items must be set by the ``line content`` (tailing spaces) +* the starting alignment (left, center, right) depends on the setting ``Align content (global)`` +* the font is taken from the setting ``Modify font`` of the first line, the ``Alignment`` settings of the lines are ignored (always right aligned) +* the ticker speed depends on the length of the ticker string and the setting ``Interval`` setting +* the footer is automatically hidden * **GPIO <- Display button**: Setting up a ``Display Button``, allows to configure a Display Timeout and wake the display on demand, either by a button, or by using some presence detection. @@ -125,7 +135,7 @@ The user defined texts may also contain a split token ``<|>`` to display the lin * **Alignment**: For each line, the alignment to be used can be selected, or the global setting can be used. -* **Interval** By default, Interval will be set to 0. If set to a non-zero value, the pre-configured content will be updated automatically using that interval (seconds). +* **Interval** By default, Interval will be set to 0. It needs to be set to a non-zero value, to switch between the frames using that interval (seconds). General diff --git a/docs/source/Plugin/P036_ScrollOptions.png b/docs/source/Plugin/P036_ScrollOptions.png index cfed679c78..c549d93db4 100644 Binary files a/docs/source/Plugin/P036_ScrollOptions.png and b/docs/source/Plugin/P036_ScrollOptions.png differ diff --git a/docs/source/Plugin/P036_commands.repl b/docs/source/Plugin/P036_commands.repl index ea218075da..b6a3496cd8 100644 --- a/docs/source/Plugin/P036_commands.repl +++ b/docs/source/Plugin/P036_commands.repl @@ -39,6 +39,7 @@ The updated line text is not stored in the settings itself, but kept in memory. After a reboot the stored plugin settings will be used. + The line text can also be restored from the settings by the command ``restore``. All template notations can be used, like system variables, or reference to a task value. @@ -50,6 +51,7 @@ This command is to display a specific frame (aka page), or the next frame. When reaching the last frame, a 'next' (0) will display the first frame. The parameter corresponds to the desired frame (1..) to display. The number of frames is determined by dividing the lines in use (at least one line in that frame with some data), by the number of Lines per Frame. So practically, the range is 1..3 when all lines are used and 4 Lines per Frame is set, or 1..12 if Line per frames is set to 1. The number of frames is updated if a frame would initially be empty, and an external source places text on a line of that frame (see above). + If scroll is set to ``ticker`` only = 1 is supported, it starts the ticker from the beginning. When omitting , or providing 0, the next frame is displayed. @@ -59,6 +61,7 @@ ``oledframedcmd,linecount,<1..4>`` "," This command changes the number of lines in each frame. When the next frame is to be displayed, the frames are recalculated and the sequence is restarted at the first frame. + If scroll is set to ``ticker`` this command is not supported. If Generate events for 'Linecount' is selected, a ```` event is generated on initialization of the plugin and when changing the setting. " @@ -85,4 +88,18 @@ Set the user defined header nr. 2 with any desired text value. Use ``$$`` instead of ``%%`` to use system variables. - " \ No newline at end of file + " + " + ``oledframedcmd,restore,`` + "," + If the parameter is set to 0 all line contents will be restored from the settings. + Otherwise the parameter corresponds with the same lines as the plugin configuration has, + and only the content of this line will be restored from the settings. + " + " + ``oledframedcmd,scroll,`` + "," + The parameter corresponds with the line number of the scroll parameter of the settings (1=Very slow ... 6=Ticker). + After applying the new scroll speed the display restarts with the first page. + " + diff --git a/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp b/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp index 40986a3d19..8978c48fe7 100644 --- a/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp +++ b/lib/esp8266-oled-ssd1306/OLEDDisplay.cpp @@ -532,6 +532,15 @@ uint16_t OLEDDisplay::getStringWidth(const String& strUser) { return width; } +uint8_t OLEDDisplay::getCharWidth(const char c) { + uint8_t firstChar = pgm_read_byte(fontData + FIRST_CHAR_POS); + if (utf8ascii(c) == 0) + return 0; + if (c < firstChar) + return 0; + return pgm_read_byte(fontData + JUMPTABLE_START + (c- firstChar) * JUMPTABLE_BYTES + JUMPTABLE_WIDTH); +} + void OLEDDisplay::setTextAlignment(OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { this->textAlignment = textAlignment; } diff --git a/lib/esp8266-oled-ssd1306/OLEDDisplay.h b/lib/esp8266-oled-ssd1306/OLEDDisplay.h index 978fc9cc10..16433c0a64 100644 --- a/lib/esp8266-oled-ssd1306/OLEDDisplay.h +++ b/lib/esp8266-oled-ssd1306/OLEDDisplay.h @@ -190,6 +190,11 @@ class OLEDDisplay : public Print { // Convencience method for the const char version uint16_t getStringWidth(const String& text); + // Returns the width of c with the already set fontData + // returns a 0 if c is non-ascii + // in this case the next char must be converted + uint8_t getCharWidth(const char c); + // Specifies relative to which anchor point // the text is rendered. Available constants: // TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, TEXT_ALIGN_RIGHT, TEXT_ALIGN_CENTER_BOTH diff --git a/src/Custom-sample.h b/src/Custom-sample.h index aeb550cc57..00d228573b 100644 --- a/src/Custom-sample.h +++ b/src/Custom-sample.h @@ -378,6 +378,7 @@ static const char DATA_ESPEASY_DEFAULT_MIN_CSS[] PROGMEM = { // #define USES_P036 // FrameOLED // #define P036_FEATURE_DISPLAY_PREVIEW 1 // Enable Preview feature, shows on-display content on Devices overview page // #define P036_FEATURE_ALIGN_PREVIEW 1 // Enable center/right-align feature when preview is enabled (auto-disabled for 1M builds) +// #define P036_ENABLE_TICKER 1 // Enable ticker function // #define USES_P037 // MQTTImport // #define P037_MAPPING_SUPPORT 1 // Enable Value mapping support // #define P037_FILTER_SUPPORT 1 // Enable filtering support diff --git a/src/_P036_FrameOLED.ino b/src/_P036_FrameOLED.ino index 65636101b3..9d9efdc5b7 100644 --- a/src/_P036_FrameOLED.ino +++ b/src/_P036_FrameOLED.ino @@ -14,8 +14,20 @@ // Added to the main repository with some optimizations and some limitations. // As long as the device is not enabled, no RAM is wasted. // +// @uwekaditz: 2023-08-10 +// BUG: Individual font setting can only enlarge or maximize the font, if more than 1 line should be displayed (it was buggy not only for ticker!) +// BUG: CalculateIndividualFontSettings() must be called until the font fits (it was buggy not only for ticker!) +// BUG: Compiler error for '#ifdef P036_FONT_CALC_LOG' // @tonhuisman: 2023-08-08 // CHG: Enable Userdefined headers feature, even on LIMIT_BUILD_SIZE builds +// @uwekaditz: 2023-07-25 +// BUG: Calculation for ticker IdxStart and IdxEnd was wrong for 64x48 display +// CHG: Start page updates after network has connected in PLUGIN_ONCE_A_SECOND, faster than waiting for the next PLUGIN_READ +// @uwekaditz: 2023-07-23 +// NEW: Add ticker for scrolling speed, solves issue #4188 +// ADD: Setting and support for oledframedcmd,restore,<0|> subcommand par2: (0=all|Line Content) +// ADD: Setting and support for oledframedcmd,scroll,<1..6> subcommand, par2: (casted to ePageScrollSpeeds) +// CHG: Minor change in debug messages (addLogMove() for dynamic messages) // @tonhuisman: 2023-07-01 // CHG: Make compile-time defines for P036_SEND_EVENTS boolean // CHG: Make compile-time defines for P036_ENABLE_LINECOUNT boolean @@ -207,12 +219,14 @@ # define PLUGIN_NAME_036 "Display - OLED SSD1306/SH1106 Framed" # define PLUGIN_VALUENAME1_036 "OLED" -# if P036_SEND_EVENTS # define P036_EVENT_DISPLAY 0 // event: #display=0/1 # define P036_EVENT_CONTRAST 1 // event: #contrast=0/1/2 # define P036_EVENT_FRAME 2 // event: #frame=1..n # define P036_EVENT_LINE 3 // event: #line=1..n # define P036_EVENT_LINECNT 4 // event: #linecount=1..4 +# define P036_EVENT_RESTORE 5 // event: #restore=1..n +# define P036_EVENT_SCROLL 6 // event: #scroll=ePSS_VerySlow..ePSS_Ticker +# if P036_SEND_EVENTS void P036_SendEvent(struct EventStruct *event, uint8_t eventId, int16_t eventValue); @@ -321,20 +335,32 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) } # endif // if P036_ENABLE_LEFT_ALIGN { - const __FlashStringHelper *options[5] = { +# if P036_ENABLE_TICKER + const int optionCnt = 6; +# else // if P036_ENABLE_TICKER + const int optionCnt = 5; +# endif // if P036_ENABLE_TICKER + const __FlashStringHelper *options[optionCnt] = { F("Very Slow"), F("Slow"), F("Fast"), F("Very Fast"), - F("Instant") + F("Instant"), +# if P036_ENABLE_TICKER + F("Ticker"), +# endif // if P036_ENABLE_TICKER }; - const int optionValues[5] = + const int optionValues[optionCnt] = { static_cast(ePageScrollSpeed::ePSS_VerySlow), static_cast(ePageScrollSpeed::ePSS_Slow), static_cast(ePageScrollSpeed::ePSS_Fast), static_cast(ePageScrollSpeed::ePSS_VeryFast), - static_cast(ePageScrollSpeed::ePSS_Instant) }; - addFormSelector(F("Scroll"), F("scroll"), 5, options, optionValues, P036_SCROLL); + static_cast(ePageScrollSpeed::ePSS_Instant), +# if P036_ENABLE_TICKER + static_cast(ePageScrollSpeed::ePSS_Ticker), +# endif // if P036_ENABLE_TICKER + }; + addFormSelector(F("Scroll"), F("scroll"), optionCnt, options, optionValues, P036_SCROLL); } // FIXME TD-er: Why is this using pin3 and not pin1? And why isn't this using the normal pin selection functions? @@ -698,6 +724,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) (P036_ROTATE == 2), // 1 = Normal, 2 = Rotated P036_CONTRAST, P036_TIMER, + static_cast(P036_SCROLL), // Scroll speed P036_NLINES ))) { clearPluginTaskData(event->TaskIndex); @@ -896,6 +923,12 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) if (P036_DisplayIsOn) { // Display is on. + if (!P036_data->bRunning && NetworkConnected() && (P036_data->ScrollingPages.Scrolling == 0)) { + // start page updates after network has connected + P036_data->P036_DisplayPage(event); + } + else { + P036_data->HeaderContent = static_cast(get8BitFromUL(P036_FLAGS_0, P036_FLAG_HEADER)); // HeaderContent P036_data->HeaderContentAlternative = static_cast(get8BitFromUL(P036_FLAGS_0, P036_FLAG_HEADER_ALTERNATIVE)); @@ -907,6 +940,7 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) P036_data->update_display(); } } + } success = true; break; @@ -1023,15 +1057,17 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) addLog(LOG_LEVEL_INFO, F("P036_PLUGIN_WRITE ...")); # endif // PLUGIN_036_DEBUG + bool bUpdateDisplay = false; + bool bDisplayON = false; + uint8_t eventId = 0; const String command = parseString(string, 1); - const int LineNo = event->Par1; + const String subcommand = parseString(string, 2); + int LineNo = event->Par1; # if P036_SEND_EVENTS const bool sendEvents = bitRead(P036_FLAGS_0, P036_FLAG_SEND_EVENTS); // Bit 28 Send Events # endif // if P036_SEND_EVENTS if ((equals(command, F("oledframedcmd"))) && P036_data->isInitialized()) { - const String subcommand = parseString(string, 2); - if (equals(subcommand, F("display"))) { // display functions const String para1 = parseString(string, 3); @@ -1067,49 +1103,25 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) else if (equals(para1, F("low"))) { success = true; P036_data->setContrast(OLED_CONTRAST_LOW); - # if P036_SEND_EVENTS - - if (sendEvents) { - P036_SendEvent(event, P036_EVENT_CONTRAST, 0); - - if (!P036_DisplayIsOn) { - P036_SendEvent(event, P036_EVENT_DISPLAY, 1); - } - } - # endif // if P036_SEND_EVENTS - P036_SetDisplayOn(1); // Save the fact that the display is now ON + LineNo = 0; // is event parameter + eventId = P036_EVENT_CONTRAST; + bDisplayON = true; } else if (equals(para1, F("med"))) { success = true; P036_data->setContrast(OLED_CONTRAST_MED); - # if P036_SEND_EVENTS - - if (sendEvents) { - P036_SendEvent(event, P036_EVENT_CONTRAST, 1); - - if (!P036_DisplayIsOn) { - P036_SendEvent(event, P036_EVENT_DISPLAY, 1); - } - } - # endif // if P036_SEND_EVENTS - P036_SetDisplayOn(1); // Save the fact that the display is now ON + LineNo = 1; // is event parameter + eventId = P036_EVENT_CONTRAST; + bDisplayON = true; } else if (equals(para1, F("high"))) { success = true; P036_data->setContrast(OLED_CONTRAST_HIGH); - # if P036_SEND_EVENTS - - if (sendEvents) { - P036_SendEvent(event, P036_EVENT_CONTRAST, 2); - - if (!P036_DisplayIsOn) { - P036_SendEvent(event, P036_EVENT_DISPLAY, 1); - } - } - # endif // if P036_SEND_EVENTS - P036_SetDisplayOn(1); // Save the fact that the display is now ON + LineNo = 2; // is event parameter + eventId = P036_EVENT_CONTRAST; + bDisplayON = true; } else if (equals(para1, F("user")) && @@ -1120,17 +1132,9 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) success = true; P036_data->display->setContrast(static_cast(event->Par3), static_cast(event->Par4), static_cast(event->Par5)); - # if P036_SEND_EVENTS - - if (sendEvents) { - P036_SendEvent(event, P036_EVENT_CONTRAST, 3); - - if (!P036_DisplayIsOn) { - P036_SendEvent(event, P036_EVENT_DISPLAY, 1); - } - } - # endif // if P036_SEND_EVENTS - P036_SetDisplayOn(1); // Save the fact that the display is now ON + LineNo = 3; // is event parameter + eventId = P036_EVENT_CONTRAST; + bDisplayON = true; } } else if ((equals(subcommand, F("frame"))) && (event->Par2 >= 0) && @@ -1162,11 +1166,19 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) else if ((equals(subcommand, F("linecount"))) && (event->Par2 >= 1) && (event->Par2 <= 4)) { + # if P036_ENABLE_TICKER + + if (static_cast(P036_SCROLL) == ePageScrollSpeed::ePSS_Ticker) { + // Ticker supports only 1 line, can not be changed + success = (event->Par2 == 1); + return success; + } + # endif // if P036_ENABLE_TICKER success = true; if (P036_NLINES != event->Par2) { P036_NLINES = event->Par2; - P036_data->setNrLines(P036_NLINES); + P036_data->setNrLines(event, P036_NLINES); # if P036_SEND_EVENTS if (sendEvents && bitRead(P036_FLAGS_0, P036_FLAG_EVENTS_FRAME_LINE)) { // Bit 29 Send Events Frame & Line @@ -1176,6 +1188,47 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) } } # endif // if P036_ENABLE_LINECOUNT + else if ((equals(subcommand, F("restore"))) && + (event->Par2 >= 0) && // 0: restore all line contents + (event->Par2 <= P36_Nlines)) { + // restore content functions + success = true; + LineNo = event->Par2; + P036_data->RestoreLineContent(event->TaskIndex, + get4BitFromUL(P036_FLAGS_0, P036_FLAG_SETTINGS_VERSION), // Bit23-20 Version CustomTaskSettings + LineNo); + + if (LineNo == 0) + LineNo = 1; // after restoring all contents start with first Line + eventId = P036_EVENT_RESTORE; + bUpdateDisplay = true; + } + else if ((equals(subcommand, F("scroll"))) && + (event->Par2 >= 1)) { + // set scroll + success = true; + + switch (event->Par2) { + case 1: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_VerySlow); break; + case 2: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_Slow); break; + case 3: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_Fast); break; + case 4: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_VeryFast); break; + case 5: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_Instant); break; +# if P036_ENABLE_TICKER + case 6: P036_SCROLL = static_cast(ePageScrollSpeed::ePSS_Ticker); break; +# endif // if P036_ENABLE_TICKER + default: + success = false; + break; + } + + if (success) { + P036_data->prepare_pagescrolling(static_cast(P036_SCROLL), P036_NLINES); + eventId = P036_EVENT_SCROLL; + LineNo = 1; // after change scroll start with first Line + bUpdateDisplay = true; + } + } # if P036_ENABLE_LEFT_ALIGN else if ((equals(subcommand, F("leftalign"))) && ((event->Par2 == 0) || @@ -1219,25 +1272,48 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) *currentLine = parseStringKeepCaseNoTrim(string, 3); *currentLine = P036_data->P36_parseTemplate(*currentLine, LineNo - 1); - // calculate Pix length of new Content - uint16_t PixLength = P036_data->CalcPixLength(LineNo - 1); + if (!P036_data->bUseTicker) { + // calculate Pix length of new content, not necessary for ticker + uint16_t PixLength = P036_data->CalcPixLength(LineNo - 1); + + if (PixLength > 255) { + String str_error = F("Pixel length of "); + str_error += PixLength; + str_error += F(" too long for line! Max. 255 pix!"); + addHtmlError(str_error); + + const unsigned int strlen = currentLine->length(); + + if (strlen > 0) { + const float fAvgPixPerChar = static_cast(PixLength) / strlen; + const unsigned int iCharToRemove = ceilf((static_cast(PixLength - 255)) / fAvgPixPerChar); - if (PixLength > 255) { - String str_error = F("Pixel length of "); - str_error += PixLength; - str_error += F(" too long for line! Max. 255 pix!"); - addHtmlError(str_error); + // shorten string because OLED controller can not handle such long strings + *currentLine = currentLine->substring(0, strlen - iCharToRemove); + } + } + } + eventId = P036_EVENT_LINE; + bUpdateDisplay = true; + } + } - const unsigned int strlen = currentLine->length(); + if (success && (eventId > 0)) { + if (bDisplayON) { + # if P036_SEND_EVENTS - if (strlen > 0) { - const float fAvgPixPerChar = static_cast(PixLength) / strlen; - const unsigned int iCharToRemove = ceilf((static_cast(PixLength - 255)) / fAvgPixPerChar); + if (sendEvents) { + P036_SendEvent(event, eventId, LineNo); - // shorten string because OLED controller can not handle such long strings - *currentLine = currentLine->substring(0, strlen - iCharToRemove); + if (!P036_DisplayIsOn) { + P036_SendEvent(event, P036_EVENT_DISPLAY, 1); } } + # endif // if P036_SEND_EVENTS + P036_SetDisplayOn(1); // Save the fact that the display is now ON + } + + if (bUpdateDisplay) { P036_data->MaxFramesToDisplay = 0xff; // update frame count # if P036_SEND_EVENTS @@ -1245,7 +1321,8 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) # endif // if P036_SEND_EVENTS if (!P036_DisplayIsOn && - !bitRead(P036_FLAGS_0, P036_FLAG_NODISPLAY_ONRECEIVE)) { // Bit 18 NoDisplayOnReceivedText + (!bitRead(P036_FLAGS_0, P036_FLAG_NODISPLAY_ONRECEIVE) || // Bit 18 NoDisplayOnReceivedText + (eventId == P036_EVENT_SCROLL))) { // display was OFF, turn it ON P036_data->display->displayOn(); P036_SetDisplayOn(1); // Save the fact that the display is now ON @@ -1262,8 +1339,13 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) } if (P036_DisplayIsOn) { - P036_data->P036_JumpToPageOfLine(event, LineNo - 1); // Start to display the selected page - // function needs 65ms! + P036_data->bLineScrollEnabled=false; // disable scrolling temporary + # if P036_ENABLE_TICKER + if (P036_data->bUseTicker) + P036_data->P036_JumpToPage(event, 0); // Restart the Ticker + else + # endif // if P036_ENABLE_TICKER + P036_data->P036_JumpToPageOfLine(event, LineNo - 1); // Start to display the selected page, function needs 65ms! # if P036_SEND_EVENTS if (sendEvents && bitRead(P036_FLAGS_0, P036_FLAG_EVENTS_FRAME_LINE) && (currentFrame != P036_data->currentFrameToDisplay)) { @@ -1273,23 +1355,25 @@ boolean Plugin_036(uint8_t function, struct EventStruct *event, String& string) } # ifdef PLUGIN_036_DEBUG + + if (eventId == P036_EVENT_LINE) { String log; if (loglevelActiveFor(LOG_LEVEL_INFO) && log.reserve(200)) { // estimated - log += F("[P36] Line: "); + log = F("[P36] Line: "); log += LineNo; - log += F(" NewContent:"); - log += NewContent; log += F(" Content:"); - log += P036_data->DisplayLinesV1[LineNo - 1].Content; + log += P036_data->LineContent->DisplayLinesV1[LineNo - 1].Content; log += F(" Length:"); - log += P036_data->DisplayLinesV1[LineNo - 1].Content.length(); + log += P036_data->LineContent->DisplayLinesV1[LineNo - 1].Content.length(); log += F(" Pix: "); - log += P036_data->display->getStringWidth(P036_data->DisplayLinesV1[LineNo - 1].Content); + log += P036_data->display->getStringWidth(P036_data->LineContent->DisplayLinesV1[LineNo - 1].Content); log += F(" Reserved:"); - log += P036_data->DisplayLinesV1[LineNo - 1].reserved; + log += P036_data->LineContent->DisplayLinesV1[LineNo - 1].reserved; addLogMove(LOG_LEVEL_INFO, log); + delay(5); // otherwise it is may be to fast for the serial monitor + } } # endif // PLUGIN_036_DEBUG } @@ -1323,6 +1407,8 @@ const __FlashStringHelper* P36_eventId_toString(uint8_t eventId) # if P036_ENABLE_LINECOUNT case P036_EVENT_LINECNT: return F("linecount"); # endif // if P036_ENABLE_LINECOUNT + case P036_EVENT_RESTORE: return F("restore"); + case P036_EVENT_SCROLL: return F("scroll"); } return F(""); } diff --git a/src/src/PluginStructs/P036_data_struct.cpp b/src/src/PluginStructs/P036_data_struct.cpp index 913609506a..dd20d1a781 100644 --- a/src/src/PluginStructs/P036_data_struct.cpp +++ b/src/src/PluginStructs/P036_data_struct.cpp @@ -114,33 +114,18 @@ void P036_data_struct::reset() { # ifdef P036_FONT_CALC_LOG const __FlashStringHelper * tFontSettings::FontName() const { - if (fontData == ArialMT_Plain_24) { - return F("Arial_24"); - } - -# ifndef P036_LIMIT_BUILD_SIZE - - if (fontData == Dialog_plain_18) { - return F("Dialog_18"); - } -# endif // ifndef P036_LIMIT_BUILD_SIZE - - if (fontData == ArialMT_Plain_16) { - return F("Arial_16"); - } - -# ifndef P036_LIMIT_BUILD_SIZE - - if (fontData == Dialog_plain_12) { - return F("Dialog_12"); - } -# endif // ifndef P036_LIMIT_BUILD_SIZE - - if (fontData == ArialMT_Plain_10) { - return F("Arial_10"); - } - else { - return F("Unknown font"); + switch (fontIdx) { + case 0: return F("Arial_24"); break; + # ifndef P036_LIMIT_BUILD_SIZE + case 1: return F("Dialog_18"); break; + case 2: return F("Arial_16"); break; + case 3: return F("Dialog_12"); break; + case 4: return F("Arial_10"); break; + # else // ifndef P036_LIMIT_BUILD_SIZE + case 1: return F("Arial_16"); break; + case 2: return F("Arial_10"); break; + # endif // ifndef P036_LIMIT_BUILD_SIZE + default: return F("Unknown font"); } } @@ -195,6 +180,7 @@ bool P036_data_struct::init(taskIndex_t taskIndex, bool Rotated, uint8_t Contrast, uint16_t DisplayTimer, + ePageScrollSpeed ScrollSpeed, uint8_t NrLines) { reset(); @@ -272,9 +258,11 @@ bool P036_data_struct::init(taskIndex_t taskIndex, } // prepare font and positions for page and line scrolling - prepare_pagescrolling(); + prepare_pagescrolling(ScrollSpeed, NrLines); } + bRunning = NetworkConnected(); + return isInitialized(); } @@ -295,11 +283,30 @@ void P036_data_struct::setOrientationRotated(bool rotated) { } } +void P036_data_struct::RestoreLineContent(taskIndex_t taskIndex, + uint8_t LoadVersion, + uint8_t LineNo) { + P036_LineContent *TempContent = new (std::nothrow) P036_LineContent(); + + if (TempContent != nullptr) { + TempContent->loadDisplayLines(taskIndex, LoadVersion); + + if (LineNo == 0) { + for (int i = 0; i < P36_Nlines; ++i) { + *(&LineContent->DisplayLinesV1[i].Content) = TempContent->DisplayLinesV1[i].Content; + } + } + else { + *(&LineContent->DisplayLinesV1[LineNo - 1].Content) = TempContent->DisplayLinesV1[LineNo - 1].Content; + } + delete TempContent; + } +} + # if P036_ENABLE_LINECOUNT -void P036_data_struct::setNrLines(uint8_t NrLines) { +void P036_data_struct::setNrLines(struct EventStruct *event, uint8_t NrLines) { if ((NrLines >= 1) && (NrLines <= P36_MAX_LinesPerPage)) { - ScrollingPages.linesPerFrameDef = NrLines; - prepare_pagescrolling(); // Recalculate font + prepare_pagescrolling(static_cast(P036_SCROLL), NrLines); // Recalculate font MaxFramesToDisplay = 0xFF; // Recalculate page indicator CalcMaxPageCount(); // Update max page count nextFrameToDisplay = 0; // Reset to first page @@ -543,7 +550,7 @@ int16_t P036_data_struct::GetHeaderHeight() const { } int16_t P036_data_struct::GetIndicatorTop() const { - if (bHideFooter) { + if (bHideFooter || bUseTicker) { // no footer (indicator) -> returm max. display height return getDisplaySizeSettings(disp_resolution).Height; } @@ -580,14 +587,20 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_ switch (iModifyFont) { case eModifyFont::eEnlarge: + if (ScrollingPages.linesPerFrameDef > 1) { + // Font can only be enlarged if more than 1 line is displayed lFontIndex--; - if (lFontIndex < IdxForBiggestFont) { lFontIndex = IdxForBiggestFont; } - result.IdxForBiggestFontUsed = lFontIndex; + if (lFontIndex < IdxForBiggestFont) { lFontIndex = IdxForBiggestFont; } + result.IdxForBiggestFontUsed = lFontIndex; + } break; case eModifyFont::eMaximize: - lFontIndex = IdxForBiggestFont; - result.IdxForBiggestFontUsed = lFontIndex; + if (ScrollingPages.linesPerFrameDef > 1) { + // Font can only be maximized if more than 1 line is displayed + lFontIndex = IdxForBiggestFont; + result.IdxForBiggestFontUsed = lFontIndex; + } break; case eModifyFont::eReduce: lFontIndex++; @@ -621,6 +634,9 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_ // just one lines per frame -> no space inbetween lSpace = 0; lTop = (MaxHeight - lHeight) / 2; + if (lHeight > MaxHeight) { + result.NextLineNo = 0xFF; // settings do not fit + } } else { if (deltaHeight >= (lLinesPerFrame - 1)) { // individual line setting fits @@ -661,6 +677,27 @@ tIndividualFontSettings P036_data_struct::CalculateIndividualFontSettings(uint8_ LineSettings[k].ypos = LineSettings[k - 1].ypos + FontSizes[LineSettings[k - 1].fontIdx].Height + lSpace; } } +# ifdef P036_CHECK_INDIVIDUAL_FONT + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log1; + + if (log1.reserve(140)) { // estimated + delay(10); // otherwise it is may be to fast for the serial monitor + log1 = F("IndividualFontSettings:"); + log1 += F(" result.NextLineNo:"); log1 += result.NextLineNo; + log1 += F(" result.IdxForBiggestFontUsed:"); log1 += result.IdxForBiggestFontUsed; + log1 += F(" LineNo:"); log1 += LineNo; + log1 += F(" LinesPerFrame:"); log1 += LinesPerFrame; + if (result.NextLineNo != 0xFF) { + log1 += F(" FrameNo:"); log1 += FrameNo; + log1 += F(" lTop:"); log1 += lTop; + log1 += F(" lSpace:"); log1 += lSpace; + } + addLogMove(LOG_LEVEL_INFO, log1); + } + } +#endif // # ifdef P036_CHECK_INDIVIDUAL_FONT return result; } @@ -690,10 +727,9 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { strformat(F("P036 CalculateFontSettings lines: %d, height: %d, header: %s, footer: %s"), iLinesPerFrame, iHeight, - boolToString(!bHideHeader).c_str(), - boolToString(!bHideFooter).c_str())); + boolToString(!bHideHeader), + boolToString(!bHideFooter))); } - String log; # endif // ifdef P036_FONT_CALC_LOG iMaxHeightForFont = lround(iHeight / (iLinesPerFrame * 1.0f)); // no extra space between lines @@ -712,13 +748,13 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { # ifdef P036_FONT_CALC_LOG String log1; log1.reserve(80); - log1.clear(); # endif // ifdef P036_FONT_CALC_LOG for (i = 0; i < P36_MaxFontCount - 1; i++) { // check available fonts for the line setting # ifdef P036_FONT_CALC_LOG - log1 += F(" -> i: "); + delay(5); // otherwise it is may be to fast for the serial monitor + log1 = F(" -> i: "); log1 += i; log1 += F(", h: "); log1 += FontSizes[i].Height; @@ -740,7 +776,7 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { # ifdef P036_FONT_CALC_LOG log1 += F(", no font fits, fontIdx: "); log1 += iFontIndex; - addLog(LOG_LEVEL_INFO, log1); + addLogMove(LOG_LEVEL_INFO, log1); # endif // ifdef P036_FONT_CALC_LOG break; @@ -793,6 +829,13 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { uint8_t iIdxForBiggestFont = 0; while (currentLine < P36_Nlines) { +# if P036_ENABLE_TICKER + + if (bUseTicker && (currentLine > 0)) { + // for ticker only the first line defines the font + break; + } +# endif // if P036_ENABLE_TICKER // calculate individual font settings IndividualFontSettings = CalculateIndividualFontSettings(currentLine, iFontIndex, @@ -803,8 +846,8 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { if (IndividualFontSettings.NextLineNo == 0xFF) { // individual settings do not fit - if (bReduceLinesPerFrame) { - currentLinesPerFrame--; // reduce numer of lines per frame + if ((bReduceLinesPerFrame) && (currentLinesPerFrame > 1)) { + currentLinesPerFrame--; // reduce number of lines per frame } else { iIdxForBiggestFont = IndividualFontSettings.IdxForBiggestFontUsed + 1; // use smaller font size as maximum } @@ -823,24 +866,15 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { String log1; if (log1.reserve(140)) { // estimated - log1.clear(); - log1 = F("IndividualFontSettings:"); - log1 += F(" iFontIndex:"); log1 += iFontIndex; - log1 += F(" iLinesPerFrame:"); log1 += iLinesPerFrame; - log1 += F(" TopLineOffset:"); log1 += TopLineOffset; - log1 += F(" iHeight:"); log1 += iHeight; - log1 += F(" iUsedHeightForFonts:"); log1 += iUsedHeightForFonts; - log1 += F(" iMaxHeightForFont:"); log1 += iMaxHeightForFont; - addLog(LOG_LEVEL_INFO, log1); - for (uint8_t i = 0; i < P36_Nlines; i++) { + delay(5); // otherwise it is may be to fast for the serial monitor log1.clear(); - log1 += F("Line["); log1 += i; + log1 = F("Line["); log1 += i; log1 += F("]: Frame:"); log1 += LineSettings[i].frame; log1 += F(" FontIdx:"); log1 += LineSettings[i].fontIdx; log1 += F(" ypos:"); log1 += LineSettings[i].ypos - TopLineOffset; log1 += F(" FontHeight:"); log1 += LineSettings[i].FontHeight; - addLog(LOG_LEVEL_INFO, log1); + addLogMove(LOG_LEVEL_INFO, log1); } } } @@ -855,7 +889,7 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { String log1; if (log1.reserve(140)) { // estimated - log1.clear(); + delay(5); // otherwise it is may be to fast for the serial monitor log1 = F("CalculateFontSettings: Font:"); log1 += result.FontName(); log1 += F(" Idx:"); @@ -887,10 +921,23 @@ tFontSettings P036_data_struct::CalculateFontSettings(uint8_t lDefaultLines) { return result; } -void P036_data_struct::prepare_pagescrolling() { +void P036_data_struct::prepare_pagescrolling(ePageScrollSpeed lscrollspeed, + uint8_t NrLines) { if (!isInitialized()) { return; } +# if P036_ENABLE_TICKER + bUseTicker = (lscrollspeed == ePageScrollSpeed::ePSS_Ticker); +# else // if P036_ENABLE_TICKER + bUseTicker = false; +# endif //if P036_ENABLE_TICKER + + if (bUseTicker) { + ScrollingPages.linesPerFrameDef = 1; + } + else { + ScrollingPages.linesPerFrameDef = NrLines; + } CalculateFontSettings(0); } @@ -908,7 +955,7 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas if (loglevelActiveFor(LOG_LEVEL_INFO) && log.reserve(32)) { - log += F("Start Scrolling: Speed: "); + log = F("Start Scrolling: Speed: "); log += static_cast(lscrollspeed); addLogMove(LOG_LEVEL_INFO, log); } @@ -920,6 +967,9 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas if (lscrollspeed == ePageScrollSpeed::ePSS_Instant) { // no scrolling, just the handling time to build the new page iPageScrollTime = P36_PageScrollTick - P36_PageScrollTimer; + } else if (lscrollspeed == ePageScrollSpeed::ePSS_Ticker) { + // for ticker, no scrolling, just the handling time to build the new page + iPageScrollTime = P36_PageScrollTick - P36_PageScrollTimer; } else { iPageScrollTime = (P36_MaxDisplayWidth / (P36_PageScrollPix * static_cast(lscrollspeed))) * P36_PageScrollTick; } @@ -930,7 +980,7 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas if (loglevelActiveFor(LOG_LEVEL_INFO)) { String log; log.reserve(32); - log += F("PageScrollTime: "); + log = F("PageScrollTime: "); log += iPageScrollTime; addLogMove(LOG_LEVEL_INFO, log); } @@ -943,6 +993,22 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas MaxPixWidthForPageScrolling -= getDisplaySizeSettings(disp_resolution).PixLeft; } +# if P036_ENABLE_TICKER + + if (bUseTicker) { + ScrollingLines.Ticker.Tcontent = EMPTY_STRING; + ScrollingLines.Ticker.IdxEnd = 0; + ScrollingLines.Ticker.IdxStart = 0; + + for (uint8_t i = 0; i < P36_Nlines; i++) { + String tmpString(LineContent->DisplayLinesV1[i].Content); + tmpString.replace(F("<|>"), " "); // replace the split token with three space char + ScrollingLines.Ticker.Tcontent += P36_parseTemplate(tmpString, i); + } + ScrollingLines.Ticker.len = ScrollingLines.Ticker.Tcontent.length(); + } +# endif // if P036_ENABLE_TICKER + for (uint8_t j = 0; j < ScrollingPages.linesPerFrameDef; j++) { // default no line scrolling and strings are centered uint16_t PixLengthLineOut = 0; // pix length of line out @@ -965,9 +1031,56 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas ScrollingLines.SLine[j].LastWidth = PixLengthLineOut; // while page scrolling this line is right aligned } - if ((PixLengthLineIn > getDisplaySizeSettings(disp_resolution).Width) && + if ((bUseTicker || (PixLengthLineIn > getDisplaySizeSettings(disp_resolution).Width)) && (iScrollTime > 0)) { // width of the line > display width -> scroll line + if (bUseTicker) { +# if P036_ENABLE_TICKER + ScrollingLines.SLine[j].Width = 0; + uint16_t AddPixTicker; + + switch (textAlignment) { + case TEXT_ALIGN_CENTER: ScrollingLines.SLine[j].CurrentLeft = getDisplaySizeSettings(disp_resolution).PixLeft + + getDisplaySizeSettings(disp_resolution).Width / 2; + AddPixTicker = getDisplaySizeSettings(disp_resolution).Width / 2; // half width at begin + break; + case TEXT_ALIGN_RIGHT: ScrollingLines.SLine[j].CurrentLeft = getDisplaySizeSettings(disp_resolution).PixLeft + + getDisplaySizeSettings(disp_resolution).Width; + AddPixTicker = getDisplaySizeSettings(disp_resolution).Width; // full width at begin + break; + default: ScrollingLines.SLine[j].CurrentLeft = getDisplaySizeSettings(disp_resolution).PixLeft; + AddPixTicker = 0; + } + ScrollingLines.SLine[j].fPixSum = ScrollingLines.SLine[j].CurrentLeft; + + display->setFont(FontSizes[LineSettings[j].fontIdx].fontData); + ScrollingLines.SLine[j].dPix = (static_cast(display->getStringWidth(ScrollingLines.Ticker.Tcontent) + AddPixTicker)) / + static_cast(iScrollTime); + ScrollingLines.SLine[j].SLcontent = EMPTY_STRING; + + ScrollingLines.Ticker.TickerAvgPixPerChar = lround(static_cast(display->getStringWidth( + ScrollingLines.Ticker.Tcontent)) / + static_cast(ScrollingLines.Ticker.len)); + + if (ScrollingLines.Ticker.TickerAvgPixPerChar < ScrollingLines.SLine[j].dPix) { + ScrollingLines.Ticker.TickerAvgPixPerChar = round(2 * ScrollingLines.SLine[j].dPix); + } + ScrollingLines.Ticker.MaxPixLen = getDisplaySizeSettings(disp_resolution).Width + 2 * ScrollingLines.Ticker.TickerAvgPixPerChar; + + // add more characters to display + while (true) { + char c = ScrollingLines.Ticker.Tcontent.charAt(ScrollingLines.Ticker.IdxEnd); + uint8_t PixForChar = display->getCharWidth(c); + + if ((ScrollingLines.SLine[0].Width + PixForChar) >= ScrollingLines.Ticker.MaxPixLen) { + break; // no more characters necessary to add + } + ScrollingLines.Ticker.IdxEnd++; + ScrollingLines.SLine[j].Width += PixForChar; + } +# endif // if P036_ENABLE_TICKER + } + else { ScrollingLines.SLine[j].SLcontent = ScrollingPages.In[j].SPLcontent; ScrollingLines.SLine[j].SLidx = ScrollingPages.In[j].SPLidx; // index to LineSettings[] ScrollingLines.SLine[j].Width = PixLengthLineIn; // while page scrolling this line is left aligned @@ -977,19 +1090,38 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas // pix change per scrolling line tick ScrollingLines.SLine[j].dPix = (static_cast(PixLengthLineIn - getDisplaySizeSettings(disp_resolution).Width)) / iScrollTime; + } # ifdef P036_SCROLL_CALC_LOG if (loglevelActiveFor(LOG_LEVEL_INFO)) { + delay(5); // otherwise it is may be to fast for the serial monitor String log; log.reserve(32); - log += F("Line: "); + log = F("Line: "); log += (j + 1); log += F(" width: "); log += ScrollingLines.SLine[j].Width; log += F(" dPix: "); log += ScrollingLines.SLine[j].dPix; addLogMove(LOG_LEVEL_INFO, log); +# if P036_ENABLE_TICKER + + if (bUseTicker) { + delay(5); // otherwise it is may be to fast for the serial monitor + String log1; + log1.reserve(200); + log1 = F("+++ iScrollTime: "); + log1 += iScrollTime; + log1 += F(" StrLength: "); + log1 += ScrollingLines.Ticker.len; + log1 += F(" StrInPix: "); + log1 += display->getStringWidth(ScrollingLines.Ticker.Tcontent); + log1 += F(" PixPerChar: "); + log1 += ScrollingLines.Ticker.TickerAvgPixPerChar; + addLogMove(LOG_LEVEL_INFO, log1); + } +# endif // if P036_ENABLE_TICKER } # endif // P036_SCROLL_CALC_LOG } @@ -1040,13 +1172,14 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas if (loglevelActiveFor(LOG_LEVEL_INFO) && log.reserve(128)) { - log += F("Line: "); log += (j + 1); + delay(5); // otherwise it is may be to fast for the serial monitor + log = F("Line: "); log += (j + 1); log += F(" LineIn: "); log += LineInStr; log += F(" Length: "); log += strlen; log += F(" PixLength: "); log += PixLengthLineIn; log += F(" AvgPixPerChar: "); log += fAvgPixPerChar; log += F(" CharsRemoved: "); log += iCharToRemove; - addLog(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, log); log.clear(); log += F(" -> Changed to: "); log += ScrollingPages.In[j].SPLcontent; log += F(" Length: "); log += ScrollingPages.In[j].SPLcontent.length(); @@ -1145,13 +1278,15 @@ uint8_t P036_data_struct::display_scroll(ePageScrollSpeed lscrollspeed, int lTas if (loglevelActiveFor(LOG_LEVEL_INFO) && log.reserve(128)) { - log += F("Line: "); log += (j + 1); + delay(5); // otherwise it is may be to fast for the serial monitor + log = F("Line: "); log += (j + 1); log += F(" LineOut: "); log += LineOutStr; log += F(" Length: "); log += strlen; log += F(" PixLength: "); log += PixLengthLineOut; log += F(" AvgPixPerChar: "); log += fAvgPixPerChar; log += F(" CharsRemoved: "); log += iCharToRemove; - addLog(LOG_LEVEL_INFO, log); + addLogMove(LOG_LEVEL_INFO, log); + delay(5); // otherwise it is may be to fast for the serial monitor log.clear(); log += F(" -> Changed to: "); log += ScrollingPages.Out[j].SPLcontent; log += F(" Length: "); log += ScrollingPages.Out[j].SPLcontent.length(); @@ -1198,18 +1333,30 @@ uint8_t P036_data_struct::display_scroll_timer(bool initialScroll, } } + if (!bUseTicker) { for (uint8_t j = 0; j < ScrollingPages.linesPerFrameOut; j++) { if ((initialScroll && (lscrollspeed < ePageScrollSpeed::ePSS_Instant)) || !initialScroll) { - // scrolling, prepare scrolling out to right + // scrolling, prepare scrolling page out to right DrawScrollingPageLine(&ScrollingPages.Out[j], ScrollingLines.SLine[j].LastWidth, TEXT_ALIGN_RIGHT); } } for (uint8_t j = 0; j < ScrollingPages.linesPerFrameIn; j++) { - // non-scrolling or scrolling prepare scrolling in from left + // non-scrolling or scrolling prepare scrolling page in from left DrawScrollingPageLine(&ScrollingPages.In[j], ScrollingLines.SLine[j].Width, TEXT_ALIGN_LEFT); } + } +# if P036_ENABLE_TICKER + else { + // for Ticker start with the set alignment + display->setTextAlignment(TEXT_ALIGN_LEFT); + display->setFont(FontSizes[LineSettings[ScrollingLines.SLine[0].SLidx].fontIdx].fontData); + display->drawString(ScrollingLines.SLine[0].CurrentLeft, + LineSettings[ScrollingLines.SLine[0].SLidx].ypos, + ScrollingLines.Ticker.Tcontent.substring(ScrollingLines.Ticker.IdxStart, ScrollingLines.Ticker.IdxEnd)); + } +# endif // if P036_ENABLE_TICKER update_display(); @@ -1245,9 +1392,8 @@ void P036_data_struct::display_scrolling_lines() { } if (bscroll) { - ScrollingLines.wait++; - if (ScrollingLines.wait < P36_WaitScrollLines) { + ScrollingLines.wait++; return; // wait before scrolling line not finished } @@ -1268,18 +1414,74 @@ void P036_data_struct::display_scrolling_lines() { display->setFont(FontSizes[LineSettings[ScrollingLines.SLine[i].SLidx].fontIdx].fontData); - if (((ScrollingLines.SLine[i].CurrentLeft - getDisplaySizeSettings(disp_resolution).PixLeft) + - ScrollingLines.SLine[i].Width) >= getDisplaySizeSettings(disp_resolution).Width) { + if (bUseTicker || (((iCurrentLeft - getDisplaySizeSettings(disp_resolution).PixLeft) + + ScrollingLines.SLine[i].Width) >= getDisplaySizeSettings(disp_resolution).Width)) { display->setTextAlignment(TEXT_ALIGN_LEFT); - display->drawString(ScrollingLines.SLine[i].CurrentLeft, + + if (bUseTicker) { +#if P036_ENABLE_TICKER + display->drawString(iCurrentLeft, + LineSettings[ScrollingLines.SLine[0].SLidx].ypos, + ScrollingLines.Ticker.Tcontent.substring(ScrollingLines.Ticker.IdxStart, ScrollingLines.Ticker.IdxEnd)); + + // add more characters to display + iCurrentLeft -= getDisplaySizeSettings(disp_resolution).PixLeft; + + while (true) { + if (ScrollingLines.Ticker.IdxEnd >= ScrollingLines.Ticker.len) {// end of string + break; + } + uint8_t c = ScrollingLines.Ticker.Tcontent.charAt(ScrollingLines.Ticker.IdxEnd); + uint8_t PixForChar = display->getCharWidth(c);// PixForChar can be 0 if c is non ascii + + if ((static_cast(ScrollingLines.SLine[0].Width + PixForChar) + iCurrentLeft) >= ScrollingLines.Ticker.MaxPixLen) { + break; // no more characters necessary to add + } + ScrollingLines.Ticker.IdxEnd++; + ScrollingLines.SLine[0].Width += PixForChar; + } + + // remove already displayed characters + float fCurrentPixLeft = static_cast(getDisplaySizeSettings(disp_resolution).PixLeft) - 2.0f * + ScrollingLines.Ticker.TickerAvgPixPerChar; + + while (ScrollingLines.SLine[0].fPixSum < fCurrentPixLeft) { + uint8_t c = ScrollingLines.Ticker.Tcontent.charAt(ScrollingLines.Ticker.IdxStart); + uint8_t PixForChar = display->getCharWidth(c);// PixForChar can be 0 if c is non ascii + ScrollingLines.SLine[0].fPixSum += static_cast(PixForChar); + ScrollingLines.Ticker.IdxStart++; + + if (ScrollingLines.Ticker.IdxStart >= ScrollingLines.Ticker.IdxEnd) { + ScrollingLines.SLine[0].Width = 0;// Stop scrolling + +# ifdef PLUGIN_036_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + addLog(LOG_LEVEL_INFO, F("Ticker finished")); + } +# endif // PLUGIN_036_DEBUG + break; + } + + if (ScrollingLines.SLine[0].Width > PixForChar) { + ScrollingLines.SLine[0].Width -= PixForChar; + } + } + break; +#endif // if P036_ENABLE_TICKER + } else { + display->drawString(iCurrentLeft, LineSettings[ScrollingLines.SLine[i].SLidx].ypos, ScrollingLines.SLine[i].SLcontent); + } } else { + if (!bUseTicker) { // line scrolling finished -> line is shown as aligned right display->setTextAlignment(TEXT_ALIGN_RIGHT); display->drawString(P36_MaxDisplayWidth - getDisplaySizeSettings(disp_resolution).PixLeft, LineSettings[ScrollingLines.SLine[i].SLidx].ypos, ScrollingLines.SLine[i].SLcontent); + } ScrollingLines.SLine[i].Width = 0; // Stop scrolling } } @@ -1369,7 +1571,7 @@ void P036_data_struct::P036_JumpToPage(struct EventStruct *event, uint8_t nextFr bPageScrollDisabled = true; // show next page without scrolling disableFrameChangeCnt = 2; // disable next page change in PLUGIN_READ if PLUGIN_READ was already scheduled P036_DisplayPage(event); // Display the selected page, function needs 65ms! - displayTimer = PCONFIG(4); // Restart timer + displayTimer = P036_TIMER; // Restart timer } void P036_data_struct::P036_JumpToPageOfLine(struct EventStruct *event, uint8_t LineNo) @@ -1378,6 +1580,9 @@ void P036_data_struct::P036_JumpToPageOfLine(struct EventStruct *event, uint8_t P036_JumpToPage(event, LineSettings[LineNo].DisplayedPageNo); } +// Defines the Scroll area layout +// Displays the selected page, function needs 65ms! +// Called by PLUGIN_READ and P036_JumpToPage() void P036_data_struct::P036_DisplayPage(struct EventStruct *event) { # ifdef PLUGIN_036_DEBUG @@ -1478,26 +1683,28 @@ void P036_data_struct::P036_DisplayPage(struct EventStruct *event) # if P036_FEATURE_DISPLAY_PREVIEW && P036_FEATURE_ALIGN_PREVIEW - // Preview: Center or Right-Align add spaces on the left - const bool isAlignCenter = ScrollingPages.In[i].Alignment == OLEDDISPLAY_TEXT_ALIGNMENT::TEXT_ALIGN_CENTER; - const bool isAlignRight = ScrollingPages.In[i].Alignment == OLEDDISPLAY_TEXT_ALIGNMENT::TEXT_ALIGN_RIGHT; + if (!bUseTicker) { + // Preview: Center or Right-Align add spaces on the left + const bool isAlignCenter = ScrollingPages.In[i].Alignment == OLEDDISPLAY_TEXT_ALIGNMENT::TEXT_ALIGN_CENTER; + const bool isAlignRight = ScrollingPages.In[i].Alignment == OLEDDISPLAY_TEXT_ALIGNMENT::TEXT_ALIGN_RIGHT; - if (isAlignRight || isAlignCenter) { - const uint16_t maxlength = getDisplaySizeSettings(disp_resolution).Width; - const uint16_t pixlength = display->getStringWidth(currentLines[i]); // pix length for entire string - const uint16_t charlength = display->getStringWidth(F(" ")); // pix length for a space char - int16_t addSpaces = (maxlength - pixlength) / charlength; + if (isAlignRight || isAlignCenter) { + const uint16_t maxlength = getDisplaySizeSettings(disp_resolution).Width; + const uint16_t pixlength = display->getStringWidth(currentLines[i]); // pix length for entire string + const uint16_t charlength = display->getStringWidth(F(" ")); // pix length for a space char + int16_t addSpaces = (maxlength - pixlength) / charlength; - if (isAlignCenter) { - addSpaces /= 2; - } + if (isAlignCenter) { + addSpaces /= 2; + } - if (addSpaces > 0) { - currentLines[i].reserve(currentLines[i].length() + addSpaces); + if (addSpaces > 0) { + currentLines[i].reserve(currentLines[i].length() + addSpaces); - while (addSpaces > 0) { - currentLines[i] = ' ' + currentLines[i]; - addSpaces--; + while (addSpaces > 0) { + currentLines[i] = ' ' + currentLines[i]; + addSpaces--; + } } } } @@ -1537,10 +1744,12 @@ void P036_data_struct::P036_DisplayPage(struct EventStruct *event) const bool bScrollWithoutWifi = bitRead(PCONFIG_LONG(0), 24); // Bit 24 const bool bScrollLines = bitRead(PCONFIG_LONG(0), 17); // Bit 17 - bLineScrollEnabled = (bScrollLines && (NetworkConnected() || bScrollWithoutWifi)); // scroll lines only if WifiIsConnected, + bRunning = NetworkConnected() || bScrollWithoutWifi; + bLineScrollEnabled = ((bScrollLines || bUseTicker) && bRunning);// scroll lines only if WifiIsConnected, + // WifiIsConnected, // otherwise too slow - ePageScrollSpeed lscrollspeed = static_cast(PCONFIG(3)); + ePageScrollSpeed lscrollspeed = static_cast(P036_SCROLL); if (bPageScrollDisabled) { lscrollspeed = ePageScrollSpeed::ePSS_Instant; } // first page after INIT without scrolling @@ -1550,7 +1759,7 @@ void P036_data_struct::P036_DisplayPage(struct EventStruct *event) Scheduler.setPluginTaskTimer(P36_PageScrollTimer, event->TaskIndex, event->Par1); // calls next page scrollng tick } - if (NetworkConnected() || bScrollWithoutWifi) { + if (bRunning) { // scroll lines only if WifiIsConnected, otherwise too slow bPageScrollDisabled = false; // next PLUGIN_READ will do page scrolling } @@ -1582,7 +1791,16 @@ String P036_data_struct::P36_parseTemplate(String& tmpString, uint8_t lineIdx) { const eAlignment iAlignment = static_cast(get3BitFromUL(LineContent->DisplayLinesV1[lineIdx].ModifyLayout, P036_FLAG_ModifyLayout_Alignment)); - switch (getTextAlignment(iAlignment)) { + OLEDDISPLAY_TEXT_ALIGNMENT iTextAlignment = getTextAlignment(static_cast(iAlignment)); + +# if P036_ENABLE_TICKER + + if (bUseTicker) { + iTextAlignment = TEXT_ALIGN_RIGHT; // ticker is always right aligned + } +# endif // if P036_ENABLE_TICKER + + switch (iTextAlignment) { case TEXT_ALIGN_LEFT: // add leading spaces from tmpString to the result @@ -1727,19 +1945,19 @@ void P036_data_struct::CalcMaxPageCount(void) { String log1; if (log1.reserve(140)) { // estimated - log1.clear(); log1 = F("CalcMaxPageCount: MaxFramesToDisplay:"); log1 += MaxFramesToDisplay; - addLog(LOG_LEVEL_INFO, log1); + addLogMove(LOG_LEVEL_INFO, log1); for (uint8_t i = 0; i < P36_Nlines; i++) { log1.clear(); - log1 += F("Line["); log1 += i; + delay(5); // otherwise it is may be to fast for the serial monitor + log1 = F("Line["); log1 += i; log1 += F("]: Frame:"); log1 += LineSettings[i].frame; log1 += F(" DisplayedPageNo:"); log1 += LineSettings[i].DisplayedPageNo; log1 += F(" FontIdx:"); log1 += LineSettings[i].fontIdx; log1 += F(" ypos:"); log1 += LineSettings[i].ypos - TopLineOffset; log1 += F(" FontHeight:"); log1 += LineSettings[i].FontHeight; - addLog(LOG_LEVEL_INFO, log1); + addLogMove(LOG_LEVEL_INFO, log1); } } } @@ -1771,6 +1989,15 @@ void P036_data_struct::DrawScrollingPageLine(tScrollingPageLines *Scrollin OLEDDISPLAY_TEXT_ALIGNMENT textAlignment) { int16_t LeftOffset = 0; + switch (textAlignment) { + case TEXT_ALIGN_LEFT: LeftOffset = -P36_MaxDisplayWidth; + break; + case TEXT_ALIGN_RIGHT: LeftOffset = 0; + break; + default: + LeftOffset = 0; + break; + } display->setFont(FontSizes[LineSettings[ScrollingPageLine->SPLidx].fontIdx].fontData); if (Width > 0) { @@ -1784,10 +2011,6 @@ void P036_data_struct::DrawScrollingPageLine(tScrollingPageLines *Scrollin // line is kept aligned while scrolling page display->setTextAlignment(ScrollingPageLine->Alignment); - if (textAlignment == TEXT_ALIGN_LEFT) { - LeftOffset = -P36_MaxDisplayWidth; - } - // textAlignment=TEXT_ALIGN_LEFT: for non-scrolling pages ScrollingPages.dPixSum=P36_MaxDisplayWidth -> therefore the calculation must // use P36_MaxDisplayWidth, too display->drawString(LeftOffset + GetTextLeftMargin(ScrollingPageLine->Alignment) + ScrollingPages.dPixSum, @@ -1797,6 +2020,12 @@ void P036_data_struct::DrawScrollingPageLine(tScrollingPageLines *Scrollin } void P036_data_struct::CreateScrollingPageLine(tScrollingPageLines *ScrollingPageLine, uint8_t Counter) { + if (bUseTicker) { +# if P036_ENABLE_TICKER + ScrollingPageLine->SPLcontent = EMPTY_STRING; +# endif // if P036_ENABLE_TICKER + } + else { String tmpString(LineContent->DisplayLinesV1[Counter].Content); ScrollingPageLine->SPLcontent = P36_parseTemplate(tmpString, Counter); @@ -1828,6 +2057,7 @@ void P036_data_struct::CreateScrollingPageLine(tScrollingPageLines *ScrollingPag ScrollingPageLine->Alignment = getTextAlignment(iAlignment); ScrollingPageLine->SPLidx = Counter; // index to LineSettings[] } +} # if P036_FEATURE_DISPLAY_PREVIEW bool P036_data_struct::web_show_values() { diff --git a/src/src/PluginStructs/P036_data_struct.h b/src/src/PluginStructs/P036_data_struct.h index 6aaeaf2b2b..65a41510d1 100644 --- a/src/src/PluginStructs/P036_data_struct.h +++ b/src/src/PluginStructs/P036_data_struct.h @@ -22,11 +22,11 @@ // # define P036_FONT_CALC_LOG // Enable to add extra logging during font calculation (selection) // # define P036_SCROLL_CALC_LOG // Enable to add extra logging during scrolling calculation (selection) // # define P036_CHECK_HEAP // Enable to add extra logging during Plugin_036() -// # define P036_CHECK_INDIVIDUAL_FONT // /Enable to add extra logging for individual font calculation +// # define P036_CHECK_INDIVIDUAL_FONT // Enable to add extra logging for individual font calculation # ifndef P036_FEATURE_DISPLAY_PREVIEW # define P036_FEATURE_DISPLAY_PREVIEW 1 # endif // ifndef P036_FEATURE_DISPLAY_PREVIEW -# ifdef P036_FEATURE_ALIGN_PREVIEW +# ifndef P036_FEATURE_ALIGN_PREVIEW # define P036_FEATURE_ALIGN_PREVIEW 1 # endif // ifdef P036_FEATURE_ALIGN_PREVIEW @@ -45,6 +45,9 @@ # ifndef P036_USERDEF_HEADERS # define P036_USERDEF_HEADERS 1 // Enable User defined headers # endif // ifndef P036_USERDEF_HEADERS +# ifndef P036_ENABLE_TICKER +# define P036_ENABLE_TICKER 1 // Enable ticker function +# endif // ifndef # else // ifndef P036_LIMIT_BUILD_SIZE # if defined(P036_SEND_EVENTS) && P036_SEND_EVENTS # undef P036_SEND_EVENTS @@ -65,6 +68,9 @@ // # ifndef P036_USERDEF_HEADERS // # define P036_USERDEF_HEADERS 0 // Disable User defined headers // # endif // ifndef P036_USERDEF_HEADERS +# ifndef P036_ENABLE_TICKER +# define P036_ENABLE_TICKER 0 // Disable ticker function +# endif // ifndef # endif // ifndef P036_LIMIT_BUILD_SIZE # ifndef P036_USERDEF_HEADERS # define P036_USERDEF_HEADERS 1 // Enable User defined headers if not handled yet @@ -128,7 +134,7 @@ # define P036_FLAG_SCROLL_WITHOUTWIFI 24 // Bit 24 ScrollWithoutWifi # define P036_FLAG_HIDE_HEADER 25 // Bit 25 Hide header # define P036_FLAG_INPUT_PULLUP 26 // Bit 26 Input PullUp -// # define P036_FLAG_INPUT_PULLDOWN 27 // Bit 27 Input PullDown, 2022-09-04 no longer used +// # define P036_FLAG_INPUT_PULLDOWN 27 // Bit 27 Input PullDown, 2022-09-04 not longer used # define P036_FLAG_SEND_EVENTS 28 // Bit 28 SendEvents # define P036_FLAG_EVENTS_FRAME_LINE 29 // Bit 29 SendEvents also on Frame & Line # define P036_FLAG_HIDE_FOOTER 30 // Bit 30 Hide footer @@ -169,7 +175,8 @@ enum class ePageScrollSpeed : uint8_t { ePSS_Slow = 2u, // 400ms ePSS_Fast = 4u, // 200ms ePSS_VeryFast = 8u, // 100ms - ePSS_Instant = 32u // 20ms + ePSS_Instant = 32u, // 20ms + ePSS_Ticker = 255u // tickerspeed depends on line length }; enum class eP036pinmode : uint8_t { @@ -187,8 +194,20 @@ typedef struct { uint8_t SLidx = 0; // index to DisplayLinesV1 } tScrollLine; +typedef struct { + String Tcontent; // content (all parsed lines) + uint16_t len = 0; // length of content + uint16_t IdxStart = 0; // Start index of TickerContent for displaying (left side) + uint16_t IdxEnd = 0; // End index of TickerContent for displaying (right side) + uint16_t TickerAvgPixPerChar = 0; // max of average pixel per character or pix change per scroll time (100ms) + int16_t MaxPixLen = 0; // Max pix length to display (display width + 2*TickerAvgPixPerChar) +} tTicker; + typedef struct { tScrollLine SLine[P36_MAX_LinesPerPage]{}; +# if P036_ENABLE_TICKER + tTicker Ticker; +# endif // if P036_ENABLE_TICKER uint16_t wait = 0; // waiting time before scrolling } tScrollingLines; @@ -203,7 +222,7 @@ typedef struct { tScrollingPageLines Out[P36_MAX_LinesPerPage]{}; int dPixSum = 0; // act pix change uint8_t Scrolling = 0; // 0=Ready, 1=Scrolling - uint8_t dPix = 0; // pix change per scroll time (25ms) + uint8_t dPix = 0; // pix change per scroll time (25ms per page, 100ms per line) uint8_t linesPerFrameDef = 0; // the default number of lines in frame in/out uint8_t linesPerFrameIn = 0; // the number of lines in frame in uint8_t linesPerFrameOut = 0; // the number of lines in frame out @@ -343,6 +362,7 @@ struct P036_data_struct : public PluginTaskData_base { bool Rotated, uint8_t Contrast, uint16_t DisplayTimer, + ePageScrollSpeed ScrollSpeed, uint8_t NrLines); bool isInitialized() const; @@ -355,9 +375,16 @@ struct P036_data_struct : public PluginTaskData_base { void setOrientationRotated(bool rotated); # if P036_ENABLE_LINECOUNT - void setNrLines(uint8_t NrLines); + void setNrLines(struct EventStruct *event, + uint8_t NrLines); # endif // if P036_ENABLE_LINECOUNT + // Restores line content from flash memory + // LineNo == 0: all line contents + // otherwise just the line content of the given LineNo + void RestoreLineContent(taskIndex_t taskIndex, + uint8_t LoadVersion, + uint8_t LineNo); // The screen is set up as: // - 10 rows at the top for the header @@ -368,7 +395,8 @@ struct P036_data_struct : public PluginTaskData_base { void display_title(const String& title); void display_logo(); void display_indicator(); - void prepare_pagescrolling(); + void prepare_pagescrolling(ePageScrollSpeed lscrollspeed, + uint8_t NrLines); uint8_t display_scroll(ePageScrollSpeed lscrollspeed, int lTaskTimer); uint8_t display_scroll_timer(bool initialScroll = false, @@ -417,8 +445,8 @@ struct P036_data_struct : public PluginTaskData_base { // Instantiate display here - does not work to do this within the INIT call OLEDDisplay *display = nullptr; - tScrollingLines ScrollingLines{}; - tScrollingPages ScrollingPages{}; + tScrollingLines ScrollingLines{}; // scrolling lines in from right, out to left + tScrollingPages ScrollingPages{}; // scrolling pages in from left, out to right // CustomTaskSettings P036_LineContent *LineContent = nullptr; @@ -453,6 +481,8 @@ struct P036_data_struct : public PluginTaskData_base { uint8_t frameCounter = 0; // need to keep track of framecounter from call to call uint8_t disableFrameChangeCnt = 0; // counter to disable frame change after JumpToPage in case PLUGIN_READ already scheduled bool bPageScrollDisabled = true; // first page after INIT or after JumpToPage without scrolling + bool bRunning = false; // page updates are rumming = (NetworkConnected() || bScrollWithoutWifi) + bool bUseTicker = false; // scroll line like a ticker OLEDDISPLAY_TEXT_ALIGNMENT textAlignment = TEXT_ALIGN_CENTER;