232 lines
8.5 KiB
C++
232 lines
8.5 KiB
C++
#ifndef BWIDGETS_INPUT_IMPL_HPP
|
|
#define BWIDGETS_INPUT_IMPL_HPP
|
|
|
|
#include <iomanip>
|
|
#include <sstream>
|
|
|
|
#include <basic_widgets/core/math.hpp>
|
|
#include <basic_widgets/w/base/input.hpp>
|
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
|
#include <basic_widgets/w/caption.hpp>
|
|
#include <basic_widgets/w/feat/font_handler_impl.hpp>
|
|
#include <basic_widgets/w/feat/keyboard_handler_impl.hpp>
|
|
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
|
|
#include <basic_widgets/w/widget_factory.hpp>
|
|
|
|
namespace bwidgets
|
|
{
|
|
template<typename T>
|
|
class InputImpl : public virtual Input<T>,
|
|
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<T>) {
|
|
const auto _s = s.length() > 0 ? s : "0";
|
|
if constexpr (std::is_floating_point_v<T>)
|
|
return (T)std::stold(_s.data());
|
|
else if constexpr (std::is_integral_v<T>)
|
|
return (T)std::stoll(_s.data());
|
|
}
|
|
else if constexpr (std::is_same_v<
|
|
T,
|
|
std::string> || std::convertible_to<std::string, T>)
|
|
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::string,
|
|
T> || std::convertible_to<T, std::string>)
|
|
return std::move(value);
|
|
else if constexpr (CanToString<T>) {
|
|
std::stringstream ss;
|
|
ss << std::fixed << std::setprecision(InputImpl::float_precision)
|
|
<< value;
|
|
return std::move(ss).str();
|
|
}
|
|
else if constexpr (Printable<T>) {
|
|
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<Caption> _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<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);
|
|
_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<Renderer>& 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
|