neoGFX
Cross-platform C++ app/game engine
Loading...
Searching...
No Matches
spin_box.ipp
Go to the documentation of this file.
1// spin_box.inl
2/*
3 neogfx C++ App/Game Engine
4 Copyright (c) 2015, 2020 Leigh Johnston. All Rights Reserved.
5
6 This program is free software: you can redistribute it and / or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18*/
19
20#pragma once
21
22#include <cmath>
23#include <boost/format.hpp>
25#include <neogfx/app/i_app.hpp>
29
30namespace neogfx
31{
32 template <typename T>
34 base_type{ 2.0 },
35 iPrimaryLayout{ *this },
36 iTextBox{ iPrimaryLayout },
37 iSecondaryLayout{ iPrimaryLayout },
38 iStepUpButton{ iSecondaryLayout, std::string{}, push_button_style::SpinBox },
39 iStepDownButton{ iSecondaryLayout, std::string{}, push_button_style::SpinBox },
40 iMinimum{},
41 iMaximum{},
42 iStep{},
43 iValue{},
44 iFormat{ "%1%" }
45 {
46 init();
47 }
48
49 template <typename T>
51 base_type{ aParent, 2.0 },
52 iPrimaryLayout{ *this },
53 iTextBox{ iPrimaryLayout },
54 iSecondaryLayout{ iPrimaryLayout },
55 iStepUpButton{ iSecondaryLayout, std::string{}, push_button_style::SpinBox },
56 iStepDownButton{ iSecondaryLayout, std::string{}, push_button_style::SpinBox },
57 iMinimum{},
58 iMaximum{},
59 iStep{},
60 iValue{},
61 iFormat{ "%1%" }
62 {
63 init();
64 }
66 template <typename T>
68 base_type{ aLayout, 2.0 },
69 iPrimaryLayout{ *this },
70 iTextBox{ iPrimaryLayout },
71 iSecondaryLayout{ iPrimaryLayout },
72 iStepUpButton{ iSecondaryLayout, std::string{}, push_button_style::SpinBox },
73 iStepDownButton{ iSecondaryLayout, std::string{}, push_button_style::SpinBox },
74 iMinimum{},
75 iMaximum{},
76 iStep{},
77 iValue{},
78 iFormat{ "%1%" }
79 {
80 init();
81 }
82
83 template <typename T>
84 neogfx::size_policy basic_spin_box<T>::size_policy() const
85 {
86 if (base_type::has_size_policy())
87 return base_type::size_policy();
88 else if (base_type::has_fixed_size())
90 return neogfx::size_policy{ text_box().size_hint() ? size_constraint::Minimum : size_constraint::Expanding, size_constraint::Minimum };
91 }
92
93 template <typename T>
95 {
96 if (has_palette_color(aColorRole))
97 return base_type::palette_color(aColorRole);
98 if (aColorRole == color_role::Background)
99 return palette_color(color_role::Base);
100 return base_type::palette_color(aColorRole);
101 }
102
103 template <typename T>
105 {
106 return iTextBox.frame_color();
107 }
108
109 template <typename T>
110 bool basic_spin_box<T>::mouse_wheel_scrolled(mouse_wheel aWheel, const point& aPosition, delta aDelta, key_modifiers_e aKeyModifiers)
111 {
112 if (aWheel == mouse_wheel::Vertical)
113 {
114 do_step(aDelta.dy > 0.0 ? step_direction::Up : step_direction::Down);
115 return true;
116 }
117 else
118 return base_type::mouse_wheel_scrolled(aWheel, aPosition, aDelta, aKeyModifiers);
119 }
120
121 template <typename T>
123 {
124 if (aScanCode == ScanCode_UP)
125 {
126 do_step(step_direction::Up);
127 return true;
128 }
129 else if (aScanCode == ScanCode_DOWN)
130 {
131 do_step(step_direction::Down);
132 return true;
133 }
134 else
135 return base_type::key_pressed(aScanCode, aKeyCode, aKeyModifiers);
136 }
137
138 template <typename T>
140 {
141 return iText;
142 }
143
144 template <typename T>
146 {
147 return iTextBox;
148 }
149
150 template <typename T>
152 {
153 return iTextBox;
154 }
155
156 template <typename T>
158 {
159 iSecondaryLayout.enable();
160 }
161
162 template <typename T>
164 {
165 iSecondaryLayout.disable();
166 }
167
168 template <typename T>
169 void basic_spin_box<T>::do_step(step_direction aDirection, uint32_t aAmount)
170 {
171 auto result = std::max(minimum(),
172 std::min(maximum(),
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);
176 };
177
178 template <typename T>
179 inline std::optional<T> basic_spin_box<T>::string_to_value(std::string const& aText) const
180 {
181 if (aText.empty())
182 return std::optional<value_type>{};
183 value_type result = minimum();
184 if (maximum() == minimum())
185 return result;
186 if (aText == "-")
187 return result;
188 std::istringstream iss(aText);
189 if (!(iss >> result))
190 return std::optional<value_type>{};
191 std::string guff;
192 if (iss >> guff)
193 return std::optional<value_type>{};
194 return result;
195 }
196
197 template <typename T>
198 void basic_spin_box<T>::init()
199 {
200 set_padding(neogfx::padding{});
201
202 set_background_opacity(1.0);
203
204 iPrimaryLayout.set_padding(neogfx::padding{});
205 iPrimaryLayout.set_spacing(dip(INTERNAL_SPACING));
206 iSecondaryLayout.set_padding(neogfx::padding{});
207 iSecondaryLayout.set_spacing(size{});
208 iStepUpButton.set_minimum_size(dip(SPIN_BUTTON_MINIMUM_SIZE));
209 iStepUpButton.set_size_policy(neogfx::size_policy{ size_constraint::Minimum, size_constraint::Expanding });
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));
213 iStepDownButton.set_size_policy(neogfx::size_policy{ size_constraint::Minimum, size_constraint::Expanding });
214 iStepDownButton.Clicked.set_trigger_type(trigger_type::Synchronous);
215 iStepDownButton.DoubleClicked.set_trigger_type(trigger_type::Synchronous);
216 iTextBox.set_frame_style(frame_style::NoFrame);
217
218 iSink += iTextBox.TextFilter([this](std::string const& aText, bool& aAccept)
219 {
220 aAccept = aText.find_first_not_of(valid_text_characters()) == std::string::npos;
221 if (!aAccept)
222 service<i_basic_services>().system_beep();
223 });
224
225 iSink += iTextBox.TextChanged([this]()
226 {
227 auto const& text = iTextBox.text();
228 auto result = string_to_value(text);
229 if (result != std::nullopt)
230 {
231 neolib::scoped_flag sf{ iDontSetText };
232 iText = text;
233 iTextCursorPos = iTextBox.cursor().position();
234 set_value(std::min(maximum(), std::max(minimum(), *result)));
235 }
236 else if (!text.empty())
237 {
238 iTextBox.set_text(iText);
239 iTextBox.cursor().set_position(iTextCursorPos);
240 service<i_basic_services>().system_beep();
241 }
242 else
243 {
244 neolib::scoped_flag sf{ iDontSetText };
245 iText = text;
246 iTextCursorPos = iTextBox.cursor().position();
247 set_value(minimum());
248 }
249 });
250
251 auto step_up = [this]()
252 {
253 do_step(step_direction::Up);
254 iStepper.emplace(*this, [this](widget_timer& aTimer)
255 {
256 aTimer.set_duration(std::chrono::milliseconds{ 125 }, true);
257 aTimer.again();
258 do_step(step_direction::Up);
259 }, std::chrono::milliseconds{ 500 });
260 };
261 iSink += iStepUpButton.Pressed(step_up);
262 iSink += iStepUpButton.clicked([this]()
263 {
264 if (iStepper == std::nullopt) // key press?
265 do_step(step_direction::Up);
266 });
267 iSink += iStepUpButton.DoubleClicked(step_up);
268 iSink += iStepUpButton.Released([this]()
269 {
270 iStepper = std::nullopt;
271 });
272
273 auto step_down = [this]()
274 {
275 do_step(step_direction::Down);
276 iStepper.emplace(*this, [this](widget_timer& aTimer)
277 {
278 aTimer.set_duration(std::chrono::milliseconds{ 125 }, true);
279 aTimer.again();
280 do_step(step_direction::Down);
281 }, std::chrono::milliseconds{ 500 });
282 };
283 iSink += iStepDownButton.Pressed(step_down);
284 iSink += iStepDownButton.clicked([this]()
285 {
286 if (iStepper == std::nullopt) // key press?
287 do_step(step_direction::Down);
288 });
289 iSink += iStepDownButton.DoubleClicked(step_down);
290 iSink += iStepDownButton.Released([this]()
291 {
292 iStepper = std::nullopt;
293 });
294
295 update_arrows();
296 iSink += service<i_app>().current_style_changed([this](style_aspect aAspect)
297 {
298 if ((aAspect & style_aspect::Color) == style_aspect::Color)
299 update_arrows();
300 });
301
302 iSink += service<i_surface_manager>().dpi_changed([this](i_surface&) { update_arrows(); });
303
304 iSink += iTextBox.focus_event([&](neogfx::focus_event, focus_reason) { update(true); });
305 }
306
307 template <typename T>
308 void basic_spin_box<T>::update_size_hint()
309 {
310 if (text_box_size_hint())
311 text_box().set_size_hint(*text_box_size_hint());
312 else
313 {
314 std::string hintText;
315 try
316 {
317 std::string tryText;
318 tryText = boost::str(boost::format(iFormat) % minimum());
319 if (tryText.length() > hintText.length())
320 hintText = tryText;
321 tryText = boost::str(boost::format(iFormat) % (minimum() + step()));
322 if (tryText.length() > hintText.length())
323 hintText = tryText;
324 tryText = boost::str(boost::format(iFormat) % maximum());
325 if (tryText.length() > hintText.length())
326 hintText = tryText;
327 tryText = boost::str(boost::format(iFormat) % (maximum() - step()));
328 if (tryText.length() > hintText.length())
329 hintText = tryText;
330 }
331 catch (...)
332 {
333 }
334 text_box().set_size_hint(size_hint{ hintText });
335 }
336 }
337
338 template <typename T>
339 void basic_spin_box<T>::update_arrows()
340 {
341 auto ink = service<i_app>().current_style().palette().color(color_role::Text);
342 const char* sUpArrowImagePattern
343 {
344 "[9,5]"
345 "{0,paper}"
346 "{1,ink}"
347
348 "000010000"
349 "000111000"
350 "001111100"
351 "011111110"
352 "111111111"
353 };
354 const char* sUpArrowHighDpiImagePattern
355 {
356 "[18,9]"
357 "{0,paper}"
358 "{1,ink}"
359
360 "000000001100000000"
361 "000000011110000000"
362 "000000111111000000"
363 "000001111111100000"
364 "000011111111110000"
365 "000111111111111000"
366 "001111111111111100"
367 "011111111111111110"
368 "111111111111111111"
369 };
370 iUpArrow.emplace(ink,
371 image{
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
375 {
376 "[9,5]"
377 "{0,paper}"
378 "{1,ink}"
379
380 "111111111"
381 "011111110"
382 "001111100"
383 "000111000"
384 "000010000"
385 };
386 const char* sDownArrowHighDpiImagePattern
387 {
388 "[18,9]"
389 "{0,paper}"
390 "{1,ink}"
391
392 "111111111111111111"
393 "011111111111111110"
394 "001111111111111100"
395 "000111111111111000"
396 "000011111111110000"
397 "000001111111100000"
398 "000000111111000000"
399 "000000011110000000"
400 "000000001100000000"
401 };
402 iDownArrow.emplace(ink,
403 image{
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) });
406 iStepUpButton.label().set_placement(label_placement::ImageVertical);
407 iStepDownButton.label().set_placement(label_placement::ImageVertical);
408 iStepUpButton.set_image(iUpArrow->second);
409 iStepDownButton.set_image(iDownArrow->second);
410 }
411
412 template <typename T>
414 {
415 return iMinimum;
416 }
417
418 template <typename T>
419 inline void basic_spin_box<T>::set_minimum(value_type aMinimum)
420 {
421 iMinimum = aMinimum;
422 std::string text;
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());
429 update_size_hint();
430 }
431
432 template <typename T>
434 {
435 return iMaximum;
436 }
437
438 template <typename T>
439 inline void basic_spin_box<T>::set_maximum(value_type aMaximum)
440 {
441 iMaximum = aMaximum;
442 ConstraintsChanged.trigger();
443 if (iValue > maximum())
444 set_value(maximum());
445 update_size_hint();
446 }
447
448 template <typename T>
450 {
451 return iStep;
452 }
453
454 template <typename T>
455 inline void basic_spin_box<T>::set_step(value_type aStep)
456 {
457 iStep = aStep;
458 ConstraintsChanged.trigger();
459 update_size_hint();
460 }
461
462 template <typename T>
464 {
465 return iValue;
466 }
467
468 template <typename T>
469 inline void basic_spin_box<T>::set_value(value_type aValue, bool aNotify)
470 {
471 aValue = std::max(minimum(), std::min(maximum(), aValue));
472 if (iValue != aValue)
473 {
474 iValue = aValue;
475 if (!iDontSetText)
476 iTextBox.set_text(string{ value_to_string() });
477 if (aNotify && (!text().empty() || value() != minimum()))
478 ValueChanged.trigger();
479 }
480 else if (!iDontSetText && iTextBox.text().empty())
481 iTextBox.set_text(string{ value_to_string() });
482 }
483
484 template <typename T>
485 inline std::string const& basic_spin_box<T>::format() const
486 {
487 return iFormat;
488 }
489
490 template <typename T>
491 inline void basic_spin_box<T>::set_format(std::string const& aFormat)
492 {
493 iFormat = aFormat;
494 update_size_hint();
495 update();
496 }
497
498 template <typename T>
499 const std::optional<size_hint>& basic_spin_box<T>::text_box_size_hint() const
500 {
501 return iTextBoxSizeHint;
502 }
503
504 template <typename T>
505 void basic_spin_box<T>::set_text_box_size_hint(const std::optional<size_hint>& aSizeHint)
506 {
507 iTextBoxSizeHint = aSizeHint;
508 update_size_hint();
509 }
510
511 template <typename T>
512 inline std::string const& basic_spin_box<T>::valid_text_characters() const
513 {
514 if (std::is_integral<T>::value)
515 {
516 if (std::is_signed<T>::value)
517 {
518 static const std::string sValid{ "01234567890+-" };
519 return sValid;
520 }
521 else
522 {
523 static const std::string sValid{ "01234567890" };
524 return sValid;
525 }
526 }
527 else if (std::is_floating_point<T>::value)
528 {
529 static const std::string sValid{ "01234567890.+-eE" };
530 return sValid;
531 }
532 else
533 {
534 static const std::string sValid;
535 return sValid;
536 }
537 }
538
539 template <typename T>
540 inline std::string basic_spin_box<T>::value_to_string() const
541 {
542 std::string text;
543 try { text = boost::str(boost::format(iFormat) % value()); } catch (...) {}
544 return text;
545 }
546}
coordinate_type dy
color frame_color() const override
Definition spin_box.ipp:104
i_string const & text()
Definition spin_box.ipp:139
void set_step(value_type aStep)
Definition spin_box.ipp:455
value_type maximum() const
Definition spin_box.ipp:433
void set_maximum(value_type aMaximum)
Definition spin_box.ipp:439
void set_value(value_type aValue, bool aNotify=true)
Definition spin_box.ipp:469
color palette_color(color_role aColorRole) const override
Definition spin_box.ipp:94
void set_minimum(value_type aMinimum)
Definition spin_box.ipp:419
value_type step() const
Definition spin_box.ipp:449
value_type minimum() const
Definition spin_box.ipp:413
neogfx::size_policy size_policy() const override
Definition spin_box.ipp:84
const line_edit & text_box() const
Definition spin_box.ipp:145
value_type value() const
Definition spin_box.ipp:463
bool mouse_wheel_scrolled(mouse_wheel aWheel, const point &aPosition, delta aDelta, key_modifiers_e aKeyModifiers) override
Definition spin_box.ipp:110
bool key_pressed(scan_code_e aScanCode, key_code_e aKeyCode, key_modifiers_e aKeyModifiers) override
Definition spin_box.ipp:122
style_aspect
Definition i_style.hpp:31
mouse_wheel
Definition i_mouse.hpp:42
@ ScanCode_DOWN
basic_length< T > dip(T aValue)
Definition units.hpp:696
sRGB_color color
Definition color.hpp:1067
@ Background
Definition i_palette.hpp:33
basic_size< coordinate > size
Definition plf_hive.h:79