86 if (base_type::has_size_policy())
87 return base_type::size_policy();
88 else if (base_type::has_fixed_size())
96 if (has_palette_color(aColorRole))
97 return base_type::palette_color(aColorRole);
100 return base_type::palette_color(aColorRole);
103 template <
typename T>
106 return iTextBox.frame_color();
109 template <
typename T>
114 do_step(aDelta.
dy > 0.0 ? step_direction::Up : step_direction::Down);
118 return base_type::mouse_wheel_scrolled(aWheel, aPosition, aDelta, aKeyModifiers);
121 template <
typename T>
126 do_step(step_direction::Up);
131 do_step(step_direction::Down);
135 return base_type::key_pressed(aScanCode, aKeyCode, aKeyModifiers);
138 template <
typename T>
144 template <
typename T>
150 template <
typename T>
156 template <
typename T>
159 iSecondaryLayout.enable();
162 template <
typename T>
165 iSecondaryLayout.disable();
168 template <
typename T>
171 auto result = std::max(minimum(),
173 static_cast<value_type
>(aDirection == step_direction::Up ? value() +
static_cast<value_type
>(aAmount * step()) : value() - static_cast<value_type>(aAmount * step()))));
174 if ((aDirection == step_direction::Up && result > value()) || (aDirection == step_direction::Down && result < value()))
175 set_value(result,
true);
178 template <
typename T>
179 inline std::optional<T> basic_spin_box<T>::string_to_value(std::string
const& aText)
const
182 return std::optional<value_type>{};
183 value_type result = minimum();
184 if (maximum() == minimum())
188 std::istringstream iss(aText);
189 if (!(iss >> result))
190 return std::optional<value_type>{};
193 return std::optional<value_type>{};
197 template <
typename T>
198 void basic_spin_box<T>::init()
202 set_background_opacity(1.0);
205 iPrimaryLayout.set_spacing(
dip(INTERNAL_SPACING));
207 iSecondaryLayout.set_spacing(
size{});
208 iStepUpButton.set_minimum_size(
dip(SPIN_BUTTON_MINIMUM_SIZE));
210 iStepUpButton.Clicked.set_trigger_type(trigger_type::Synchronous);
211 iStepUpButton.DoubleClicked.set_trigger_type(trigger_type::Synchronous);
212 iStepDownButton.set_minimum_size(
dip(SPIN_BUTTON_MINIMUM_SIZE));
214 iStepDownButton.Clicked.set_trigger_type(trigger_type::Synchronous);
215 iStepDownButton.DoubleClicked.set_trigger_type(trigger_type::Synchronous);
218 iSink += iTextBox.TextFilter([
this](std::string
const& aText,
bool& aAccept)
220 aAccept = aText.find_first_not_of(valid_text_characters()) == std::string::npos;
222 service<i_basic_services>().system_beep();
225 iSink += iTextBox.TextChanged([
this]()
227 auto const& text = iTextBox.text();
228 auto result = string_to_value(text);
229 if (result != std::nullopt)
233 iTextCursorPos = iTextBox.cursor().position();
234 set_value(std::min(maximum(), std::max(minimum(), *result)));
236 else if (!text.empty())
238 iTextBox.set_text(iText);
239 iTextBox.cursor().set_position(iTextCursorPos);
240 service<i_basic_services>().system_beep();
246 iTextCursorPos = iTextBox.cursor().position();
247 set_value(minimum());
251 auto step_up = [
this]()
253 do_step(step_direction::Up);
254 iStepper.emplace(*
this, [
this](widget_timer& aTimer)
256 aTimer.set_duration(std::chrono::milliseconds{ 125 },
true);
258 do_step(step_direction::Up);
259 }, std::chrono::milliseconds{ 500 });
261 iSink += iStepUpButton.Pressed(step_up);
262 iSink += iStepUpButton.clicked([
this]()
264 if (iStepper == std::nullopt)
265 do_step(step_direction::Up);
267 iSink += iStepUpButton.DoubleClicked(step_up);
268 iSink += iStepUpButton.Released([
this]()
270 iStepper = std::nullopt;
273 auto step_down = [
this]()
275 do_step(step_direction::Down);
276 iStepper.emplace(*
this, [
this](widget_timer& aTimer)
278 aTimer.set_duration(std::chrono::milliseconds{ 125 },
true);
280 do_step(step_direction::Down);
281 }, std::chrono::milliseconds{ 500 });
283 iSink += iStepDownButton.Pressed(step_down);
284 iSink += iStepDownButton.clicked([
this]()
286 if (iStepper == std::nullopt)
287 do_step(step_direction::Down);
289 iSink += iStepDownButton.DoubleClicked(step_down);
290 iSink += iStepDownButton.Released([
this]()
292 iStepper = std::nullopt;
296 iSink += service<i_app>().current_style_changed([
this](
style_aspect aAspect)
302 iSink += service<i_surface_manager>().dpi_changed([
this](i_surface&) { update_arrows(); });
304 iSink += iTextBox.focus_event([&](neogfx::focus_event, focus_reason) { update(
true); });
307 template <
typename T>
308 void basic_spin_box<T>::update_size_hint()
310 if (text_box_size_hint())
311 text_box().set_size_hint(*text_box_size_hint());
314 std::string hintText;
318 tryText = boost::str(boost::format(iFormat) % minimum());
319 if (tryText.length() > hintText.length())
321 tryText = boost::str(boost::format(iFormat) % (minimum() + step()));
322 if (tryText.length() > hintText.length())
324 tryText = boost::str(boost::format(iFormat) % maximum());
325 if (tryText.length() > hintText.length())
327 tryText = boost::str(boost::format(iFormat) % (maximum() - step()));
328 if (tryText.length() > hintText.length())
334 text_box().set_size_hint(size_hint{ hintText });
338 template <
typename T>
339 void basic_spin_box<T>::update_arrows()
341 auto ink = service<i_app>().current_style().palette().color(
color_role::Text);
342 const char* sUpArrowImagePattern
354 const char* sUpArrowHighDpiImagePattern
370 iUpArrow.emplace(ink,
372 dpi_select(
"neogfx::basic_spin_box<T>::iUpArrow::"s,
"neogfx::basic_spin_box<T>::iUpArrowHighDpi::"s) + ink.to_string(),
373 dpi_select(sUpArrowImagePattern, sUpArrowHighDpiImagePattern), { {
"paper",
color{} },{
"ink", ink } }, dpi_select(1.0, 2.0) });
374 const char* sDownArrowImagePattern
386 const char* sDownArrowHighDpiImagePattern
402 iDownArrow.emplace(ink,
404 dpi_select(
"neogfx::basic_spin_box<T>::iDownArrow::"s,
"neogfx::basic_spin_box<T>::iDownArrowHighDpi::"s) + ink.to_string(),
405 dpi_select(sDownArrowImagePattern, sDownArrowHighDpiImagePattern), { {
"paper",
color{} },{
"ink", ink } }, dpi_select(1.0, 2.0) });
408 iStepUpButton.set_image(iUpArrow->second);
409 iStepDownButton.set_image(iDownArrow->second);
412 template <
typename T>
418 template <
typename T>
423 try { text = boost::str(boost::format(iFormat) % minimum()); }
catch (...) {}
424 if (text_box().text().empty())
425 text_box().set_text(
string{ text });
426 ConstraintsChanged.trigger();
427 if (iValue < minimum())
428 set_value(minimum());
432 template <
typename T>
438 template <
typename T>
442 ConstraintsChanged.trigger();
443 if (iValue > maximum())
444 set_value(maximum());
448 template <
typename T>
454 template <
typename T>
458 ConstraintsChanged.trigger();
462 template <
typename T>
468 template <
typename T>
471 aValue = std::max(minimum(), std::min(maximum(), aValue));
472 if (iValue != aValue)
476 iTextBox.set_text(
string{ value_to_string() });
477 if (aNotify && (!text().empty() || value() != minimum()))
478 ValueChanged.trigger();
480 else if (!iDontSetText && iTextBox.text().empty())
481 iTextBox.set_text(
string{ value_to_string() });
484 template <
typename T>
490 template <
typename T>
498 template <
typename T>
501 return iTextBoxSizeHint;
504 template <
typename T>
507 iTextBoxSizeHint = aSizeHint;
511 template <
typename T>
514 if (std::is_integral<T>::value)
516 if (std::is_signed<T>::value)
518 static const std::string sValid{
"01234567890+-" };
523 static const std::string sValid{
"01234567890" };
527 else if (std::is_floating_point<T>::value)
529 static const std::string sValid{
"01234567890.+-eE" };
534 static const std::string sValid;
539 template <
typename T>
543 try { text = boost::str(boost::format(iFormat) % value()); }
catch (...) {}