#ifndef BWIDGETS_NUMERIC_INPUT_HPP #define BWIDGETS_NUMERIC_INPUT_HPP #include #include #include #include #include #include namespace bwidgets { template class NumericInput : public Input { protected: Button _increment_button; SDL_Rect _increment_button_area {}; Button _decrement_button; SDL_Rect _decrement_button_area {}; std::pair _value_range {std::numeric_limits::lowest(), std::numeric_limits::max()}; void _handle_font_change(Font* f) override { _decrement_button.font(f); _increment_button.font(f); Input::_handle_font_change(f); } void _handle_font_color_change(const Color& fg, const Color& bg) override { Input::_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); } void _handle_geometry_change(const SDL_Rect& vp) noexcept override { Input::_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; Widget::_widget_area.x = Widget::_widget_area.x + button_area_width; Widget::_widget_area.w = field_width; Input::_input_caption.viewport( rect_offset(rect_margin(Widget::_widget_area, {Input::border_width, Input::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(rect_offset(_increment_button_area, vp)); _decrement_button.viewport(rect_offset(_decrement_button_area, vp)); } void _handle_renderer_change(Renderer* r) override { Input::_handle_renderer_change(r); _decrement_button.renderer(r); _increment_button.renderer(r); } void _handle_rendering() override { Input::_handle_rendering(); _increment_button.render(); _decrement_button.render(); } public: T button_step = 1; NumericInput(Widget* parent = nullptr) : Input(parent), _increment_button(this), _decrement_button(this) { Input::_input_caption.alignment = Caption::Alignment::RIGHT; Input::input_min_width = 10; // NOLINT(readability-magic-numbers) Input::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)); }; } auto handle_event(const SDL_Event& ev) -> Widget* override { Input::handle_event(ev); _increment_button.handle_event(ev); _decrement_button.handle_event(ev); return this; } [[nodiscard]] auto is_valid_input(const std::string& input) const noexcept -> bool override { bool valid = false; if (input[0] >= '0' && input[0] <= '9') valid = true; if constexpr (std::is_floating_point_v) if (input[0] == '.' && Input::input_text().find('.') == std::string::npos) valid = true; if constexpr (std::is_signed_v) { std::string displayed_value = Input::input_text(); if (input[0] == '-' && displayed_value.empty()) valid = true; } return valid; } [[nodiscard]] auto process_value(T x) const noexcept -> T 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; } [[nodiscard]] inline auto size() const noexcept -> Size override { auto base = Input::size(); auto btns_width = 4 * _increment_button.size().w; return {base.w + btns_width, base.h}; } [[nodiscard]] virtual auto value_range() const noexcept -> const std::pair& { return _value_range; } virtual auto value_range(T min, T max) -> NumericInput* { if (min > max) throw std::invalid_argument("min cannot be greater than max"); _value_range = {min, max}; Input::value = process_value(Input::value); Input::input_text(Input::value_to_string(Input::value)); return this; } }; } #endif