bwidgets/inc/basic_widgets/w/numeric_input.hpp

176 lines
6.3 KiB
C++

#ifndef BWIDGETS_NUMERIC_INPUT_HPP_
#define BWIDGETS_NUMERIC_INPUT_HPP_
#include <limits>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/type/concepts.hpp>
#include <basic_widgets/w/base/input.hpp>
#include <basic_widgets/w/button.hpp>
#include <type_traits>
namespace bwidgets::widget
{
template<core::Numeric T>
class NumericInput : public Input<T>
{
private:
Button _increment_button;
SDL_Rect _increment_button_area;
Button _decrement_button;
SDL_Rect _decrement_button_area;
std::pair<T, T> _value_range {std::numeric_limits<T>::lowest(),
std::numeric_limits<T>::max()};
virtual void _handle_font_change(core::Font* f) override
{
_decrement_button.font(f);
_increment_button.font(f);
Input<T>::_handle_font_change(f);
}
virtual void _handle_font_color_change(const SDL_Color& fg,
const SDL_Color& bg) override
{
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);
}
virtual void _handle_geometry_change(const SDL_Rect& vp) noexcept override
{
Input<T>::_handle_geometry_change(vp);
const int widest_button = _increment_button.size().w
? _increment_button.size().w
: _decrement_button.size().w;
const int button_area_width = 2 * widest_button;
const int spacing = core::center_rect(button_area_width, widest_button);
const int field_width = vp.w - 2 * button_area_width;
Widget::_widget_area.x = Widget::_widget_area.x + button_area_width;
Widget::_widget_area.w = field_width;
Input<T>::_input_caption.viewport(core::rect_offset(
core::rect_margin(Widget::_widget_area,
{Input<T>::_border_width, Input<T>::_border_width}),
vp));
_decrement_button_area = {spacing, Widget::_widget_area.y,
_decrement_button.size().w,
_decrement_button.size().h};
_increment_button_area = {vp.w - spacing - _increment_button.size().w,
Widget::_widget_area.y, _increment_button.size().w,
_increment_button.size().h};
_increment_button.viewport(core::rect_offset(_increment_button_area, vp));
_decrement_button.viewport(core::rect_offset(_decrement_button_area, vp));
}
virtual void _handle_renderer_change(core::Renderer* r) override
{
Input<T>::_handle_renderer_change(r);
_decrement_button.renderer(r);
_increment_button.renderer(r);
}
virtual void _handle_rendering() override
{
Input<T>::_handle_rendering();
_increment_button.render();
_decrement_button.render();
}
public:
T button_step = 1;
NumericInput(Widget* parent = nullptr)
: Input<T>(parent), _increment_button(this), _decrement_button(this)
{
Input<T>::_input_caption.alignment = widget::Caption::Alignment::RIGHT;
Input<T>::input_min_width = 10;
Input<T>::input_width_unit = '0';
_increment_button.text("+");
_increment_button.click_handler = [this](const SDL_MouseButtonEvent&) {
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));
};
_decrement_button.text("-");
_decrement_button.click_handler = [this](const SDL_MouseButtonEvent&) {
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));
};
}
virtual Widget* handle_event(const SDL_Event& ev) override
{
Widget::handle_event(ev);
_increment_button.handle_event(ev);
_decrement_button.handle_event(ev);
return this;
}
virtual bool is_valid_input(const std::string input) const noexcept override
{
bool valid = false;
if (input.at(0) >= '0' && input.at(0) <= '9') valid = true;
if constexpr (std::is_floating_point_v<T>)
if (input.at(0) == '.'
&& Input<T>::input_text().find('.') == std::string::npos)
valid = true;
if constexpr (std::is_signed_v<T>) {
std::string displayed_value = Input<T>::input_text();
if (input.at(0) == '-' && displayed_value.empty()) valid = true;
}
return valid;
}
virtual T process_value(T x) const noexcept override
{
T value = x;
if (x < _value_range.first) value = _value_range.first;
else if (x > _value_range.second) value = _value_range.second;
return value;
}
virtual inline core::Size size() const noexcept override
{
auto base = Input<T>::size();
auto btns_width = 4 * _increment_button.size().w;
return {base.w + btns_width, base.h};
}
virtual std::pair<T, T> value_range() const noexcept
{
return _value_range;
}
virtual NumericInput<T>* value_range(T min, T max)
{
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;
}
};
}
#endif