bwidgets/inc/basic_widgets/w/numeric_input_impl.hpp

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