diff --git a/src/GEMSpinner.cpp b/src/GEMSpinner.cpp index d51bcc0..ca6eb3b 100644 --- a/src/GEMSpinner.cpp +++ b/src/GEMSpinner.cpp @@ -38,33 +38,37 @@ #include "GEMSpinner.h" #include "constants.h" -GEMSpinner::GEMSpinner(GEMSpinnerBoundariesByte boundaries_, bool loop_) +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesByte boundaries_, bool loop_, GEMSpinnerValueStringCallback valueStringCallback_) : _boundaries{ { .boundariesByte = { .step = boundaries_.step, .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } , _type(GEM_VAL_BYTE) , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) , _loop(loop_) + , _valueStringCallback(valueStringCallback_) { } -GEMSpinner::GEMSpinner(GEMSpinnerBoundariesInt boundaries_, bool loop_) +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesInt boundaries_, bool loop_, GEMSpinnerValueStringCallback valueStringCallback_) : _boundaries{ { .boundariesInt = { .step = abs(boundaries_.step), .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } , _type(GEM_VAL_INTEGER) , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) , _loop(loop_) + , _valueStringCallback(valueStringCallback_) { } #ifdef GEM_SUPPORT_FLOAT_EDIT -GEMSpinner::GEMSpinner(GEMSpinnerBoundariesFloat boundaries_, bool loop_) +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesFloat boundaries_, bool loop_, GEMSpinnerValueStringCallback valueStringCallback_) : _boundaries{ { .boundariesFloat = { .step = abs(boundaries_.step), .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } , _type(GEM_VAL_FLOAT) , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) , _loop(loop_) + , _valueStringCallback(valueStringCallback_) { } -GEMSpinner::GEMSpinner(GEMSpinnerBoundariesDouble boundaries_, bool loop_) +GEMSpinner::GEMSpinner(GEMSpinnerBoundariesDouble boundaries_, bool loop_, GEMSpinnerValueStringCallback valueStringCallback_) : _boundaries{ { .boundariesDouble = { .step = abs(boundaries_.step), .min = boundaries_.min < boundaries_.max ? boundaries_.min : boundaries_.max, .max = boundaries_.max > boundaries_.min ? boundaries_.max : boundaries_.min } } } , _type(GEM_VAL_DOUBLE) , _length(abs((boundaries_.max - boundaries_.min) / boundaries_.step) + 1) , _loop(loop_) + , _valueStringCallback(valueStringCallback_) { } #endif @@ -196,3 +200,60 @@ void GEMSpinner::setValue(void* variable, int index, void* referenceVariable) { #endif } } + + +byte GEMSpinner::produceStringForValue(char* stringBuffer, byte stringBufferLength, const GEMSpinnerValue& value, int valuePrecision) const +{ + if (_valueStringCallback != nullptr) + { + const char* const string = _valueStringCallback(value, _type, valuePrecision); + if (string != nullptr) + { + const size_t stringLength = MIN(strlen(string), (size_t)stringBufferLength - 1); + memcpy(stringBuffer, string, stringLength); + if (stringLength == stringBufferLength - 1) + { + stringBuffer[stringLength - 1] = '~'; // our "ellipsis" + } + stringBuffer[stringLength] = '\0'; + return (byte)stringLength; + } + } + + switch (_type) + { + case GEM_VAL_BYTE: + itoa(value.valByte, stringBuffer, 10); + break; + case GEM_VAL_INTEGER: + itoa(value.valInt, stringBuffer, 10); + break; + #ifdef GEM_SUPPORT_FLOAT_EDIT + case GEM_VAL_FLOAT: + dtostrf(value.valFloat, valuePrecision + 1, valuePrecision, stringBuffer); + break; + case GEM_VAL_DOUBLE: + dtostrf(value.valDouble, valuePrecision + 1, valuePrecision, stringBuffer); + break; + #endif + default: + *stringBuffer = '\0'; + break; + } + + return strlen(stringBuffer); +} + + +GEMSpinner& GEMSpinner::setValueStringCallback(GEMSpinnerValueStringCallback callback) +{ + _valueStringCallback = callback; + return *this; +} + + +GEMSpinner& GEMSpinner::removeValueStringCallback() +{ + _valueStringCallback = nullptr; + return *this; +} diff --git a/src/GEMSpinner.h b/src/GEMSpinner.h index c3bb44a..b13b822 100644 --- a/src/GEMSpinner.h +++ b/src/GEMSpinner.h @@ -94,6 +94,8 @@ struct GEMSpinnerValue { }; }; +typedef const char* const (*GEMSpinnerValueStringCallback)(GEMSpinnerValue /* value */, byte /* value type */, int /* value precision */); + // Declaration of GEMSpinner class class GEMSpinner { friend class GEM; @@ -105,21 +107,25 @@ class GEMSpinner { @param 'loop_' (optional) - whether iteration over options should be looped values GEM_LOOP (alias for true) */ - GEMSpinner(GEMSpinnerBoundariesByte boundaries_, bool loop_ = false); - GEMSpinner(GEMSpinnerBoundariesInt boundaries_, bool loop_ = false); + GEMSpinner(GEMSpinnerBoundariesByte boundaries_, bool loop_ = false, GEMSpinnerValueStringCallback valueStringCallback_ = nullptr); + GEMSpinner(GEMSpinnerBoundariesInt boundaries_, bool loop_ = false, GEMSpinnerValueStringCallback valueStringCallback_ = nullptr); #ifdef GEM_SUPPORT_FLOAT_EDIT - GEMSpinner(GEMSpinnerBoundariesFloat boundaries_, bool loop_ = false); - GEMSpinner(GEMSpinnerBoundariesDouble boundaries_, bool loop_ = false); + GEMSpinner(GEMSpinnerBoundariesFloat boundaries_, bool loop_ = false, GEMSpinnerValueStringCallback valueStringCallback_ = nullptr); + GEMSpinner(GEMSpinnerBoundariesDouble boundaries_, bool loop_ = false, GEMSpinnerValueStringCallback valueStringCallback_ = nullptr); #endif GEMSpinner& setLoop(bool mode = true); // Explicitly set or unset loop mode bool getLoop(); // Get current value of loop mode GEM_VIRTUAL GEMSpinnerValue getOptionNameByIndex(void* variable, int index); // Get option by its index + GEM_VIRTUAL byte produceStringForValue(char* stringBuffer, byte stringBufferLength, const GEMSpinnerValue& value, int valuePrecision) const; + GEM_VIRTUAL GEMSpinner& setValueStringCallback(GEMSpinnerValueStringCallback callback); + GEM_VIRTUAL GEMSpinner& removeValueStringCallback(); protected: GEMSpinnerBoundaries _boundaries; byte _type; int _length; bool _loop = false; byte getType(); + GEMSpinnerValueStringCallback _valueStringCallback; int getLength(); GEM_VIRTUAL int getSelectedOptionNum(void* variable); GEM_VIRTUAL void setValue(void* variable, int index, void* referenceVariable = nullptr); // Assign value of the selected option to supplied variable, optionally setting reference variable for counting options diff --git a/src/GEM_adafruit_gfx.cpp b/src/GEM_adafruit_gfx.cpp index 8ec89fd..51eed9d 100644 --- a/src/GEM_adafruit_gfx.cpp +++ b/src/GEM_adafruit_gfx.cpp @@ -425,22 +425,8 @@ void GEM_adafruit_gfx::printMenuItem(GEMItem* menuItemTmp, byte yText, byte yDra case GEM_VAL_SPINNER: { GEMSpinner* spinner = menuItemTmp->spinner; - switch (spinner->getType()) { - case GEM_VAL_BYTE: - itoa(*(byte*)menuItemTmp->linkedVariable, valueStringTmp, 10); - break; - case GEM_VAL_INTEGER: - itoa(*(int*)menuItemTmp->linkedVariable, valueStringTmp, 10); - break; - #ifdef GEM_SUPPORT_FLOAT_EDIT - case GEM_VAL_FLOAT: - dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - break; - case GEM_VAL_DOUBLE: - dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - break; - #endif - } + GEMSpinnerValue valueTmp = spinner->getOptionNameByIndex(menuItemTmp->linkedVariable, _valueSelectNum); + spinner->produceStringForValue(valueStringTmp, sizeof(valueStringTmp), valueTmp, menuItemTmp->precision); printMenuItemValue(valueStringTmp, -1 * calculateSpriteOverlap(GEM_ICON_SPINNER_ARROWS)); drawSprite(_agfx.width() - getSprite(GEM_ICON_SPINNER_ARROWS)->width - 1 * _spriteSize, yDraw, GEM_ICON_SPINNER_ARROWS, color, menuItemTmp); } diff --git a/src/GEM_u8g2.cpp b/src/GEM_u8g2.cpp index 31459fa..6b8a7b5 100644 --- a/src/GEM_u8g2.cpp +++ b/src/GEM_u8g2.cpp @@ -496,42 +496,12 @@ void GEM_u8g2::printMenuItems() { GEMSpinner* spinner = menuItemTmp->spinner; if (_editValueMode && menuItemTmp == _menuPageCurrent->getCurrentMenuItem()) { GEMSpinnerValue valueTmp = spinner->getOptionNameByIndex(menuItemTmp->linkedVariable, _valueSelectNum); - switch (spinner->getType()) { - case GEM_VAL_BYTE: - itoa(valueTmp.valByte, valueStringTmp, 10); - break; - case GEM_VAL_INTEGER: - itoa(valueTmp.valInt, valueStringTmp, 10); - break; - #ifdef GEM_SUPPORT_FLOAT_EDIT - case GEM_VAL_FLOAT: - dtostrf(valueTmp.valFloat, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - break; - case GEM_VAL_DOUBLE: - dtostrf(valueTmp.valDouble, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - break; - #endif - } + spinner->produceStringForValue(valueStringTmp, sizeof(valueStringTmp), valueTmp, menuItemTmp->precision); printMenuItemValue(valueStringTmp, -1 * calculateSpriteOverlap(GEM_ICON_SPINNER_ARROWS)); drawSprite(_u8g2.getDisplayWidth() - getSprite(GEM_ICON_SPINNER_ARROWS)->width - 1, yDraw, GEM_ICON_SPINNER_ARROWS, menuItemTmp); drawEditValueCursor(); } else { - switch (spinner->getType()) { - case GEM_VAL_BYTE: - itoa(*(byte*)menuItemTmp->linkedVariable, valueStringTmp, 10); - break; - case GEM_VAL_INTEGER: - itoa(*(int*)menuItemTmp->linkedVariable, valueStringTmp, 10); - break; - #ifdef GEM_SUPPORT_FLOAT_EDIT - case GEM_VAL_FLOAT: - dtostrf(*(float*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - break; - case GEM_VAL_DOUBLE: - dtostrf(*(double*)menuItemTmp->linkedVariable, menuItemTmp->precision + 1, menuItemTmp->precision, valueStringTmp); - break; - #endif - } + spinner->produceStringForValue(valueStringTmp, sizeof(valueStringTmp), *static_cast(menuItemTmp->linkedVariable), menuItemTmp->precision); printMenuItemValue(valueStringTmp, -1 * calculateSpriteOverlap(GEM_ICON_SPINNER_ARROWS)); drawSprite(_u8g2.getDisplayWidth() - getSprite(GEM_ICON_SPINNER_ARROWS)->width - 1, yDraw, GEM_ICON_SPINNER_ARROWS, menuItemTmp); }