diff --git a/button.cpp b/button.cpp index 17e6362..56c3874 100644 --- a/button.cpp +++ b/button.cpp @@ -5,6 +5,23 @@ Button::Button(SDL_Renderer* r) { } +int Button::border_width() const +{ + return _border_width; +} + +void Button::border_width(int w) +{ + _border_width = w; + _update_widget_area(); +} + +int Button::height() const +{ + return _caption.height() * _caption_height_multiplicator + + 2 * _border_width; +} + void Button::render() { Widget::render(); @@ -67,36 +84,41 @@ void Button::render() SDL_RenderSetViewport(_renderer, &old_vp); } +const std::string& Button::text() const +{ + return _caption.text(); +} + void Button::text(std::string txt) { _caption.text(txt); + _update_widget_area(); _update_caption_area(); } void Button::font(TTF_Font* f) { _caption.font(f); + _update_widget_area(); _update_caption_area(); } void Button::viewport(SDL_Rect vp) { Widget::viewport(vp); - _widget_area.y = center( - _viewport.h, - _caption.rendered_text_size().h * _caption_height_multiplicator - ) - _border_width; _update_caption_area(); } +int Button::width() const +{ + return _caption.width() * _caption_height_multiplicator + + 2 * _border_width; +} + void Button::_update_caption_area() { SDL_Rect txt_size = _caption.rendered_text_size(); - _widget_area.w = txt_size.w + 2 * _border_width; - _widget_area.h = _caption.rendered_text_size().h * _caption_height_multiplicator - + 2 * _border_width; - _caption_area = { center(_viewport.w, txt_size.w), center(_viewport.h, txt_size.h), @@ -110,3 +132,19 @@ void Button::_update_caption_area() _caption_area.h }); } + +void Button::_update_widget_area() +{ + int h = _caption.rendered_text_size().h * _caption_height_multiplicator + + 2 * _border_width; + + _widget_area = { + 0, + center( + _viewport.h, + _caption.rendered_text_size().h * _caption_height_multiplicator + ) - _border_width, + _viewport.w, + h + }; +} diff --git a/button.hpp b/button.hpp index 5357323..0350d70 100644 --- a/button.hpp +++ b/button.hpp @@ -10,22 +10,28 @@ class Button : public Widget { protected: - static const int _border_width = 3; + int _border_width = 3; Caption _caption; SDL_Rect _caption_area; - static constexpr const float _caption_height_multiplicator = 1.2; - static constexpr const SDL_Color _color_background = {150, 150, 150, SDL_ALPHA_OPAQUE}; - static constexpr const SDL_Color _color_background_hover = {175, 175, 175, SDL_ALPHA_OPAQUE}; - static constexpr const SDL_Color _color_foreground = {0, 0, 0, SDL_ALPHA_OPAQUE}; + float _caption_height_multiplicator = 1.2; + SDL_Color _color_background = {150, 150, 150, SDL_ALPHA_OPAQUE}; + SDL_Color _color_background_hover = {175, 175, 175, SDL_ALPHA_OPAQUE}; + SDL_Color _color_foreground = {0, 0, 0, SDL_ALPHA_OPAQUE}; void _update_caption_area(); + void _update_widget_area(); public: Button(SDL_Renderer*); - void render(); - void text(std::string); + int border_width() const; + void border_width(int); void font(TTF_Font*); + int height() const; + void render(); + const std::string& text() const; + void text(std::string); void viewport(SDL_Rect); + int width() const; }; #endif diff --git a/examples/button_example.cpp b/examples/button_example.cpp new file mode 100644 index 0000000..3086227 --- /dev/null +++ b/examples/button_example.cpp @@ -0,0 +1,85 @@ +#include + +#include "button.hpp" +#include "horizontal_layout.hpp" +#include "vertical_layout.hpp" + +int main() +{ + int width = 640; + int height = 480; + + TTF_Init(); + TTF_Font* font = TTF_OpenFont("fonts/FiraMono-Bold.ttf", 20); + + SDL_Init(SDL_INIT_VIDEO); + SDL_Window* win = SDL_CreateWindow( + "Widget test", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + width, + height, + SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE + ); + SDL_Renderer* rend = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED); + SDL_SetRenderDrawBlendMode(rend, SDL_BLENDMODE_BLEND); + + SDL_Rect layout_vp = {0, 0, width, height}; + auto layout = new Horizontal_Layout(rend); + for (int i = 0; i < 3; i++) + { + auto column = new Vertical_Layout(rend); + for (int j = 0; j < 3; j++) + { + auto widget = new Button(rend); + widget->text("hello"); + widget->click_handler = [i, j](const SDL_MouseButtonEvent&) { + std::cerr << "click@" << j << ":" << i << std::endl; + }; + widget->font(font); + column->add_widget(widget); + std::cerr << " " << i << "@" << widget; + } + layout->add_widget(column); + } + layout->viewport(layout_vp); + bool quit = false; + while (!quit) + { + SDL_Event ev; + while (SDL_PollEvent(&ev) != 0) + { + switch (ev.type) + { + case SDL_QUIT: + quit = true; + break; + case SDL_WINDOWEVENT: + if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) + { + SDL_GetRendererOutputSize(rend, &width, &height); + layout_vp.w = width; + layout_vp.h = height; + std::cerr << "== WINDOW RESIZED == " << width << "x" << height << std::endl; + layout->viewport(layout_vp); + } + break; + } + layout->handle_event(ev); + } + + SDL_SetRenderDrawColor(rend, 75, 75, 75, SDL_ALPHA_OPAQUE); + SDL_RenderClear(rend); + + layout->render(); + SDL_RenderPresent(rend); + } + + delete layout; + SDL_DestroyRenderer(rend); + SDL_DestroyWindow(win); + SDL_Quit(); + TTF_CloseFont(font); + TTF_Quit(); + return 0; +} diff --git a/widget_test.cpp b/examples/input_example.cpp similarity index 93% rename from widget_test.cpp rename to examples/input_example.cpp index 29a6b50..2b1b0a7 100644 --- a/widget_test.cpp +++ b/examples/input_example.cpp @@ -1,6 +1,5 @@ #include -#include "button.hpp" #include "horizontal_layout.hpp" #include "numeric_input.hpp" #include "vertical_layout.hpp" @@ -33,7 +32,9 @@ int main() for (int j = 0; j < 3; j++) { auto widget = new Numeric_Input(rend); + widget->button_step = 0.5; widget->font(font); + widget->value_range(-2.5, 2.5); column->add_widget(widget); std::cerr << " " << i << "@" << widget; } @@ -61,9 +62,6 @@ int main() layout->viewport(layout_vp); } break; - case SDL_MOUSEBUTTONUP: - //_handle_click({ev.button.x, ev.button.y}); - break; } layout->handle_event(ev); } diff --git a/horizontal_layout.cpp b/horizontal_layout.cpp index 1555184..f351b13 100644 --- a/horizontal_layout.cpp +++ b/horizontal_layout.cpp @@ -19,8 +19,8 @@ void Horizontal_Layout::_update_layout() (int)(widget_size - _interspace), _viewport.h }; - _widgets.at(i)->viewport(widget_vp); - std::cerr << "h-layout:widget:" << i << " " << widget_vp.x << "," + std::cerr << "h-layout@" << this << ":widget:" << i << " " << widget_vp.x << "," << widget_vp.y << "," << widget_vp.w << "," << widget_vp.h << std::endl; + _widgets.at(i)->viewport(widget_vp); } } diff --git a/input.hpp b/input.hpp index a73b6c0..2e7590c 100644 --- a/input.hpp +++ b/input.hpp @@ -1,6 +1,8 @@ #ifndef INPUT_HPP #define INPUT_HPP +#include +#include #include #include #include @@ -48,7 +50,7 @@ protected: { case SDLK_BACKSPACE: { - std::string txt = _input_text.text(); + std::string txt = input_text(); if (txt.length() > 0) { txt.pop_back(); @@ -111,6 +113,7 @@ protected: public: SDL_Color border_color {160, 160, 160, SDL_ALPHA_OPAQUE}; SDL_Color background_color {200, 200, 200, SDL_ALPHA_OPAQUE}; + int float_precision {2}; SDL_Color focused_background_color {255, 255, 255, SDL_ALPHA_OPAQUE}; virtual const std::string& input_text() const @@ -135,6 +138,11 @@ public: _update_drawing_areas(); } + virtual T process_value(T x) const + { + return x; + } + virtual void render() { Widget::render(); @@ -191,27 +199,34 @@ public: _render_value(); } - static T value_from_string(std::string s) + T value_from_string(std::string s) { - T value; + T v; if constexpr(std::is_arithmetic_v) { s = s.length() > 0 ? s : "0"; - if constexpr(std::is_floating_point_v) - value = (T)std::stold(s); - else if constexpr(std::is_integral_v) - value = (T)std::stoll(s); + try + { + if constexpr(std::is_floating_point_v) + v = (T)std::stold(s); + else if constexpr(std::is_integral_v) + v = (T)std::stoll(s); + } + catch(std::exception&) + { + v = value(); + } } else if constexpr(std::is_same_v || std::convertible_to) - value = s; + v = s; else - static_assert(sizeof(T) && false, "string cannot be converted to value type T."); + static_assert(sizeof(T) && false, "string cannot be converted to v type T."); - return value; + return process_value(v); } - static std::string value_to_string(T value) + std::string value_to_string(T value) { std::string s; @@ -219,17 +234,17 @@ public: s = value; else if constexpr(CanToString) { - s = std::to_string(value); - for (char& last = s.back(); - (last == '0' || last == '.') && s.length() > 1; - last = s.back()) - s.pop_back(); + 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; - return std::move(ss).str(); + s = std::move(ss).str(); } else static_assert(sizeof(T) && false, "_value cannot be converted to string."); diff --git a/meson.build b/meson.build index e6cd51b..6e072d5 100644 --- a/meson.build +++ b/meson.build @@ -2,16 +2,25 @@ project('sdl2_basic_widgets', 'cpp', version : '0.1', default_options : ['warning_level=3', 'cpp_std=c++20']) -executable('sdl2_basic_widgets', - 'horizontal_layout.cpp', - 'layout.cpp', +lib = static_library('sdl2_basic_widgets', + 'horizontal_layout.cpp', + 'layout.cpp', 'button.cpp', 'caption.cpp', 'vertical_layout.cpp', 'widget.cpp', - 'widget_test.cpp', dependencies : [ dependency('sdl2'), dependency('SDL2_ttf') ], install : true) + +executable('button_demo', + 'examples/button_example.cpp', + link_with : lib, + install : false) + +executable('input_demo', + 'examples/input_example.cpp', + link_with : lib, + install : false) diff --git a/numeric_input.hpp b/numeric_input.hpp index d60ed48..5758ead 100644 --- a/numeric_input.hpp +++ b/numeric_input.hpp @@ -1,6 +1,8 @@ #ifndef NUMERIC_INPUT_HPP #define NUMERIC_INPUT_HPP +#include +#include #include #include "button.hpp" @@ -18,6 +20,10 @@ private: 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_event(const SDL_Event& ev) { @@ -28,37 +34,44 @@ private: void _update_drawing_areas() { - int widest_button = _increment_button.width() > _decrement_button.width() ? - _increment_button.width() : _decrement_button.width(); - int heighest_button = _increment_button.height() > _decrement_button.height() ? - _increment_button.height() : _decrement_button.height(); - int field_width = Widget::_viewport.w - 2 * 1.25 * widest_button; - int field_height = heighest_button; + const int widest_button = + _increment_button.width() > _decrement_button.width() + ? _increment_button.width() + : _decrement_button.width(); + const int heighest_button = + _increment_button.height() > _decrement_button.height() + ? _increment_button.height() + : _decrement_button.height(); + const int button_area_width = 2 * widest_button; + const int spacing = Widget::center(button_area_width, widest_button); + const int field_width = Widget::_viewport.w - 2 * button_area_width; + const int field_height = heighest_button; Input::_input_field_area = { - (int)(1.25 * widest_button), + (int)button_area_width, Widget::center(Widget::_viewport.h, field_height), field_width, field_height }; _increment_button_area = { - Widget::center(1.25 * widest_button, _increment_button.width()), + spacing, Widget::center(Widget::_viewport.h, _increment_button.height()), _increment_button.width(), _increment_button.height() }; _decrement_button_area = { - Widget::_viewport.w - (Widget::center(1.25 * widest_button, _decrement_button.width()) + _decrement_button.width()), + Widget::_viewport.w - spacing - _decrement_button.width(), Widget::center(Widget::_viewport.h, _decrement_button.height()), _decrement_button.width(), _decrement_button.height() }; Widget::_widget_area = { - Widget::_viewport.x + Widget::center(1.25 * widest_button, widest_button), - Widget::_viewport.y + Widget::center(Widget::_viewport.h, heighest_button), - (int)(1.25 * widest_button / 2) + Widget::_viewport.w - widest_button - Widget::_viewport.x, + button_area_width, + Widget::center(Widget::_viewport.h, heighest_button), + Widget::_viewport.w + - 2 * button_area_width, heighest_button }; @@ -75,13 +88,21 @@ public: { _increment_button.text("+"); _increment_button.click_handler = [this](const SDL_MouseButtonEvent&) { - this->value(this->value() + button_step); - this->input_text(this->value_to_string(this->value())); + 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&) { - this->value(this->value() - button_step); - this->input_text(this->value_to_string(this->value())); + 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)); }; } @@ -99,11 +120,33 @@ public: if (input.at(0) >= '0' && input.at(0) <= '9') valid = true; if constexpr(std::is_floating_point_v) - if (input.at(0) == '.') + if (input.at(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.at(0) == '-' + && !std::regex_search( + displayed_value, + std::regex("[^\\s]") + )) + valid = true; + } return valid; } + T process_value(T x) const + { + T value = x; + if (x < _value_range.first) + value = _value_range.first; + else if (x > _value_range.second) + value = _value_range.second; + + return value; + } + void render() { Input::render(); @@ -111,6 +154,19 @@ public: _decrement_button.render(); } + std::pair value_range() const + { + return _value_range; + } + + void value_range(T min, T max) + { + + if (min > max) + throw std::invalid_argument("min cannot be greater than max"); + _value_range = {min, max}; + } + void viewport(SDL_Rect vp) { Input::viewport(vp); diff --git a/vertical_layout.cpp b/vertical_layout.cpp index 7d32c11..1fdb485 100644 --- a/vertical_layout.cpp +++ b/vertical_layout.cpp @@ -19,8 +19,8 @@ void Vertical_Layout::_update_layout() _viewport.w, (int)(widget_size - _interspace), }; - _widgets.at(i)->viewport(widget_vp); - std::cerr << "v-layout:widget:" << i << " " << widget_vp.x << "," + std::cerr << "v-layout@" << this << ":widget:" << i << " " << widget_vp.x << "," << widget_vp.y << "," << widget_vp.w << "," << widget_vp.h << std::endl; + _widgets.at(i)->viewport(widget_vp); } }