Sparkle 0.0.1
Loading...
Searching...
No Matches
spk_numeric_spin_box.hpp
1#pragma once
2
3#include <algorithm>
4#include <charconv>
5#include <limits>
6#include <sstream>
7#include <string>
8#include <type_traits>
9
10#include "structure/container/spk_expected.hpp"
11#include "structure/math/spk_vector2.hpp"
12#include "structure/widget/spk_icon_button.hpp"
13#include "structure/widget/spk_linear_layout.hpp"
14#include "structure/widget/spk_text_edit.hpp"
15#include "structure/widget/spk_widget.hpp"
16
17namespace spk
18{
19 template <typename TType>
31 {
32 static_assert(std::is_arithmetic_v<TType>, "NumericSpinBox requires an arithmetic type.");
33
34 public:
38 using value_type = TType;
39
52
61
66
67 private:
68 spk::HorizontalLayout _layout;
69 TextEdit _valueEdit;
70 TextEdit::EditionContract _onTextEditionContract;
71
72 IconButton _raiseButton;
73 IconButton _lowerButton;
74 IconButton::Contract _raiseContract;
75 IconButton::Contract _lowerContract;
76
78 value_type _step{1};
79
80 uint32_t _minimalTextEditSize = 0;
81
82 private:
83 static bool _isEmptyOrSignOnly(const std::wstring &p_text)
84 {
85 if (p_text.empty() == true)
86 {
87 return true;
88 }
89
90 if (p_text == L"-" || p_text == L"+")
91 {
92 return true;
93 }
94
95 if constexpr (std::is_floating_point_v<value_type>)
96 {
97 if (p_text == L"." || p_text == L"-." || p_text == L"+.")
98 {
99 return true;
100 }
101 }
102
103 return false;
104 }
105
106 static bool _hasError(const ParseResult &p_result)
107 {
108 return (p_result.reportStatus().value() != ValidationState::Valid);
109 }
110
111 static ParseResult _makeParseError(ValidationState p_state, const std::wstring &p_message)
112 {
113 return ParseResult::generateReport(p_state, p_message);
114 }
115
116 ValidationStatus _validateTextForTextEdit(const std::wstring &p_text) const
117 {
118 const ParseResult parsed = _parseValue(p_text);
119
120 return ValidationStatus(
121 parsed.reportStatus().value(),
122 parsed.reportStatus().status());
123 }
124
125 static ParseResult _parseValue(const std::wstring &p_text)
126 {
127 if (_isEmptyOrSignOnly(p_text) == true)
128 {
129 return _makeParseError(ValidationState::Undefined, L"NumericSpinBox value is undefined.");
130 }
131
132 if constexpr (std::is_unsigned_v<value_type>)
133 {
134 if (p_text.empty() == false && p_text.front() == L'-')
135 {
136 return _makeParseError(ValidationState::Invalid, L"Unsigned NumericSpinBox does not accept negative values.");
137 }
138 }
139
140 // Integers: prefer from_chars (fast, strict).
141 if constexpr (std::is_integral_v<value_type>)
142 {
143 std::string narrow;
144 narrow.reserve(p_text.size());
145 for (wchar_t c : p_text)
146 {
147 if (c > 127)
148 {
149 return _makeParseError(ValidationState::Invalid, L"NumericSpinBox value contains invalid characters.");
150 }
151 narrow.push_back(static_cast<char>(c));
152 }
153
154 value_type v{};
155 const char *begin = narrow.data();
156 const char *end = narrow.data() + narrow.size();
157
158 auto [ptr, ec] = std::from_chars(begin, end, v, 10);
159 if (ec != std::errc() || ptr != end)
160 {
161 return _makeParseError(ValidationState::Invalid, L"Failed to parse NumericSpinBox value.");
162 }
163
164 return ParseResult(v, ValidationState::Valid, L"");
165 }
166 // Floating: use strict stream parse and ensure full consumption.
167 else
168 {
169 std::wistringstream stream(p_text);
170 value_type v{};
171 stream >> v;
172
173 if (stream.fail() == true)
174 {
175 return _makeParseError(ValidationState::Invalid, L"Failed to parse NumericSpinBox value.");
176 }
177
178 stream >> std::ws;
179 if (stream.eof() == false)
180 {
181 return _makeParseError(ValidationState::Invalid, L"NumericSpinBox value contains invalid characters.");
182 }
183
184 return ParseResult(v, ValidationState::Valid, L"");
185 }
186 }
187
188 static std::wstring _formatValue(const value_type &p_value)
189 {
190 // std::to_wstring is fine; if you later want nicer formatting, change it here.
191 return std::to_wstring(p_value);
192 }
193
194 void _syncValueFromTextIfValid()
195 {
196 const ParseResult parsed = _parseValue(_valueEdit.text());
197 if (_hasError(parsed) == false)
198 {
199 _value = parsed.value();
200 }
201 }
202
203 protected:
207 void _onGeometryChange() override
208 {
209 _raiseButton.sizeHint().setDesired({geometry().size.y, geometry().size.y});
210 _lowerButton.sizeHint().setDesired({geometry().size.y, geometry().size.y});
211
212 _layout.setGeometry(geometry().atOrigin());
213 }
214
215 public:
221 NumericSpinBox(const std::wstring &p_name, spk::Widget *p_parent) :
222 spk::Widget(p_name, p_parent),
223 _valueEdit(p_name + L"/TextEdit", this),
224 _raiseButton(p_name + L"/RaiseButton", this),
225 _lowerButton(p_name + L"/LowerButton", this)
226 {
227 _layout.addElement(&_valueEdit, spk::Layout::SizePolicy::Extend);
228 _layout.addElement(&_lowerButton, spk::Layout::SizePolicy::Desired);
229 _layout.addElement(&_raiseButton, spk::Layout::SizePolicy::Desired);
230
231 _valueEdit.activate();
232 _raiseButton.activate();
233 _lowerButton.activate();
234
235 _raiseButton.setIconTexture(Widget::Style::defaultIconset);
236 _lowerButton.setIconTexture(Widget::Style::defaultIconset);
237
238 _raiseButton.setIconSpriteID({5, 0});
239 _lowerButton.setIconSpriteID({4, 0});
240
241 _raiseButton.setIconPadding({2, 2});
242 _lowerButton.setIconPadding({2, 2});
243
244 _valueEdit.setValidationCallback([this](const std::wstring &p_text) {
245 return _validateTextForTextEdit(p_text);
246 });
247
248 _onTextEditionContract = _valueEdit.subscribeToEdition([this](const std::wstring &) {
249 _syncValueFromTextIfValid();
250 });
251
252 _raiseContract = _raiseButton.subscribe([this]() {
253 increase();
254 });
255 _lowerContract = _lowerButton.subscribe([this]() {
256 decrease();
257 });
258
259 setValue(_value);
260
262 return _layout.sizeHint().minimal();
263 });
264
266 return _layout.sizeHint().maximal();
267 });
268
270 return _layout.sizeHint().desired();
271 });
272 }
273
280 {
281 return _value.onEdition().subscribe(p_job);
282 }
283
288 void setMinimalTextEditSize(uint32_t p_minimalTextEditSize)
289 {
290 _valueEdit.sizeHint().release();
291 spk::Vector2UInt minimalTextSize = _valueEdit.sizeHint().minimal();
292 _valueEdit.sizeHint().setMinimal({std::max(minimalTextSize.x, p_minimalTextEditSize), minimalTextSize.y});
293
294 _layout.sizeHint().release();
295 sizeHint().release();
296 }
297
302 void setValue(const value_type &p_value)
303 {
304 _value = p_value;
305
306 // Keep TextEdit as the single source of display.
307 _valueEdit.setText(_formatValue(_value));
308
309 _layout.sizeHint().release();
310 sizeHint().release();
311 }
312
317 const value_type &value() const
318 {
319 return _value.get();
320 }
321
326 void setStep(const value_type &p_step)
327 {
328 _step = p_step;
329 }
330
335 const value_type &step() const
336 {
337 return _step;
338 }
339
343 void increase()
344 {
345 setValue(static_cast<value_type>(_value + _step));
346 }
347
351 void decrease()
352 {
353 setValue(static_cast<value_type>(_value - _step));
354 }
355
361 {
362 return _valueEdit;
363 }
364
369 const TextEdit &valueEdit() const
370 {
371 return _valueEdit;
372 }
373
379 {
380 return _raiseButton;
381 }
382
388 {
389 return _lowerButton;
390 }
391 };
392
393 using FloatSpinBox = NumericSpinBox<float>;
394 using IntSpinBox = NumericSpinBox<int>;
395 using UnsignedIntSpinBox = NumericSpinBox<unsigned int>;
396}
Carries a value alongside a task report status.
Definition spk_expected.hpp:24
static Expected generateReport(const ValidationState &p_reportStatusID, const std::wstring &p_reportStatusMessage)
Definition spk_expected.hpp:69
Button widget that displays a background and icon per state.
Definition spk_icon_button.hpp:22
spk::TContractProvider<>::Contract Contract
Contract type for click subscriptions.
Definition spk_icon_button.hpp:27
void increase()
Increases the value by the current step.
Definition spk_numeric_spin_box.hpp:343
spk::TextEdit::ValidationCallback ValidationCallback
Validation callback type for the text edit.
Definition spk_numeric_spin_box.hpp:51
void setValue(const value_type &p_value)
Sets the current numeric value.
Definition spk_numeric_spin_box.hpp:302
IconButton & lowerButton()
Returns the decrement button.
Definition spk_numeric_spin_box.hpp:387
spk::ObservableValue< value_type >::Contract EditionContract
Edition contract type for value changes.
Definition spk_numeric_spin_box.hpp:60
EditionContract subscribeToEdition(const EditionJob &p_job)
Subscribes to value edition notifications.
Definition spk_numeric_spin_box.hpp:279
TextEdit & valueEdit()
Returns the value text edit.
Definition spk_numeric_spin_box.hpp:360
spk::TextEdit::ValidationState ValidationState
Validation state type for the text edit.
Definition spk_numeric_spin_box.hpp:43
IconButton & raiseButton()
Returns the increment button.
Definition spk_numeric_spin_box.hpp:378
const TextEdit & valueEdit() const
Returns the value text edit.
Definition spk_numeric_spin_box.hpp:369
void _onGeometryChange() override
Updates layout geometry when size changes.
Definition spk_numeric_spin_box.hpp:207
void setMinimalTextEditSize(uint32_t p_minimalTextEditSize)
Sets the minimal width of the text edit field.
Definition spk_numeric_spin_box.hpp:288
void setStep(const value_type &p_step)
Sets the increment/decrement step.
Definition spk_numeric_spin_box.hpp:326
spk::TextEdit::ValidationStatus ValidationStatus
Validation status type for the text edit.
Definition spk_numeric_spin_box.hpp:47
const value_type & value() const
Returns the current numeric value.
Definition spk_numeric_spin_box.hpp:317
NumericSpinBox(const std::wstring &p_name, spk::Widget *p_parent)
Builds a numeric spin box widget.
Definition spk_numeric_spin_box.hpp:221
TType value_type
Underlying numeric value type.
Definition spk_numeric_spin_box.hpp:38
void decrease()
Decreases the value by the current step.
Definition spk_numeric_spin_box.hpp:351
const value_type & step() const
Returns the increment/decrement step.
Definition spk_numeric_spin_box.hpp:335
spk::ObservableValue< value_type >::Job EditionJob
Edition job type for value changes.
Definition spk_numeric_spin_box.hpp:56
spk::Expected< value_type, ValidationState > ParseResult
Parse result type for numeric text.
Definition spk_numeric_spin_box.hpp:65
Stores a value and notifies subscribers whenever it changes.
Definition spk_observable_value.hpp:22
spk::TContractProvider< TType >::Contract Contract
Contract type for subscriptions.
Definition spk_observable_value.hpp:27
spk::TContractProvider< TType >::Job Job
Job type dispatched on updates.
Definition spk_observable_value.hpp:31
void configureMaximalGenerator(Generator p_generator)
Configures the maximal size generator.
Definition spk_resizable_element.cpp:31
void configureDesiredGenerator(Generator p_generator)
Configures the desired size generator.
Definition spk_resizable_element.cpp:26
void release()
Releases all cached size hint values.
Definition spk_resizable_element.cpp:36
void configureMinimalGenerator(Generator p_generator)
Configures the minimal size generator.
Definition spk_resizable_element.cpp:21
SizeHint & sizeHint()
Returns mutable size hint data.
Definition spk_resizable_element.cpp:86
Editable text field with validation and cursor handling.
Definition spk_text_edit.hpp:29
spk::ObservableValue< std::wstring >::Contract EditionContract
Edition contract type for text changes.
Definition spk_text_edit.hpp:54
ValidationState
Validation state of the text content.
Definition spk_text_edit.hpp:36
spk::TaskResult< ValidationState > ValidationStatus
Validation status result type.
Definition spk_text_edit.hpp:44
std::function< ValidationStatus(const std::wstring &)> ValidationCallback
Validation callback signature.
Definition spk_text_edit.hpp:49
Base class for interactive UI elements handling focus and events.
Definition spk_widget.hpp:52
const spk::Extend2D & geometry() const
Returns the current widget geometry.
Definition spk_widget.cpp:195
Widget(const std::wstring &p_name, Widget *p_parent)
Builds a widget optionally attached to a parent.
Definition spk_widget.cpp:103
TType x
X component.
Definition spk_vector2.hpp:44
TType y
Y component.
Definition spk_vector2.hpp:48
static spk::SpriteSheet defaultIconset
Default sprite sheet used for widget icons.
Definition spk_widget.hpp:72