Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integrate Fast and Efficient String Formatting Library #244

Merged
merged 6 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions docs/source/overview/items/range.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ An integer range item can be created using the following syntax:

When the ``Brightness`` menu item is selected, the user can adjust the brightness level within the range of 0 to 100.

You can also optionally specify a unit string to be displayed next to the value:
You can also optionally specify formatting for the value by providing a format string argument:

.. code-block:: cpp

Expand All @@ -40,10 +40,10 @@ You can also optionally specify a unit string to be displayed next to the value:
// Callback function to handle value change
// value is the selected value within the range
// Do something with the selected value
}, (const char*) "dB"),
}, "%02ddB"), // Print the value with two digits and the unit string "dB" e.g. "50dB", "05dB"
// ... More menu items

When the ``Volume`` menu item is selected, the user can adjust the volume level within the range of 0 to 100, with the unit string **"dB"** displayed next to the value.
When the ``Volume`` menu item is selected, the user can adjust the volume level within the range of 0 to 100, and the value will be displayed with the format string ``"%02ddB"``.

.. image:: images/item-int-range.gif
:width: 400px
Expand All @@ -63,15 +63,12 @@ A float range item can be created using the following syntax:
// Callback function to handle value change
// value is the selected value within the range
// Do something with the selected value
}, (const char*) " km", 0.5f),
}, "%.02f km", 0.5f), // Print the value with two decimal places and the unit string "km" e.g. "5.00 km", "5.50 km"
// ... More menu items

- The last argument is the step size of the range (the increment or decrement value when changing the value).
This argument also determines the number of decimal places to display.
For example, a step size of ``0.5`` will display values with one decimal place,
while a step size of ``0.01`` will display values with two decimal places.

When the ``Dist`` menu item is selected, the user can adjust the pressure within the range of 0.0 to 100.0, with the unit string **"km"** displayed next to the value.
When the ``Dist`` menu item is selected, the user can adjust the distance within the range of 0.0 to 100.0

.. image:: images/item-float-range.png
:width: 400px
Expand Down
4 changes: 2 additions & 2 deletions examples/IntFloatValues/IntFloatValues.ino
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ void callbackFloat(float value);
// clang-format off
MENU_SCREEN(mainScreen, mainItems,
ITEM_BASIC("Con"),
ITEM_INT_RANGE("Dist", 100, 200, 100, callbackInt, (const char*) "m"),
ITEM_FLOAT_RANGE("Curr", -1.0f, 1.0f, -1.0f, callbackFloat, (const char*) "mA", 0.01f),
ITEM_INT_RANGE("Dist", 100, 200, 100, callbackInt, "%02dm"),
ITEM_FLOAT_RANGE("Curr", -1.0f, 1.0f, -1.0f, callbackFloat, "%.2fmA", 0.01f),
ITEM_BASIC("Blink SOS"),
ITEM_BASIC("Blink random"));
// clang-format on
Expand Down
2 changes: 1 addition & 1 deletion examples/SimpleRotary/SimpleRotary.ino
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ MENU_SCREEN(mainScreen, mainItems,
ITEM_BASIC("Connect to WiFi"),
ITEM_STRING_LIST("Color", colors, 8, colorsCallback),
ITEM_BASIC("Blink SOS"),
ITEM_INT_RANGE("Dist", 0, 50, 0, callback, (const char*) "m"),
ITEM_INT_RANGE("Dist", 0, 50, 0, callback, "%dm"),
ITEM_TOGGLE("Backlight", toggleBacklight),
ITEM_BASIC("Blink random"));
// clang-format on
Expand Down
57 changes: 4 additions & 53 deletions src/ItemFloatRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,30 @@
#define ItemFloatRange_H

#include "ItemRangeBase.h"
#include <utils/printf.h>

/**
* @brief Item that allows user to input float information within a range.
* The value can be incremented or decremented by a specified step.
*/
class ItemFloatRange : public ItemRangeBase<float> {
private:
uint8_t decimalPlaces = 1;

public:
ItemFloatRange(
const char* text,
const float min,
const float max,
float startingValue,
fptrFloat callback,
const char* unit = NULL,
const char* format = "%.2f",
float step = 0.1f,
bool commitOnChange = false)
: ItemRangeBase(text, min, max, startingValue, callback, unit, step, commitOnChange) {
decimalPlaces = calculateDecimalPlaces(step);
}
: ItemRangeBase(text, min, max, startingValue, callback, format, step, commitOnChange) {}

char* getDisplayValue() override {
static char buffer[10];
dtostrf(currentValue, calculateWidth(currentValue, decimalPlaces), decimalPlaces, buffer);
if (unit == NULL) {
return buffer;
}
concat(buffer, unit, buffer);
snprintf(buffer, sizeof(buffer), format, currentValue);
return buffer;
}

/**
* @brief Calculates the width of a floating-point number when displayed with a specified number of decimal places.
*
* This function computes the width of the string representation of a floating-point number, including the decimal point
* and the specified number of decimal places.
*
* @param currentValue The floating-point number whose width is to be calculated.
* @param decimalPlaces The number of decimal places to include in the width calculation.
* @return The width of the floating-point number when displayed as a string.
*/
static inline uint8_t calculateWidth(float currentValue, uint8_t decimalPlaces) {
int intPart = static_cast<int>(currentValue);
int intPartWidth = (intPart == 0) ? 1 : static_cast<int>(log10(abs(intPart))) + 1;
return intPartWidth + 1 + decimalPlaces; // +1 for the decimal point
}

/**
* @brief Calculates the number of decimal places in a given floating-point step value.
*
* This function converts the floating-point step value to a string representation,
* then determines the number of decimal places by counting the digits after the
* decimal point. Trailing zeros are not counted as decimal places.
*
* @param step The floating-point step value to analyze.
* @return The number of decimal places in the step value.
*/
static uint8_t calculateDecimalPlaces(float step) {
char buffer[10];
dtostrf(step, 5, 5, buffer);

char* decimalPos = strchr(buffer, '.');
if (decimalPos == NULL) return 0;

uint8_t numDecimalPlaces = strlen(decimalPos + 1);
while (numDecimalPlaces > 0 && buffer[strlen(buffer) - 1] == '0') {
buffer[strlen(buffer) - 1] = '\0';
numDecimalPlaces--;
}
return numDecimalPlaces;
}
};

#define ITEM_FLOAT_RANGE(...) (new ItemFloatRange(__VA_ARGS__))
Expand Down
16 changes: 0 additions & 16 deletions src/ItemInput.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,22 +61,6 @@ class ItemInput : public MenuItem {
* First parameter will be a `value` string.
*/
fptrStr callback;
/**
* @brief The number of visible characters.
*
* ```
* visible area
* ┌───────────────┐
* X X X X│X X X X █ X X X│X X
* ├───────────────┤
* │<── viewSize ─>│
* ```
*
* Effectively const, but initialized lately when renderer is injected.
*/
inline uint8_t getViewSize(MenuRenderer* renderer) const {
return renderer->getEffectiveCols() - strlen(text) - 1 + renderer->viewShift;
};

public:
/**
Expand Down
10 changes: 3 additions & 7 deletions src/ItemIntRange.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,14 @@ class ItemIntRange : public ItemRangeBase<int> {
const int max,
int startingValue,
fptrInt callback,
const char* unit = NULL,
const char* format = "%d",
int step = 1,
bool commitOnChange = false)
: ItemRangeBase(text, min, max, startingValue, callback, unit, step, commitOnChange) {}
: ItemRangeBase(text, min, max, startingValue, callback, format, step, commitOnChange) {}

char* getDisplayValue() override {
static char buffer[10];
itoa(currentValue, buffer, 10);
if (unit == NULL) {
return buffer;
}
concat(buffer, unit, buffer);
sprintf(buffer, format, currentValue);
return buffer;
}
};
Expand Down
17 changes: 9 additions & 8 deletions src/ItemRangeBase.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ template <typename T>
/**
* @brief Item that allows user to select a value from a range.
* It can be used to display all sorts of values that can be incremented or decremented.
* You can additionally pass a unit to be displayed after the value. e.g. "%", "°C", "°F" etc.
* You can additionally pass format string to format the value.
* e.g. you can use `%.2f` to display float with 2 decimal places.
*
* ```
* ┌──────────────────────────────────┐
* │ > T E X T : V A L U E U N I T
* └──────────────────────────────────┘
* ┌────────────────────────────────────────
* │ > T E X T : F O R M A T E D V A L U E
* └────────────────────────────────────────
* ```
*
* Additionally to `text` this item has float `currentValue`.
Expand All @@ -26,7 +27,7 @@ class ItemRangeBase : public MenuItem {
const T maxValue;
T currentValue;
void (*callback)(T);
const char* unit;
const char* format;
const T step;
bool commitOnChange;

Expand All @@ -39,7 +40,7 @@ class ItemRangeBase : public MenuItem {
* @param max The maximum value.
* @param startingValue The current value.
* @param callback A pointer to the callback function to execute when this menu item is selected.
* @param unit The unit e.g. "%", "°C", "°F".
* @param format The format string to format the value.
* @param step The step value for increment/decrement.
* @param commitOnChange If true, the callback will be called every time the value changes.
*/
Expand All @@ -49,10 +50,10 @@ class ItemRangeBase : public MenuItem {
const T max,
T startingValue,
void (*callback)(T),
const char* unit = NULL,
const char* format = "%s",
T step = 1,
bool commitOnChange = false)
: MenuItem(text), minValue(min), maxValue(max), currentValue(startingValue), callback(callback), unit(unit), step(step), commitOnChange(commitOnChange) {}
: MenuItem(text), minValue(min), maxValue(max), currentValue(startingValue), callback(callback), format(format), step(step), commitOnChange(commitOnChange) {}

/**
* @brief Increments the value.
Expand Down
16 changes: 16 additions & 0 deletions src/MenuItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,22 @@ class MenuItem {
~MenuItem() noexcept = default;

protected:
/**
* @brief The number of available columns for the potential value of the item.
*
* ```
* visible area
* ┌───────────────┐
* X X X X│X X X X █ X X X│X X
* ├───────────────┤
* │<── viewSize ─>│
* ```
*
* Effectively const, but initialized lately when renderer is injected.
*/
inline uint8_t getViewSize(MenuRenderer* renderer) const {
return renderer->getEffectiveCols() - strlen(text) - 1 + renderer->viewShift;
};
/**
* @brief Process a command decoded in 1 byte.
* It can be a printable character or a control command like `ENTER` or `LEFT`.
Expand Down
Loading
Loading