Numeric Values

PixelUI separates numeric presentation from component drawing. The numeric APIs below provide validated ranges, allocation-free formatting, bindings, and edit transactions.

Range and normalization

1
2
3
4
NumericRange range;
if (!NumericRange::tryCreate(-40, 120, 5, range)) {
// minimum > maximum or step <= 0
}
API Signature Description
Constructor constexpr NumericRange() Creates the default range [0, 100] with step 1.
Static factory static constexpr bool tryCreate(int32_t minimum, int32_t maximum, int32_t step, NumericRange& output) Validates minimum <= maximum and step > 0. On success, writes the range to output and returns true.
Getter constexpr int32_t minimum() const Returns the lower bound.
Getter constexpr int32_t maximum() const Returns the upper bound.
Getter constexpr int32_t step() const Returns the step size.
Constraint constexpr int32_t clamp(int32_t value) const Clamps a value to the closed interval [minimum, maximum].
State query constexpr bool canIncrement(int32_t value) const Reports whether the clamped value is below the upper bound.
State query constexpr bool canDecrement(int32_t value) const Reports whether the clamped value is above the lower bound.
Arithmetic constexpr int32_t incremented(int32_t value) const Adds one step with overflow protection and upper-bound saturation.
Arithmetic constexpr int32_t decremented(int32_t value) const Subtracts one step with overflow protection and lower-bound saturation.
Free function constexpr uint32_t normalizeToExtent(const NumericRange& range, int32_t value, uint32_t extent) Maps the value’s position in the range to [0, extent] with nearest-integer rounding.

clamp(), canIncrement(), canDecrement(), incremented(), and decremented() are saturating. An uneven final step lands directly on the boundary. normalizeToExtent(range, value, pixels) maps a clamped value to an integer pixel extent with nearest-integer rounding. These operations are safe across the full INT32_MIN to INT32_MAX range.

tryCreate() returns false for minimum > maximum or step <= 0. Check the result before retaining or passing the range.

Formatting

NumericFormatter formats the value supplied to each call. Built-in policies cover integers, zero padding, scaled integers, suffixes, and percentages:

1
2
3
4
5
6
7
8
IntegerFormat padded{3, nullptr};
NumericFormatter digits = NumericFormatter::integer(padded); // -7 -> "-007"

ScaledIntegerFormat volts{10, 1, 1, " V"};
NumericFormatter voltage = NumericFormatter::scaled(volts); // -7 -> "-0.7 V"

PercentageFormat percentage{&range, "%"};
NumericFormatter percent = NumericFormatter::percentage(percentage);

Formatting is allocation-free. format() returns false when the formatter is invalid or the destination is too small; a non-empty destination is reset to an empty string on failure. Use FixedBufferWriter::appendInteger() to compose text such as current/total.

The formatter stores only a function pointer and a non-owning context pointer. Every referenced format object, suffix string, percentage range, or custom context must outlive all calls through that formatter. A local formatter is safe for immediate formatting; a formatter retained by a Widget or queued Popup must refer to static storage or persistent App members.

Declare retained dependencies before their formatter so reverse member destruction keeps them alive:

1
2
3
4
NumericRange temperatureRange_{};
ScaledIntegerFormat temperatureFormat_{10, 1, 1, " C"};
NumericFormatter temperatureFormatter_ =
NumericFormatter::scaled(temperatureFormat_);

Formatter API

Module / class Signature Description
FixedBufferWriter FixedBufferWriter(char* buffer, size_t capacity) Binds an externally owned fixed buffer and its total capacity.
bool append(const char* text) Appends a null-terminated string. Returns false and records failure when capacity is insufficient.
bool appendCharacter(char character) Appends one character.
bool appendInteger(int32_t value, uint8_t minimumDigits = 1U) Appends a signed 32-bit integer with optional leading-zero padding.
bool appendUnsigned(uint32_t value, uint8_t minimumDigits = 1U) Appends an unsigned 32-bit integer with optional leading-zero padding.
bool finish() Writes the terminating \0 and returns true when the complete operation succeeded.
bool valid() const Reports whether all writes have remained within the buffer.
size_t size() const Returns the number of written characters, excluding the terminating \0.
NumericFormatter static constexpr NumericFormatter custom(const void* context, FormatFunction function) Creates a formatter from a context pointer and formatting function.
static constexpr NumericFormatter integer() Creates the default integer formatter.
static constexpr NumericFormatter integer(const IntegerFormat& format) Creates an integer formatter from the supplied configuration.
static constexpr NumericFormatter scaled(const ScaledIntegerFormat& format) Creates a fixed-point formatter with scale and fractional digits.
static constexpr NumericFormatter percentage(const PercentageFormat& format) Creates a percentage formatter based on a NumericRange.
bool format(int32_t value, char* buffer, size_t bufferSize) const Formats value into the destination buffer.
constexpr bool valid() const Reports whether a formatting function is bound.
constexpr const void* context() const Returns the bound context pointer.
constexpr FormatFunction function() const Returns the bound formatting function.

Format configuration

Structure Field Type Default Description
IntegerFormat minimumDigits uint8_t 1U Minimum digit count, padded with leading zeros.
suffix const char* nullptr Optional suffix such as "px" or "ms".
ScaledIntegerFormat scale uint32_t 1U Scale factor; for example, 100 formats 1234 as 12.34.
fractionalDigits uint8_t 0U Number of fractional digits.
minimumIntegerDigits uint8_t 1U Minimum integer-part digit count, padded with leading zeros.
suffix const char* nullptr Optional suffix.
PercentageFormat range const NumericRange* nullptr Range used for percentage normalization.
suffix const char* "%" Percentage suffix.

Binding and edit session

ValueEditorBinding defines a non-owning read, write, and notification boundary. ValueEditSession stores the transaction’s original value, draft value, and editing policy.

1
2
3
4
5
6
7
static void changed(void* context, int32_t value) {
static_cast<Settings*>(context)->brightness = value;
}

ValueEditorBinding binding =
ValueEditorBinding::reference(brightness_, &changed, this);
ValueEditSession edit(binding, ValueEditPolicy::CommitOnConfirm);

With CommitOnConfirm, draft changes remain inside the session until commit() writes and notifies; cancel() discards the draft. With Live, each successful setDraftValue() writes and notifies immediately; cancel() restores the original value and notifies that restoration. A session constructed from an initial integer has no external binding but follows the same draft/commit/cancel path.

Operation CommitOnConfirm Live
setDraftValue(value) Update only the draft Write, notify, then update the draft
commit() Write and notify if changed; make draft the new original Make the current draft the new original
cancel() Discard the draft Restore and notify the original value if changed

All three operations return false when the session is invalid or a required write fails. A failed write preserves the current transaction state; the caller should keep the editor open or restore its visible controls from draftValue().

Binding value and changed contexts are non-owning and must outlive the session and every operation performed through it. A Popup request that copies the binding extends this requirement through both its pending and active lifetime.

Scaled integer formatting function

Signature Description
bool formatScaledInteger(char* buffer, size_t bufferSize, int32_t raw, uint32_t scale, uint8_t fractionalDigits, const char* suffix = nullptr) Formats the fixed-point value raw with the supplied scale, fractional digit count, and optional suffix.

PixelUIValue::Binding API

Signature Description
constexpr Binding() Creates an empty binding.
static constexpr Binding custom(const void* object, FormatFunction formatter, const char* suffix = nullptr) Binds an object pointer, custom formatter, and optional suffix.
static constexpr Binding integer(const int32_t& value, const char* suffix = nullptr) Binds an int32_t value for integer formatting.
template <uint8_t FractionalDigits, int32_t Scale> static constexpr Binding decimal(const ScaledInt32<Scale>& value, const char* suffix = nullptr) Binds a ScaledInt32<Scale> value with up to nine fractional digits.
bool format(char* buffer, size_t bufferSize) const Formats the bound value into buffer; failures clear a non-empty destination and return false.
constexpr const void* object() const Returns the bound object pointer.
constexpr FormatFunction formatter() const Returns the formatting function.
constexpr const char* suffix() const Returns the suffix pointer.

Component examples

  • NumScroll uses NumericRange for stepping and NumericFormatter for labels. Configure it with setRange(min, max, step) or setRange(range), followed by setFormatter(formatter). Configure zero padding through the formatter.
  • PopupProgress maps LEFT/RIGHT through range.step(), normalizes only the bar geometry, and delegates displayed text to the formatter.
  • PopupValueDigits uses a ValueEditSession; construction leaves the external value unchanged. SELECT on a digit finishes that digit’s local edit while keeping the session open for other digits. The OK button commits the whole session; CANCEL or BACK cancels it.