From 81abd5eeacda95c9832d4e862bf1fc43524a99d6 Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Fri, 3 Apr 2020 22:40:03 +0200 Subject: [PATCH 01/10] Add navigation animations --- workspace/TS100/Core/Inc/OLED.hpp | 7 ++- workspace/TS100/Core/Src/OLED.cpp | 79 +++++++++++++++++++++++++++++++ workspace/TS100/Core/Src/gui.cpp | 23 +++++++-- 3 files changed, 105 insertions(+), 4 deletions(-) diff --git a/workspace/TS100/Core/Inc/OLED.hpp b/workspace/TS100/Core/Inc/OLED.hpp index 381fd9f0..2bbd23a2 100644 --- a/workspace/TS100/Core/Inc/OLED.hpp +++ b/workspace/TS100/Core/Inc/OLED.hpp @@ -78,7 +78,7 @@ public: // Draws a number at the current cursor location // Clears the buffer static void clearScreen() { - memset(&screenBuffer[FRAMEBUFFER_START], 0, OLED_WIDTH * 2); + memset(firstStripPtr, 0, OLED_WIDTH * 2); } // Draws the battery level symbol static void drawBattery(uint8_t state) { @@ -99,6 +99,10 @@ public: static void drawFilledRect(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, bool clear); static void drawHeatSymbol(uint8_t state); + static void presentSecondScreenBufferAnimatedBack(); + static void presentSecondScreenBufferAnimated(); + static void use_first_buffer(); + static void use_second_buffer(); private: static void drawChar(char c); // Draw a character to a specific location static const uint8_t* currentFont;// Pointer to the current font used for rendering to the buffer @@ -110,6 +114,7 @@ private: static int16_t cursor_x, cursor_y; static uint8_t displayOffset; static uint8_t screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer + static uint8_t secondFrameBuffer[OLED_WIDTH * 2]; // The second frame buffer }; #endif /* OLED_HPP_ */ diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index bef541d1..00e35974 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -24,6 +24,7 @@ uint8_t OLED::fontWidth, OLED::fontHeight; int16_t OLED::cursor_x, OLED::cursor_y; uint8_t OLED::displayOffset; uint8_t OLED::screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer +uint8_t OLED::secondFrameBuffer[OLED_WIDTH * 2]; // The second frame buffer /*Setup params for the OLED screen*/ /*http://www.displayfuture.com/Display/datasheet/controller/SSD1307.pdf*/ @@ -85,6 +86,16 @@ void OLED::initialize() { sizeof(OLED_Setup_Array)); } +void OLED::use_first_buffer() { + firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; + secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; +} + +void OLED::use_second_buffer() { + firstStripPtr = &secondFrameBuffer[0]; + secondStripPtr = &secondFrameBuffer[OLED_WIDTH]; +} + /* * Prints a char to the screen. * UTF font handling is done using the two input chars. @@ -106,6 +117,74 @@ void OLED::drawChar(char c) { cursor_x += fontWidth; } +void OLED::presentSecondScreenBufferAnimatedBack() { + OLED::use_first_buffer(); + + uint32_t totalDuration = 50; + + uint32_t duration = 0; + uint32_t start = xTaskGetTickCount(); + uint8_t offset = 0; + while (duration <= totalDuration) + { + duration = xTaskGetTickCount() - start; + + uint8_t progress = (duration * OLED_WIDTH) / totalDuration; + + for (uint8_t i = OLED_WIDTH - 1; i > progress; i--) { + firstStripPtr[i] = firstStripPtr[(i - progress) + offset]; + secondStripPtr[i] = secondStripPtr[(i - progress) + offset]; + } + + offset = progress; + + uint8_t *firstBackStripPtr = &secondFrameBuffer[0]; + uint8_t *secondBackStripPtr = &secondFrameBuffer[OLED_WIDTH]; + + for (uint8_t i = 0; i < progress; i++) { + firstStripPtr[i] = firstBackStripPtr[(i - progress) + OLED_WIDTH]; + secondStripPtr[i] = secondBackStripPtr[(i - progress) + OLED_WIDTH]; + } + + refresh(); + osDelay(40); + } +} + +void OLED::presentSecondScreenBufferAnimated() { + OLED::use_first_buffer(); + + uint32_t totalDuration = 50; + + uint32_t duration = 0; + uint32_t start = xTaskGetTickCount(); + uint8_t offset = 0; + while (duration < totalDuration) + { + duration = xTaskGetTickCount() - start; + + uint8_t progress = (duration * OLED_WIDTH) / totalDuration; + + for (uint8_t i = 0; i < OLED_WIDTH - progress; i++) { + firstStripPtr[i] = firstStripPtr[i + progress - offset]; + secondStripPtr[i] = secondStripPtr[i + progress - offset]; + } + + offset = progress; + + uint8_t *firstBackStripPtr = &secondFrameBuffer[0]; + uint8_t *secondBackStripPtr = &secondFrameBuffer[OLED_WIDTH]; + + for (uint8_t i = OLED_WIDTH - progress; i < OLED_WIDTH; i++) { + firstStripPtr[i] = firstBackStripPtr[i - (OLED_WIDTH - progress)]; + secondStripPtr[i] = secondBackStripPtr[i - (OLED_WIDTH - progress)]; + } + + refresh(); + osDelay(40); + } +} + void OLED::setRotation(bool leftHanded) { #ifdef MODEL_TS80 leftHanded = !leftHanded; diff --git a/workspace/TS100/Core/Src/gui.cpp b/workspace/TS100/Core/Src/gui.cpp index 42834655..99cdcd4f 100644 --- a/workspace/TS100/Core/Src/gui.cpp +++ b/workspace/TS100/Core/Src/gui.cpp @@ -823,6 +823,15 @@ void gui_Menu(const menuitem *menu) { int16_t lastOffset = -1; bool lcdRefresh = true; ButtonState lastButtonState = BUTTON_NONE; + + if (menu[currentScreen].draw.func != NULL) { + OLED::use_second_buffer(); + OLED::setFont(0); + OLED::setCursor(0, 0); + OLED::clearScreen(); + menu[currentScreen].draw.func(); + OLED::presentSecondScreenBufferAnimated(); + } while ((menu[currentScreen].draw.func != NULL) && earlyExit == false) { OLED::setFont(0); @@ -871,10 +880,18 @@ void gui_Menu(const menuitem *menu) { case BUTTON_F_SHORT: // increment if (descriptionStart == 0) { - if (menu[currentScreen].incrementHandler.func != NULL) + if (menu[currentScreen].incrementHandler.func != NULL) { menu[currentScreen].incrementHandler.func(); - else + // MARK: Might jump in submenu here + OLED::use_second_buffer(); + OLED::setFont(0); + OLED::setCursor(0, 0); + OLED::clearScreen(); + menu[currentScreen].draw.func(); + OLED::presentSecondScreenBufferAnimatedBack(); + } else { earlyExit = true; + } } else descriptionStart = 0; break; @@ -920,7 +937,7 @@ void gui_Menu(const menuitem *menu) { osDelay(40); lcdRefresh = false; } - if ((xTaskGetTickCount() - lastButtonTime) > (1000 * 30)) { + if ((xTaskGetTickCount() - lastButtonTime) > (100 * 30)) { // If user has not pressed any buttons in 30 seconds, exit back a menu layer // This will trickle the user back to the main screen eventually earlyExit = true; From ec6140317c9a57cab5c8f8252381e7419b9310d4 Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Mon, 6 Apr 2020 17:38:24 +0200 Subject: [PATCH 02/10] Remove `secondFrameBuffer` and instead add `set_framebuffer` method --- workspace/TS100/Core/Inc/OLED.hpp | 4 +--- workspace/TS100/Core/Src/OLED.cpp | 30 +++++++++++++++--------------- workspace/TS100/Core/Src/gui.cpp | 3 ++- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/workspace/TS100/Core/Inc/OLED.hpp b/workspace/TS100/Core/Inc/OLED.hpp index 2bbd23a2..3d5dfda8 100644 --- a/workspace/TS100/Core/Inc/OLED.hpp +++ b/workspace/TS100/Core/Inc/OLED.hpp @@ -101,8 +101,7 @@ public: static void drawHeatSymbol(uint8_t state); static void presentSecondScreenBufferAnimatedBack(); static void presentSecondScreenBufferAnimated(); - static void use_first_buffer(); - static void use_second_buffer(); + static void set_framebuffer(uint8_t *buffer); private: static void drawChar(char c); // Draw a character to a specific location static const uint8_t* currentFont;// Pointer to the current font used for rendering to the buffer @@ -114,7 +113,6 @@ private: static int16_t cursor_x, cursor_y; static uint8_t displayOffset; static uint8_t screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer - static uint8_t secondFrameBuffer[OLED_WIDTH * 2]; // The second frame buffer }; #endif /* OLED_HPP_ */ diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index 00e35974..2881e39b 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -24,7 +24,6 @@ uint8_t OLED::fontWidth, OLED::fontHeight; int16_t OLED::cursor_x, OLED::cursor_y; uint8_t OLED::displayOffset; uint8_t OLED::screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer -uint8_t OLED::secondFrameBuffer[OLED_WIDTH * 2]; // The second frame buffer /*Setup params for the OLED screen*/ /*http://www.displayfuture.com/Display/datasheet/controller/SSD1307.pdf*/ @@ -86,14 +85,15 @@ void OLED::initialize() { sizeof(OLED_Setup_Array)); } -void OLED::use_first_buffer() { - firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; - secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; -} - -void OLED::use_second_buffer() { - firstStripPtr = &secondFrameBuffer[0]; - secondStripPtr = &secondFrameBuffer[OLED_WIDTH]; +void OLED::set_framebuffer(uint8_t *buffer) { + if (buffer == NULL) { + firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; + secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; + return; + } + + firstStripPtr = &buffer[0]; + secondStripPtr = &buffer[OLED_WIDTH]; } /* @@ -118,7 +118,9 @@ void OLED::drawChar(char c) { } void OLED::presentSecondScreenBufferAnimatedBack() { - OLED::use_first_buffer(); + uint8_t *firstBackStripPtr = &firstStripPtr[0]; + uint8_t *secondBackStripPtr = &secondStripPtr[0]; + set_framebuffer(NULL); uint32_t totalDuration = 50; @@ -138,8 +140,6 @@ void OLED::presentSecondScreenBufferAnimatedBack() { offset = progress; - uint8_t *firstBackStripPtr = &secondFrameBuffer[0]; - uint8_t *secondBackStripPtr = &secondFrameBuffer[OLED_WIDTH]; for (uint8_t i = 0; i < progress; i++) { firstStripPtr[i] = firstBackStripPtr[(i - progress) + OLED_WIDTH]; @@ -152,7 +152,9 @@ void OLED::presentSecondScreenBufferAnimatedBack() { } void OLED::presentSecondScreenBufferAnimated() { - OLED::use_first_buffer(); + uint8_t *firstBackStripPtr = &firstStripPtr[0]; + uint8_t *secondBackStripPtr = &secondStripPtr[0]; + set_framebuffer(NULL); uint32_t totalDuration = 50; @@ -172,8 +174,6 @@ void OLED::presentSecondScreenBufferAnimated() { offset = progress; - uint8_t *firstBackStripPtr = &secondFrameBuffer[0]; - uint8_t *secondBackStripPtr = &secondFrameBuffer[OLED_WIDTH]; for (uint8_t i = OLED_WIDTH - progress; i < OLED_WIDTH; i++) { firstStripPtr[i] = firstBackStripPtr[i - (OLED_WIDTH - progress)]; diff --git a/workspace/TS100/Core/Src/gui.cpp b/workspace/TS100/Core/Src/gui.cpp index 99cdcd4f..f2a67f55 100644 --- a/workspace/TS100/Core/Src/gui.cpp +++ b/workspace/TS100/Core/Src/gui.cpp @@ -825,7 +825,8 @@ void gui_Menu(const menuitem *menu) { ButtonState lastButtonState = BUTTON_NONE; if (menu[currentScreen].draw.func != NULL) { - OLED::use_second_buffer(); + uint8_t secondFrameBuffer[OLED_WIDTH * 2]; + OLED::set_framebuffer(secondFrameBuffer); OLED::setFont(0); OLED::setCursor(0, 0); OLED::clearScreen(); From bbb724e8f04738eb28052bcaacda33fa006d66df Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Mon, 6 Apr 2020 17:39:05 +0200 Subject: [PATCH 03/10] Only play navigation animation when menus changed --- workspace/TS100/Core/Src/gui.cpp | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/workspace/TS100/Core/Src/gui.cpp b/workspace/TS100/Core/Src/gui.cpp index f2a67f55..0d57f3e4 100644 --- a/workspace/TS100/Core/Src/gui.cpp +++ b/workspace/TS100/Core/Src/gui.cpp @@ -823,6 +823,8 @@ void gui_Menu(const menuitem *menu) { int16_t lastOffset = -1; bool lcdRefresh = true; ButtonState lastButtonState = BUTTON_NONE; + static bool enterGUIMenu = true; + enterGUIMenu = true; if (menu[currentScreen].draw.func != NULL) { uint8_t secondFrameBuffer[OLED_WIDTH * 2]; @@ -882,14 +884,18 @@ void gui_Menu(const menuitem *menu) { // increment if (descriptionStart == 0) { if (menu[currentScreen].incrementHandler.func != NULL) { + enterGUIMenu = false; menu[currentScreen].incrementHandler.func(); - // MARK: Might jump in submenu here - OLED::use_second_buffer(); - OLED::setFont(0); - OLED::setCursor(0, 0); - OLED::clearScreen(); - menu[currentScreen].draw.func(); - OLED::presentSecondScreenBufferAnimatedBack(); + + if (enterGUIMenu) { + uint8_t secondFrameBuffer[OLED_WIDTH * 2]; + OLED::set_framebuffer(secondFrameBuffer); + OLED::setFont(0); + OLED::setCursor(0, 0); + OLED::clearScreen(); + menu[currentScreen].draw.func(); + OLED::presentSecondScreenBufferAnimatedBack(); + } } else { earlyExit = true; } From 172eea4909dbf0b0bc287cffb5144d30bf361904 Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Mon, 6 Apr 2020 17:40:01 +0200 Subject: [PATCH 04/10] Add ease in / out and cleanup animation methods --- workspace/TS100/Core/Src/OLED.cpp | 64 ++++++++++++++++++------------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index 2881e39b..405786b7 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -62,6 +62,14 @@ uint8_t OLED_Setup_Array[] = { const uint8_t REFRESH_COMMANDS[17] = { 0x80, 0xAF, 0x80, 0x21, 0x80, 0x20, 0x80, 0x7F, 0x80, 0xC0, 0x80, 0x22, 0x80, 0x00, 0x80, 0x01, 0x40 }; +static uint8_t easeInOutTiming(uint8_t t) { + return t * t * (300 - 2 * t) / 10000; +} + +static uint8_t lerp(uint8_t a, uint8_t b, uint8_t t) { + return a + t * (b - a) / 100; +} + void OLED::initialize() { cursor_x = cursor_y = 0; currentFont = USER_FONT_12; @@ -123,28 +131,30 @@ void OLED::presentSecondScreenBufferAnimatedBack() { set_framebuffer(NULL); uint32_t totalDuration = 50; - uint32_t duration = 0; uint32_t start = xTaskGetTickCount(); uint8_t offset = 0; - while (duration <= totalDuration) - { + while (duration <= totalDuration) { duration = xTaskGetTickCount() - start; - uint8_t progress = (duration * OLED_WIDTH) / totalDuration; - - for (uint8_t i = OLED_WIDTH - 1; i > progress; i--) { - firstStripPtr[i] = firstStripPtr[(i - progress) + offset]; - secondStripPtr[i] = secondStripPtr[(i - progress) + offset]; + uint8_t progress = (duration * 100) / totalDuration; + progress = easeInOutTiming(progress); + progress = lerp(0, OLED_WIDTH, progress); + if (progress > OLED_WIDTH) { + progress = OLED_WIDTH; } + memmove(&firstStripPtr[progress], &firstStripPtr[offset], OLED_WIDTH - progress); + memmove(&secondStripPtr[progress], &secondStripPtr[offset], OLED_WIDTH - progress); offset = progress; - - for (uint8_t i = 0; i < progress; i++) { - firstStripPtr[i] = firstBackStripPtr[(i - progress) + OLED_WIDTH]; - secondStripPtr[i] = secondBackStripPtr[(i - progress) + OLED_WIDTH]; - } + memmove( + &firstStripPtr[0], + &firstBackStripPtr[OLED_WIDTH - progress], + progress); + memmove(&secondStripPtr[0], + &secondBackStripPtr[OLED_WIDTH - progress], + progress); refresh(); osDelay(40); @@ -157,28 +167,30 @@ void OLED::presentSecondScreenBufferAnimated() { set_framebuffer(NULL); uint32_t totalDuration = 50; - uint32_t duration = 0; uint32_t start = xTaskGetTickCount(); uint8_t offset = 0; - while (duration < totalDuration) - { + while (duration < totalDuration) { duration = xTaskGetTickCount() - start; - uint8_t progress = (duration * OLED_WIDTH) / totalDuration; - - for (uint8_t i = 0; i < OLED_WIDTH - progress; i++) { - firstStripPtr[i] = firstStripPtr[i + progress - offset]; - secondStripPtr[i] = secondStripPtr[i + progress - offset]; + uint8_t progress = (duration * 100) / totalDuration; + progress = easeInOutTiming(progress); + progress = lerp(0, OLED_WIDTH, progress); + if (progress > OLED_WIDTH) { + progress = OLED_WIDTH; } + memmove(&firstStripPtr[0], &firstStripPtr[progress - offset], OLED_WIDTH - progress); + memmove(&secondStripPtr[0], &secondStripPtr[progress - offset], OLED_WIDTH - progress); offset = progress; - - for (uint8_t i = OLED_WIDTH - progress; i < OLED_WIDTH; i++) { - firstStripPtr[i] = firstBackStripPtr[i - (OLED_WIDTH - progress)]; - secondStripPtr[i] = secondBackStripPtr[i - (OLED_WIDTH - progress)]; - } + memmove( + &firstStripPtr[OLED_WIDTH - progress], + &firstBackStripPtr[0], + progress); + memmove(&secondStripPtr[OLED_WIDTH - progress], + &secondBackStripPtr[0], + progress); refresh(); osDelay(40); From f3d0bc39651a1068c69d34820c840f8d3544fd98 Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Mon, 6 Apr 2020 18:49:10 +0200 Subject: [PATCH 05/10] Simplified animation methods --- workspace/TS100/Core/Inc/OLED.hpp | 3 +- workspace/TS100/Core/Src/OLED.cpp | 79 +++++++++++-------------------- workspace/TS100/Core/Src/gui.cpp | 19 +++++--- 3 files changed, 42 insertions(+), 59 deletions(-) diff --git a/workspace/TS100/Core/Inc/OLED.hpp b/workspace/TS100/Core/Inc/OLED.hpp index 3d5dfda8..8fcacefe 100644 --- a/workspace/TS100/Core/Inc/OLED.hpp +++ b/workspace/TS100/Core/Inc/OLED.hpp @@ -99,8 +99,7 @@ public: static void drawFilledRect(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1, bool clear); static void drawHeatSymbol(uint8_t state); - static void presentSecondScreenBufferAnimatedBack(); - static void presentSecondScreenBufferAnimated(); + static void transitionToContents(uint8_t *framebuffer, bool forwardNavigation); static void set_framebuffer(uint8_t *buffer); private: static void drawChar(char c); // Draw a character to a specific location diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index 405786b7..8d4e2f77 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -125,72 +125,49 @@ void OLED::drawChar(char c) { cursor_x += fontWidth; } -void OLED::presentSecondScreenBufferAnimatedBack() { - uint8_t *firstBackStripPtr = &firstStripPtr[0]; - uint8_t *secondBackStripPtr = &secondStripPtr[0]; - set_framebuffer(NULL); +/** + * Plays a transition animation between two framebuffers. + * @param framebuffer Second framebuffer to use for animation. + * @param forward Direction of the navigation animation. + * + * If forward is true, this displays a forward navigation to the second framebuffer contents. + * Otherwise a rewinding navigation animation is shown to the second framebuffer contents. + */ +void OLED::transitionToContents(uint8_t *framebuffer, bool forwardNavigation) { + uint8_t *firstBackStripPtr = &framebuffer[0]; + uint8_t *secondBackStripPtr = &framebuffer[OLED_WIDTH]; - uint32_t totalDuration = 50; + uint32_t totalDuration = 50; // 500ms uint32_t duration = 0; uint32_t start = xTaskGetTickCount(); uint8_t offset = 0; + while (duration <= totalDuration) { duration = xTaskGetTickCount() - start; - - uint8_t progress = (duration * 100) / totalDuration; + uint8_t progress = duration * 100 / totalDuration; progress = easeInOutTiming(progress); progress = lerp(0, OLED_WIDTH, progress); if (progress > OLED_WIDTH) { progress = OLED_WIDTH; } - memmove(&firstStripPtr[progress], &firstStripPtr[offset], OLED_WIDTH - progress); - memmove(&secondStripPtr[progress], &secondStripPtr[offset], OLED_WIDTH - progress); + // When forward, current contents move to the left out. + // Otherwise the contents move to the right out. + uint8_t oldStart = forwardNavigation ? 0 : progress; + uint8_t oldPrevious = forwardNavigation ? progress - offset : offset; + + // Content from the second framebuffer moves in from the right (forward) + // or from the left (not forward). + uint8_t newStart = forwardNavigation ? OLED_WIDTH - progress : 0; + uint8_t newEnd = forwardNavigation ? 0 : OLED_WIDTH - progress; + offset = progress; - - memmove( - &firstStripPtr[0], - &firstBackStripPtr[OLED_WIDTH - progress], - progress); - memmove(&secondStripPtr[0], - &secondBackStripPtr[OLED_WIDTH - progress], - progress); - refresh(); - osDelay(40); - } -} - -void OLED::presentSecondScreenBufferAnimated() { - uint8_t *firstBackStripPtr = &firstStripPtr[0]; - uint8_t *secondBackStripPtr = &secondStripPtr[0]; - set_framebuffer(NULL); - - uint32_t totalDuration = 50; - uint32_t duration = 0; - uint32_t start = xTaskGetTickCount(); - uint8_t offset = 0; - while (duration < totalDuration) { - duration = xTaskGetTickCount() - start; + memmove(&firstStripPtr[oldStart], &firstStripPtr[oldPrevious], OLED_WIDTH - progress); + memmove(&secondStripPtr[oldStart], &secondStripPtr[oldPrevious], OLED_WIDTH - progress); - uint8_t progress = (duration * 100) / totalDuration; - progress = easeInOutTiming(progress); - progress = lerp(0, OLED_WIDTH, progress); - if (progress > OLED_WIDTH) { - progress = OLED_WIDTH; - } - - memmove(&firstStripPtr[0], &firstStripPtr[progress - offset], OLED_WIDTH - progress); - memmove(&secondStripPtr[0], &secondStripPtr[progress - offset], OLED_WIDTH - progress); - offset = progress; - - memmove( - &firstStripPtr[OLED_WIDTH - progress], - &firstBackStripPtr[0], - progress); - memmove(&secondStripPtr[OLED_WIDTH - progress], - &secondBackStripPtr[0], - progress); + memmove(&firstStripPtr[newStart], &firstBackStripPtr[newEnd], progress); + memmove(&secondStripPtr[newStart], &secondBackStripPtr[newEnd], progress); refresh(); osDelay(40); diff --git a/workspace/TS100/Core/Src/gui.cpp b/workspace/TS100/Core/Src/gui.cpp index 0d57f3e4..34779be6 100644 --- a/workspace/TS100/Core/Src/gui.cpp +++ b/workspace/TS100/Core/Src/gui.cpp @@ -826,14 +826,20 @@ void gui_Menu(const menuitem *menu) { static bool enterGUIMenu = true; enterGUIMenu = true; + // Animated menu opening. if (menu[currentScreen].draw.func != NULL) { - uint8_t secondFrameBuffer[OLED_WIDTH * 2]; - OLED::set_framebuffer(secondFrameBuffer); + // This menu is drawn in a secondary framebuffer. + // Then we play a transition from the current primary + // framebuffer to the new buffer. + // The extra buffer is discarded at the end of the transition. + uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; + OLED::set_framebuffer(secondaryFrameBuffer); OLED::setFont(0); OLED::setCursor(0, 0); OLED::clearScreen(); menu[currentScreen].draw.func(); - OLED::presentSecondScreenBufferAnimated(); + OLED::set_framebuffer(NULL); + OLED::transitionToContents(secondaryFrameBuffer, true); } while ((menu[currentScreen].draw.func != NULL) && earlyExit == false) { @@ -888,13 +894,14 @@ void gui_Menu(const menuitem *menu) { menu[currentScreen].incrementHandler.func(); if (enterGUIMenu) { - uint8_t secondFrameBuffer[OLED_WIDTH * 2]; - OLED::set_framebuffer(secondFrameBuffer); + uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; + OLED::set_framebuffer(secondaryFrameBuffer); OLED::setFont(0); OLED::setCursor(0, 0); OLED::clearScreen(); menu[currentScreen].draw.func(); - OLED::presentSecondScreenBufferAnimatedBack(); + OLED::set_framebuffer(NULL); + OLED::transitionToContents(secondaryFrameBuffer, false); } } else { earlyExit = true; From 330c4868c2c657a0135faf3cdb05f81a8c9e0f2a Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Mon, 6 Apr 2020 18:55:48 +0200 Subject: [PATCH 06/10] Fix navigation animation not playing sometimes. --- workspace/TS100/Core/Src/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/workspace/TS100/Core/Src/gui.cpp b/workspace/TS100/Core/Src/gui.cpp index 34779be6..5a5aa30c 100644 --- a/workspace/TS100/Core/Src/gui.cpp +++ b/workspace/TS100/Core/Src/gui.cpp @@ -903,6 +903,7 @@ void gui_Menu(const menuitem *menu) { OLED::set_framebuffer(NULL); OLED::transitionToContents(secondaryFrameBuffer, false); } + enterGUIMenu = true; } else { earlyExit = true; } From 3d33a6ea57e1d39c016872c8be36a36f20e0ef70 Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Fri, 17 Apr 2020 09:46:01 +0200 Subject: [PATCH 07/10] Fix whitespaces --- workspace/TS100/Core/Inc/OLED.hpp | 4 +- workspace/TS100/Core/Src/OLED.cpp | 96 +++++++++++++++---------------- workspace/TS100/Core/Src/gui.cpp | 72 +++++++++++------------ 3 files changed, 86 insertions(+), 86 deletions(-) diff --git a/workspace/TS100/Core/Inc/OLED.hpp b/workspace/TS100/Core/Inc/OLED.hpp index 5beb156d..e6077494 100644 --- a/workspace/TS100/Core/Inc/OLED.hpp +++ b/workspace/TS100/Core/Inc/OLED.hpp @@ -101,8 +101,8 @@ public: bool clear); static void drawHeatSymbol(uint8_t state); static void drawScrollIndicator(uint8_t p, uint8_t h); // Draws a scrolling position indicator - static void transitionToContents(uint8_t *framebuffer, bool forwardNavigation); - static void set_framebuffer(uint8_t *buffer); + static void transitionToContents(uint8_t *framebuffer, bool forwardNavigation); + static void set_framebuffer(uint8_t *buffer); private: static void drawChar(char c); // Draw a character to a specific location static const uint8_t* currentFont;// Pointer to the current font used for rendering to the buffer diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index 37e55762..5893556c 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -63,11 +63,11 @@ const uint8_t REFRESH_COMMANDS[17] = { 0x80, 0xAF, 0x80, 0x21, 0x80, 0x20, 0x80, 0x7F, 0x80, 0xC0, 0x80, 0x22, 0x80, 0x00, 0x80, 0x01, 0x40 }; static uint8_t easeInOutTiming(uint8_t t) { - return t * t * (300 - 2 * t) / 10000; + return t * t * (300 - 2 * t) / 10000; } static uint8_t lerp(uint8_t a, uint8_t b, uint8_t t) { - return a + t * (b - a) / 100; + return a + t * (b - a) / 100; } void OLED::initialize() { @@ -94,14 +94,14 @@ void OLED::initialize() { } void OLED::set_framebuffer(uint8_t *buffer) { - if (buffer == NULL) { - firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; - secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; - return; - } - - firstStripPtr = &buffer[0]; - secondStripPtr = &buffer[OLED_WIDTH]; + if (buffer == NULL) { + firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; + secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; + return; + } + + firstStripPtr = &buffer[0]; + secondStripPtr = &buffer[OLED_WIDTH]; } /* @@ -153,44 +153,44 @@ void OLED::drawScrollIndicator(uint8_t y, uint8_t height) { * Otherwise a rewinding navigation animation is shown to the second framebuffer contents. */ void OLED::transitionToContents(uint8_t *framebuffer, bool forwardNavigation) { - uint8_t *firstBackStripPtr = &framebuffer[0]; - uint8_t *secondBackStripPtr = &framebuffer[OLED_WIDTH]; - - uint32_t totalDuration = 50; // 500ms - uint32_t duration = 0; - uint32_t start = xTaskGetTickCount(); - uint8_t offset = 0; - - while (duration <= totalDuration) { - duration = xTaskGetTickCount() - start; - uint8_t progress = duration * 100 / totalDuration; - progress = easeInOutTiming(progress); - progress = lerp(0, OLED_WIDTH, progress); - if (progress > OLED_WIDTH) { - progress = OLED_WIDTH; - } - - // When forward, current contents move to the left out. - // Otherwise the contents move to the right out. - uint8_t oldStart = forwardNavigation ? 0 : progress; - uint8_t oldPrevious = forwardNavigation ? progress - offset : offset; - - // Content from the second framebuffer moves in from the right (forward) - // or from the left (not forward). - uint8_t newStart = forwardNavigation ? OLED_WIDTH - progress : 0; - uint8_t newEnd = forwardNavigation ? 0 : OLED_WIDTH - progress; - - offset = progress; - - memmove(&firstStripPtr[oldStart], &firstStripPtr[oldPrevious], OLED_WIDTH - progress); - memmove(&secondStripPtr[oldStart], &secondStripPtr[oldPrevious], OLED_WIDTH - progress); - - memmove(&firstStripPtr[newStart], &firstBackStripPtr[newEnd], progress); - memmove(&secondStripPtr[newStart], &secondBackStripPtr[newEnd], progress); - - refresh(); - osDelay(40); - } + uint8_t *firstBackStripPtr = &framebuffer[0]; + uint8_t *secondBackStripPtr = &framebuffer[OLED_WIDTH]; + + uint32_t totalDuration = 50; // 500ms + uint32_t duration = 0; + uint32_t start = xTaskGetTickCount(); + uint8_t offset = 0; + + while (duration <= totalDuration) { + duration = xTaskGetTickCount() - start; + uint8_t progress = duration * 100 / totalDuration; + progress = easeInOutTiming(progress); + progress = lerp(0, OLED_WIDTH, progress); + if (progress > OLED_WIDTH) { + progress = OLED_WIDTH; + } + + // When forward, current contents move to the left out. + // Otherwise the contents move to the right out. + uint8_t oldStart = forwardNavigation ? 0 : progress; + uint8_t oldPrevious = forwardNavigation ? progress - offset : offset; + + // Content from the second framebuffer moves in from the right (forward) + // or from the left (not forward). + uint8_t newStart = forwardNavigation ? OLED_WIDTH - progress : 0; + uint8_t newEnd = forwardNavigation ? 0 : OLED_WIDTH - progress; + + offset = progress; + + memmove(&firstStripPtr[oldStart], &firstStripPtr[oldPrevious], OLED_WIDTH - progress); + memmove(&secondStripPtr[oldStart], &secondStripPtr[oldPrevious], OLED_WIDTH - progress); + + memmove(&firstStripPtr[newStart], &firstBackStripPtr[newEnd], progress); + memmove(&secondStripPtr[newStart], &secondBackStripPtr[newEnd], progress); + + refresh(); + osDelay(40); + } } void OLED::setRotation(bool leftHanded) { diff --git a/workspace/TS100/Core/Src/gui.cpp b/workspace/TS100/Core/Src/gui.cpp index 4a5221f3..019ed102 100644 --- a/workspace/TS100/Core/Src/gui.cpp +++ b/workspace/TS100/Core/Src/gui.cpp @@ -826,29 +826,29 @@ void gui_Menu(const menuitem *menu) { int16_t lastOffset = -1; bool lcdRefresh = true; ButtonState lastButtonState = BUTTON_NONE; - static bool enterGUIMenu = true; - enterGUIMenu = true; - uint8_t scrollContentSize = 0; - + static bool enterGUIMenu = true; + enterGUIMenu = true; + uint8_t scrollContentSize = 0; + for (uint8_t i = 0; menu[i].draw.func != NULL; i++) { scrollContentSize += 1; } - - // Animated menu opening. - if (menu[currentScreen].draw.func != NULL) { - // This menu is drawn in a secondary framebuffer. - // Then we play a transition from the current primary - // framebuffer to the new buffer. - // The extra buffer is discarded at the end of the transition. - uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; - OLED::set_framebuffer(secondaryFrameBuffer); - OLED::setFont(0); - OLED::setCursor(0, 0); - OLED::clearScreen(); - menu[currentScreen].draw.func(); - OLED::set_framebuffer(NULL); - OLED::transitionToContents(secondaryFrameBuffer, true); - } + + // Animated menu opening. + if (menu[currentScreen].draw.func != NULL) { + // This menu is drawn in a secondary framebuffer. + // Then we play a transition from the current primary + // framebuffer to the new buffer. + // The extra buffer is discarded at the end of the transition. + uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; + OLED::set_framebuffer(secondaryFrameBuffer); + OLED::setFont(0); + OLED::setCursor(0, 0); + OLED::clearScreen(); + menu[currentScreen].draw.func(); + OLED::set_framebuffer(NULL); + OLED::transitionToContents(secondaryFrameBuffer, true); + } while ((menu[currentScreen].draw.func != NULL) && earlyExit == false) { OLED::setFont(0); @@ -900,24 +900,24 @@ void gui_Menu(const menuitem *menu) { case BUTTON_F_SHORT: // increment if (descriptionStart == 0) { - if (menu[currentScreen].incrementHandler.func != NULL) { - enterGUIMenu = false; + if (menu[currentScreen].incrementHandler.func != NULL) { + enterGUIMenu = false; menu[currentScreen].incrementHandler.func(); - - if (enterGUIMenu) { - uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; - OLED::set_framebuffer(secondaryFrameBuffer); - OLED::setFont(0); - OLED::setCursor(0, 0); - OLED::clearScreen(); - menu[currentScreen].draw.func(); - OLED::set_framebuffer(NULL); - OLED::transitionToContents(secondaryFrameBuffer, false); - } - enterGUIMenu = true; - } else { + + if (enterGUIMenu) { + uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; + OLED::set_framebuffer(secondaryFrameBuffer); + OLED::setFont(0); + OLED::setCursor(0, 0); + OLED::clearScreen(); + menu[currentScreen].draw.func(); + OLED::set_framebuffer(NULL); + OLED::transitionToContents(secondaryFrameBuffer, false); + } + enterGUIMenu = true; + } else { earlyExit = true; - } + } } else descriptionStart = 0; break; From d69293342d280cee9cee9ac3a1db090cfd7ea51d Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Sat, 25 Apr 2020 11:43:14 +0200 Subject: [PATCH 08/10] Add secondary framebuffer, instead of allocating on stack --- workspace/TS100/Core/Inc/OLED.hpp | 6 ++++-- workspace/TS100/Core/Src/OLED.cpp | 20 ++++++++++++++------ workspace/TS100/Core/Src/gui.cpp | 14 ++++++-------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/workspace/TS100/Core/Inc/OLED.hpp b/workspace/TS100/Core/Inc/OLED.hpp index e6077494..6fffb1fe 100644 --- a/workspace/TS100/Core/Inc/OLED.hpp +++ b/workspace/TS100/Core/Inc/OLED.hpp @@ -101,10 +101,11 @@ public: bool clear); static void drawHeatSymbol(uint8_t state); static void drawScrollIndicator(uint8_t p, uint8_t h); // Draws a scrolling position indicator - static void transitionToContents(uint8_t *framebuffer, bool forwardNavigation); - static void set_framebuffer(uint8_t *buffer); + static void transitionSecondaryFramebuffer(bool forwardNavigation); + static void useSecondaryFramebuffer(bool useSecondary); private: static void drawChar(char c); // Draw a character to a specific location + static void setFramebuffer(uint8_t *buffer); static const uint8_t* currentFont;// Pointer to the current font used for rendering to the buffer static uint8_t* firstStripPtr; // Pointers to the strips to allow for buffer having extra content static uint8_t* secondStripPtr; //Pointers to the strips @@ -114,6 +115,7 @@ private: static int16_t cursor_x, cursor_y; static uint8_t displayOffset; static uint8_t screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer + static uint8_t secondFrameBuffer[OLED_WIDTH * 2]; }; #endif /* OLED_HPP_ */ diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index 5893556c..91d47149 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -24,6 +24,7 @@ uint8_t OLED::fontWidth, OLED::fontHeight; int16_t OLED::cursor_x, OLED::cursor_y; uint8_t OLED::displayOffset; uint8_t OLED::screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer +uint8_t OLED::secondFrameBuffer[OLED_WIDTH * 2]; /*Setup params for the OLED screen*/ /*http://www.displayfuture.com/Display/datasheet/controller/SSD1307.pdf*/ @@ -93,7 +94,7 @@ void OLED::initialize() { sizeof(OLED_Setup_Array)); } -void OLED::set_framebuffer(uint8_t *buffer) { +void OLED::setFramebuffer(uint8_t *buffer) { if (buffer == NULL) { firstStripPtr = &screenBuffer[FRAMEBUFFER_START]; secondStripPtr = &screenBuffer[FRAMEBUFFER_START + OLED_WIDTH]; @@ -146,15 +147,14 @@ void OLED::drawScrollIndicator(uint8_t y, uint8_t height) { /** * Plays a transition animation between two framebuffers. - * @param framebuffer Second framebuffer to use for animation. - * @param forward Direction of the navigation animation. + * @param forwardNavigation Direction of the navigation animation. * * If forward is true, this displays a forward navigation to the second framebuffer contents. * Otherwise a rewinding navigation animation is shown to the second framebuffer contents. */ -void OLED::transitionToContents(uint8_t *framebuffer, bool forwardNavigation) { - uint8_t *firstBackStripPtr = &framebuffer[0]; - uint8_t *secondBackStripPtr = &framebuffer[OLED_WIDTH]; +void OLED::transitionSecondaryFramebuffer(bool forwardNavigation) { + uint8_t *firstBackStripPtr = &secondFrameBuffer[0]; + uint8_t *secondBackStripPtr = &secondFrameBuffer[OLED_WIDTH]; uint32_t totalDuration = 50; // 500ms uint32_t duration = 0; @@ -193,6 +193,14 @@ void OLED::transitionToContents(uint8_t *framebuffer, bool forwardNavigation) { } } +void OLED::useSecondaryFramebuffer(bool useSecondary) { + if (useSecondary) { + setFramebuffer(secondFrameBuffer); + } else { + setFramebuffer(NULL); + } +} + void OLED::setRotation(bool leftHanded) { #ifdef MODEL_TS80 leftHanded = !leftHanded; diff --git a/workspace/TS100/Core/Src/gui.cpp b/workspace/TS100/Core/Src/gui.cpp index 019ed102..a38aae4d 100644 --- a/workspace/TS100/Core/Src/gui.cpp +++ b/workspace/TS100/Core/Src/gui.cpp @@ -840,14 +840,13 @@ void gui_Menu(const menuitem *menu) { // Then we play a transition from the current primary // framebuffer to the new buffer. // The extra buffer is discarded at the end of the transition. - uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; - OLED::set_framebuffer(secondaryFrameBuffer); + OLED::useSecondaryFramebuffer(true); OLED::setFont(0); OLED::setCursor(0, 0); OLED::clearScreen(); menu[currentScreen].draw.func(); - OLED::set_framebuffer(NULL); - OLED::transitionToContents(secondaryFrameBuffer, true); + OLED::useSecondaryFramebuffer(false); + OLED::transitionSecondaryFramebuffer(true); } while ((menu[currentScreen].draw.func != NULL) && earlyExit == false) { @@ -905,14 +904,13 @@ void gui_Menu(const menuitem *menu) { menu[currentScreen].incrementHandler.func(); if (enterGUIMenu) { - uint8_t secondaryFrameBuffer[OLED_WIDTH * 2]; - OLED::set_framebuffer(secondaryFrameBuffer); + OLED::useSecondaryFramebuffer(true); OLED::setFont(0); OLED::setCursor(0, 0); OLED::clearScreen(); menu[currentScreen].draw.func(); - OLED::set_framebuffer(NULL); - OLED::transitionToContents(secondaryFrameBuffer, false); + OLED::useSecondaryFramebuffer(false); + OLED::transitionSecondaryFramebuffer(false); } enterGUIMenu = true; } else { From dbba4f999a7a2c59f983b3ca562ace1d89cac3a4 Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Sat, 25 Apr 2020 11:46:34 +0200 Subject: [PATCH 09/10] Use tabs instead of spaces --- workspace/TS100/Core/Inc/OLED.hpp | 6 +++--- workspace/TS100/Core/Src/OLED.cpp | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/workspace/TS100/Core/Inc/OLED.hpp b/workspace/TS100/Core/Inc/OLED.hpp index 6fffb1fe..5994e9f6 100644 --- a/workspace/TS100/Core/Inc/OLED.hpp +++ b/workspace/TS100/Core/Inc/OLED.hpp @@ -102,10 +102,10 @@ public: static void drawHeatSymbol(uint8_t state); static void drawScrollIndicator(uint8_t p, uint8_t h); // Draws a scrolling position indicator static void transitionSecondaryFramebuffer(bool forwardNavigation); - static void useSecondaryFramebuffer(bool useSecondary); + static void useSecondaryFramebuffer(bool useSecondary); private: static void drawChar(char c); // Draw a character to a specific location - static void setFramebuffer(uint8_t *buffer); + static void setFramebuffer(uint8_t *buffer); static const uint8_t* currentFont;// Pointer to the current font used for rendering to the buffer static uint8_t* firstStripPtr; // Pointers to the strips to allow for buffer having extra content static uint8_t* secondStripPtr; //Pointers to the strips @@ -115,7 +115,7 @@ private: static int16_t cursor_x, cursor_y; static uint8_t displayOffset; static uint8_t screenBuffer[16 + (OLED_WIDTH * 2) + 10]; // The data buffer - static uint8_t secondFrameBuffer[OLED_WIDTH * 2]; + static uint8_t secondFrameBuffer[OLED_WIDTH * 2]; }; #endif /* OLED_HPP_ */ diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index 91d47149..313704ea 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -194,11 +194,11 @@ void OLED::transitionSecondaryFramebuffer(bool forwardNavigation) { } void OLED::useSecondaryFramebuffer(bool useSecondary) { - if (useSecondary) { - setFramebuffer(secondFrameBuffer); - } else { - setFramebuffer(NULL); - } + if (useSecondary) { + setFramebuffer(secondFrameBuffer); + } else { + setFramebuffer(NULL); + } } void OLED::setRotation(bool leftHanded) { From 7eb50e6d8a61e7468e1f13b40bb2e1a3a4fd0a22 Mon Sep 17 00:00:00 2001 From: Patrick Horlebein Date: Sat, 25 Apr 2020 12:27:54 +0200 Subject: [PATCH 10/10] Add documentation --- workspace/TS100/Core/Src/OLED.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/workspace/TS100/Core/Src/OLED.cpp b/workspace/TS100/Core/Src/OLED.cpp index 313704ea..3949b17a 100644 --- a/workspace/TS100/Core/Src/OLED.cpp +++ b/workspace/TS100/Core/Src/OLED.cpp @@ -63,10 +63,23 @@ uint8_t OLED_Setup_Array[] = { const uint8_t REFRESH_COMMANDS[17] = { 0x80, 0xAF, 0x80, 0x21, 0x80, 0x20, 0x80, 0x7F, 0x80, 0xC0, 0x80, 0x22, 0x80, 0x00, 0x80, 0x01, 0x40 }; + +/* + * Animation timing function that follows a bezier curve. + * @param t A given percentage value [0..<100] + * Returns a new percentage value with ease in and ease out. + * Original floating point formula: t * t * (3.0f - 2.0f * t); + */ static uint8_t easeInOutTiming(uint8_t t) { return t * t * (300 - 2 * t) / 10000; } +/* + * Returns the value between a and b, using a percentage value t. + * @param a The value associated with 0% + * @param b The value associated with 100% + * @param t The percentage [0..<100] + */ static uint8_t lerp(uint8_t a, uint8_t b, uint8_t t) { return a + t * (b - a) / 100; }