229 lines
7.5 KiB
C++
229 lines
7.5 KiB
C++
#ifndef BWIDGETS_INPUT_HPP
|
|
#define BWIDGETS_INPUT_HPP
|
|
|
|
#include <cmath>
|
|
#include <exception>
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
#include <SDL2/SDL_events.h>
|
|
#include <SDL2/SDL_keyboard.h>
|
|
#include <SDL2/SDL_render.h>
|
|
|
|
#include <basic_widgets/core/math.hpp>
|
|
#include <basic_widgets/core/type/concepts.hpp>
|
|
#include <basic_widgets/w/caption.hpp>
|
|
#include <basic_widgets/w/feat/keyboard_handler.hpp>
|
|
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
|
|
|
namespace bwidgets
|
|
{
|
|
|
|
template<typename T>
|
|
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<Font>& 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<Renderer>& 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<T>*
|
|
{
|
|
_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<T>*
|
|
{
|
|
_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<T>) {
|
|
s = s.length() > 0 ? s : "0";
|
|
if constexpr (std::is_floating_point_v<T>) v = (T)std::stold(s);
|
|
else if constexpr (std::is_integral_v<T>) v = (T)std::stoll(s);
|
|
}
|
|
else if constexpr (std::is_same_v<
|
|
T, std::string> || std::convertible_to<std::string, T>)
|
|
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::string,
|
|
T> || std::convertible_to<T, std::string>)
|
|
s = std::move(value);
|
|
else if constexpr (CanToString<T>) {
|
|
std::stringstream ss;
|
|
ss << std::fixed << std::setprecision(float_precision) << value;
|
|
s = std::move(ss).str();
|
|
}
|
|
else if constexpr (Printable<T>) {
|
|
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
|