some comment edits, readme draft

This commit is contained in:
Andrea Blankenstijn 2021-08-27 01:21:25 +02:00
parent d8f56e0e5f
commit a54dc9c2b2
17 changed files with 159 additions and 17 deletions

123
README.md
View File

@ -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<T>](inc/basic_widgets/w/base/input.hpp): base
class for widgets using an input field.
- [NumericInput<Numeric>](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.

View File

@ -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&,

View File

@ -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;

View File

@ -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<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
const auto& w {_widgets[i]};

View File

@ -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;

View File

@ -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);

View File

@ -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<Renderer>) = 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.

View File

@ -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;

View File

@ -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;

View File

@ -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;
};
}

View File

@ -20,10 +20,10 @@ namespace bwidgets
// Set the used font.
virtual void font(std::shared_ptr<Font> 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:

View File

@ -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<T(T, T)> op) {
const T new_value = [this, &op]() {
auto _new_value = op(this->value, NumericInput<T>::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<T>) {
std::string_view displayed_value = Self::input_text();
if (input[0] == '-' && displayed_value.empty()) return true;

View File

@ -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')

View File

@ -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};

View File

@ -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};

View File

@ -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});

View File

@ -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);