diff --git a/README.md b/README.md index 8a6cd24..44821b8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,125 @@ # sdl2_basic_widgets -A (very) basic widget library for SDL applications. +A (very) basic and incomplete widget library for SDL applications. +## Build +The build dependencies are SDL2, SDL_ttf and fontconfig dev libs +and headers. Meson is used as build system, so you'll need it too. + +As the code use some C++20 features, you'll need a recent compiler +which support that version of the standard. + +Then, inside the project root directory, run the following commands: + +```ShellSession +$ meson build +$ meson compile -C build -j0 +``` + +After that you'll have in the build directory the static library and the +example programs. + +## API overview + +The library API is divided in two main parts: +- the core, which provides some basic wrapping of + SDL, SDL_ttf and fontconfig functionalities. +- the widget API built on top of the core. + +### Core + +The main classes of the core are: + +- [Font](inc/basic_widgets/core/font.hpp): + wraps TTF_Font object with the most common operations + of SDL_ttf. Also provide a helper function to + search system fonts by using fontconfig. +- [Renderer](inc/basic_widgets/core/renderer.hpp): + wraps SDL_Renderer object with common operation from + the SDL 2D accelerated rendering API. +- [Texture](inc/basic_widgets/core/texture.hpp): + wraps SDL_Texture object with common operations + to manipulate it. + +Apart from the SDL API wrappers, some generic utility functions and +data types are defined: + +- [Color](inc/basic_widgets/core/type/color.hpp): + wraps SDL_Color object and adds convenience operators + overloads to operate on all color channels and/or + alpha channel. +- [draw.hpp](inc/basic_widgets/core/draw.hpp): + 2D drawing utilities. Currently, provides a couple of basic utility + functions for software rendered textures: circle rendering + and an antialising algorithm. +- [math.hpp](inc/basic_widgets/core/math.hpp): mostly provides + frequently used formulas to work with rectangles and points + on a 2D coordinate system. +- [concepts.hpp](inc/basic_widgets/core/type/concepts.hpp): + a few concept definitions for used for generic programming. +- [Size](inc/basic_widgets/core/type/size.hpp): representation + of a 2D size. There are also a few operator overloads to + operate on Size objects. + +## Widgets + +Widgets are classes that implements the common widget interface and +optionally one or more "handler" interfaces. Handlers are interfaces +for event or external resources handling. + +These interfaces are: + +- [Widget](inc/basic_widgets/w/base/widget.hpp): the common base + interface of every widget. +- [EventHandler](inc/basic_widgets/w/feat/event_handler.hpp): base + interface for event handlers. +- [FocusHandler](inc/basic_widgets/w/feat/focus_handler.hpp): base + interface for handler having a focus notion. +- [KeyboardHandler](inc/basic_widgets/w/feat/keyboard_handler.hpp): + interface for key and text input events handling. +- [MouseHandler](inc/basic_widgets/w/feat/mouse_handler.hpp): + interface for mouse events handling. Handle click and focus state + changes and also introduce a notion of "pushed" state. +- [FontHandler](inc/basic_widgets/w/feat/font_handler.hpp): interface + for widgets using a font. Declares methods to set the widget font and + set background and text color used for text rendering. +- [TextureHandler](inc/basic_widgets/w/feat/texture_handler.hpp): it exists + but I'm thinking now that it shouldn't. + +Based on these interfaces, there's implementation for a few basic widgets. +Currently, the following ones are defined: + +- [Button](inc/basic_widgets/w/button.hpp): a push button. +- [Caption](inc/basic_widgets/w/caption.hpp): a caption + that displays a rendered string. No multiline support. +- [Input](inc/basic_widgets/w/base/input.hpp): base + class for widgets using an input field. +- [NumericInput](inc/basic_widgets/w/numeric_input.hpp) an + input widget for numeric values with two buttons to increase/decrease + the hold value. + +There are also "containers" widgets which are widget that hold a collection of +widgets and manage their geometry: + +- [Layout](inc/basic_widgets/w/base/layout.hpp): abstract class for container widgets. +- [AlignedLayout](inc/basic_widgets/w/aligned_layout.hpp): layout that is done by distributing + widgets on a horizontal or vertical row. + +To create widget objects, the corresponding function from [widget_factory.hpp](inc/basic_widgets/w/widget_factory.hpp) +should be used. It's done this way for interface-implementation sake. + +## Source code organization overview + +The header and source directory hierarchies is organized in the following way: + +- core: C→C++ wrappers and commonly used generic functions. +- core/type: basic data types and concepts. +- w: the widget library +- w/base: the abstract widget classes. +- w/feat: handlers (optional widget features) + +Widget code intent to separate public API from implementation details. For that +a Widget has two classes, a pure virtual one (Widget) which is the public API and another +class (WidgetImpl) that implements the interface of the first one. Unless an access +to implementation details is required, the interface (pure virtual) type should +be used. diff --git a/inc/basic_widgets/core/draw.hpp b/inc/basic_widgets/core/draw.hpp index 0ac9c02..ef2be54 100644 --- a/inc/basic_widgets/core/draw.hpp +++ b/inc/basic_widgets/core/draw.hpp @@ -13,12 +13,12 @@ namespace bwidgets class Renderer; class Texture; - // Add transparency to color c relative to the distance d from + // Add transparency to color base_color relative to the distance d from // a limit to produce an AntiAliasing effect. // d >= 0 → full transparency; // −(aa_pixels) <= d < 0 → smoothsteped transparency gradient; // d < −(aa_pixels) → fully opaque. - [[nodiscard]] auto aa(Color c, int aa_pixels, float d) noexcept -> Color; + [[nodiscard]] auto aa(Color base_color, int aa_pixels, float d) noexcept -> Color; // Render a filled circle texture of color c and diameter resolution with // aa_pixels used for antialiasing. [[nodiscard]] auto filled_circle(Color c, int resolution, const Renderer&, diff --git a/inc/basic_widgets/core/font.hpp b/inc/basic_widgets/core/font.hpp index a923dc6..7b28b96 100644 --- a/inc/basic_widgets/core/font.hpp +++ b/inc/basic_widgets/core/font.hpp @@ -55,6 +55,7 @@ namespace bwidgets const std::string style_name; explicit Font(TTF_Font*); + // Load font at given path and with given size. Font(std::string_view, int); Font(const Font&) = delete; Font(Font&&) = delete; diff --git a/inc/basic_widgets/w/aligned_layout_impl.hpp b/inc/basic_widgets/w/aligned_layout_impl.hpp index 65f5de3..a302002 100644 --- a/inc/basic_widgets/w/aligned_layout_impl.hpp +++ b/inc/basic_widgets/w/aligned_layout_impl.hpp @@ -16,6 +16,7 @@ namespace bwidgets public: using LayoutImpl::LayoutImpl; + // Return smallest usable size. [[nodiscard]] auto size() const noexcept -> Size override { Size min_size {0, 0}; @@ -31,6 +32,7 @@ namespace bwidgets min_size.h + 2 * margins.h}; } + // Vertical for (const auto& w : _widgets) { if (w->size().w > min_size.w) min_size.w = w->size().w; @@ -55,7 +57,7 @@ namespace bwidgets w->size().h}); } } - else { + else { // Vertical int offset = 0; for (std::vector::size_type i = 0; i < _widgets.size(); i++) { const auto& w {_widgets[i]}; diff --git a/inc/basic_widgets/w/base/input.hpp b/inc/basic_widgets/w/base/input.hpp index 50b3d17..6974a90 100644 --- a/inc/basic_widgets/w/base/input.hpp +++ b/inc/basic_widgets/w/base/input.hpp @@ -32,7 +32,7 @@ namespace bwidgets 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'; + char input_width_unit = 'W'; // char used as unit for text length computation. T value {}; // Get the current displayed string. @@ -40,8 +40,8 @@ namespace bwidgets // Set displayed string virtual void input_text(std::string) = 0; virtual void input_text_color(Color) = 0; - // Check if a character is allowed to be inputed. String is used because we could - // have multibytes characters to represent using 8bits chars. + // Check if a character is allowed to be inputted. String are used because we + // could have multibytes characters to represent using 8bits chars. [[nodiscard]] virtual auto is_valid_input(std::string_view) const noexcept -> bool = 0; diff --git a/inc/basic_widgets/w/base/input_impl.hpp b/inc/basic_widgets/w/base/input_impl.hpp index 7df1d63..238d42a 100644 --- a/inc/basic_widgets/w/base/input_impl.hpp +++ b/inc/basic_widgets/w/base/input_impl.hpp @@ -120,6 +120,8 @@ namespace bwidgets 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); @@ -137,6 +139,7 @@ namespace bwidgets 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)); @@ -149,6 +152,7 @@ namespace bwidgets } 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: @@ -158,6 +162,9 @@ namespace bwidgets } } }); + // 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); @@ -165,6 +172,7 @@ namespace bwidgets }); 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); diff --git a/inc/basic_widgets/w/base/widget.hpp b/inc/basic_widgets/w/base/widget.hpp index 0747376..efac2dc 100644 --- a/inc/basic_widgets/w/base/widget.hpp +++ b/inc/basic_widgets/w/base/widget.hpp @@ -17,6 +17,7 @@ namespace bwidgets { public: // Parent widget for bidirectional message passing between widgets. + // Currently not used by this lib. Widget* parent; explicit Widget(Widget* p = nullptr) noexcept : parent {p} {} @@ -27,7 +28,7 @@ namespace bwidgets virtual void renderer(std::shared_ptr) = 0; // Get prefered or minimal widget size. [[nodiscard]] virtual auto size() const noexcept -> Size = 0; - // Set the viewport delimiting the widget rendering area and origin + // Set the viewport delimiting the widget rendering area and the origin // for relative coordinates. virtual void viewport(const SDL_Rect&) = 0; // Get current viewport. diff --git a/inc/basic_widgets/w/button_impl.hpp b/inc/basic_widgets/w/button_impl.hpp index f1c4b32..4e40707 100644 --- a/inc/basic_widgets/w/button_impl.hpp +++ b/inc/basic_widgets/w/button_impl.hpp @@ -17,6 +17,7 @@ namespace bwidgets public: ButtonImpl(Widget* parent = nullptr) noexcept; + // Smallest valid button size. [[nodiscard]] auto size() const noexcept -> Size override; [[nodiscard]] auto text() const noexcept -> std::string_view override; void text(std::string) override; diff --git a/inc/basic_widgets/w/caption.hpp b/inc/basic_widgets/w/caption.hpp index 95b4679..2006dd8 100644 --- a/inc/basic_widgets/w/caption.hpp +++ b/inc/basic_widgets/w/caption.hpp @@ -24,13 +24,13 @@ namespace bwidgets inline static const Color default_color_fg = {0, 0, 0, SDL_ALPHA_OPAQUE}; inline static const Size default_margins = {3, 3}; - // How text is horizontally aligned on the rendering viewport. + // Text horizontal alignment on the caption viewport. Alignment alignment {Alignment::LEFT}; Size margins {default_margins}; using Widget::Widget; - // Set used font rendering mode. + // Set caption text rendering mode. virtual void render_mode(Font::RenderMode) = 0; // Get caption text. [[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0; diff --git a/inc/basic_widgets/w/feat/event_handler.hpp b/inc/basic_widgets/w/feat/event_handler.hpp index 1cf7228..de387be 100644 --- a/inc/basic_widgets/w/feat/event_handler.hpp +++ b/inc/basic_widgets/w/feat/event_handler.hpp @@ -23,7 +23,7 @@ namespace bwidgets virtual ~EventHandler() = default; - // Pass an event to be handled. + // Pass the event to be handled. virtual void handle_event(const SDL_Event&) = 0; }; } diff --git a/inc/basic_widgets/w/feat/font_handler.hpp b/inc/basic_widgets/w/feat/font_handler.hpp index 2fa09fc..c65a5b8 100644 --- a/inc/basic_widgets/w/feat/font_handler.hpp +++ b/inc/basic_widgets/w/feat/font_handler.hpp @@ -20,10 +20,10 @@ namespace bwidgets // Set the used font. virtual void font(std::shared_ptr f) = 0; - // Set font background color for shaded font rendering - // mode + // Set background color used for shaded text rendering + // mode. virtual void font_color_bg(Color c) = 0; - // Set font foreground (text) color. + // Set foreground (glyphs) color. virtual void font_color_fg(Color c) = 0; protected: diff --git a/inc/basic_widgets/w/numeric_input_impl.hpp b/inc/basic_widgets/w/numeric_input_impl.hpp index 2b52a72..8a751c2 100644 --- a/inc/basic_widgets/w/numeric_input_impl.hpp +++ b/inc/basic_widgets/w/numeric_input_impl.hpp @@ -23,7 +23,8 @@ namespace bwidgets Self::input_min_width = 10; // NOLINT(readability-magic-numbers) Self::input_width_unit = '0'; - // +/- buttons click handler. op is the function to apply on current value. + // +/- buttons click handler. op is the operation (+/-) to apply on current + // value. const auto button_action = [this](const std::function op) { const T new_value = [this, &op]() { auto _new_value = op(this->value, NumericInput::button_step); @@ -79,7 +80,7 @@ namespace bwidgets if (input[0] == '.' && Self::input_text().find('.') == std::string::npos) return true; - // Allow "-" at input string start for signed types. + // Allow "-" as first input char for signed types. if constexpr (std::is_signed_v) { std::string_view displayed_value = Self::input_text(); if (input[0] == '-' && displayed_value.empty()) return true; diff --git a/meson.build b/meson.build index 4f9b5f2..9c616b5 100644 --- a/meson.build +++ b/meson.build @@ -9,7 +9,7 @@ project('sdl2_basic_widgets', 'cpp', ], license: 'EUPL-1.2') -add_project_arguments('-pedantic', language: 'cpp') +add_project_arguments('-Wconversion', '-pedantic', language: 'cpp') if (get_option('buildtype').startswith('debug')) add_project_arguments('-DBWIDGETS_DEBUG', language: 'cpp') diff --git a/src/core/draw.cpp b/src/core/draw.cpp index b747e39..2447ec5 100644 --- a/src/core/draw.cpp +++ b/src/core/draw.cpp @@ -14,6 +14,7 @@ namespace bwidgets { Color c = [aa_pixels, base_color, d]() -> Color { const auto [r, g, b, a] = base_color(); + // AA disabled, returns fully opaque or fully transparent colors if (aa_pixels == 0) { if (d > 0) return {r, g, b, 0}; return {r, g, b, a}; diff --git a/src/w/button_impl.cpp b/src/w/button_impl.cpp index 558e38f..899182c 100644 --- a/src/w/button_impl.cpp +++ b/src/w/button_impl.cpp @@ -75,6 +75,7 @@ void ButtonImpl::_handle_rendering() border_size.w > border_size.h ? border_size.w : border_size.h; const auto& max_xy = border_size.w > border_size.h ? x : y; + // Render button borders. while (x < border_size.w || y < border_size.h) { _renderer->draw_color(border_gradient(biggest_dimension, max_xy, divider)) ->draw_rect(rect_margin(_widget_area, {x, y})); @@ -89,6 +90,7 @@ void ButtonImpl::_handle_rendering() void ButtonImpl::_on_push(const bool state) { + // Move slightly the caption position to give a push effect. const auto offset = [state, this]() -> SDL_Point { if (state) { return {_viewport.x + 1, _viewport.y + 1}; diff --git a/src/w/caption_impl.cpp b/src/w/caption_impl.cpp index f97ec19..c47ad22 100644 --- a/src/w/caption_impl.cpp +++ b/src/w/caption_impl.cpp @@ -77,6 +77,8 @@ void CaptionImpl::_handle_rendering() { if (!_text_texture) _handle_texture_update(); + // fill caption viewport with background color when using + // shaded text rendering mode. if (_render_mode == Font::RenderMode::SHADED) { _renderer->draw_color(_font_color_bg) ->fill_rect({0, 0, _viewport.w, _viewport.h}); diff --git a/src/w/feat/mouse_handler_impl.cpp b/src/w/feat/mouse_handler_impl.cpp index a87a6f6..ee8d73f 100644 --- a/src/w/feat/mouse_handler_impl.cpp +++ b/src/w/feat/mouse_handler_impl.cpp @@ -29,6 +29,8 @@ void MouseHandlerImpl::enable_mouse_handler(const SDL_Rect& rel_area, _add_event_handler({SDL_MOUSEBUTTONUP, [this, mouse_in_rect](const SDL_Event& ev) { if (_pushed) { _on_push(_pushed = false); + // Only execute handler and give focus if mouse button + // has been pushed and released over the widget. if (mouse_in_rect({ev.button.x, ev.button.y})) { focus(true); _click_handler(ev.button);