diff --git a/ColorCodes.hpp b/ColorCodes.hpp new file mode 100644 index 0000000..3ba7eab --- /dev/null +++ b/ColorCodes.hpp @@ -0,0 +1,162 @@ +// Black & Grays +const uint16_t BLACK = 0x0000; +const uint16_t GRAY = 0x8410; +const uint16_t LIGHTGRAY = 0xD69A; +const uint16_t SLATEGRAY = 0x7412; +const uint16_t DIMGRAY = 0x6B4D; +const uint16_t DARKSLATEGRAY = 0x2A69; +const uint16_t DARKGRAY = 0xAD55; +const uint16_t LIGHTSLATEGRAY = 0x7453; + +// White & Offwhites +const uint16_t ANTIQUEWHITE = 0xFF5A; +const uint16_t FLORALWHITE = 0xFFDE; +const uint16_t FORESTGREEN = 0x2444; +const uint16_t NAVAJOWHITE = 0xFEF5; +const uint16_t GHOSTWHITE = 0xFFDF; +const uint16_t WHITE = 0xFFFF; +const uint16_t WHITESMOKE = 0xF7BE; +const uint16_t BEIGE = 0xF7BB; + +// Blues +const uint16_t ALICEBLUE = 0xF7DF; +const uint16_t BLUE = 0x001F; +const uint16_t BLUEVIOLET = 0x895C; +const uint16_t CADETBLUE = 0x5CF4; +const uint16_t CORNFLOWERBLUE = 0x64BD; +const uint16_t DARKBLUE = 0x0011; +const uint16_t DARKSLATEBLUE = 0x49F1; +const uint16_t DEEPSKYBLUE = 0x05FF; +const uint16_t DODGERBLUE = 0x1C9F; +const uint16_t LIGHTBLUE = 0xAEDC; +const uint16_t LIGHTSKYBLUE = 0x867F; +const uint16_t LIGHTSTEELBLUE = 0xB63B; +const uint16_t MEDIUMBLUE = 0x0019; +const uint16_t MEDIUMSLATEBLUE = 0x7B5D; +const uint16_t MIDNIGHTBLUE = 0x18CE; +const uint16_t POWDERBLUE = 0xB71C; +const uint16_t ROYALBLUE = 0x435C; +const uint16_t SKYBLUE = 0x867D; +const uint16_t SLATEBLUE = 0x6AD9; +const uint16_t STEELBLUE = 0x4416; +const uint16_t NAVY = 0x0010; +const uint16_t CYAN = 0x07FF; +const uint16_t DARKCYAN = 0x0451; +const uint16_t LIGHTCYAN = 0xE7FF; +const uint16_t AQUAMARINE = 0x7FFA; +const uint16_t AZURE = 0xF7FF; + + +// Greens +const uint16_t DARKGREEN = 0x0320; +const uint16_t DARKOLIVEGREEN = 0x5345; +const uint16_t DARKSEAGREEN = 0x8DF1; +const uint16_t GREEN = 0x0400; +const uint16_t GREENYELLOW = 0xAFE5; +const uint16_t LAWNGREEN = 0x7FE0; +const uint16_t LIGHTGREEN = 0x9772; +const uint16_t LIGHTSEAGREEN = 0x2595; +const uint16_t LIMEGREEN = 0x3666; +const uint16_t MEDIUMSEAGREEN = 0x3D8E; +const uint16_t MEDIUMSPRINGGREEN = 0x07D3; +const uint16_t PALEGREEN = 0x9FD3; +const uint16_t SEAGREEN = 0x2C4A; +const uint16_t SPRINGGREEN = 0x07EF; +const uint16_t YELLOWGREEN = 0x9E66; +const uint16_t LIME = 0x07E0; +const uint16_t AQUA = 0x07FF; +const uint16_t OLIVE = 0x8400; +const uint16_t OLIVEDRAB = 0x6C64; +const uint16_t ORCHID = 0xDB9A; + +// Reds +const uint16_t DARKRED = 0x8800; +const uint16_t INDIANRED = 0xCAEB; +const uint16_t MEDIUMVIOLETRED = 0xC0B0; +const uint16_t PALEVIOLETRED = 0xDB92; +const uint16_t RED = 0xF800; +const uint16_t TOMATO = 0xFB08; +const uint16_t DARKMAGENTA = 0x8811; + +// Pinks +const uint16_t PINK = 0xFE19; +const uint16_t LIGHTPINK = 0xFDB8; +const uint16_t HOTPINK = 0xFB56; +const uint16_t DEEPPINK = 0xF8B2; + +// Purples +const uint16_t PURPLE = 0x8010; + +// Orange +const uint16_t ORANGE = 0xFD20; +const uint16_t DARKORANGE = 0xFC60; +const uint16_t ORANGERED = 0xFA20; + +// Yellows +const uint16_t YELLOW = 0xFFE0; +const uint16_t LIGHTYELLOW = 0xFFFC; +const uint16_t LIGHTGOLDENRODYELLOW = 0xFFDA; +const uint16_t GOLD = 0xFEA0; + +// Browns +const uint16_t BROWN = 0xA145; +const uint16_t ROSYBROWN = 0xBC71; +const uint16_t SADDLEBROWN = 0x8A22; +const uint16_t SANDYBROWN = 0xF52C; +const uint16_t CHOCOLATE = 0xD343; + +// Colors yet to be grouped +const uint16_t BISQUE = 0xFF38; +const uint16_t BLANCHEDALMOND = 0xFF59; +const uint16_t BURLYWOOD = 0xDDD0; +const uint16_t CHARTREUSE = 0x7FE0; +const uint16_t CORAL = 0xFBEA; +const uint16_t CORNSILK = 0xFFDB; +const uint16_t CRIMSON = 0xD8A7; +const uint16_t DARKGOLDENROD = 0xBC21; +const uint16_t DARKKHAKI = 0xBDAD; +const uint16_t DARKORCHID = 0x9999; +const uint16_t DARKSALMON = 0xECAF; +const uint16_t DARKTURQUOISE = 0x067A; +const uint16_t DARKVIOLET = 0x901A; +const uint16_t FIREBRICK = 0xB104; +const uint16_t FUCHSIA = 0xF81F; +const uint16_t GAINSBORO = 0xDEFB; +const uint16_t GOLDENROD = 0xDD24; +const uint16_t HONEYDEW = 0xF7FE; +const uint16_t INDIGO = 0x4810; +const uint16_t IVORY = 0xFFFE; +const uint16_t KHAKI = 0xF731; +const uint16_t LAVENDER = 0xE73F; +const uint16_t LAVENDERBLUSH = 0xFF9E; +const uint16_t LEMONCHIFFON = 0xFFD9; +const uint16_t LIGHTCORAL = 0xF410; +const uint16_t LIGHTSALMON = 0xFD0F; +const uint16_t LINEN = 0xFF9C; +const uint16_t MAGENTA = 0xF81F; +const uint16_t MAROON = 0x8000; +const uint16_t MEDIUMAQUAMARINE = 0x6675; +const uint16_t MEDIUMORCHID = 0xBABA; +const uint16_t MEDIUMPURPLE = 0x939B; +const uint16_t MEDIUMTURQUOISE = 0x4E99; +const uint16_t MINTCREAM = 0xF7FF; +const uint16_t MISTYROSE = 0xFF3C; +const uint16_t MOCCASIN = 0xFF36; +const uint16_t OLDLACE = 0xFFBC; +const uint16_t PALEGOLDENROD = 0xEF55; +const uint16_t PALETURQUOISE = 0xAF7D; +const uint16_t PAPAYAWHIP = 0xFF7A; +const uint16_t PEACHPUFF = 0xFED7; +const uint16_t PERU = 0xCC27; +const uint16_t PLUM = 0xDD1B; +const uint16_t SALMON = 0xFC0E; +const uint16_t SEASHELL = 0xFFBD; +const uint16_t SIENNA = 0xA285; +const uint16_t SILVER = 0xC618; +const uint16_t SNOW = 0xFFDF; +const uint16_t TAN = 0xD5B1; +const uint16_t TEAL = 0x0410; +const uint16_t THISTLE = 0xDDFB; +const uint16_t TURQUOISE = 0x471A; +const uint16_t VIOLET = 0xEC1D; +const uint16_t WHEAT = 0xF6F6; diff --git a/Settings.hpp b/Settings.hpp new file mode 100644 index 0000000..9fbaa8c --- /dev/null +++ b/Settings.hpp @@ -0,0 +1,13 @@ +// Tells compiler this file is to be included only once in a single compilation +#pragma once + +#ifndef SIMPLE_UI_SETTINGS_H +#define SIMPLE_UI_SETTINGS_H + +const size_t MAX_CHAR_LENGTH = 60; // Char length of strings in UI + +const int FONT_DEFAULT_SIZE = 1; +const uint16_t FONT_FG_COLOR = 0x95e7; // White font color +const uint16_t FONT_BG_COLOR = 0x0000; // Black font background + +#endif \ No newline at end of file diff --git a/SimpleUI.cpp b/SimpleUI.cpp new file mode 100644 index 0000000..7544a76 --- /dev/null +++ b/SimpleUI.cpp @@ -0,0 +1,88 @@ +#pragma once + +#include "SimpleUI.hpp" + +// Type-specific implementations. + + +SimpleUI::SimpleUI(Adafruit_SSD1351 &GFXHandler) : GFXHandler (&GFXHandler) {} + + +TextAnchor * SimpleUI::newAnchor( int x, int y ){ + std::cout << "Inside SimpleUI::newAnchor\n\tX: " << x << "\n\tY: " << y << std::endl; + + TextAnchor * _anchor = new TextAnchor( * GFXHandler ); + _anchor->setCursor( x, y ); + _anchor->setFontSize( _textSize ); + + if ( _defFgColor != NULL && _defBgColor != NULL ){ + _anchor->setColor( _defFgColor, _defBgColor ); + } + else if ( _defFgColor != NULL ){ + _anchor->setColor( _defFgColor ); + } + + _anchor->setAutoPrint( true ); + _anchor->print(); + + return _anchor; +} + + +/* +void SimpleUI::whatever(char str){ + std::cout << "whatever - str was a string: " << str << std::endl; +} +*/ + +void SimpleUI::begin(){ + GFXHandler->begin(); + GFXHandler->setFont(); + GFXHandler->fillScreen(_defBgColor); + GFXHandler->setTextColor(_defFgColor); + //GFXHandler.setRotation(1); + GFXHandler->setTextSize(1); +} + +void SimpleUI::begin( uint16_t fgColor , uint16_t bgColor ){ + _defFgColor = fgColor; + _defBgColor = bgColor; + + _begin(); +} + +void SimpleUI::begin( uint16_t fgColor ){ + _defFgColor = fgColor; + + _begin(); +} + +void SimpleUI::_begin(){ + GFXHandler->begin(); + GFXHandler->setFont(); + GFXHandler->fillScreen(_defBgColor); + GFXHandler->setTextColor(_defFgColor); + //GFXHandler.setRotation(1); + GFXHandler->setTextSize(1); +} + +void SimpleUI::setColor( uint16_t fgColor ){ + std::cout << "Inside: SimpleUI::setColor(fg)" << std::endl; + std::cout << "\tfgColor: " << fgColor << std::endl; + + _defFgColor = fgColor; +} + +void SimpleUI::setColor( uint16_t fgColor, uint16_t bgColor ){ + std::cout << "Inside: SimpleUI::setColor(fg, bg)" << std::endl; + std::cout << "\tfgColor: " << fgColor << std::endl; + std::cout << "\tbgColor: " << bgColor << std::endl; + + _defFgColor = fgColor; + _defBgColor = bgColor; +} +/* +void SimpleUI::print( char val ){ + +} +*/ \ No newline at end of file diff --git a/SimpleUI.hpp b/SimpleUI.hpp new file mode 100644 index 0000000..456cb1b --- /dev/null +++ b/SimpleUI.hpp @@ -0,0 +1,56 @@ +#pragma once + +#ifndef SIMPLE_UI_H +#define SIMPLE_UI_H + +#include +#include +#include +//#include +#include +#include +#include +#include "Settings.hpp" +#include "TextAnchor.hpp" + + +class SimpleUI { + public: + + SimpleUI(Adafruit_SSD1351 &GFXHandler); + + void begin(); + void begin( uint16_t fgColor ); + void begin( uint16_t fgColor, uint16_t bgColor ); + + void setColor( uint16_t fgColor ); + void setColor( uint16_t fgColor, uint16_t bgColor ); + + + TextAnchor * newAnchor( int x, int y ); + + // void whatever(char str); + + //template void whatever(T param) { std::cout << "param: " << param << "; TYPE_NAME(): " << TYPE_NAME(param) << std::endl; } + + // void print( char val ); + + private: + + Adafruit_SSD1351 * GFXHandler; + + void _begin(); + + //int _x, _y; + + // DEFAULT fg/bg colors (can be changed on a TextArea basis); + uint16_t _defFgColor = 0x95e7; + uint16_t _defBgColor = 0x0000; + + int _textSize = FONT_DEFAULT_SIZE; + +}; + + + +#endif // SIMPLE_UI_H \ No newline at end of file diff --git a/SimpleUI.ino b/SimpleUI.ino new file mode 100644 index 0000000..f71e9c5 --- /dev/null +++ b/SimpleUI.ino @@ -0,0 +1,249 @@ +#include +#include +#include +#include +//#include "TextAnchor.hpp" +#include +//#include +//#include + +#include "SimpleUI.hpp" +#include "ColorCodes.hpp" + + +// START ROTARY ENCODER STUFF +#define ENCODER_DT 3 // Encoder output A (... or is it B?... idk) +#define ENCODER_CLK 4 // Encoder output B? +#define ENCODER_SW 2 // Encoder switch/button, will set the digipot to encoders val + +int buttonState; // the current reading from the input pin +int lastButtonState = LOW; // the previous reading from the input pin + +unsigned long lastDebounceTime = 0; // the last time the output pin was toggled +unsigned long debounceDelay = 50; // the debounce time; increase if the output flickers + +volatile int encoderPos = 0; // Position of encoder, restricted to the ENCODER_[MAX/MIN] vals +volatile int currentStateCLK; // Input from encoders CLK pin +volatile int lastStateCLK; // Value of the CLK in the last loop (checking for updates) +volatile bool clkClockwise; // True if rotary is turning clockwise (this isn't really used). +// END ROTARY ENCODER STUFF + + + +// declare size of working string buffers. Basic strlen("d hh:mm:ss") = 10 +const size_t MaxString = 20; + +// the string being displayed on the SSD1331 (initially empty) +char oldTimeString[MaxString] = { 0 }; + +//Adafruit_SSD1351 oled = Adafruit_SSD1351(128, 128, &SPI, 10, 9, -1 ); +Adafruit_SSD1351 oled(128, 128, &SPI, 10, 9, -1 ); +//OledText Greeting(oled); + +int32_t iter = 0; + +uint8_t maxRows = 10; +int thisRow = 1; +char buffer[8]; + +SimpleUI SUI(oled); + + +//TextAnchor TimeDisplay, Milliseconds; + +// Display stuff +//TextAnchor * TimeDisplay; + +TextAnchor * Milliseconds; +TextAnchor * MillisecondsLabel; +TextAnchor * Minutes; +TextAnchor * MinutesLabel; +TextAnchor * Iterations; +TextAnchor * IterationsLabel; +TextAnchor * EncoderPos; +TextAnchor * EncoderPosLabel; + +/* +// Menu Items +TextAnchor * PumpDurtion; // How long should the pump run for overall +TextAnchor * PumpDrain; // How often should the pumps drain open? +TextAnchor * DrainDuration; // How long should the drain stay open for? +*/ + + +void setup() { + Serial.begin(115200); + + delay(1000); + + pinMode(ENCODER_CLK,INPUT); + pinMode(ENCODER_DT,INPUT); + pinMode(ENCODER_SW, INPUT); + + // Read the initial state of CLK + lastStateCLK = digitalRead(ENCODER_CLK); + + // Call updateEncoder() when any high/low changed seen + // on interrupt 0 (pin 2), or interrupt 1 (pin 3) + attachInterrupt(digitalPinToInterrupt(ENCODER_CLK), updateEncoder, CHANGE); + attachInterrupt(digitalPinToInterrupt(ENCODER_DT), updateEncoder, CHANGE); + + + SUI.begin(0x95e7, 0x0000); + + oled.sendCommand(0xA0); + + oled.sendCommand(0xA0); // + oled.sendCommand(0x72); // RGB Color + oled.sendCommand(0xA1); // 0xA1 + oled.sendCommand(0x0); + + Serial.println("Pausing for 1 sec.."); + delay(1000); + oled.fillScreen(BLACK); + + + //TimeDisplay = SUI.newAnchor(120, 0)->rightAlign(true)->setColor(0xc7a9)->print(""); + //PumpDurtion = SUI.newAnchor(0, 10)->setColor(LIGHTSTEELBLUE)->print(""); + //PumpDrain = SUI.newAnchor(0, 20)->setColor(LIGHTSTEELBLUE)->print(""); + //DrainDuration = SUI.newAnchor(0, 30)->setColor(LIGHTSTEELBLUE)->print(""); + + + MillisecondsLabel = SUI.newAnchor(65, 15)->rightAlign(true)->setColor(DIMGRAY)->print("MS:"); + Milliseconds = SUI.newAnchor(67, 15)->setColor(0xF800)->print(""); + + MinutesLabel = SUI.newAnchor(65, 25)->rightAlign(true)->setColor(DIMGRAY)->print("Min:"); + Minutes = SUI.newAnchor(67, 25)->setColor(0x3666)->print(""); + + IterationsLabel = SUI.newAnchor(65, 35)->rightAlign(true)->setColor(DIMGRAY)->print("Iterations:"); + Iterations = SUI.newAnchor(67, 35)->setColor(0x4416)->print(""); + + EncoderPosLabel = SUI.newAnchor(65, 45)->rightAlign(true)->setColor(DIMGRAY)->setHighlightColor(MEDIUMSLATEBLUE)->print("Encoder:"); + EncoderPos = SUI.newAnchor(67, 45)->setColor(DARKORANGE) + //->setHighlightColor(ORANGERED) + ->print(""); + + delay(300); +} + + +void loop(){ + iter++; + //std::cout << "lastChangeMs: " << TimeDisplay->lastChangeMs() << "; millis - lastChangeMs: " << (millis() - TimeDisplay->lastChangeMs()) << std::endl; + + /* + if ( millis() - TimeDisplay->lastChangeMs() > 1000 ){ + setUptime(); + } + */ + + if ( millis() - Milliseconds->lastChangeMs() > 500 ){ + Milliseconds->print(millis()); + } + + if ( millis() - Minutes->lastChangeMs() > 60000 ){ + Minutes->print(millis()/60000); + } + + if ( millis() - Iterations->lastChangeMs() > 250 ){ + Iterations->print(iter); + } + + int reading = digitalRead(ENCODER_SW); + + // If the switch changed, due to noise or pressing: + if (reading != lastButtonState) { + // reset the debouncing timer + lastDebounceTime = millis(); + } + + if ((millis() - lastDebounceTime) > debounceDelay) { + // whatever the reading is at, it's been there for longer than the debounce + // delay, so take it as the actual current state: + + // if the button state has changed: + if (reading != buttonState) { + buttonState = reading; + + // only toggle the LED if the new button state is HIGH + if (buttonState == LOW) { + std::cout << "Button clicked - Encoder: " << encoderPos << std::endl; + EncoderPos->highlight(); + } + } + } + + // set the LED: + + // save the reading. Next time through the loop, it'll be the lastButtonState: + lastButtonState = reading; + +} + +void setUptime() { + return; + unsigned long upSeconds = millis() / 1000; + unsigned long days = upSeconds / 86400; + upSeconds = upSeconds % 86400; + unsigned long hours = upSeconds / 3600; + upSeconds = upSeconds % 3600; + unsigned long minutes = upSeconds / 60; + upSeconds = upSeconds % 60; + + char newTimeString[ MaxString ] = { 0 }; + + // construct the string representation + sprintf( + newTimeString, + "%lu %02lu:%02lu:%02lu", + days, hours, minutes, upSeconds + ); + + // std::cout << "oldTimeString: " << oldTimeString << std::endl; + //std::cout << "newTimeString: " << newTimeString << std::endl; + + // has the time string changed since the last oled update? + if ( strcmp( newTimeString, oldTimeString ) != 0) + strcpy( oldTimeString, newTimeString ); + + + //TimeDisplay->print(newTimeString); + //return newTimeString; +} + +void updateEncoder(){ + + // Read the current state of CLK + currentStateCLK = digitalRead(ENCODER_CLK); + + //std::cout << "[updateEncoder] digitalRead(ENCODER_CLK): " << currentStateCLK << std::endl; + //std::cout << "[updateEncoder] digitalRead(ENCODER_DT): " << digitalRead(ENCODER_DT) << std::endl; + + + // If last and current state of CLK are different, then pulse occurred + // React to only 1 state change to avoid double count + //if (currentStateCLK != lastStateCLK && currentStateCLK == 1){ + if (currentStateCLK != lastStateCLK && currentStateCLK == 1){ + + // If the DT state is different than the CLK state then + // the encoder is rotating CCW so decrement + if (digitalRead(ENCODER_DT) != currentStateCLK) { + + encoderPos --; + + clkClockwise = false;// ="CCW"; + } + else { + // Encoder is rotating CW so increment + encoderPos ++; + + clkClockwise = true; + } + std::cout << "EncoderPos: " << encoderPos << std::endl; + + EncoderPos->print(encoderPos); + } + + // Remember last CLK state + lastStateCLK = currentStateCLK; +} diff --git a/TextAnchor.cpp b/TextAnchor.cpp new file mode 100644 index 0000000..6271402 --- /dev/null +++ b/TextAnchor.cpp @@ -0,0 +1,585 @@ +#pragma once + +#include "TextAnchor.hpp" + + + +MAKE_TYPE_INFO( int ) +MAKE_TYPE_INFO( int* ) +MAKE_TYPE_INFO( float ) +MAKE_TYPE_INFO( float* ) +MAKE_TYPE_INFO( long ) +MAKE_TYPE_INFO( unsigned long ) +MAKE_TYPE_INFO( long* ) +MAKE_TYPE_INFO( short* ) +MAKE_TYPE_INFO( short ) +MAKE_TYPE_INFO( double ) +MAKE_TYPE_INFO( double* ) +MAKE_TYPE_INFO( char ) +MAKE_TYPE_INFO( char* ) +MAKE_TYPE_INFO( char& ) +//MAKE_TYPE_INFO( String ) +//MAKE_TYPE_INFO( char[5] ) +//MAKE_TYPE_INFO( char(*)[5] ) + + + +/* +https://github.com/adafruit/Adafruit-GFX-Library/blob/master/Adafruit_GFX.cpp +*/ +TextAnchor::TextAnchor(Adafruit_SSD1351 &GFXHandler) : GFXHandler (&GFXHandler) {} + +// If user were to set/change the printer to a CHAR... +TextAnchor * TextAnchor::print( char *charValue ) { + //std::cout << "You provided the char: " << charValue << std::endl; + + //if ( strcmp( _printerVal, _printerValPrev ) != 0 ) _lastChangeMs = millis(); + + + //_printerVal = charValue; + strcpy(_printerVal, charValue); + + print(); + + return this; +} + +TextAnchor::~TextAnchor(void) { + std::cout << "Were in ~TextAnchor" << std::endl; + free(this); +} + +TextAnchor * TextAnchor::setAutoPrint( bool autoPrint ){ + _autoReprint = autoPrint; + return this; +} + +TextAnchor * TextAnchor::setCursor( int x, int y ) { + _x = x; + _y = y; + + print(); + + return this; +} + +// A specific x/y location is provided, and the test will always end there +// whenever updated. +TextAnchor * TextAnchor::rightAlign( void ){ + return rightAlign( true ); +} + +TextAnchor * TextAnchor::rightAlign( bool rightAlign ){ + if ( rightAlign == _rightAlign ) + return this; + + _rightAlign = rightAlign; + print(); + + return this; +} + +// This would print the stored value in the specified area, but for this forum +// post, I'm just outputting the value to serial +TextAnchor * TextAnchor::print( void ){ + // width, height, getRotation, getCursorX, getCursorY + + /* + std::cout << "width: " << GFXHandler->width() << std::endl; + std::cout << "height: " << GFXHandler->height() << std::endl; + std::cout << "getRotation: " << GFXHandler->getRotation() << std::endl; + std::cout << "getCursorX: " << GFXHandler->getCursorX() << std::endl; + std::cout << "getCursorY: " << GFXHandler->getCursorY() << std::endl; + */ + + + + //std::cout << _printerVal << " has charWidth: " + //<< GFXHandler->charWidth(_printerVal) << " and textWidth: " + //<< GFXHandler->textWidth(_printerVal) << std::endl; + + + uint16_t colFg = _fgColor, + colBg = _bgColor; + + // If highlight is true, then set the fg/bg colors to the highlight colors + if ( _highlightStatus == true ) { + // If no fg/bg highlight color is set, then just swap the values + if ( _fgHlColor == NULL && _bgHlColor == NULL ){ + colFg = _bgColor; + colBg = _fgColor; + } + // But if only one of the fg or bg highlight color is set, then use that + // color only for that fg/bg (and leave the other as-is) + else { + if ( _fgHlColor != NULL ) colFg = _fgHlColor; + + if ( _bgHlColor != NULL ) colBg = _bgHlColor; + } + } + + int diff = 0; + + // If the printed text isn't the same as the text printed last time, then check the difference + // in length (to override the text that would otherwise show from the side), and update the + // _printerValPrev + if ( strcmp( _printerVal, _printerValPrev ) != 0 ) { + //std::cout << "\t_printerVal is different than _printerValPrev: " << std::endl; + + if ( strlen(_printerValPrev) > strlen( _printerVal ) ){ + diff = strlen(_printerValPrev) - strlen(_printerVal ); + + //std::cout << "\tdiff: " << diff << std::endl; + } + } + + // Pause interrupts while we set the font details and placement. This prevents other + // updates that may be triggered via an interrupt from unintentionally injecting + // print values into this location. For example, this happens if a text anchor is + // updated in an interrupt function triggered by an encoder (if the encoder is spun + // too quickly). + if ( _allowInterruptPauses == true ) noInterrupts(); + + //std::cout << "getBuffer: " << GFXHandler->getBuffer() << std::endl; + //GFXHandler.setRotation(1); + GFXHandler->setTextSize( _textSize ); + GFXHandler->setFont( NULL ); + + //int realX = 0; + + // If right align is enabled, then set X to be n characters to left of _x, + // where n = length of _printerVal + if ( _rightAlign == true ){ + GFXHandler->setCursor( _x - (strlen(_printerVal) + diff) * _fontCharWidth, _y ); + } + else { + GFXHandler->setCursor( _x, _y ); + } + + GFXHandler->setTextColor( colFg, colBg ); + + /* + std::cout << "Printing: " << _printerVal << std::endl; + std::cout << "\t_x: " << _x << std::endl; + std::cout << "\t_y: " << _y << std::endl; + std::cout << "\t_fgColor: " << _fgColor << std::endl; + std::cout << "\t_bgColor: " << _bgColor << std::endl; + std::cout << "\t_printerValPrev: " << _printerValPrev << std::endl; + */ + + if ( diff > 0 && _rightAlign == true ){ + for ( int i = 0; diff > i; i++ ){ + //std::cout << "\tAdding prefix space #" << i << std::endl; + GFXHandler->print(" "); + } + } + + GFXHandler->print( _printerVal ); + + if ( diff > 0 && _rightAlign == false ){ + for ( int i = 0; diff > i; i++ ){ + //std::cout << "\tAdding suffix space #" << i << std::endl; + GFXHandler->print(" "); + } + } + + // If the printed text isn't the same as the text printed last time, then check the difference + // in length (to override the text that would otherwise show from the side), and update the + // _printerValPrev + if ( strcmp( _printerVal, _printerValPrev ) != 0 ) { + _lastChangeMs = millis(); + + //std::cout << "\t_printerVal is different than _printerValPrev: " << std::endl; + + /* + if ( strlen(_printerValPrev) > strlen( _printerVal ) ){ + int diff = strlen(_printerValPrev) - strlen(_printerVal ); + + //std::cout << "\tdiff: " << diff << std::endl; + + for ( int i = 0; diff > i; i++ ){ + //std::cout << "\tAdding space #" << i << std::endl; + GFXHandler->print(" "); + } + } + */ + strcpy(_printerValPrev, _printerVal); + } + + if ( _allowInterruptPauses == true ) interrupts(); + + return this; +} + +// User wants to prepend (or append, in a diff function) a string +// to the beginning of the char value.. +TextAnchor * TextAnchor::prepend( char *prependTxt ){ + if ( strlen( _printerVal ) == 0 ) + { + // Nothing to append anything to, just switch to a regular print + return print( prependTxt ); + + //return ; + } + + char newCharValue[ strlen(prependTxt) + strlen(_printerVal) +1 ]; // add +1 for end?... + //char newCharValue[ MAX_CHAR_LENGTH ]; + strcpy( newCharValue, prependTxt ); + //strncpy( newCharValue, prependTxt, MAX_CHAR_LENGTH ); + strcat( newCharValue, _printerVal ); + + // Print the new char (with the prefix prepended) + print( newCharValue ); + + return this; +} + +TextAnchor * TextAnchor::append( char *appendTxt ){ + if ( strlen( _printerVal ) == 0 ) + { + // Nothing to append anything to, just switch to a regular print + return print( appendTxt ); + } + + char newCharValue[ strlen(appendTxt) + strlen(_printerVal) +1 ]; // add +1 for end?... + //char newCharValue[ MAX_CHAR_LENGTH ]; + strcpy( newCharValue, _printerVal ); + //strcpy( newCharValue, prependTxt ); + //strncpy( newCharValue, prependTxt, MAX_CHAR_LENGTH ); + strcat( newCharValue, appendTxt ); + + // Print the new char (with the prefix prepended) + print( newCharValue ); + + return this; +} + +TextAnchor * TextAnchor::clear(){ + return this; +} + +TextAnchor * TextAnchor::clearFromDisplay(){ + GFXHandler->setTextSize( _textSize ); + //GFXHandler->setFont( NULL ); + GFXHandler->setTextColor( _bgColor, _bgColor ); + GFXHandler->setCursor( _x, _y ); + GFXHandler->print(_printerVal); + GFXHandler->setTextColor( _fgColor, _bgColor ); + + return this; +} + +TextAnchor * TextAnchor::incrementPos ( int x, int y ){ + return move( _x+x, _y+y); +} + +// Move a text box to a new location. This will involve clearing the current location (covering it with +// the background color), moving the cursor to the new location, and printing the content. +TextAnchor * TextAnchor::move( int x, int y ){ + clearFromDisplay(); + setCursor( x, y ); + print(); + + return this; +} +/* +void setFont(const GFXfont *f = NULL); + GFXHandler->setFont(GFXfont); + //GFXHandler->setFont(&FreeMonoBoldOblique12pt7b); +} +*/ + +TextAnchor * TextAnchor::setFontSize( int ftSize ){ + _textSize = ftSize; + + print(); + + return this; +} + +TextAnchor * TextAnchor::setFontSize( int ftSize, bool reprint ){ + _textSize = ftSize; + + print(); + + return this; +} + + + +TextAnchor * TextAnchor::setColor( uint16_t fgColor ){ + _fgColor = fgColor; + + print(); + + return this; +} + +TextAnchor * TextAnchor::setColor( uint16_t fgColor, uint16_t bgColor ){ + _fgColor = fgColor; + _bgColor = bgColor; + + //std::cout << "Inside: TextAnchor::setColor" << std::endl; + //std::cout << "\t_fgColor: " << _fgColor << std::endl; + //std::cout << "\t_bgColor: " << _bgColor << std::endl; + + print(); + + return this; +} + +TextAnchor * TextAnchor::setColor( uint16_t fgColor, uint16_t bgColor, uint16_t fgHlColor, uint16_t bgHlColor ){ + return this; +} + +TextAnchor * TextAnchor::setHighlightColor( uint16_t fgHlColor ){ + _fgHlColor = fgHlColor; + + if ( _highlightStatus == true ) print(); + + return this; +} + +TextAnchor * TextAnchor::setHighlightColor( uint16_t fgHlColor, uint16_t bgHlColor ){ + _fgHlColor = fgHlColor; + _bgHlColor = bgHlColor; + + //std::cout << "Inside: TextAnchor::setColor" << std::endl; + //std::cout << "\t_fgColor: " << _fgColor << std::endl; + //std::cout << "\t_bgColor: " << _bgColor << std::endl; + + if ( _highlightStatus == true ) print(); + + return this; +} + +TextAnchor * TextAnchor::highlight( void ){ + _highlightStatus = !_highlightStatus; + + std::cout << "In highlight - highlight:" << _highlightStatus << std::endl; + + + /* + if ( _highlightStatus == true ){ + setColor( _bgColor, _fgColor ); + } + else { + setColor( _fgColor, _bgColor ); + } + */ + + return print(); + + //return this; +} + +// Set highlight status +TextAnchor * TextAnchor::highlight( bool highlight ){ + _highlightStatus = highlight; + + if ( _highlightStatus == true ){ + setColor( _bgColor, _fgColor ); + } + else { + setColor( _fgColor, _bgColor ); + } + + print(); + + return this; +} + +uint32_t TextAnchor::lastChangeMs(void){ + return _lastChangeMs; +} + +void TextAnchor::_appendInt( const int intValue ){ + char cstr[MAX_CHAR_LENGTH]; + + itoa( intValue, cstr, 10 ); + + append( cstr ); +} + +void TextAnchor::_prependInt( const int intValue ){ + char cstr[MAX_CHAR_LENGTH]; + + itoa( intValue, cstr, 10 ); + + prepend( cstr ); +} + +void TextAnchor::_appendDouble( const double intValue ){ + char cstr[MAX_CHAR_LENGTH]; + + dtostrf(intValue, 2, 2, cstr); + + append( cstr ); +} + +void TextAnchor::_prependDouble( const double intValue ){ + char cstr[MAX_CHAR_LENGTH]; + + dtostrf(intValue, 2, 2, cstr); + + prepend( cstr ); +} + +void TextAnchor::_printInt( const int intValue ){ + //std::cout << "The value " << intValue << " is a REGULAR int" << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + + itoa( intValue, cstr, 10 ); + + print(cstr); +} + +void TextAnchor::_printUInt( const unsigned int intValue ){ + //std::cout << "The value " << intValue << " is an UNSIGNED REGULAR int" << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + + itoa( intValue, cstr, 10 ); + + print(cstr); + //print(cstr); +} + +void TextAnchor::_printLong( const long intValue ){ + //std::cout << "The value " << intValue << " is a LONG int" << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + + ltoa( intValue, cstr, 10 ); + + if ( _autoReprint == true ) print(cstr); +} + +void TextAnchor::_printULong( const unsigned long intValue ){ + std::cout << "The value " << intValue << " is a UNSIGNED LONG int" << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + + ltoa( intValue, cstr, 10 ); + + print(cstr); +} + +void TextAnchor::_printFloat( const float intValue ){ + //std::cout << "The value " << intValue << " is a FLOAT int" << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + + dtostrf(intValue, 2, 2, cstr); + + print(cstr); +} + +void TextAnchor::_printDouble( const double intValue ){ + std::cout << "The value " << intValue << " is a DOUBLE int" << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + + dtostrf(intValue, 2, 2, cstr); + + print(cstr); +} + +uint16_t TextAnchor::charWidth(const char ch) +{ + char onecharstring[2] = {ch, 0}; + int16_t x1, y1; + uint16_t w, h; + GFXHandler->getTextBounds(onecharstring, 0, 0, &x1, &y1, &w, &h); + return w; +} + +uint16_t TextAnchor::textWidth(const char* text) +{ + int16_t x1, y1; + uint16_t w, h; + GFXHandler->getTextBounds(text, 0, 0, &x1, &y1, &w, &h); + return w; +} + +/* +// User provides a new NUMERICAL value, store it as a char +void TextAnchor::print( int intValue ) +{ + //typeof( intValue ) MyType1; + std::cout << "printing int :" << intValue << std::endl; + + //std::cout << "MyType1:" << std::endl; + //this->TypeOf(intValue); + + //typeof( 12345 ) an_int; + + //typedef typeof( intValue ) MyType2; + + //std::cout << "MyType2:" << MyType2 << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + ltoa( intValue, cstr, 10 ); + + print(cstr); +} +*/ +/* +// User provides a new NUMERICAL value, store it as a char +void TextAnchor::print( float intValue ) +{ + //std::cout << "Type of intValue::" << typeid(intValue).name() << std::endl; + std::cout << "Printing a DOUBLE:" << intValue << std::endl; + + char cstr[MAX_CHAR_LENGTH]; + //ltoa( intValue, cstr, 10 ); + // dtostrf(float_value, min_width, num_digits_after_decimal, where_to_store_string) + + dtostrf(intValue, 2, 2, cstr); + + print(cstr); + +} +*/ + + + +/* +char* TextAnchor::TypeOf( const double& ) +{ + static const char type[] = "double"; + return type; +} + +char* TextAnchor::TypeOf( const double* ) +{ + static const char type[] = "double*"; + return type; +} + +char* TextAnchor::TypeOf( const float& ) +{ + static const char type[] = "float"; + return type; +} + +char* TextAnchor::TypeOf( const float* ) +{ + static const char type[] = "float*"; + return type; +} + +char* TextAnchor::TypeOf(const int&) +{ + static const char type[] = "int"; + return type; +} + +char* TextAnchor::TypeOf(const int*) +{ + static const char type[] = "int*"; + return type; +} +*/ + diff --git a/TextAnchor.hpp b/TextAnchor.hpp new file mode 100644 index 0000000..30dc91a --- /dev/null +++ b/TextAnchor.hpp @@ -0,0 +1,290 @@ +#pragma once + +#ifndef TEXT_ANCHOR_H +#define TEXT_ANCHOR_H + +#include +#include +#include +//#include +#include +#include +#include +#include "gfxfont.h" +//#include +//#include +//#include +#include + + +#include "Settings.hpp" +//#include "TextAnchor_Templates.hpp" + +// Including "settings" here just to make sharing it simpler +//constexpr unsigned int VALUE_TYPE_NONE = 0; + +//const size_t MAX_CHAR_LENGTH = 60; + + + + +template struct TypeInfo { static const char * name; }; +template const char * TypeInfo::name = "unknown"; + + + + +#define TYPE_NAME(var) TypeInfo< typeof(var) >::name + +// Handy macro to make defining stuff easier. +#define MAKE_TYPE_INFO(type) template <> const char * TypeInfo::name = #type; + + + +class TextAnchor { + public: + + TextAnchor(Adafruit_SSD1351 &GFXHandler); + + ~TextAnchor(void); + + template + auto print(T val) + -> typename std::enable_if::value || std::is_floating_point::value>::type + //-> typename std::conditional::value || std::is_floating_point::value>::type + { + //std::cout << "Printing number: " << val << std::endl; + + char * _t = TYPE_NAME(val); + std::cout << "is_integral val: " << val << "; TYPE_NAME(val): " << _t << std::endl; + + if ( _t == "long" || _t == "long*" || _t == "unsigned long" ){ + return _printLong(val); + } + + if ( _t == "double" || _t == "double*" ){ + return _printFloat((double) val); + } + + if ( _t == "float" || _t == "float*" ){ + return _printFloat(val); + } + + if ( _t == "int" || _t == "int*" ){ + return _printInt((int)val); + } + + } + /* + template + auto print(T val) + -> typename std::enable_if::value>::type + { + //std::cout << "val[float]: " << val << "; TYPE_NAME(val): " << TYPE_NAME(val) << std::endl; + char * _t = TYPE_NAME(val); + std::cout << "is_floating_point val: " << val << "; TYPE_NAME(val): " << _t << std::endl; + + if ( _t == "double" ){ + return _printFloat((double) val); + } + + _printFloat(val); + } + */ + + + template + auto append(T val) + -> typename std::enable_if::value || std::is_floating_point::value>::type + //-> typename std::conditional::value || std::is_floating_point::value>::type + { + char * _t = TYPE_NAME(val); + std::cout << "is_integral val: " << val << "; TYPE_NAME(val): " << _t << std::endl; + if ( _t == "long" || _t == "long*" || _t == "unsigned long" ){ + return _appendLong(val); + } + + if ( _t == "double" || _t == "double*" ){ + return _appendFloat((double) val); + } + + if ( _t == "float" || _t == "float*" ){ + return _appendFloat(val); + } + + return _appendInt((int)val); + } + + template + auto prepend(T val) + -> typename std::enable_if::value || std::is_floating_point::value>::type + //-> typename std::conditional::value || std::is_floating_point::value>::type + { + char * _t = TYPE_NAME(val); + //std::cout << "is_integral val: " << val << "; TYPE_NAME(val): " << _t << std::endl; + if ( _t == "long" || _t == "long*" || _t == "unsigned long" ){ + return _prependLong(val); + } + + if ( _t == "double" || _t == "double*" ){ + return _prependFloat((double) val); + } + + if ( _t == "float" || _t == "float*" ){ + return _prependFloat(val); + } + + _prependInt((int)val); + } + + + + + // Set/update the print containers display value to a string, then display it + TextAnchor * print(char *charValue); + //void print(char charValue[]); + //void print(char charValue[25]); + + // Set/update the print containers display value to an integer, then display it + //void print(long intValue); + /* + void print( const int intValue ) { _printInt( intValue ); }; + void print( const unsigned int intValue ) { _printUInt( intValue ); }; + void print( const float intValue ) { _printDouble( intValue ); }; + void print( const double intValue ) { _printDouble( intValue ); }; + void print( const long intValue ) { _printLong( intValue ); }; + void print( const unsigned long intValue ) { _printULong( intValue ); }; + */ + TextAnchor * append( const int intValue ) { _appendInt( intValue ); }; + + TextAnchor * append( const double intValue ) { _appendDouble( intValue ); }; + + TextAnchor * prepend( const int intValue ) { _prependInt( intValue ); }; + + TextAnchor * prepend( const double intValue ) { _prependDouble( intValue ); }; + + TextAnchor * incrementPos ( int x = 0, int y = 0); + + TextAnchor * move( int x = 0, int y = 0 ); + + TextAnchor * rightAlignAt( int x, int y ); + + TextAnchor * setCursor( int x = 0, int y = 0 ); + + TextAnchor * setColor( uint16_t fgColor ); + + TextAnchor * setColor( uint16_t fgColor, uint16_t bgColor ); + + TextAnchor * setColor( uint16_t fgColor, uint16_t bgColor, uint16_t fgHlColor, uint16_t bgHlColor ); + + // Function to re-print whatever value/type is saved (will be useful if the + // printed text needs to be "reloaded" for various reasons) + TextAnchor * print( void ); + + // Function to prepend some text to the value currently in the print container + TextAnchor * prepend(char *prependTxt); + //void prepend(char prependTxt[25]); + //void prepend(char prependTxt[]); + + TextAnchor * append(char *appendTxt); + //void append(const int intValue ) { _appendInt( intValue ); }; + + TextAnchor * clearFromDisplay(); + + TextAnchor * clear(); + + TextAnchor * setFontSize( int ftSize ); + + //void setFont(); + + //void setFont(const GFXfont *f = NULL); + + //void setFont(const GFXfont *f = NULL); + + TextAnchor * setFontSize( int ftSize, bool reprint ); + + // If this is set to true, then changing settings (font size, color, value, etc) won't automatically + // update the display unless you call the print() function (with no parameters). + TextAnchor * setAutoPrint( bool autoPrint ); + + // Toggle highlight status + TextAnchor * highlight(); + + // Set highlight status + TextAnchor * highlight( bool highlight ); + + // Set the highlight color of just the text + TextAnchor * setHighlightColor( uint16_t fgHlColor ); + + // Set the highlight color of the text and background + TextAnchor * setHighlightColor( uint16_t fgHlColor, uint16_t bgHlColor ); + + // Calling rightAlign with no params will enable the right alignment + TextAnchor * rightAlign( void ); + + // Calling it with a boolean will set the right align to that specific val + TextAnchor * rightAlign( bool rightAlign ); + + uint16_t charWidth(const char ch); + + uint16_t textWidth(const char* text); + + // Retrieve the ms the last update was done + uint32_t lastChangeMs(void); + + private: + + Adafruit_SSD1351 * GFXHandler; + GFXfont *gfxFont; + + uint16_t _fontCharWidth = 6; // How many pixels wide is 1 character + uint16_t _fontCharHeight; // How many pixels high is 1 character + + // When set to true, calling any of the set(FontSize|Color|Location) will automatically + // reprint the text area with the new settings. + bool _autoReprint = true; + bool _highlightStatus = false; + bool _rightAlign = false; + bool _allowInterruptPauses = true; // Will allow noInterrupts() and interrupts() to be + // called from the print() function. Without this, + // sometimes other print() executions can be called + // between the cursor placement and actual print, + // which places values in the wrong locations. + + int _x = NULL; + int _y = NULL; + uint32_t _lastChangeMs = millis(); + + int _textSize = FONT_DEFAULT_SIZE; + + uint16_t _fgHlColor = NULL; + uint16_t _bgHlColor = NULL; + + uint16_t _fgColor = NULL; + uint16_t _bgColor = NULL; + + // Current char/string being displayed (or to be displayed) + char _printerVal[ MAX_CHAR_LENGTH ]; + + // The previous string val, used when overriding it with shorter text + // NOTE: This may not be needed. Should be able to check for the + // value/length of the currently displayed text using whats + // stored in _printerVal. + char _printerValPrev[ MAX_CHAR_LENGTH ]; + + + void _printInt( int intValue ); + void _printUInt( unsigned int intValue ); + void _printLong( long intValue ); + void _printULong( unsigned long intValue ); + void _printFloat( float intValue ); + void _printDouble( double intValue ); + + void _appendInt( int intValue ); + void _appendDouble( double intValue ); + + void _prependInt( int intValue ); + void _prependDouble( double intValue ); +}; + +#endif // TEXT_ANCHOR_H \ No newline at end of file