#ifndef BWIDGETS_INPUT_HPP #define BWIDGETS_INPUT_HPP #include #include #include #include #include #include #include #include #include #include #include #include namespace bwidgets { template class Input : public Widget, public FontHandler, public KeyboardHandler, public MouseHandler { protected: Caption _input_caption; Input(Widget* parent = nullptr) : Widget(parent), _input_caption(this) { FocusHandler::_focus_area = &_widget_area; MouseHandler::_click_area = &_widget_area; _input_caption.text(value_to_string(value)); } void _handle_focus_change(bool focus) override { if (focus) { SDL_StartTextInput(); } else { value = value_from_string(input_text()); input_text(value_to_string(value)); SDL_StopTextInput(); } } void _handle_font_change(const std::shared_ptr& f) override { _input_caption.font(f); _handle_geometry_change(_viewport); } void _handle_font_color_change(const Color& fg, const Color& bg) override { _input_caption.font_color_bg(bg)->font_color_fg(fg); } void _handle_geometry_change(const SDL_Rect& vp) override { const auto input_h = _input_caption.size().h + 2 * border_width; _widget_area = {0, center_line(vp.h, input_h), vp.w, input_h}; _input_caption.viewport(rect_offset( {rect_margin(_widget_area, {border_width, border_width})}, vp)); } void _handle_key(const SDL_KeyboardEvent& key) override { if (key.type == SDL_KEYDOWN) { switch (key.keysym.sym) { case SDLK_BACKSPACE: { std::string txt = input_text(); if (txt.length() > 0) { txt.pop_back(); input_text(txt); } break; } case SDLK_RETURN: case SDLK_RETURN2: // what is return2 btw? case SDLK_KP_ENTER: value = value_from_string(input_text()); focus(false); break; } } } void _handle_renderer_change(const std::shared_ptr& r) override { _input_caption.renderer(r); } void _handle_rendering() override { for (int i = border_width - 1; i >= 0; i--) { const auto factor = linear(i, 0, border_width); const auto& color_end = _has_focus ? color_bg_focused : color_bg; const auto color_start = color_end / 2; _renderer->draw_color(lerp(color_start, color_end, factor)) ->draw_rect(rect_margin(_widget_area, {i, i})); } if (MouseHandler::_is_hovered || FocusHandler::_has_focus) _input_caption.font_color_bg(color_bg_focused); else _input_caption.font_color_bg(color_bg); _input_caption.render(); } void _handle_text_input(const SDL_TextInputEvent& input) override { if (is_valid_input(input.text)) { input_text(input_text() + input.text); } } public: static const int default_border_width = 3; inline static const Color default_color_bg = {200, 200, 200, SDL_ALPHA_OPAQUE}; inline static const Color default_color_bg_focused = {255, 255, 255, SDL_ALPHA_OPAQUE}; static const int default_float_precision = 2; static const int default_min_width = 1; int border_width = default_border_width; Color color_bg = default_color_bg; Color color_bg_focused = default_color_bg_focused; int float_precision = default_float_precision; int input_min_width = default_min_width; char input_width_unit = 'W'; T value {}; Input(const Input&) = delete; Input(Input&&) = delete; ~Input() override = default; auto operator=(const Input&) = delete; auto operator=(Input&&) = delete; virtual auto color_fg(const Color& c) -> Input* { _input_caption.font_color_fg(c); return this; } [[nodiscard]] virtual auto input_text() const -> const std::string& { return _input_caption.text(); } virtual auto input_text(const std::string& txt) -> Input* { _input_caption.text(txt); return this; } [[nodiscard]] virtual auto is_valid_input(const std::string&) const noexcept -> bool { return true; } [[nodiscard]] virtual auto process_value(T x) const noexcept -> T { return x; } [[nodiscard]] auto size() const noexcept -> Size override { if (_font == nullptr) return {0, 0}; return { _font->text_size(std::string(input_min_width, input_width_unit)).w + 2 * border_width, _font->line_skip + 4 * border_width // _why_ 4 and not 2?… }; } [[nodiscard]] auto value_from_string(std::string s) noexcept { T v; if constexpr (std::is_arithmetic_v) { s = s.length() > 0 ? s : "0"; if constexpr (std::is_floating_point_v) v = (T)std::stold(s); else if constexpr (std::is_integral_v) v = (T)std::stoll(s); } else if constexpr (std::is_same_v< T, std::string> || std::convertible_to) v = s; else // NOLINTNEXTLINE(readability-simplify-boolean-expr) static_assert((bool)sizeof(T) && false, "string cannot be converted to v type T."); return process_value(v); } [[nodiscard]] auto value_to_string(T value) noexcept { std::string s; if constexpr (std::is_same_v || std::convertible_to) s = std::move(value); else if constexpr (CanToString) { std::stringstream ss; ss << std::fixed << std::setprecision(float_precision) << value; s = std::move(ss).str(); } else if constexpr (Printable) { std::stringstream ss; ss << value; s = std::move(ss).str(); } else // NOLINTNEXTLINE(readability-simplify-boolean-expr) static_assert((bool)sizeof(T) && false, "value cannot be converted to string."); return s; } }; } #endif