#ifndef BWIDGETS_INPUT_IMPL_HPP #define BWIDGETS_INPUT_IMPL_HPP #include #include #include #include #include #include #include #include #include #include namespace bwidgets { template class InputImpl : public virtual Input, public virtual FontHandlerImpl, public virtual KeyboardHandlerImpl, public virtual MouseHandlerImpl, public virtual WidgetImpl { public: [[nodiscard]] auto input_text() const -> std::string_view override { return _input_caption->text(); } void input_text(std::string txt) override { _input_caption->text(std::move(txt)); } void input_text_color(const Color c) override { _input_caption->font_color_fg(c); } [[nodiscard]] auto is_valid_input(const std::string_view) const noexcept -> bool override { return true; } [[nodiscard]] auto process_value(const T x) const noexcept -> T override { return x; } [[nodiscard]] auto size() const noexcept -> Size override { if (!_font) return {0, 0}; return { _font ->text_size( std::string(InputImpl::input_min_width, InputImpl::input_width_unit)) .w + 2 * InputImpl::border_width, _font->line_skip + 4 * InputImpl::border_width // _why_ 4 and not 2?… }; } [[nodiscard]] auto value_from_string(const std::string_view s) const noexcept -> T override { const T v = [s]() { if constexpr (std::is_arithmetic_v) { const auto _s = s.length() > 0 ? s : "0"; if constexpr (std::is_floating_point_v) return (T)std::stold(_s.data()); else if constexpr (std::is_integral_v) return (T)std::stoll(_s.data()); } else if constexpr (std::is_same_v< T, std::string> || std::convertible_to) return std::string(s); else // NOLINTNEXTLINE(readability-simplify-boolean-expr) static_assert((bool)sizeof(T) && false, "string cannot be converted to type T."); }(); return process_value(v); } [[nodiscard]] auto value_to_string(const T value) const noexcept -> std::string override { std::string s = [value, this]() { if constexpr (std::is_same_v || std::convertible_to) return std::move(value); else if constexpr (CanToString) { std::stringstream ss; ss << std::fixed << std::setprecision(InputImpl::float_precision) << value; return std::move(ss).str(); } else if constexpr (Printable) { std::stringstream ss; ss << value; return std::move(ss).str(); } else // NOLINTNEXTLINE(readability-simplify-boolean-expr) static_assert((bool)sizeof(T) && false, "value cannot be converted to string."); }(); return s; } protected: std::unique_ptr _input_caption; InputImpl(Widget* parent = nullptr) : WidgetImpl {parent}, _input_caption {create_caption(this)} { // Start text input on focus. Stop it on focus loss and save the inputted // value. focus_handler([this](const bool focus) { if (focus) { _input_caption->font_color_bg(InputImpl::color_bg_focused); SDL_StartTextInput(); return; } SDL_StopTextInput(); _input_caption->font_color_bg(InputImpl::color_bg_default); InputImpl::value = value_from_string(input_text()); input_text(value_to_string(InputImpl::value)); _input_caption->text(value_to_string(InputImpl::value)); }); key_handler([this](const SDL_KeyboardEvent& key) { if (key.type == SDL_KEYDOWN) { switch (key.keysym.sym) { // Discard inputted value on escape key. case SDLK_ESCAPE: { focus(false); input_text(value_to_string(InputImpl::value)); break; } case SDLK_BACKSPACE: { const auto& txt = input_text(); if (const auto len = txt.length(); len > 0) { input_text(std::string(txt.substr(0, len - 1))); } break; } // Save inputted value and unfocus the widget on enter key. case SDLK_RETURN: case SDLK_RETURN2: // what is return2 btw? case SDLK_KP_ENTER: focus(false); InputImpl::value = value_from_string(input_text()); break; } } }); // Just keep the string representation of input in the input field caption // text but don't save the parsed value until some other event // (Esc/Enter/focus loss) text_input_handler([this](const SDL_TextInputEvent& input) { if (is_valid_input(input.text)) { input_text(std::string(input_text()) + input.text); } }); enable_keyboard_handler(); // Highlight input field on mouse hover. hover_handler([this](const bool _hover) { if (_hover) _input_caption->font_color_bg(InputImpl::color_bg_focused); else _input_caption->font_color_bg(InputImpl::color_bg_default); }); enable_mouse_handler(_widget_area, _viewport); _input_caption->font_color_bg(InputImpl::color_bg_default); } 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); _input_caption->font_color_fg(fg); } void _handle_geometry_change(const SDL_Rect& vp) override { const auto input_h = _input_caption->size().h + 2 * InputImpl::border_width; _widget_area = {0, center_line(vp.h, input_h), vp.w, input_h}; _input_caption->viewport( rect_offset({rect_margin(_widget_area, {InputImpl::border_width, InputImpl::border_width})}, vp)); } void _handle_renderer_change(const std::shared_ptr& r) override { _input_caption->renderer(r); } void _handle_rendering() override { const auto& color_end = focus() ? InputImpl::color_bg_focused : InputImpl::color_bg_default; const auto color_start = color_end / 2; // Render input borders. for (int i = InputImpl::border_width - 1; i >= 0; i--) { const auto factor = linear(i, 0, InputImpl::border_width); _renderer->draw_color(lerp(color_start, color_end, factor)) ->draw_rect(rect_margin(_widget_area, {i, i})); } _input_caption->render(); } }; } #endif