some comment edits, readme draft
This commit is contained in:
parent
d8f56e0e5f
commit
a54dc9c2b2
123
README.md
123
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<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.
|
||||
|
|
|
@ -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&,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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]};
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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});
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue