2021-08-06 14:22:56 +02:00
|
|
|
#ifndef BWIDGETS_NUMERIC_INPUT_HPP
|
|
|
|
#define BWIDGETS_NUMERIC_INPUT_HPP
|
2021-07-18 18:26:12 +02:00
|
|
|
|
|
|
|
#include <limits>
|
2021-08-15 20:04:53 +02:00
|
|
|
#include <type_traits>
|
2021-07-18 18:26:12 +02:00
|
|
|
|
|
|
|
#include <basic_widgets/core/math.hpp>
|
|
|
|
#include <basic_widgets/core/type/concepts.hpp>
|
|
|
|
#include <basic_widgets/w/base/input.hpp>
|
2021-08-17 11:40:03 +02:00
|
|
|
#include <basic_widgets/w/button_impl.hpp>
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-08-06 14:22:56 +02:00
|
|
|
namespace bwidgets
|
2021-07-18 18:26:12 +02:00
|
|
|
{
|
2021-08-06 14:22:56 +02:00
|
|
|
template<Numeric T>
|
2021-07-18 18:26:12 +02:00
|
|
|
class NumericInput : public Input<T>
|
|
|
|
{
|
2021-08-06 14:22:56 +02:00
|
|
|
protected:
|
2021-08-17 11:40:03 +02:00
|
|
|
ButtonImpl _increment_button;
|
2021-07-30 20:41:47 +02:00
|
|
|
SDL_Rect _increment_button_area {};
|
2021-08-17 11:40:03 +02:00
|
|
|
ButtonImpl _decrement_button;
|
2021-07-30 20:41:47 +02:00
|
|
|
SDL_Rect _decrement_button_area {};
|
2021-07-29 16:06:03 +02:00
|
|
|
std::pair<T, T> _value_range {std::numeric_limits<T>::lowest(),
|
|
|
|
std::numeric_limits<T>::max()};
|
|
|
|
|
2021-08-10 23:48:19 +02:00
|
|
|
void _handle_font_change(const std::shared_ptr<Font>& f) override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
_decrement_button.font(f);
|
|
|
|
_increment_button.font(f);
|
|
|
|
Input<T>::_handle_font_change(f);
|
|
|
|
}
|
2021-07-19 23:50:50 +02:00
|
|
|
|
2021-08-14 16:37:02 +02:00
|
|
|
void _handle_font_color_change(Color fg, Color bg) override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
Input<T>::_handle_font_color_change(fg, bg);
|
|
|
|
_decrement_button.font_color_bg(bg)->font_color_fg(fg);
|
|
|
|
_increment_button.font_color_bg(bg)->font_color_fg(fg);
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-08-08 16:00:22 +02:00
|
|
|
void _handle_geometry_change(const SDL_Rect& vp) override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
Input<T>::_handle_geometry_change(vp);
|
|
|
|
|
2021-07-30 20:41:47 +02:00
|
|
|
const int widest_button = _increment_button.size().w != 0
|
2021-07-29 16:06:03 +02:00
|
|
|
? _increment_button.size().w
|
|
|
|
: _decrement_button.size().w;
|
|
|
|
const int button_area_width = 2 * widest_button;
|
2021-08-06 14:22:56 +02:00
|
|
|
const int spacing = center_line(button_area_width, widest_button);
|
|
|
|
const int field_width = vp.w - 2 * button_area_width;
|
2021-07-29 16:06:03 +02:00
|
|
|
|
2021-08-17 00:27:51 +02:00
|
|
|
WidgetImpl::_widget_area.x = WidgetImpl::_widget_area.x + button_area_width;
|
|
|
|
WidgetImpl::_widget_area.w = field_width;
|
2021-07-29 16:06:03 +02:00
|
|
|
|
2021-08-06 14:22:56 +02:00
|
|
|
Input<T>::_input_caption.viewport(
|
2021-08-17 00:27:51 +02:00
|
|
|
rect_offset(rect_margin(WidgetImpl::_widget_area,
|
2021-08-06 14:22:56 +02:00
|
|
|
{Input<T>::border_width, Input<T>::border_width}),
|
|
|
|
vp));
|
2021-08-17 00:27:51 +02:00
|
|
|
_decrement_button_area = {spacing, WidgetImpl::_widget_area.y,
|
2021-07-29 16:06:03 +02:00
|
|
|
_decrement_button.size().w,
|
|
|
|
_decrement_button.size().h};
|
|
|
|
|
|
|
|
_increment_button_area = {vp.w - spacing - _increment_button.size().w,
|
2021-08-17 00:27:51 +02:00
|
|
|
WidgetImpl::_widget_area.y, _increment_button.size().w,
|
2021-07-29 16:06:03 +02:00
|
|
|
_increment_button.size().h};
|
|
|
|
|
2021-08-06 14:22:56 +02:00
|
|
|
_increment_button.viewport(rect_offset(_increment_button_area, vp));
|
|
|
|
_decrement_button.viewport(rect_offset(_decrement_button_area, vp));
|
2021-07-29 16:06:03 +02:00
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-08-10 23:48:19 +02:00
|
|
|
void _handle_renderer_change(const std::shared_ptr<Renderer>& r) override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
Input<T>::_handle_renderer_change(r);
|
|
|
|
_decrement_button.renderer(r);
|
|
|
|
_increment_button.renderer(r);
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-07-30 20:41:47 +02:00
|
|
|
void _handle_rendering() override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
Input<T>::_handle_rendering();
|
|
|
|
_increment_button.render();
|
|
|
|
_decrement_button.render();
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-07-29 16:06:03 +02:00
|
|
|
public:
|
|
|
|
T button_step = 1;
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-08-17 00:27:51 +02:00
|
|
|
NumericInput(WidgetImpl* parent = nullptr)
|
2021-07-29 16:06:03 +02:00
|
|
|
: Input<T>(parent), _increment_button(this), _decrement_button(this)
|
2021-07-18 18:26:12 +02:00
|
|
|
{
|
2021-08-06 14:22:56 +02:00
|
|
|
Input<T>::_input_caption.alignment = Caption::Alignment::RIGHT;
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-07-30 20:41:47 +02:00
|
|
|
Input<T>::input_min_width = 10; // NOLINT(readability-magic-numbers)
|
2021-07-23 20:08:52 +02:00
|
|
|
Input<T>::input_width_unit = '0';
|
|
|
|
|
2021-07-18 18:26:12 +02:00
|
|
|
_increment_button.text("+");
|
|
|
|
|
2021-08-15 20:04:53 +02:00
|
|
|
_increment_button.click_handler([this](const SDL_MouseButtonEvent&) {
|
2021-07-18 18:26:12 +02:00
|
|
|
T new_value = this->value + button_step;
|
|
|
|
if (_value_range.second - this->value < button_step)
|
|
|
|
new_value = _value_range.second;
|
|
|
|
|
|
|
|
this->value = new_value;
|
|
|
|
this->input_text(this->value_to_string(new_value));
|
2021-08-15 20:04:53 +02:00
|
|
|
});
|
2021-07-18 18:26:12 +02:00
|
|
|
|
|
|
|
_decrement_button.text("-");
|
|
|
|
|
2021-08-15 20:04:53 +02:00
|
|
|
_decrement_button.click_handler([this](const SDL_MouseButtonEvent&) {
|
2021-07-18 18:26:12 +02:00
|
|
|
T new_value = this->value - button_step;
|
|
|
|
if (this->value - _value_range.first < button_step)
|
|
|
|
new_value = _value_range.first;
|
|
|
|
|
|
|
|
this->value = new_value;
|
|
|
|
this->input_text(this->value_to_string(new_value));
|
2021-08-15 20:04:53 +02:00
|
|
|
});
|
2021-07-18 18:26:12 +02:00
|
|
|
}
|
|
|
|
|
2021-08-12 23:50:22 +02:00
|
|
|
NumericInput(const NumericInput&) = delete;
|
2021-08-14 09:25:28 +02:00
|
|
|
NumericInput(NumericInput&&) = delete;
|
|
|
|
~NumericInput() override = default;
|
2021-08-12 23:50:22 +02:00
|
|
|
auto operator=(const NumericInput&) = delete;
|
|
|
|
auto operator=(NumericInput&&) = delete;
|
|
|
|
|
2021-08-15 20:04:53 +02:00
|
|
|
void handle_event(const SDL_Event& ev) override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
2021-08-06 14:22:56 +02:00
|
|
|
Input<T>::handle_event(ev);
|
2021-07-29 16:06:03 +02:00
|
|
|
_increment_button.handle_event(ev);
|
|
|
|
_decrement_button.handle_event(ev);
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-07-30 20:41:47 +02:00
|
|
|
[[nodiscard]] auto is_valid_input(const std::string& input) const noexcept
|
|
|
|
-> bool override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
bool valid = false;
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-08-06 14:22:56 +02:00
|
|
|
if (input[0] >= '0' && input[0] <= '9') valid = true;
|
2021-07-29 16:06:03 +02:00
|
|
|
if constexpr (std::is_floating_point_v<T>)
|
2021-08-06 14:22:56 +02:00
|
|
|
if (input[0] == '.'
|
2021-07-29 16:06:03 +02:00
|
|
|
&& Input<T>::input_text().find('.') == std::string::npos)
|
2021-07-18 18:26:12 +02:00
|
|
|
valid = true;
|
2021-07-29 16:06:03 +02:00
|
|
|
if constexpr (std::is_signed_v<T>) {
|
|
|
|
std::string displayed_value = Input<T>::input_text();
|
2021-08-06 14:22:56 +02:00
|
|
|
if (input[0] == '-' && displayed_value.empty()) valid = true;
|
2021-07-18 18:26:12 +02:00
|
|
|
}
|
2021-07-29 16:06:03 +02:00
|
|
|
return valid;
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-08-06 14:22:56 +02:00
|
|
|
[[nodiscard]] auto process_value(T x) const noexcept -> T override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
T value = x;
|
|
|
|
if (x < _value_range.first) value = _value_range.first;
|
|
|
|
else if (x > _value_range.second) value = _value_range.second;
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-07-29 16:06:03 +02:00
|
|
|
return value;
|
|
|
|
}
|
2021-07-23 20:08:52 +02:00
|
|
|
|
2021-08-14 09:17:51 +02:00
|
|
|
[[nodiscard]] auto size() const noexcept -> Size override
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
2021-08-08 14:30:56 +02:00
|
|
|
const auto base = Input<T>::size();
|
|
|
|
const auto btns_width = 4 * _increment_button.size().w;
|
2021-07-29 16:06:03 +02:00
|
|
|
return {base.w + btns_width, base.h};
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-08-06 14:22:56 +02:00
|
|
|
[[nodiscard]] virtual auto value_range() const noexcept -> const std::pair<T, T>&
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
return _value_range;
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
|
2021-07-30 20:41:47 +02:00
|
|
|
virtual auto value_range(T min, T max) -> NumericInput<T>*
|
2021-07-29 16:06:03 +02:00
|
|
|
{
|
|
|
|
if (min > max) throw std::invalid_argument("min cannot be greater than max");
|
|
|
|
_value_range = {min, max};
|
|
|
|
Input<T>::value = process_value(Input<T>::value);
|
|
|
|
Input<T>::input_text(Input<T>::value_to_string(Input<T>::value));
|
|
|
|
return this;
|
|
|
|
}
|
2021-07-18 18:26:12 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|