OLEDDisplayUi.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. /**
  2. * The MIT License (MIT)
  3. *
  4. * Copyright (c) 2016 by Daniel Eichhorn
  5. * Copyright (c) 2016 by Fabrice Weinberg
  6. *
  7. * Permission is hereby granted, free of charge, to any person obtaining a copy
  8. * of this software and associated documentation files (the "Software"), to deal
  9. * in the Software without restriction, including without limitation the rights
  10. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. * copies of the Software, and to permit persons to whom the Software is
  12. * furnished to do so, subject to the following conditions:
  13. *
  14. * The above copyright notice and this permission notice shall be included in all
  15. * copies or substantial portions of the Software.
  16. *
  17. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  23. * SOFTWARE.
  24. *
  25. */
  26. #include "OLEDDisplayUi.h"
  27. OLEDDisplayUi::OLEDDisplayUi(OLEDDisplay *display) {
  28. this->display = display;
  29. }
  30. void OLEDDisplayUi::init() {
  31. this->display->init();
  32. }
  33. void OLEDDisplayUi::setTargetFPS(uint8_t fps){
  34. float oldInterval = this->updateInterval;
  35. this->updateInterval = ((float) 1.0 / (float) fps) * 1000;
  36. // Calculate new ticksPerFrame
  37. float changeRatio = oldInterval / (float) this->updateInterval;
  38. this->ticksPerFrame *= changeRatio;
  39. this->ticksPerTransition *= changeRatio;
  40. }
  41. // -/------ Automatic controll ------\-
  42. void OLEDDisplayUi::enableAutoTransition(){
  43. this->autoTransition = true;
  44. }
  45. void OLEDDisplayUi::disableAutoTransition(){
  46. this->autoTransition = false;
  47. }
  48. void OLEDDisplayUi::setAutoTransitionForwards(){
  49. this->state.frameTransitionDirection = 1;
  50. this->lastTransitionDirection = 1;
  51. }
  52. void OLEDDisplayUi::setAutoTransitionBackwards(){
  53. this->state.frameTransitionDirection = -1;
  54. this->lastTransitionDirection = -1;
  55. }
  56. void OLEDDisplayUi::setTimePerFrame(uint16_t time){
  57. this->ticksPerFrame = (int) ( (float) time / (float) updateInterval);
  58. }
  59. void OLEDDisplayUi::setTimePerTransition(uint16_t time){
  60. this->ticksPerTransition = (int) ( (float) time / (float) updateInterval);
  61. }
  62. // -/------ Customize indicator position and style -------\-
  63. void OLEDDisplayUi::enableIndicator(){
  64. this->state.isIndicatorDrawen = true;
  65. }
  66. void OLEDDisplayUi::disableIndicator(){
  67. this->state.isIndicatorDrawen = false;
  68. }
  69. void OLEDDisplayUi::enableAllIndicators(){
  70. this->shouldDrawIndicators = true;
  71. }
  72. void OLEDDisplayUi::disableAllIndicators(){
  73. this->shouldDrawIndicators = false;
  74. }
  75. void OLEDDisplayUi::setIndicatorPosition(IndicatorPosition pos) {
  76. this->indicatorPosition = pos;
  77. }
  78. void OLEDDisplayUi::setIndicatorDirection(IndicatorDirection dir) {
  79. this->indicatorDirection = dir;
  80. }
  81. void OLEDDisplayUi::setActiveSymbol(const char* symbol) {
  82. this->activeSymbol = symbol;
  83. }
  84. void OLEDDisplayUi::setInactiveSymbol(const char* symbol) {
  85. this->inactiveSymbol = symbol;
  86. }
  87. // -/----- Frame settings -----\-
  88. void OLEDDisplayUi::setFrameAnimation(AnimationDirection dir) {
  89. this->frameAnimationDirection = dir;
  90. }
  91. void OLEDDisplayUi::setFrames(FrameCallback* frameFunctions, uint8_t frameCount) {
  92. this->frameFunctions = frameFunctions;
  93. this->frameCount = frameCount;
  94. this->resetState();
  95. }
  96. // -/----- Overlays ------\-
  97. void OLEDDisplayUi::setOverlays(OverlayCallback* overlayFunctions, uint8_t overlayCount){
  98. this->overlayFunctions = overlayFunctions;
  99. this->overlayCount = overlayCount;
  100. }
  101. // -/----- Loading Process -----\-
  102. void OLEDDisplayUi::setLoadingDrawFunction(LoadingDrawFunction loadingDrawFunction) {
  103. this->loadingDrawFunction = loadingDrawFunction;
  104. }
  105. void OLEDDisplayUi::runLoadingProcess(LoadingStage* stages, uint8_t stagesCount) {
  106. uint8_t progress = 0;
  107. uint8_t increment = 100 / stagesCount;
  108. for (uint8_t i = 0; i < stagesCount; i++) {
  109. display->clear();
  110. this->loadingDrawFunction(this->display, &stages[i], progress);
  111. display->display();
  112. stages[i].callback();
  113. progress += increment;
  114. yield();
  115. }
  116. display->clear();
  117. this->loadingDrawFunction(this->display, &stages[stagesCount-1], progress);
  118. display->display();
  119. delay(150);
  120. }
  121. // -/----- Manuel control -----\-
  122. void OLEDDisplayUi::nextFrame() {
  123. if (this->state.frameState != IN_TRANSITION) {
  124. this->state.manuelControll = true;
  125. this->state.frameState = IN_TRANSITION;
  126. this->state.ticksSinceLastStateSwitch = 0;
  127. this->lastTransitionDirection = this->state.frameTransitionDirection;
  128. this->state.frameTransitionDirection = 1;
  129. }
  130. }
  131. void OLEDDisplayUi::previousFrame() {
  132. if (this->state.frameState != IN_TRANSITION) {
  133. this->state.manuelControll = true;
  134. this->state.frameState = IN_TRANSITION;
  135. this->state.ticksSinceLastStateSwitch = 0;
  136. this->lastTransitionDirection = this->state.frameTransitionDirection;
  137. this->state.frameTransitionDirection = -1;
  138. }
  139. }
  140. void OLEDDisplayUi::switchToFrame(uint8_t frame) {
  141. if (frame >= this->frameCount) return;
  142. this->state.ticksSinceLastStateSwitch = 0;
  143. if (frame == this->state.currentFrame) return;
  144. this->state.frameState = FIXED;
  145. this->state.currentFrame = frame;
  146. this->state.isIndicatorDrawen = true;
  147. }
  148. void OLEDDisplayUi::transitionToFrame(uint8_t frame) {
  149. if (frame >= this->frameCount) return;
  150. this->state.ticksSinceLastStateSwitch = 0;
  151. if (frame == this->state.currentFrame) return;
  152. this->nextFrameNumber = frame;
  153. this->lastTransitionDirection = this->state.frameTransitionDirection;
  154. this->state.manuelControll = true;
  155. this->state.frameState = IN_TRANSITION;
  156. this->state.frameTransitionDirection = frame < this->state.currentFrame ? -1 : 1;
  157. }
  158. // -/----- State information -----\-
  159. OLEDDisplayUiState* OLEDDisplayUi::getUiState(){
  160. return &this->state;
  161. }
  162. int8_t OLEDDisplayUi::update(){
  163. long frameStart = millis();
  164. int8_t timeBudget = this->updateInterval - (frameStart - this->state.lastUpdate);
  165. if ( timeBudget <= 0) {
  166. // Implement frame skipping to ensure time budget is keept
  167. if (this->autoTransition && this->state.lastUpdate != 0) this->state.ticksSinceLastStateSwitch += ceil(-timeBudget / this->updateInterval);
  168. this->state.lastUpdate = frameStart;
  169. this->tick();
  170. }
  171. return this->updateInterval - (millis() - frameStart);
  172. }
  173. void OLEDDisplayUi::tick() {
  174. this->state.ticksSinceLastStateSwitch++;
  175. switch (this->state.frameState) {
  176. case IN_TRANSITION:
  177. if (this->state.ticksSinceLastStateSwitch >= this->ticksPerTransition){
  178. this->state.frameState = FIXED;
  179. this->state.currentFrame = getNextFrameNumber();
  180. this->state.ticksSinceLastStateSwitch = 0;
  181. this->nextFrameNumber = -1;
  182. }
  183. break;
  184. case FIXED:
  185. // Revert manuelControll
  186. if (this->state.manuelControll) {
  187. this->state.frameTransitionDirection = this->lastTransitionDirection;
  188. this->state.manuelControll = false;
  189. }
  190. if (this->state.ticksSinceLastStateSwitch >= this->ticksPerFrame){
  191. if (this->autoTransition){
  192. this->state.frameState = IN_TRANSITION;
  193. }
  194. this->state.ticksSinceLastStateSwitch = 0;
  195. }
  196. break;
  197. }
  198. this->display->clear();
  199. this->drawFrame();
  200. if (shouldDrawIndicators) {
  201. this->drawIndicator();
  202. }
  203. this->drawOverlays();
  204. this->display->display();
  205. }
  206. void OLEDDisplayUi::resetState() {
  207. this->state.lastUpdate = 0;
  208. this->state.ticksSinceLastStateSwitch = 0;
  209. this->state.frameState = FIXED;
  210. this->state.currentFrame = 0;
  211. this->state.isIndicatorDrawen = true;
  212. }
  213. void OLEDDisplayUi::drawFrame(){
  214. switch (this->state.frameState){
  215. case IN_TRANSITION: {
  216. float progress = (float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition;
  217. int16_t x, y, x1, y1;
  218. switch(this->frameAnimationDirection){
  219. case SLIDE_LEFT:
  220. x = -128 * progress;
  221. y = 0;
  222. x1 = x + 128;
  223. y1 = 0;
  224. break;
  225. case SLIDE_RIGHT:
  226. x = 128 * progress;
  227. y = 0;
  228. x1 = x - 128;
  229. y1 = 0;
  230. break;
  231. case SLIDE_UP:
  232. x = 0;
  233. y = -64 * progress;
  234. x1 = 0;
  235. y1 = y + 64;
  236. break;
  237. case SLIDE_DOWN:
  238. x = 0;
  239. y = 64 * progress;
  240. x1 = 0;
  241. y1 = y - 64;
  242. break;
  243. }
  244. // Invert animation if direction is reversed.
  245. int8_t dir = this->state.frameTransitionDirection >= 0 ? 1 : -1;
  246. x *= dir; y *= dir; x1 *= dir; y1 *= dir;
  247. bool drawenCurrentFrame;
  248. // Prope each frameFunction for the indicator Drawen state
  249. this->enableIndicator();
  250. (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, x, y);
  251. drawenCurrentFrame = this->state.isIndicatorDrawen;
  252. this->enableIndicator();
  253. (this->frameFunctions[this->getNextFrameNumber()])(this->display, &this->state, x1, y1);
  254. // Build up the indicatorDrawState
  255. if (drawenCurrentFrame && !this->state.isIndicatorDrawen) {
  256. // Drawen now but not next
  257. this->indicatorDrawState = 2;
  258. } else if (!drawenCurrentFrame && this->state.isIndicatorDrawen) {
  259. // Not drawen now but next
  260. this->indicatorDrawState = 1;
  261. } else if (!drawenCurrentFrame && !this->state.isIndicatorDrawen) {
  262. // Not drawen in both frames
  263. this->indicatorDrawState = 3;
  264. }
  265. // If the indicator isn't draw in the current frame
  266. // reflect it in state.isIndicatorDrawen
  267. if (!drawenCurrentFrame) this->state.isIndicatorDrawen = false;
  268. break;
  269. }
  270. case FIXED:
  271. // Always assume that the indicator is drawn!
  272. // And set indicatorDrawState to "not known yet"
  273. this->indicatorDrawState = 0;
  274. this->enableIndicator();
  275. (this->frameFunctions[this->state.currentFrame])(this->display, &this->state, 0, 0);
  276. break;
  277. }
  278. }
  279. void OLEDDisplayUi::drawIndicator() {
  280. // Only draw if the indicator is invisible
  281. // for both frames or
  282. // the indiactor is shown and we are IN_TRANSITION
  283. if (this->indicatorDrawState == 3 || (!this->state.isIndicatorDrawen && this->state.frameState != IN_TRANSITION)) {
  284. return;
  285. }
  286. uint8_t posOfHighlightFrame;
  287. float indicatorFadeProgress = 0;
  288. // if the indicator needs to be slided in we want to
  289. // highlight the next frame in the transition
  290. uint8_t frameToHighlight = this->indicatorDrawState == 1 ? this->getNextFrameNumber() : this->state.currentFrame;
  291. // Calculate the frame that needs to be highlighted
  292. // based on the Direction the indiactor is drawn
  293. switch (this->indicatorDirection){
  294. case LEFT_RIGHT:
  295. posOfHighlightFrame = frameToHighlight;
  296. break;
  297. case RIGHT_LEFT:
  298. posOfHighlightFrame = this->frameCount - frameToHighlight;
  299. break;
  300. }
  301. switch (this->indicatorDrawState) {
  302. case 1: // Indicator was not drawn in this frame but will be in next
  303. // Slide IN
  304. indicatorFadeProgress = 1 - ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
  305. break;
  306. case 2: // Indicator was drawn in this frame but not in next
  307. // Slide OUT
  308. indicatorFadeProgress = ((float) this->state.ticksSinceLastStateSwitch / (float) this->ticksPerTransition);
  309. break;
  310. }
  311. uint16_t frameStartPos = (12 * frameCount / 2);
  312. const char *image;
  313. uint16_t x,y;
  314. for (byte i = 0; i < this->frameCount; i++) {
  315. switch (this->indicatorPosition){
  316. case TOP:
  317. y = 0 - (8 * indicatorFadeProgress);
  318. x = 64 - frameStartPos + 12 * i;
  319. break;
  320. case BOTTOM:
  321. y = 56 + (8 * indicatorFadeProgress);
  322. x = 64 - frameStartPos + 12 * i;
  323. break;
  324. case RIGHT:
  325. x = 120 + (8 * indicatorFadeProgress);
  326. y = 32 - frameStartPos + 2 + 12 * i;
  327. break;
  328. case LEFT:
  329. x = 0 - (8 * indicatorFadeProgress);
  330. y = 32 - frameStartPos + 2 + 12 * i;
  331. break;
  332. }
  333. if (posOfHighlightFrame == i) {
  334. image = this->activeSymbol;
  335. } else {
  336. image = this->inactiveSymbol;
  337. }
  338. this->display->drawFastImage(x, y, 8, 8, image);
  339. }
  340. }
  341. void OLEDDisplayUi::drawOverlays() {
  342. for (uint8_t i=0;i<this->overlayCount;i++){
  343. (this->overlayFunctions[i])(this->display, &this->state);
  344. }
  345. }
  346. uint8_t OLEDDisplayUi::getNextFrameNumber(){
  347. if (this->nextFrameNumber != -1) return this->nextFrameNumber;
  348. return (this->state.currentFrame + this->frameCount + this->state.frameTransitionDirection) % this->frameCount;
  349. }