197 lines
7.6 KiB
C++
197 lines
7.6 KiB
C++
#ifndef BWIDGETS_NUMERIC_INPUT_IMPL_HPP
|
|
#define BWIDGETS_NUMERIC_INPUT_IMPL_HPP
|
|
|
|
#include <basic_widgets/w/base/input_impl.hpp>
|
|
#include <basic_widgets/w/numeric_input.hpp>
|
|
|
|
namespace bwidgets
|
|
{
|
|
template<Numeric T>
|
|
class NumericInputImpl : public virtual NumericInput<T>,
|
|
public virtual InputImpl<T>
|
|
{
|
|
using Self = NumericInputImpl;
|
|
|
|
public:
|
|
NumericInputImpl(Widget* parent = nullptr)
|
|
: InputImpl<T>(parent),
|
|
_increment_button(create_button(this)),
|
|
_decrement_button(create_button(this))
|
|
{
|
|
Self::_input_caption->alignment = Caption::Alignment::RIGHT;
|
|
|
|
Self::input_min_width = 10; // NOLINT(readability-magic-numbers)
|
|
Self::input_width_unit = '0';
|
|
|
|
// +/- buttons click handler. op is the operation (+/-) to apply on current
|
|
// value.
|
|
const auto button_action = [this](const std::function<T(T, T)> op) {
|
|
const T new_value = [this, &op]() {
|
|
auto _new_value = op(this->value, NumericInput<T>::button_step);
|
|
if (_new_value < this->value) {
|
|
// if value - button_step is out of allowed range
|
|
// set value to the smallest allowed value.
|
|
if (std::abs(_value_range.first - this->value)
|
|
< std::abs(NumericInput<T>::button_step))
|
|
return _value_range.first;
|
|
return _new_value;
|
|
}
|
|
// if value + button_step exceeds maximal allowed value
|
|
// set value to max allowed value.
|
|
if (std::abs(_value_range.second - this->value)
|
|
< std::abs(NumericInput<T>::button_step))
|
|
return _value_range.second;
|
|
return _new_value;
|
|
}();
|
|
|
|
this->value = new_value;
|
|
this->input_text(this->value_to_string(new_value));
|
|
};
|
|
|
|
_increment_button->text("+");
|
|
_increment_button->click_handler(
|
|
[button_action](const SDL_MouseButtonEvent&) {
|
|
button_action([](const T a, const T b) { return a + b; });
|
|
});
|
|
|
|
_decrement_button->text("-");
|
|
|
|
_decrement_button->click_handler(
|
|
[button_action](const SDL_MouseButtonEvent&) {
|
|
button_action([](const T a, const T b) { return a - b; });
|
|
});
|
|
}
|
|
|
|
void handle_event(const SDL_Event& ev) override
|
|
{
|
|
InputImpl<T>::handle_event(ev);
|
|
_increment_button->handle_event(ev);
|
|
_decrement_button->handle_event(ev);
|
|
}
|
|
|
|
[[nodiscard]] auto is_valid_input(const std::string_view input) const noexcept
|
|
-> bool override
|
|
{
|
|
bool valid = [input, this]() {
|
|
// Allow 0-9 chars.
|
|
if (input[0] >= '0' && input[0] <= '9') return true;
|
|
// Allow one point for float types.
|
|
if constexpr (std::is_floating_point_v<T>)
|
|
if (input[0] == '.'
|
|
&& Self::input_text().find('.') == std::string::npos)
|
|
return true;
|
|
// Allow "-" as first input char for signed types.
|
|
if constexpr (std::is_signed_v<T>) {
|
|
std::string_view displayed_value = Self::input_text();
|
|
if (input[0] == '-' && displayed_value.empty()) return true;
|
|
}
|
|
return false;
|
|
}();
|
|
return valid;
|
|
}
|
|
|
|
[[nodiscard]] auto process_value(const T x) const noexcept -> T override
|
|
{
|
|
// If value is out of allowed range return either min or max allowed
|
|
// value.
|
|
T value = [x, this]() {
|
|
if (x < _value_range.first) return _value_range.first;
|
|
if (x > _value_range.second) return _value_range.second;
|
|
return x;
|
|
}();
|
|
|
|
return value;
|
|
}
|
|
|
|
[[nodiscard]] auto size() const noexcept -> Size override
|
|
{
|
|
const auto base = InputImpl<T>::size();
|
|
const auto btns_width = 4 * _increment_button->size().w;
|
|
return {base.w + btns_width, base.h};
|
|
}
|
|
|
|
[[nodiscard]] auto value_range() const noexcept
|
|
-> const std::pair<T, T>& override
|
|
{
|
|
return _value_range;
|
|
}
|
|
|
|
void value_range(const T min, const T max) override
|
|
{
|
|
if (min > max) throw std::invalid_argument("min cannot be greater than max");
|
|
_value_range = {min, max};
|
|
Self::value = process_value(Self::value);
|
|
Self::input_text(Self::value_to_string(Self::value));
|
|
}
|
|
|
|
protected:
|
|
std::unique_ptr<Button> _increment_button;
|
|
SDL_Rect _increment_button_area {};
|
|
std::unique_ptr<Button> _decrement_button;
|
|
SDL_Rect _decrement_button_area {};
|
|
std::pair<T, T> _value_range {std::numeric_limits<T>::lowest(),
|
|
std::numeric_limits<T>::max()};
|
|
|
|
void _handle_font_change(const std::shared_ptr<Font>& f) override
|
|
{
|
|
_decrement_button->font(f);
|
|
_increment_button->font(f);
|
|
InputImpl<T>::_handle_font_change(f);
|
|
}
|
|
|
|
void _handle_font_color_change(const Color fg, const Color bg) override
|
|
{
|
|
InputImpl<T>::_handle_font_color_change(fg, bg);
|
|
_decrement_button->font_color_bg(bg);
|
|
_decrement_button->font_color_fg(fg);
|
|
_increment_button->font_color_bg(bg);
|
|
_increment_button->font_color_fg(fg);
|
|
}
|
|
|
|
void _handle_geometry_change(const SDL_Rect& vp) override
|
|
{
|
|
InputImpl<T>::_handle_geometry_change(vp);
|
|
|
|
const int widest_button = _increment_button->size().w != 0
|
|
? _increment_button->size().w
|
|
: _decrement_button->size().w;
|
|
const int button_area_width = 2 * widest_button;
|
|
const int spacing = center_line(button_area_width, widest_button);
|
|
const int field_width = vp.w - 2 * button_area_width;
|
|
|
|
Self::_widget_area.x = Self::_widget_area.x + button_area_width;
|
|
Self::_widget_area.w = field_width;
|
|
|
|
Self::_input_caption->viewport(rect_offset(
|
|
rect_margin(Self::_widget_area, {Self::border_width, Self::border_width}),
|
|
vp));
|
|
_decrement_button_area = {spacing, Self::_widget_area.y,
|
|
_decrement_button->size().w,
|
|
_decrement_button->size().h};
|
|
|
|
_increment_button_area = {vp.w - spacing - _increment_button->size().w,
|
|
Self::_widget_area.y, _increment_button->size().w,
|
|
_increment_button->size().h};
|
|
|
|
_increment_button->viewport(rect_offset(_increment_button_area, vp));
|
|
_decrement_button->viewport(rect_offset(_decrement_button_area, vp));
|
|
}
|
|
|
|
void _handle_renderer_change(const std::shared_ptr<Renderer>& r) override
|
|
{
|
|
InputImpl<T>::_handle_renderer_change(r);
|
|
_decrement_button->renderer(r);
|
|
_increment_button->renderer(r);
|
|
}
|
|
|
|
void _handle_rendering() override
|
|
{
|
|
InputImpl<T>::_handle_rendering();
|
|
_increment_button->render();
|
|
_decrement_button->render();
|
|
}
|
|
};
|
|
}
|
|
|
|
#endif
|