Merge branch 'wip'

This commit is contained in:
Andrea Blankenstijn 2021-12-23 13:28:11 +01:00
commit bac66748ec
68 changed files with 2333 additions and 1682 deletions

View File

@ -34,14 +34,14 @@ EmptyLineBeforeAccessModifier: Always
FixNamespaceComments: false FixNamespaceComments: false
IncludeBlocks: Regroup IncludeBlocks: Regroup
IncludeCategories: IncludeCategories:
- Regex: '<[[:alnum:].]+>' - Regex: '^<fontconfig'
Priority: -10
- Regex: '^<fontconfig/'
Priority: -5 Priority: -5
- Regex: '^<SDL2/' - Regex: '^<SDL'
Priority: -5 Priority: -5
- Regex: '^<basic_widgets/' - Regex: '^<basic_widgets/'
Priority: 0 Priority: 0
- Regex: '<[[:alnum:]._]+>'
Priority: -10
- Regex: '^".*"$' - Regex: '^".*"$'
Priority: 5 Priority: 5
IndentCaseLabels: true IndentCaseLabels: true

View File

@ -1,2 +1,2 @@
Checks: 'bugprone-*,cert-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-misc-non-private-member-variables-in-classes,-bugprone-easily-swappable-parameters,-readability-braces-around-statements,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-named-parameter,-modernize-use-trailing-return-type' Checks: 'bugprone-*,cert-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-misc-non-private-member-variables-in-classes,-bugprone-easily-swappable-parameters,-readability-braces-around-statements,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-named-parameter,-modernize-use-trailing-return-type,-cppcoreguidelines-avoid-magic-numbers'
FormatStyle: file FormatStyle: file

123
README.md
View File

@ -1,4 +1,125 @@
# sdl2_basic_widgets # 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

@ -1,19 +1,19 @@
#include <iostream> #include <iostream>
#include <basic_widgets/w/button.hpp> #include <basic_widgets/w/widget_factory.hpp>
#include "run.hpp" #include "run.hpp"
using bwidgets::Button;
auto main() -> int auto main() -> int
{ {
run_example<Button>([](auto* w, auto* f, auto x, auto y) { run_example<bwidgets::Button>(
w->click_handler = [x, y](const SDL_MouseButtonEvent&) { []() { return bwidgets::create_button(); },
std::cout << "button(" << x << ',' << y << "):click!" << std::endl; [](auto w, auto f, auto x, auto y) {
}; w->click_handler([x, y](const SDL_MouseButtonEvent&) {
w->font(f); std::cout << "button(" << x << ',' << y << "):click!" << std::endl;
w->text("+"); });
}); w->font(f);
w->text("click me");
});
return 0; return 0;
} }

View File

@ -1,17 +1,16 @@
#include <basic_widgets/w/caption.hpp> #include <basic_widgets/w/widget_factory.hpp>
#include "run.hpp" #include "run.hpp"
using bwidgets::Caption;
auto main() -> int auto main() -> int
{ {
run_example<Caption>( run_example<bwidgets::Caption>([]() { return bwidgets::create_caption(); },
[](auto* w, auto* f, auto, auto) { [](auto w, auto f, auto, auto) {
w->alignment = Caption::Alignment::CENTER; w->alignment =
w->text("¡jello!"); bwidgets::Caption::Alignment::CENTER;
w->font(f); w->text("¡jello!");
}, w->font(f);
4, 8); // NOLINT(readability-magic-numbers) },
4, 8); // NOLINT(readability-magic-numbers)
return 0; return 0;
} }

View File

@ -3,9 +3,11 @@
auto main() -> int auto main() -> int
{ {
run_example<Example>([](auto* w, auto*, auto x, auto y) { run_example<Example>(
w->cycle_r = (x + 1) * 3000; // NOLINT(readability-magic-numbers) []() { return std::make_unique<Example>(); },
w->cycle_b = (y + 1) * 3000; // NOLINT(readability-magic-numbers) [](auto w, auto, auto x, auto y) {
w->cycle_b = (1 + x + y) * (y + 1) * 400; // NOLINT(readability-magic-numbers) w->cycle_r = (x + 1) * 3000; // NOLINT(readability-magic-numbers)
}); w->cycle_b = (y + 1) * 3000; // NOLINT(readability-magic-numbers)
w->cycle_b = (1 + x + y) * (y + 1) * 400; // NOLINT(readability-magic-numbers)
});
} }

View File

@ -1,20 +1,19 @@
#ifndef BWIDGETS_EXAMPLES_EXAMPLE_WIDGET #ifndef BWIDGETS_EXAMPLES_EXAMPLE_WIDGET
#define BWIDGETS_EXAMPLES_EXAMPLE_WIDGET #define BWIDGETS_EXAMPLES_EXAMPLE_WIDGET
#include <SDL2/SDL_timer.h> #include <chrono>
#include <basic_widgets/core/math.hpp> #include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/renderer.hpp> #include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/w/base/widget.hpp> #include <basic_widgets/w/base/widget_impl.hpp>
using bwidgets::Color; using bwidgets::Color;
using bwidgets::rect_margin; using bwidgets::rect_margin;
using bwidgets::Size; using bwidgets::Size;
using bwidgets::Widget; using bwidgets::WidgetImpl;
class Example final : public Widget class Example final : public WidgetImpl
{ {
private:
void _handle_geometry_change(const SDL_Rect& vp) noexcept override void _handle_geometry_change(const SDL_Rect& vp) noexcept override
{ {
_widget_area = {2, 2, vp.w - 4, vp.h - 4}; _widget_area = {2, 2, vp.w - 4, vp.h - 4};
@ -22,12 +21,13 @@ private:
void _handle_rendering() override void _handle_rendering() override
{ {
const auto now = SDL_GetTicks(); const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
const uint8_t r = 255 * (now % cycle_r / (float)cycle_r); // NOLINT std::chrono::steady_clock::now().time_since_epoch())
const uint8_t g = 255 * (now % cycle_g / (float)cycle_g); // NOLINT .count();
const uint8_t b = 255 * (now % cycle_b / (float)cycle_b); // NOLINT const uint8_t r = 255 * (now % cycle_r / (float)cycle_r); // NOLINT
// NOLINTNEXTLINE(readability-magic-numbers) const uint8_t g = 255 * (now % cycle_g / (float)cycle_g); // NOLINT
const Color base_color {r, g, b, 255}; const uint8_t b = 255 * (now % cycle_b / (float)cycle_b); // NOLINT
const Color base_color {r, g, b, SDL_ALPHA_OPAQUE};
const int border = 10; const int border = 10;
for (auto i = 0; i < border; i += 3) { for (auto i = 0; i < border; i += 3) {
@ -49,6 +49,8 @@ public:
unsigned int cycle_g {3500}; // NOLINT(readability-magic-numbers) unsigned int cycle_g {3500}; // NOLINT(readability-magic-numbers)
unsigned int cycle_b {3500}; // NOLINT(readability-magic-numbers) unsigned int cycle_b {3500}; // NOLINT(readability-magic-numbers)
using WidgetImpl::WidgetImpl;
[[nodiscard]] auto size() const noexcept -> Size override [[nodiscard]] auto size() const noexcept -> Size override
{ {
return {128, 64}; // NOLINT(readability-magic-numbers) return {128, 64}; // NOLINT(readability-magic-numbers)

View File

@ -1,15 +1,15 @@
#include <basic_widgets/w/numeric_input.hpp> #include <basic_widgets/w/widget_factory.hpp>
#include "run.hpp" #include "run.hpp"
using bwidgets::NumericInput;
auto main() -> int auto main() -> int
{ {
run_example<NumericInput<float>>([](auto* w, auto* f, auto, auto) { run_example<bwidgets::NumericInput<float>>(
w->font(f); []() { return bwidgets::create_input_float(); },
w->button_step = 0.5; // NOLINT(readability-magic-numbers) [](auto w, auto f, auto, auto) {
w->value_range(-3.14, 8.5); // NOLINT(readability-magic-numbers) w->font(f);
}); w->button_step = 0.5; // NOLINT(readability-magic-numbers)
w->value_range(-3.14, 8.5); // NOLINT(readability-magic-numbers)
});
return 0; return 0;
} }

View File

@ -3,58 +3,50 @@
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <stack>
#include <SDL2/SDL.h> #include <basic_widgets/core/type/deleter.hpp>
#include <SDL2/SDL_ttf.h> #include <basic_widgets/w/widget_factory.hpp>
#include <basic_widgets/core/font.hpp>
#include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/w/aligned_layout.hpp>
#include <basic_widgets/w/base/widget.hpp>
template<typename T> template<typename T>
concept WidgetType = std::derived_from<T, bwidgets::Widget>; concept WidgetType = std::derived_from<T, bwidgets::Widget>;
template<WidgetType W> template<WidgetType W>
void run_example(const std::function<void(W*, bwidgets::Font*, int, int)>& setup, void run_example(
int w = 3, int h = 3) std::function<std::unique_ptr<W>()> factory,
std::function<void(W*, const std::shared_ptr<bwidgets::Font>, int, int)> setup,
int w = 3, int h = 3)
{ {
std::stack<std::function<void()>> cleaners; std::atexit([]() {
TTF_Quit();
SDL_Quit();
});
try { try {
bwidgets::SDLError::success_or_throw(SDL_Init(SDL_INIT_VIDEO), __FILE__, bwidgets::success_or_throw<bwidgets::SDLError>(SDL_Init(SDL_INIT_VIDEO));
__FUNCTION__, __LINE__); bwidgets::success_or_throw<bwidgets::SDLError>(TTF_Init());
cleaners.emplace([]() { SDL_Quit(); });
bwidgets::SDLError::success_or_throw(TTF_Init(), __FILE__, __FUNCTION__,
__LINE__);
cleaners.emplace([]() { TTF_Quit(); });
const bwidgets::Size size_init {854, 480}; const bwidgets::Size size_init {854, 480};
auto* font {new bwidgets::Font(bwidgets::Font::find("Monospace"), auto font =
16)}; // NOLINT(readability-magic-numbers) std::make_shared<bwidgets::Font>(bwidgets::Font::find("Monospace"),
cleaners.emplace([font]() { delete font; }); 16); // NOLINT(readability-magic-numbers)
auto* win {SDL_CreateWindow("basic_widgets example", SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED, size_init.w, size_init.h, auto win = std::unique_ptr<SDL_Window, bwidgets::Deleter>(
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE bwidgets::ptr_or_throw<bwidgets::SDLError>(SDL_CreateWindow(
| SDL_WINDOW_UTILITY)}; "basic_widgets example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
cleaners.emplace([win]() { SDL_DestroyWindow(win); }); size_init.w, size_init.h,
auto* renderer {new bwidgets::Renderer(win, -1, SDL_RENDERER_ACCELERATED)}; SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_UTILITY)));
cleaners.emplace([renderer]() { delete renderer; });
auto renderer =
std::make_shared<bwidgets::Renderer>(win.get(), -1, SDL_RENDERER_ACCELERATED);
renderer->blend_mode(SDL_BLENDMODE_BLEND); renderer->blend_mode(SDL_BLENDMODE_BLEND);
auto* layout { auto layout = bwidgets::create_horizontal_layout();
new bwidgets::AlignedLayout(bwidgets::AlignedLayout::Alignment::HORIZONTAL)};
cleaners.emplace([layout]() { delete layout; });
for (auto x = 0; x < w; x++) { for (auto x = 0; x < w; x++) {
auto* col = auto col = bwidgets::create_vertical_layout();
new bwidgets::AlignedLayout(bwidgets::AlignedLayout::Alignment::VERTICAL);
for (auto y = 0; y < h; y++) { for (auto y = 0; y < h; y++) {
auto* widget = new W(); auto widget = factory();
setup(widget, font, x, y); setup(widget.get(), font, x, y);
col->add_widget(widget); col->add_widget(std::move(widget));
} }
layout->add_widget(col); layout->add_widget(std::move(col));
} }
layout->renderer(renderer); layout->renderer(renderer);
@ -85,11 +77,8 @@ void run_example(const std::function<void(W*, bwidgets::Font*, int, int)>& setup
layout->render(); layout->render();
renderer->present(); renderer->present();
} }
while (!cleaners.empty()) cleaners.top()(), cleaners.pop(); } catch (const std::exception& e) {
} catch (const bwidgets::SDLError& e) { std::cerr << "Error: " << e.what() << std::endl;
std::cerr << "Error: " << e.file << ":" << e.func << ":" << e.line << ": "
<< e.what << std::endl;
while (!cleaners.empty()) cleaners.top()(), cleaners.pop();
} }
} }

View File

@ -1,8 +1,8 @@
#ifndef BWIDGETS_DRAW_HPP #ifndef BWIDGETS_DRAW_HPP
#define BWIDGETS_DRAW_HPP #define BWIDGETS_DRAW_HPP
#include <cstdint>
#include <functional> #include <functional>
#include <memory>
struct SDL_PixelFormat; struct SDL_PixelFormat;
struct SDL_Point; struct SDL_Point;
@ -13,12 +13,21 @@ namespace bwidgets
class Renderer; class Renderer;
class Texture; class Texture;
[[nodiscard]] auto aa(const Color&, int, float) noexcept -> Color; // Add transparency to color base_color relative to the distance d from
[[nodiscard]] auto filled_circle(const Color&, int resolution, Renderer*, // a limit to produce an AntiAliasing effect.
int aa_pixels = 3) -> Texture*; // d >= 0 → full transparency;
void set_pixels_color( // (aa_pixels) <= d < 0 → smoothsteped transparency gradient;
Texture*, // d < (aa_pixels) → fully opaque.
const std::function<uint32_t(const SDL_Point&, const SDL_PixelFormat*)>&); [[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&,
int aa_pixels = 3) -> std::shared_ptr<Texture>;
// Set the color of every pixels of a Texture using the passed function to compute
// pixel color.
void
set_pixels_color(Texture*,
const std::function<uint32_t(SDL_Point, const SDL_PixelFormat&)>&);
} }
#endif #endif

View File

@ -0,0 +1,47 @@
#ifndef BWIDGETS_ERROR_HELPER_HPP
#define BWIDGETS_ERROR_HELPER_HPP
#include <functional>
#include <basic_widgets/core/type/sdl_error.hpp>
extern "C" {
auto SDL_GetError() -> const char*;
}
namespace bwidgets
{
// Throw a E exception with message w if ptr is null. Otherwise return ptr.
template<Exception E, typename T>
inline auto ptr_or_throw(T* const ptr, const std::string_view w = "unknown") -> T*
{
std::string what;
if constexpr (std::is_same_v<E, SDLError>)
what = w == "unknown" ? SDL_GetError() : w;
else what = w;
if (ptr == nullptr) throw E(what);
return ptr;
}
// Check a return code with the success function passed. If success return false
// throw an E Exception with message w.
template<Exception E, typename T>
inline auto success_or_throw(
const T code, const std::string_view w = "unknown",
const std::function<bool(T)>& success = [](const T code) { return code == 0; })
-> T
{
// If exception type is SDLError and no value or unknown was passed for w, get
// the last error message using SDL facilities.
if constexpr (std::is_same_v<E, SDLError>) {
const std::string what {w == "unknown" ? SDL_GetError() : w};
if (code < 0) throw E(what);
}
else if (!success(code)) throw E(w.data());
return code;
}
}
#endif

View File

@ -1,18 +1,23 @@
#ifndef BWIDGETS_FONT_HPP #ifndef BWIDGETS_FONT_HPP
#define BWIDGETS_FONT_HPP #define BWIDGETS_FONT_HPP
#include <memory>
#include <string> #include <string>
#include <SDL2/SDL_ttf.h> #include <SDL_ttf.h>
#include <basic_widgets/core/type/color.hpp> #include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/opaque_struct.hpp> #include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/core/type/size.hpp> #include <basic_widgets/core/type/size.hpp>
namespace bwidgets namespace bwidgets
{ {
struct Font final : OpaqueStruct<TTF_Font> // Wrap TTF_Font from SDL_ttf and provide a font finding function.
class Font final
{ {
const std::unique_ptr<TTF_Font, Deleter> _data;
public:
enum struct Hinting enum struct Hinting
{ {
LIGHT = TTF_HINTING_LIGHT, LIGHT = TTF_HINTING_LIGHT,
@ -37,8 +42,8 @@ namespace bwidgets
UNDERLINE = TTF_STYLE_UNDERLINE UNDERLINE = TTF_STYLE_UNDERLINE
}; };
inline static const Color default_color_bg {255, 255, 255, SDL_ALPHA_OPAQUE}; static const Color default_color_bg;
inline static const Color default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE}; static const Color default_color_fg;
const int ascent; const int ascent;
const int descent; const int descent;
@ -49,12 +54,15 @@ namespace bwidgets
const int line_skip; const int line_skip;
const std::string style_name; const std::string style_name;
Font(TTF_Font*); explicit Font(TTF_Font*);
Font(const std::string&, int); // Load font at given path and with given size.
Font(std::string_view, int);
Font(const Font&) = delete; Font(const Font&) = delete;
~Font() noexcept override; Font(Font&&) = delete;
~Font() noexcept = default;
auto operator=(const Font&) -> Font& = delete; auto operator=(const Font&) = delete;
auto operator=(Font&&) = delete;
[[nodiscard]] auto hinting() const noexcept -> Hinting; [[nodiscard]] auto hinting() const noexcept -> Hinting;
auto hinting(Hinting) noexcept -> Font*; auto hinting(Hinting) noexcept -> Font*;
@ -62,13 +70,15 @@ namespace bwidgets
auto kerning(bool) noexcept -> Font*; auto kerning(bool) noexcept -> Font*;
[[nodiscard]] auto outline() const noexcept -> int; [[nodiscard]] auto outline() const noexcept -> int;
auto outline(int) noexcept -> Font*; auto outline(int) noexcept -> Font*;
auto render(RenderMode, const std::string&, const Color& fg = default_color_fg, auto render(RenderMode, std::string_view, Color fg = default_color_fg,
const Color& bg = default_color_bg) -> SDL_Surface*; Color bg = default_color_bg)
-> std::unique_ptr<SDL_Surface, Deleter>;
[[nodiscard]] auto style() const noexcept -> uint8_t; [[nodiscard]] auto style() const noexcept -> uint8_t;
auto style(uint8_t) noexcept -> Font*; auto style(uint8_t) noexcept -> Font*;
[[nodiscard]] auto text_size(const std::string&) const noexcept -> Size; [[nodiscard]] auto text_size(std::string_view) const noexcept -> Size;
[[nodiscard]] static auto find(const std::string&) -> std::string; // Get file path of the font best matching a give fontconfig pattern.
[[nodiscard]] static auto find(std::string_view) -> std::string;
}; };
} }

View File

@ -4,36 +4,39 @@
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <SDL2/SDL_rect.h> #include <SDL_rect.h>
#include <basic_widgets/core/type/color.hpp> #include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/concepts.hpp>
#include <basic_widgets/core/type/size.hpp> #include <basic_widgets/core/type/size.hpp>
namespace bwidgets namespace bwidgets
{ {
// NOLINTNEXTLINE(clang-diagnostic-unused-function) // Get the start coordinate offset required to center a ligne with a length used_len on another
[[nodiscard]] static inline auto center_line(int available_len, int used_len) noexcept -> int // line with a length available_len.
[[nodiscard]] inline auto center_line(const int available_len, const int used_len) noexcept -> int
{ {
return (available_len - used_len) / 2; return (available_len - used_len) / 2;
} }
[[nodiscard]] static inline auto distance_sqrd(const SDL_Point& a, const SDL_Point& b) noexcept // Get the squared distance of two points on a 2D plan. This avoid computing square root
// when for instance we need to compare two lenght but knowing the actual lenght is not
// needed.
[[nodiscard]] inline auto distance_sqrd(const SDL_Point a, const SDL_Point b) noexcept
-> float -> float
{ {
// NOLINTNEXTLINE(bugprone-narrowing-conversions) return float(a.x - b.x) * float(a.x - b.x)
return (a.x - b.x) * (a.x - b.x) + float(a.y - b.y) * float(a.y - b.y);
+ (a.y - b.y) * (a.y - b.y);
} }
// NOLINTNEXTLINE(clang-diagnostic-unused-function) // Get distance of two points on a 2D plan.
[[nodiscard]] static inline auto distance(const SDL_Point& a, const SDL_Point& b) noexcept -> float [[nodiscard]] inline auto distance(const SDL_Point a, const SDL_Point b) noexcept -> float
{ {
return std::sqrt(distance_sqrd(a, b)); return std::sqrt(distance_sqrd(a, b));
} }
// Like std::lerp but for use with Color object. lerp function is applied only to enabled channels.
template<FloatingPoint F> template<FloatingPoint F>
[[nodiscard]] static inline auto lerp(const Color& a, const Color& b, F x, bool op_alpha=false, bool op_color=true) noexcept -> Color [[nodiscard]] inline auto lerp(const Color a, const Color b, const F x, const bool op_alpha=false, const bool op_color=true) noexcept -> Color
{ {
return {{ return {{
op_color ? (uint8_t)std::lerp(a().r, b().r, x) : a().r, op_color ? (uint8_t)std::lerp(a().r, b().r, x) : a().r,
@ -43,45 +46,48 @@ namespace bwidgets
}}; }};
} }
// Reverse lerp.
template<Numeric N> template<Numeric N>
[[nodiscard]] static inline auto linear(N x, N a, N b) noexcept -> float [[nodiscard]] inline auto linear(const N x, const N a, const N b) noexcept -> float
{ {
return (float)(x - a) / (float)(b - a); return (float)(x - a) / (float)(b - a);
} }
// NOLINTNEXTLINE(clang-diagnostic-unused-function) // Check if a rectangle is completly inside of another rectangle.
[[nodiscard]] static inline auto rect_in_rect(const SDL_Rect& outer, const SDL_Rect& inner) noexcept [[nodiscard]] inline auto rect_in_rect(const SDL_Rect& outer, const SDL_Rect& inner) noexcept
-> bool -> bool
{ {
SDL_Point top_left {inner.x, inner.y}; const SDL_Point top_left {inner.x, inner.y};
SDL_Point bottom_right {inner.x + inner.w, inner.y + inner.h}; const SDL_Point bottom_right {inner.x + inner.w, inner.y + inner.h};
return (SDL_PointInRect(&top_left, &outer) == SDL_TRUE) return (SDL_PointInRect(&top_left, &outer) == SDL_TRUE)
&& (SDL_PointInRect(&bottom_right, &outer) == SDL_TRUE); && (SDL_PointInRect(&bottom_right, &outer) == SDL_TRUE);
} }
// NOLINTNEXTLINE(clang-diagnostic-unused-function) // Get the rectangle inside r leaving margins of given size between inner and outer rectangle.
[[nodiscard]] static inline auto rect_margin(const SDL_Rect& r, const Size& margin) noexcept [[nodiscard]] inline auto rect_margin(const SDL_Rect& r, const Size margin) noexcept
-> SDL_Rect -> SDL_Rect
{ {
return {r.x + margin.w, r.y + margin.h, r.w - 2 * margin.w, r.h - 2 * margin.h}; return {r.x + margin.w, r.y + margin.h, r.w - 2 * margin.w, r.h - 2 * margin.h};
} }
[[nodiscard]] static inline auto rect_offset(const SDL_Rect& r, const SDL_Point& offset) noexcept // Get the rectangle obtained from adding offset to its origin coordinate.
[[nodiscard]] inline auto rect_offset(const SDL_Rect& r, const SDL_Point offset) noexcept
-> SDL_Rect -> SDL_Rect
{ {
return {r.x + offset.x, r.y + offset.y, r.w, r.h}; return {r.x + offset.x, r.y + offset.y, r.w, r.h};
} }
// NOLINTNEXTLINE(clang-diagnostic-unused-function) // Commodity function to use another rectangle as a size for the offset to be added to r.
[[nodiscard]] static inline auto rect_offset(const SDL_Rect& r, const SDL_Rect& offset) noexcept [[nodiscard]] inline auto rect_offset(const SDL_Rect& r, const SDL_Rect& offset) noexcept
-> SDL_Rect -> SDL_Rect
{ {
return rect_offset(r, SDL_Point {offset.x, offset.y}); return rect_offset(r, SDL_Point {offset.x, offset.y});
} }
// Standard smoothstep algorithm.
template<Numeric N> template<Numeric N>
[[nodiscard]] static inline auto smoothstep(N x, N a, N b) noexcept -> float [[nodiscard]] inline auto smoothstep(const N x, const N a, const N b) noexcept -> float
{ {
const float x_norm = linear(std::clamp<float>(x, a, b), a, b); const float x_norm = linear(std::clamp<float>(x, a, b), a, b);
return 3 * x_norm * x_norm - 2 * x_norm * x_norm * x_norm; return 3 * x_norm * x_norm - 2 * x_norm * x_norm * x_norm;

View File

@ -1,27 +1,31 @@
#ifndef BWIDGETS_RENDERER_HPP #ifndef BWIDGETS_RENDERER_HPP
#define BWIDGETS_RENDERER_HPP #define BWIDGETS_RENDERER_HPP
#include <cstdint> #include <memory>
#include <vector> #include <span>
#include <SDL2/SDL_render.h> #include <SDL_render.h>
#include <basic_widgets/core/error_helper.hpp>
#include <basic_widgets/core/type/color.hpp> #include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/opaque_struct.hpp> #include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/core/type/sdl_error.hpp>
#include <basic_widgets/core/type/size.hpp> #include <basic_widgets/core/type/size.hpp>
namespace bwidgets namespace bwidgets
{ {
class Texture; class Texture;
class Renderer final : public OpaqueStruct<SDL_Renderer> // Wrap some of SDL's 2D accelerated rendering API.
class Renderer final
{ {
static inline auto _info(SDL_Renderer* r) -> SDL_RendererInfo friend Texture;
const std::unique_ptr<SDL_Renderer, Deleter> _data;
static auto _info(SDL_Renderer* r) -> SDL_RendererInfo
{ {
SDL_RendererInfo info; SDL_RendererInfo info;
SDLError::success_or_throw(SDL_GetRendererInfo(r, &info), __FILE__, success_or_throw<SDLError>(SDL_GetRendererInfo(r, &info));
__FUNCTION__, __LINE__);
return info; return info;
} }
@ -29,59 +33,61 @@ namespace bwidgets
public: public:
const SDL_RendererInfo info; const SDL_RendererInfo info;
Renderer(SDL_Renderer*); explicit Renderer(SDL_Renderer*);
Renderer(SDL_Window*, int, uint32_t); Renderer(SDL_Window*, int, uint32_t);
Renderer(const Renderer&) = delete; Renderer(const Renderer&) = delete;
~Renderer() noexcept override; Renderer(Renderer&&) = delete;
~Renderer() noexcept = default;
auto operator=(const Renderer&) = delete; auto operator=(const Renderer&) = delete;
auto operator=(Renderer&&) = delete;
[[nodiscard]] auto blend_mode() -> SDL_BlendMode; [[nodiscard]] auto blend_mode() -> SDL_BlendMode;
auto blend_mode(SDL_BlendMode) -> Renderer*; auto blend_mode(SDL_BlendMode) -> Renderer*;
auto clear() -> Renderer*; auto clear() -> Renderer*;
auto copy(Texture*, const SDL_Rect*, const SDL_Rect*) -> Renderer*; auto copy(const Texture&, const SDL_Rect*, const SDL_Rect*) -> Renderer*;
[[nodiscard]] auto draw_color() -> Color; [[nodiscard]] auto draw_color() -> Color;
auto draw_color(const Color&) -> Renderer*; auto draw_color(Color) -> Renderer*;
auto draw_line(const SDL_Point&, const SDL_Point&) -> Renderer*; auto draw_line(SDL_Point, SDL_Point) -> Renderer*;
auto draw_lines(const std::vector<SDL_Point>&) -> Renderer*; auto draw_lines(std::span<SDL_Point>) -> Renderer*;
auto draw_point(const SDL_Point&) -> Renderer*; auto draw_point(SDL_Point) -> Renderer*;
auto draw_points(const std::vector<SDL_Point>&) -> Renderer*; auto draw_points(std::span<SDL_Point>) -> Renderer*;
auto draw_rect(const SDL_Rect*) -> Renderer*; auto draw_rect(const SDL_Rect*) -> Renderer*;
auto draw_rects(const std::vector<SDL_Rect>&) -> Renderer*; auto draw_rects(std::span<SDL_Rect>) -> Renderer*;
auto fill_rect(const SDL_Rect*) -> Renderer*; auto fill_rect(const SDL_Rect*) -> Renderer*;
auto fill_rects(const std::vector<SDL_Rect>&) -> Renderer*; auto fill_rects(std::span<SDL_Rect>) -> Renderer*;
[[nodiscard]] auto output_size() -> Size; [[nodiscard]] auto output_size() -> Size;
void present() noexcept; void present() noexcept;
[[nodiscard]] auto viewport() noexcept -> SDL_Rect; [[nodiscard]] auto viewport() noexcept -> SDL_Rect;
auto viewport(const SDL_Rect*) -> Renderer*; auto viewport(const SDL_Rect*) -> Renderer*;
inline auto* copy(Texture* t, const SDL_Rect* src, const SDL_Rect& dst) auto copy(const Texture& t, const SDL_Rect* src, const SDL_Rect& dst)
{ {
const auto d = dst; const auto d = dst;
return copy(t, src, &d); return copy(t, src, &d);
} }
inline auto* copy(Texture* t, const SDL_Rect& src, const SDL_Rect* dst) auto copy(const Texture& t, const SDL_Rect& src, const SDL_Rect* dst)
{ {
const auto s = src; const auto s = src;
return copy(t, &s, dst); return copy(t, &s, dst);
} }
inline auto* copy(Texture* t, const SDL_Rect& src, const SDL_Rect& dst) auto copy(const Texture& t, const SDL_Rect& src, const SDL_Rect& dst)
{ {
const auto s = src; const auto s = src;
const auto d = dst; const auto d = dst;
return copy(t, &s, &d); return copy(t, &s, &d);
} }
inline auto* draw_rect(const SDL_Rect& r) auto draw_rect(SDL_Rect&& r)
{ {
const auto rect = r; const auto rect = r;
return draw_rect(&rect); return draw_rect(&rect);
} }
inline auto* fill_rect(const SDL_Rect& r) auto fill_rect(SDL_Rect&& r)
{ {
const auto rect = r; const auto rect = r;
return fill_rect(&rect); return fill_rect(&rect);
} }
inline auto* viewport(const SDL_Rect& vp) auto viewport(SDL_Rect&& vp)
{ {
const auto v = vp; const auto v = vp;
return viewport(&v); return viewport(&v);

View File

@ -1,54 +1,62 @@
#ifndef BWIDGETS_TEXTURE_HPP #ifndef BWIDGETS_TEXTURE_HPP
#define BWIDGETS_TEXTURE_HPP #define BWIDGETS_TEXTURE_HPP
#include <cstdint> #include <memory>
#include <SDL2/SDL_pixels.h> #include <SDL_pixels.h>
#include <SDL2/SDL_render.h> #include <SDL_render.h>
#include <basic_widgets/core/error_helper.hpp>
#include <basic_widgets/core/type/color.hpp> #include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/opaque_struct.hpp> #include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/core/type/sdl_error.hpp>
namespace bwidgets namespace bwidgets
{ {
class Renderer; class Renderer;
class Texture final : public OpaqueStruct<SDL_Texture> // Wrap most of the texture functions of SDL 2D rendering API.
class Texture final
{ {
struct Attr friend Renderer;
struct Attr final
{ {
uint32_t format_raw; uint32_t format_raw;
SDL_PixelFormat* format; SDL_PixelFormat* format;
SDL_TextureAccess access; SDL_TextureAccess access;
int w, h; int w, h;
} _attributes {}; };
const Attr _attributes;
const std::unique_ptr<SDL_Texture, Deleter> _data;
public: public:
Texture(SDL_Texture*); explicit Texture(SDL_Texture*);
Texture(Renderer* r, SDL_PixelFormatEnum f, SDL_TextureAccess a, int w, int h); Texture(const Renderer&, SDL_PixelFormatEnum, SDL_TextureAccess, int, int);
Texture(Renderer*, SDL_Surface*); Texture(const Renderer&, SDL_Surface*);
Texture(const Texture&) = delete; Texture(const Texture&) = delete;
~Texture() noexcept override; Texture(Texture&&) = delete;
~Texture() noexcept;
auto operator=(const Texture&) = delete; auto operator=(const Texture&) = delete;
auto operator=(Texture&&) = delete;
[[nodiscard]] auto alpha_mode() -> uint8_t; [[nodiscard]] auto alpha_mode() -> uint8_t;
auto alpha_mode(uint8_t) -> Texture*; auto alpha_mode(uint8_t) -> Texture*;
[[nodiscard]] auto blend_mode() -> SDL_BlendMode; [[nodiscard]] auto blend_mode() -> SDL_BlendMode;
auto blend_mode(SDL_BlendMode) -> Texture*; auto blend_mode(SDL_BlendMode) -> Texture*;
[[nodiscard]] auto color_mode() -> Color; [[nodiscard]] auto color_mode() -> Color;
auto color_mode(const Color&) -> Texture*; auto color_mode(Color) -> Texture*;
[[nodiscard]] auto scale_mode() -> SDL_ScaleMode; [[nodiscard]] auto scale_mode() -> SDL_ScaleMode;
auto scale_mode(SDL_ScaleMode) -> Texture*; auto scale_mode(SDL_ScaleMode) -> Texture*;
auto update(SDL_Rect*, const void*, int) -> Texture*; auto update(const SDL_Rect*, const void*, int) -> Texture*;
[[nodiscard]] inline const auto& attributes() const noexcept [[nodiscard]] const auto& attributes() const noexcept
{ {
return _attributes; return _attributes;
} }
inline auto update(const SDL_Rect& r, const void* pix, int pitch) auto update(SDL_Rect&& r, const void* pix, int pitch)
{ {
SDL_Rect rect = r; SDL_Rect rect = r;
update(&rect, pix, pitch); update(&rect, pix, pitch);
@ -56,15 +64,14 @@ namespace bwidgets
return this; return this;
} }
[[nodiscard]] static inline auto attributes(SDL_Texture* t) [[nodiscard]] static auto attributes(SDL_Texture* t)
{ {
Attr attr {}; Attr attr {};
SDLError::success_or_throw(SDL_QueryTexture(t, &attr.format_raw, success_or_throw<SDLError>(
(int*)&attr.access, &attr.w, SDL_QueryTexture(t, &attr.format_raw,
&attr.h), // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
__FILE__, __FUNCTION__, __LINE__); (int*)&attr.access, &attr.w, &attr.h));
attr.format = SDLError::ptr_or_throw(SDL_AllocFormat(attr.format_raw), attr.format = ptr_or_throw<SDLError>(SDL_AllocFormat(attr.format_raw));
__FILE__, __FUNCTION__, __LINE__);
return attr; return attr;
} }

View File

@ -1,136 +1,149 @@
#ifndef BWIDGETS_COLOR_HPP #ifndef BWIDGETS_COLOR_HPP
#define BWIDGETS_COLOR_HPP #define BWIDGETS_COLOR_HPP
#include <cstdint> #include <cmath>
#include <functional> #include <functional>
#include <stdexcept>
#include <SDL2/SDL_assert.h> #include <SDL_pixels.h>
#include <SDL2/SDL_pixels.h>
#include <basic_widgets/core/type/concepts.hpp> #include <basic_widgets/core/type/concepts.hpp>
namespace bwidgets namespace bwidgets
{ {
// Wrap SDL_Color to provide operator overloads.
class Color final class Color final
{ {
public: public:
[[nodiscard]] inline auto& operator()() noexcept SDL_Color sdl_type;
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a, bool op_on_alpha = false,
bool op_on_colors = true) noexcept
: sdl_type {r, g, b, a},
_operate_on_alpha {op_on_alpha},
_operate_on_colors {op_on_colors}
{}
Color(SDL_Color c = {}, bool op_on_alpha = false,
bool op_on_colors = true) noexcept
: sdl_type(c), _operate_on_alpha(op_on_alpha), _operate_on_colors(op_on_colors)
{}
Color(const Color&) noexcept = default;
Color(Color&&) noexcept = default;
~Color() = default;
// Enable/disable operations on alpha channel. Disabled by default on
// construction.
[[nodiscard]] auto alpha(const bool state = true) const noexcept -> Color
{
return {sdl_type, _operate_on_colors, state};
}
// Enable/disable operations on color channels.Enabled by default on
// construction.
[[nodiscard]] auto colors(const bool state = true) const noexcept -> Color
{
return {sdl_type, state, _operate_on_alpha};
}
// Get reference to the wrapped SDL native object.
[[nodiscard]] auto& operator()() noexcept
{ {
return sdl_type; return sdl_type;
} }
[[nodiscard]] inline const auto& operator()() const noexcept [[nodiscard]] const auto& operator()() const noexcept
{ {
return sdl_type; return sdl_type;
} }
// Add operand to enabled channels.
template<Numeric N>
[[nodiscard]] auto operator+(const N operand) const noexcept
{
return _operate(operand, [](N a, N b) { return a + b; });
}
// Substract operand from enabled channels.
template<Numeric N>
[[nodiscard]] auto operator-(const N operand) const noexcept
{
return _operate(operand, [](N a, N b) { return a - b; });
}
// Multiply enabled channel values by opererand.
template<Numeric N>
[[nodiscard]] auto operator*(const N operand) const noexcept
{
return _operate(operand, [](N a, N b) { return a * b; });
}
// Divide enabled channel values by operand.
template<Numeric N>
[[nodiscard]] auto operator/(const N operand) const
{
if (operand == 0) throw std::domain_error("division by zero.");
return _operate<N>(operand, [](N a, N b) { return a / b; });
}
auto operator=(const Color& c) noexcept -> Color& = default;
auto operator=(Color&&) noexcept -> Color& = default;
auto& operator=(SDL_Color c) noexcept
{
sdl_type = c;
return *this;
}
// Compare for equality of the enabled channel values between two Color
// instances.
[[nodiscard]] auto operator==(const Color other) const noexcept
{
return (_operate_on_colors && sdl_type.r == other().r
&& sdl_type.g == other().g && sdl_type.b == other().b)
|| (_operate_on_alpha && sdl_type.a == other().a);
}
// Compare for inequality of the enabled channel values between two Color
// instances.
[[nodiscard]] auto operator!=(const Color other) const noexcept
{
return (sdl_type.r != other().r || sdl_type.g != other().g
|| sdl_type.b != other().b)
&& ((_operate_on_alpha && sdl_type.a != other().a)
|| !_operate_on_alpha);
}
private: private:
bool _operate_on_alpha; bool _operate_on_alpha;
bool _operate_on_colors; bool _operate_on_colors;
template<Numeric N> template<Numeric N>
inline auto auto _operate(const N operand, const std::function<N(N, N)>& operator_) const
_operate(const N& operand,
const std::function<N(const N&, const N&)>& operator_) const noexcept
{ {
const auto overunderflow_check = [](auto x) {
if (x > (uint8_t)x) throw std::overflow_error("uint8_t overflow.");
if (x < (uint8_t)x) throw std::underflow_error("uint8_t underflow.");
};
Color c(sdl_type, _operate_on_alpha, _operate_on_colors); Color c(sdl_type, _operate_on_alpha, _operate_on_colors);
if (_operate_on_alpha) { if (_operate_on_alpha) {
auto a = std::round(operator_(c().a, operand)); const auto a = std::round(operator_(c().a, operand));
SDL_assert_release(a == (uint8_t)a); // NOLINT overunderflow_check(a);
c().a = (uint8_t)a; c().a = (uint8_t)a;
} }
if (_operate_on_colors) { if (_operate_on_colors) {
auto r = std::round(operator_(c().r, operand)); const auto r = std::round(operator_(c().r, operand));
auto g = std::round(operator_(c().g, operand)); const auto g = std::round(operator_(c().g, operand));
auto b = std::round(operator_(c().b, operand)); const auto b = std::round(operator_(c().b, operand));
// NOLINTNEXTLINE for (const auto x : {r, g, b}) overunderflow_check(x);
SDL_assert_release(r == (uint8_t)r && g == (uint8_t)g
&& b == (uint8_t)b);
c().r = (uint8_t)r; c().r = (uint8_t)r;
c().g = (uint8_t)g; c().g = (uint8_t)g;
c().b = (uint8_t)b; c().b = (uint8_t)b;
} }
return c; return c;
} }
public:
SDL_Color sdl_type;
inline Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a,
bool op_on_alpha = false, bool op_on_colors = true) noexcept
: _operate_on_alpha(op_on_alpha),
_operate_on_colors(op_on_colors),
sdl_type({r, g, b, a})
{}
inline Color(const SDL_Color& c = {}, bool op_on_alpha = false,
bool op_on_colors = true) noexcept
: _operate_on_alpha(op_on_alpha), _operate_on_colors(op_on_colors), sdl_type(c)
{}
inline Color(const Color&) noexcept = default;
[[nodiscard]] inline auto alpha() const noexcept -> Color
{
return {sdl_type, _operate_on_colors, true};
}
[[nodiscard]] inline auto colors() const noexcept -> Color
{
return {sdl_type, true, _operate_on_alpha};
}
template<Numeric N>
[[nodiscard]] inline auto operator+(const N& operand) const noexcept
{
return _operate(operand, [](const N& a, const N& b) { return a + b; });
}
template<Numeric N>
[[nodiscard]] inline auto operator-(const N& operand) const noexcept
{
return _operate(operand, [](const N& a, const N& b) { return a - b; });
}
template<Numeric N>
[[nodiscard]] inline auto operator*(const N& operand) const noexcept
{
return _operate(operand, [](const N& a, const N& b) { return a * b; });
}
template<Numeric N>
[[nodiscard]] inline auto operator/(const N& operand) const noexcept
{
SDL_assert(operand != 0); // NOLINT
return _operate<N>(operand, [](const N& a, const N& b) { return a / b; });
}
inline auto& operator=(const Color& c) noexcept
{
if (this != &c) {
sdl_type = c.sdl_type;
}
return *this;
}
inline auto& operator=(const SDL_Color& c) noexcept
{
sdl_type = c;
return *this;
}
[[nodiscard]] inline auto operator==(const Color& other) const noexcept
{
return (_operate_on_colors && sdl_type.r == other().r
&& sdl_type.g == other().g && sdl_type.b == other().b)
|| (_operate_on_alpha && sdl_type.a == other().a);
}
[[nodiscard]] inline auto operator!=(const Color& other) const noexcept
{
return (sdl_type.r != other().r || sdl_type.g != other().g
|| sdl_type.b != other().b)
&& ((_operate_on_alpha && sdl_type.a != other().a)
|| !_operate_on_alpha);
}
}; };
} }

View File

@ -1,25 +1,27 @@
#ifndef BWIDGETS_CONCEPTS_HPP_ #ifndef BWIDGETS_CONCEPTS_HPP
#define BWIDGETS_CONCEPTS_HPP_ #define BWIDGETS_CONCEPTS_HPP
#include <ostream>
#include <string> #include <string>
#include <type_traits> #include <type_traits>
namespace bwidgets namespace bwidgets
{ {
// T can be converted to string with to_string.
template<typename T> template<typename T>
concept CanToString = requires(T value) concept CanToString = requires(T value)
{ {
std::to_string(value); std::to_string(value);
}; };
// T is a floating point type.
template<typename T> template<typename T>
concept FloatingPoint = std::is_floating_point_v<T>; concept FloatingPoint = std::is_floating_point_v<T>;
// T can be any numeric type.
template<typename T> template<typename T>
concept Numeric = std::is_arithmetic_v<T>; concept Numeric = std::is_arithmetic_v<T>;
// T can be converted to string by using ostream.
template<typename T> template<typename T>
concept Printable = requires(T value) concept Printable = requires(T value)
{ {

View File

@ -0,0 +1,51 @@
#ifndef BWIDGETS_OPAQUE_STRUCT_HPP
#define BWIDGETS_OPAQUE_STRUCT_HPP
#include <fontconfig/fontconfig.h>
#include <SDL_render.h>
#include <SDL_surface.h>
#include <SDL_ttf.h>
namespace bwidgets
{
// Deleter type for fancy pointers.
struct Deleter
{
void operator()(FcConfig* ptr)
{
FcConfigDestroy(ptr);
}
void operator()(FcPattern* ptr)
{
FcPatternDestroy(ptr);
}
void operator()(SDL_Renderer* ptr)
{
SDL_DestroyRenderer(ptr);
}
void operator()(SDL_Surface* ptr)
{
SDL_FreeSurface(ptr);
}
void operator()(SDL_Texture* ptr)
{
SDL_DestroyTexture(ptr);
}
void operator()(SDL_Window* ptr)
{
SDL_DestroyWindow(ptr);
}
void operator()(TTF_Font* ptr)
{
TTF_CloseFont(ptr);
}
};
}
#endif

View File

@ -1,47 +1,22 @@
#ifndef BWIDGETS_EXCEPTION_HPP #ifndef BWIDGETS_EXCEPTION_HPP
#define BWIDGETS_EXCEPTION_HPP #define BWIDGETS_EXCEPTION_HPP
#include <any> #include <stdexcept>
#include <exception>
#include <functional>
#include <map>
#include <basic_widgets/core/type/concepts.hpp> #include <basic_widgets/core/type/concepts.hpp>
namespace bwidgets namespace bwidgets
{ {
struct BaseException : std::exception // Base type for custom runtime errors.
class BaseException : std::runtime_error
{ {
const char* file; protected:
const char* func; using runtime_error::runtime_error;
const int line;
const char* what;
BaseException(const char* file, const char* func, int l, const char* w) noexcept
: file(file), func(func), line(l), what(w)
{}
}; };
// T derives from BaseException.
template<typename T> template<typename T>
concept Exception = std::derived_from<T, BaseException>; concept Exception = std::derived_from<T, BaseException>;
template<Exception E, typename T>
static inline auto ptr_or_throw(T* ptr, const char* file, const char* func, int l,
const char* w = nullptr) -> T*
{
if (ptr == nullptr) throw E(file, func, l, w);
return ptr;
}
template<Exception E, typename T>
static inline auto success_or_throw(
T code, const char* file, const char* func, int l, const char* w = nullptr,
std::function<bool(const T&)> success = [](const T& code) { return code == 0; })
-> T
{
if (!success(code)) throw E(file, func, l, w);
return code;
}
} }
#endif #endif

View File

@ -5,11 +5,10 @@
namespace bwidgets namespace bwidgets
{ {
// Exception type for fontconfig errors
struct FCError final : BaseException struct FCError final : BaseException
{ {
FCError(const char* file, const char* func, int l, const char* w = nullptr) using BaseException::BaseException;
: BaseException(file, func, l, w)
{}
}; };
} }

View File

@ -1,31 +0,0 @@
#ifndef BWIDGETS_OPAQUE_STRUCT_HPP
#define BWIDGETS_OPAQUE_STRUCT_HPP
namespace bwidgets
{
template<typename T>
class OpaqueStruct
{
protected:
T* _c_pod;
public:
OpaqueStruct(T* ptr = nullptr) : _c_pod(ptr) {}
virtual ~OpaqueStruct<T>() noexcept = default;
// NOLINTNEXTLINE(modernize-use-trailing-return-type)
[[nodiscard]] inline auto& operator()()
{
return _c_pod;
}
};
template<typename... Ts>
void discard(Ts*&... ptrs)
{
((delete ptrs), ...);
((ptrs = nullptr), ...);
}
}
#endif

View File

@ -3,40 +3,12 @@
#include <basic_widgets/core/type/exception.hpp> #include <basic_widgets/core/type/exception.hpp>
extern "C" {
auto SDL_GetError() -> const char*;
}
namespace bwidgets namespace bwidgets
{ {
// Custom exception type for SDL errors.
struct SDLError final : BaseException struct SDLError final : BaseException
{ {
SDLError(const char* file, const char* func, const int l, using BaseException::BaseException;
const char* w = nullptr) noexcept
: BaseException(file, func, l, w)
{}
template<typename T>
[[nodiscard]] static inline auto ptr_or_throw(T* ptr, const char* file,
const char* func, int l,
const char* w = nullptr) -> T*
{
if (!ptr) {
if (!w) w = SDL_GetError();
throw SDLError(file, func, l, w);
}
return ptr;
}
static inline auto success_or_throw(int code, const char* file, const char* func,
int l, const char* w = nullptr) -> int
{
if (code < 0) {
if (w == nullptr) w = SDL_GetError();
throw SDLError(file, func, l, w);
}
return code;
}
}; };
} }

View File

@ -5,30 +5,37 @@
namespace bwidgets namespace bwidgets
{ {
// Represent size of a 2D surface.
struct Size struct Size
{ {
int w; int w;
int h; int h;
[[nodiscard]] inline auto operator-() const noexcept -> Size
{
return {-w, -h};
}
[[nodiscard]] inline auto operator+(const Size& s) const noexcept -> Size
{
return {w + s.w, h + s.h};
}
[[nodiscard]] inline auto operator-(const Size& s) const noexcept -> Size
{
return {w - s.w, h - s.h};
}
template<Numeric N>
[[nodiscard]] inline auto operator*(N a) const noexcept -> Size
{
return {a * w, a * h};
}
}; };
// Addition the dimensions of two Size objects.
[[nodiscard]] inline auto operator+(const Size a, const Size b) noexcept -> Size
{
return {a.w + b.w, a.h + b.h};
}
// Substract dimensions of a Size object from another.
[[nodiscard]] inline auto operator-(const Size a, const Size b) noexcept -> Size
{
return {a.w - b.w, a.h - b.h};
}
// Multiply dimensions of a Size instance by a number.
template<Numeric N>
[[nodiscard]] inline auto operator*(const Size a, const N b) noexcept -> Size
{
return {a.w * b, a.h * b};
}
template<Numeric N>
[[nodiscard]] inline auto operator*(const N a, const Size b) noexcept -> Size
{
return b * a;
}
} }
#endif #endif

View File

@ -5,21 +5,17 @@
namespace bwidgets namespace bwidgets
{ {
class AlignedLayout final : public Layout enum struct LayoutAlignment
{ {
private: HORIZONTAL,
void _update_layout(const SDL_Rect&) override; VERTICAL
};
// Align vertically or horizontally widgets.
class AlignedLayout : public virtual Layout
{
public: public:
enum struct Alignment using Layout::Layout;
{
HORIZONTAL,
VERTICAL
} alignment;
AlignedLayout(Alignment align) noexcept : alignment(align) {}
[[nodiscard]] auto size() const noexcept -> Size override;
}; };
} }

View File

@ -0,0 +1,77 @@
#ifndef BWIDGETS_ALIGNED_LAYOUT_IMPL_HPP
#define BWIDGETS_ALIGNED_LAYOUT_IMPL_HPP
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/aligned_layout.hpp>
#include <basic_widgets/w/base/layout_impl.hpp>
#include <basic_widgets/w/base/widget_impl.hpp>
namespace bwidgets
{
template<LayoutAlignment alignment>
class AlignedLayoutImpl final : public virtual AlignedLayout,
public virtual LayoutImpl,
public virtual WidgetImpl
{
public:
using LayoutImpl::LayoutImpl;
// Return smallest usable size.
[[nodiscard]] auto size() const noexcept -> Size override
{
Size min_size {0, 0};
if constexpr (alignment == LayoutAlignment::HORIZONTAL) {
for (const auto& w : _widgets) {
if (w->size().w > min_size.w) min_size.w = w->size().w;
if (w->size().h > min_size.h) min_size.h = w->size().h;
}
return {(int)_widgets.size() * min_size.w
+ ((int)_widgets.size() + 1) * margins.w,
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;
min_size.h += w->size().h;
}
return {min_size.w + 2 * margins.w,
min_size.h + ((int)_widgets.size() + 1) * margins.h};
}
private:
void _update_layout(const SDL_Rect& vp) override
{
if constexpr (alignment == LayoutAlignment::HORIZONTAL) {
const int widget_width =
(vp.w - ((int)_widgets.size() + 1) * margins.w) / (int)_widgets.size();
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
const auto& w {_widgets[i]};
w->viewport({vp.x + margins.w + (int)i * (widget_width + margins.w),
vp.y + center_line(vp.h, w->size().h), widget_width,
w->size().h});
}
}
else { // Vertical
int offset = 0;
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
const auto& w {_widgets[i]};
w->viewport({
vp.x,
vp.y + ((int)i + 1) * margins.h + offset,
vp.w,
w->size().h,
});
offset += w->size().h;
}
}
}
};
}
#endif

View File

@ -1,18 +1,9 @@
#ifndef BWIDGETS_INPUT_HPP #ifndef BWIDGETS_INPUT_HPP
#define BWIDGETS_INPUT_HPP #define BWIDGETS_INPUT_HPP
#include <cmath>
#include <exception>
#include <iomanip>
#include <sstream>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_keyboard.h>
#include <SDL2/SDL_render.h>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/type/concepts.hpp> #include <basic_widgets/core/type/concepts.hpp>
#include <basic_widgets/w/caption.hpp> #include <basic_widgets/w/base/widget.hpp>
#include <basic_widgets/w/feat/font_handler.hpp>
#include <basic_widgets/w/feat/keyboard_handler.hpp> #include <basic_widgets/w/feat/keyboard_handler.hpp>
#include <basic_widgets/w/feat/mouse_handler.hpp> #include <basic_widgets/w/feat/mouse_handler.hpp>
@ -20,104 +11,13 @@ namespace bwidgets
{ {
template<typename T> template<typename T>
class Input : public Widget, class Input : public virtual Widget,
public FontHandler, public virtual FontHandler,
public KeyboardHandler, public virtual KeyboardHandler,
public MouseHandler public virtual MouseHandler
{ {
protected: protected:
Caption _input_caption; using Widget::Widget;
Input(Widget* parent = nullptr) : Widget(parent), _input_caption(this)
{
FocusHandler::_focus_area = &_widget_area;
MouseHandler::_click_area = &_widget_area;
_input_caption.text(value_to_string(value));
}
void _handle_focus_change(bool focus) override
{
if (focus) {
SDL_StartTextInput();
}
else {
value = value_from_string(input_text());
input_text(value_to_string(value));
SDL_StopTextInput();
}
}
void _handle_font_change(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)->font_color_fg(fg);
}
void _handle_geometry_change(const SDL_Rect& vp) override
{
const auto input_h = _input_caption.size().h + 2 * border_width;
_widget_area = {0, center_line(vp.h, input_h), vp.w, input_h};
_input_caption.viewport(rect_offset(
{rect_margin(_widget_area, {border_width, border_width})}, vp));
}
void _handle_key(const SDL_KeyboardEvent& key) override
{
if (key.type == SDL_KEYDOWN) {
switch (key.keysym.sym) {
case SDLK_BACKSPACE: {
std::string txt = input_text();
if (txt.length() > 0) {
txt.pop_back();
input_text(txt);
}
break;
}
case SDLK_RETURN:
case SDLK_RETURN2: // what is return2 btw?
case SDLK_KP_ENTER:
value = value_from_string(input_text());
focus(false);
break;
}
}
}
void _handle_renderer_change(Renderer* r) override
{
_input_caption.renderer(r);
}
void _handle_rendering() override
{
for (int i = border_width - 1; i >= 0; i--) {
const auto factor = linear(i, 0, border_width);
const auto& color_end = _has_focus ? color_bg_focused : color_bg;
const auto color_start = color_end / 2;
_renderer->draw_color(lerp(color_start, color_end, factor))
->draw_rect(rect_margin(_widget_area, {i, i}));
}
if (MouseHandler::_is_hovered || FocusHandler::_has_focus)
_input_caption.font_color_bg(color_bg_focused);
else _input_caption.font_color_bg(color_bg);
_input_caption.render();
}
void _handle_text_input(const SDL_TextInputEvent& input) override
{
if (is_valid_input(input.text)) {
input_text(input_text() + input.text);
}
}
public: public:
static const int default_border_width = 3; static const int default_border_width = 3;
@ -128,96 +28,30 @@ namespace bwidgets
static const int default_min_width = 1; static const int default_min_width = 1;
int border_width = default_border_width; int border_width = default_border_width;
Color color_bg = default_color_bg; Color color_bg_default = default_color_bg;
Color color_bg_focused = default_color_bg_focused; Color color_bg_focused = default_color_bg_focused;
int float_precision = default_float_precision; int float_precision = default_float_precision;
int input_min_width = default_min_width; 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 {}; T value {};
virtual auto color_fg(const Color& c) -> Input<T>* // Get the current displayed string.
{ [[nodiscard]] virtual auto input_text() const -> std::string_view = 0;
_input_caption.font_color_fg(c); // Set displayed string
return this; virtual void input_text(std::string) = 0;
} virtual void input_text_color(Color) = 0;
// 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;
[[nodiscard]] virtual auto input_text() const -> const std::string& // Process a value for instance to modify it before saving it.
{ [[nodiscard]] virtual auto process_value(T) const noexcept -> T = 0;
return _input_caption.text(); // Get string representation to be shown of the stored value.
} [[nodiscard]] virtual auto value_from_string(std::string_view) const noexcept
-> T = 0;
virtual auto input_text(const std::string& txt) -> Input<T>* // Parse a string to get its representation in T type of input value memory.
{ [[nodiscard]] virtual auto value_to_string(T) const noexcept -> std::string = 0;
_input_caption.text(txt);
return this;
}
[[nodiscard]] virtual auto is_valid_input(const std::string&) const noexcept
-> bool
{
return true;
}
[[nodiscard]] virtual auto process_value(T x) const noexcept -> T
{
return x;
}
[[nodiscard]] inline auto size() const noexcept -> Size override
{
if (_font == nullptr) return {0, 0};
return {
_font->text_size(std::string(input_min_width, input_width_unit)).w
+ 2 * border_width,
_font->line_skip + 4 * border_width // _why_ 4 and not 2?…
};
}
[[nodiscard]] auto value_from_string(std::string s) noexcept
{
T v;
if constexpr (std::is_arithmetic_v<T>) {
s = s.length() > 0 ? s : "0";
if constexpr (std::is_floating_point_v<T>) v = (T)std::stold(s);
else if constexpr (std::is_integral_v<T>) v = (T)std::stoll(s);
}
else if constexpr (std::is_same_v<
T, std::string> || std::convertible_to<std::string, T>)
v = s;
else
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
static_assert((bool)sizeof(T) && false,
"string cannot be converted to v type T.");
return process_value(v);
}
[[nodiscard]] auto value_to_string(T value) noexcept
{
std::string s;
if constexpr (std::is_same_v<std::string,
T> || std::convertible_to<T, std::string>)
s = std::move(value);
else if constexpr (CanToString<T>) {
std::stringstream ss;
ss << std::fixed << std::setprecision(float_precision) << value;
s = std::move(ss).str();
}
else if constexpr (Printable<T>) {
std::stringstream ss;
ss << value;
s = std::move(ss).str();
}
else
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
static_assert((bool)sizeof(T) && false,
"value cannot be converted to string.");
return s;
}
}; };
} }

View File

@ -0,0 +1,231 @@
#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

View File

@ -1,42 +1,26 @@
#ifndef BWIDGETS_LAYOUT_HPP #ifndef BWIDGETS_LAYOUT_HPP
#define BWIDGETS_LAYOUT_HPP #define BWIDGETS_LAYOUT_HPP
#include <functional>
#include <vector>
#include <basic_widgets/core/type/size.hpp> #include <basic_widgets/core/type/size.hpp>
#include <basic_widgets/w/base/widget.hpp> #include <basic_widgets/w/base/widget.hpp>
namespace bwidgets namespace bwidgets
{ {
class Renderer; class Layout : public virtual Widget
}
namespace bwidgets
{
class Layout : public Widget
{ {
protected: protected:
std::vector<Widget*> _widgets; using Widget::Widget;
void _handle_geometry_change(const SDL_Rect&) override;
void _handle_renderer_change(Renderer*) override;
void _handle_rendering() override;
virtual void _update_layout(const SDL_Rect&) = 0;
public: public:
inline static const Size default_margins {8, 8}; static inline const Size default_margins {8, 8};
// Margins between layout widgets.
Size margins = default_margins; Size margins = default_margins;
~Layout() override; // Add widget to the layout
virtual void add_widget(std::unique_ptr<Widget>) = 0;
auto handle_event(const SDL_Event&) -> Layout* override; // Apply a function to every layout widget.
[[nodiscard]] auto size() const noexcept -> Size override = 0; virtual void for_widgets(const std::function<void(Widget*)>&) = 0;
virtual auto add_widget(Widget*) -> Layout*;
virtual void for_widgets(const std::function<void(Widget*)>&);
}; };
} }

View File

@ -0,0 +1,38 @@
#ifndef BWIDGETS_LAYOUT_IMPL_HPP
#define BWIDGETS_LAYOUT_IMPL_HPP
#include <vector>
#include <basic_widgets/w/base/layout.hpp>
#include <basic_widgets/w/base/widget_impl.hpp>
namespace bwidgets
{
class Renderer;
}
namespace bwidgets
{
class LayoutImpl : public virtual Layout,
public virtual WidgetImpl
{
public:
using WidgetImpl::WidgetImpl;
void add_widget(std::unique_ptr<Widget>) override;
void for_widgets(const std::function<void(Widget*)>&) override;
void handle_event(const SDL_Event&) override;
protected:
std::vector<std::unique_ptr<Widget>> _widgets;
void _handle_geometry_change(const SDL_Rect&) override;
void _handle_renderer_change(const std::shared_ptr<Renderer>&) override;
void _handle_rendering() override;
// Compute and set widget geometry.
virtual void _update_layout(const SDL_Rect&) = 0;
};
}
#endif

View File

@ -1,8 +1,11 @@
#ifndef BWIDGETS_WIDGET_HPP #ifndef BWIDGETS_WIDGET_HPP
#define BWIDGETS_WIDGET_HPP #define BWIDGETS_WIDGET_HPP
#include <memory>
#include <basic_widgets/core/renderer.hpp> #include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/core/type/size.hpp> #include <basic_widgets/core/type/size.hpp>
#include <basic_widgets/w/feat/event_handler.hpp>
union SDL_Event; union SDL_Event;
@ -10,30 +13,26 @@ struct SDL_Renderer;
namespace bwidgets namespace bwidgets
{ {
class Widget class Widget : public virtual EventHandler
{ {
protected:
Renderer* _renderer {nullptr};
SDL_Rect _viewport {0, 0, 0, 0};
SDL_Rect _widget_area {0, 0, 0, 0};
virtual void _handle_geometry_change(const SDL_Rect&) = 0;
virtual void _handle_renderer_change(Renderer*) {}
virtual void _handle_rendering() = 0;
public: public:
// Parent widget for bidirectional message passing between widgets.
// Currently not used by this lib.
Widget* parent; Widget* parent;
Widget(Widget* p = nullptr) noexcept : parent(p) {} explicit Widget(Widget* p = nullptr) noexcept : parent {p} {}
virtual ~Widget() = default; // Render widget on current renderer target.
virtual void render() = 0;
virtual auto handle_event(const SDL_Event&) -> Widget*; // Set widget renderer.
[[nodiscard]] virtual auto size() const noexcept -> Size = 0; virtual void renderer(std::shared_ptr<Renderer>) = 0;
virtual auto render() -> Widget* final; // Get prefered or minimal widget size.
virtual auto renderer(Renderer*) -> Widget* final; [[nodiscard]] virtual auto size() const noexcept -> Size = 0;
virtual auto viewport(const SDL_Rect&) -> Widget* final; // Set the viewport delimiting the widget rendering area and the origin
[[nodiscard]] virtual auto viewport() const -> const SDL_Rect& final; // for relative coordinates.
virtual void viewport(const SDL_Rect&) = 0;
// Get current viewport.
[[nodiscard]] virtual auto viewport() const -> const SDL_Rect& = 0;
}; };
} }

View File

@ -0,0 +1,35 @@
#ifndef BWIDGETS_WIDGET_IMPL_HPP
#define BWIDGETS_WIDGET_IMPL_HPP
#include <basic_widgets/w/base/widget.hpp>
#include <basic_widgets/w/feat/event_handler_impl.hpp>
namespace bwidgets
{
class WidgetImpl : public virtual Widget,
public virtual EventHandlerImpl
{
public:
void render() override;
void renderer(std::shared_ptr<Renderer>) override;
void viewport(const SDL_Rect&) override;
[[nodiscard]] auto viewport() const -> const SDL_Rect& override;
protected:
std::shared_ptr<Renderer> _renderer;
SDL_Rect _viewport {0, 0, 0, 0};
// Area actually used by the widget.
SDL_Rect _widget_area {0, 0, 0, 0};
using Widget::Widget;
// Called on viewport change.
virtual void _handle_geometry_change(const SDL_Rect&) = 0;
// Called on renderer change.
virtual void _handle_renderer_change(const std::shared_ptr<Renderer>&) {}
// Called on rendering.
virtual void _handle_rendering() = 0;
};
}
#endif

View File

@ -1,50 +1,36 @@
#ifndef BWIDGETS_BUTTON_HPP #ifndef BWIDGETS_BUTTON_HPP
#define BWIDGETS_BUTTON_HPP #define BWIDGETS_BUTTON_HPP
#include <functional>
#include <string> #include <string>
#include <basic_widgets/core/type/color.hpp> #include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/w/caption.hpp> #include <basic_widgets/w/base/widget.hpp>
#include <basic_widgets/w/feat/font_handler.hpp>
#include <basic_widgets/w/feat/mouse_handler.hpp> #include <basic_widgets/w/feat/mouse_handler.hpp>
namespace bwidgets namespace bwidgets
{ {
class Button : public Widget, class Button : public virtual Widget,
public FontHandler, public virtual FontHandler,
public MouseHandler public virtual MouseHandler
{ {
protected:
Caption _caption;
SDL_Rect _caption_area {};
Color _color_foreground = default_color_fg;
void _handle_focus_change(bool) override {}
void _handle_font_change(Font*) override;
void _handle_font_color_change(const Color&, const Color&) override;
void _handle_geometry_change(const SDL_Rect&) override;
void _handle_renderer_change(Renderer*) override;
void _handle_rendering() override;
void _on_push(bool) override;
public: public:
inline static const Color default_color_bg {150, 150, 150, SDL_ALPHA_OPAQUE}; static inline const Color default_color_bg {150, 150, 150, SDL_ALPHA_OPAQUE};
inline static const Color default_color_bg_hover {175, 175, 175, static inline const Color default_color_bg_hover {175, 175, 175,
SDL_ALPHA_OPAQUE}; SDL_ALPHA_OPAQUE};
inline static const Color default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE}; static inline const Color default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
std::function<Color(int, int)> border_gradient; std::function<Color(int, int, float)> border_gradient;
std::function<Color(int, int)> border_gradient_pushed; Size border_size {3, 3};
Size border_size {3, 3}; Color color_bg = default_color_bg;
Color color_bg = default_color_bg; Color color_bg_hover = default_color_bg_hover;
Color color_bg_hover = default_color_bg_hover;
Button(Widget* parent = nullptr) noexcept; using Widget::Widget;
[[nodiscard]] auto size() const noexcept -> Size override; // Get button text (label).
[[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0;
[[nodiscard]] virtual auto text() const noexcept -> const std::string&; // Set button text
virtual auto text(const std::string&) -> Button*; virtual void text(std::string) = 0;
}; };
} }

View File

@ -0,0 +1,40 @@
#ifndef BWIDGETS_BUTTON_IMPL_HPP
#define BWIDGETS_BUTTON_IMPL_HPP
#include <basic_widgets/w/base/widget_impl.hpp>
#include <basic_widgets/w/button.hpp>
#include <basic_widgets/w/caption.hpp>
#include <basic_widgets/w/feat/font_handler_impl.hpp>
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
namespace bwidgets
{
class ButtonImpl : public virtual Button,
public virtual FontHandlerImpl,
public virtual MouseHandlerImpl,
public virtual WidgetImpl
{
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;
protected:
std::unique_ptr<Caption> _caption;
SDL_Rect _caption_area {};
Color _color_foreground = default_color_fg;
void _handle_font_change(const std::shared_ptr<Font>&) override;
void _handle_font_color_change(Color, Color)
override;
void _handle_geometry_change(const SDL_Rect&) override;
void _handle_renderer_change(const std::shared_ptr<Renderer>&) override;
void _handle_rendering() override;
void _on_push(bool) override;
};
}
#endif

View File

@ -1,8 +1,6 @@
#ifndef BWIDGETS_CAPTION_HPP #ifndef BWIDGETS_CAPTION_HPP
#define BWIDGETS_CAPTION_HPP #define BWIDGETS_CAPTION_HPP
#include <basic_widgets/core/texture.hpp>
#include <basic_widgets/core/type/size.hpp>
#include <basic_widgets/w/base/widget.hpp> #include <basic_widgets/w/base/widget.hpp>
#include <basic_widgets/w/feat/font_handler.hpp> #include <basic_widgets/w/feat/font_handler.hpp>
#include <basic_widgets/w/feat/texture_handler.hpp> #include <basic_widgets/w/feat/texture_handler.hpp>
@ -11,22 +9,10 @@ struct SDL_Surface;
namespace bwidgets namespace bwidgets
{ {
class Caption : public Widget, class Caption : public virtual Widget,
public FontHandler, public virtual FontHandler,
public TextureHandler public virtual TextureHandler
{ {
protected:
Font::RenderMode _render_mode {Font::RenderMode::SHADED};
std::string _text;
Texture* _text_texture {nullptr};
void _handle_font_change(Font*) override;
void _handle_font_color_change(const Color&, const Color&) override;
void _handle_geometry_change(const SDL_Rect&) override;
void _handle_renderer_change(Renderer*) override;
void _handle_rendering() override;
void _handle_texture_update() override;
public: public:
enum struct Alignment enum struct Alignment
{ {
@ -34,22 +20,22 @@ namespace bwidgets
LEFT, LEFT,
RIGHT RIGHT
}; };
inline static const Color default_color_bg = {255, 255, 255, SDL_ALPHA_OPAQUE}; inline static const Color default_color_bg = {255, 255, 255, SDL_ALPHA_OPAQUE};
inline static const Color default_color_fg = {0, 0, 0, SDL_ALPHA_OPAQUE}; inline static const Color default_color_fg = {0, 0, 0, SDL_ALPHA_OPAQUE};
inline static const Size default_margins = {3, 3}; inline static const Size default_margins = {3, 3};
// Text horizontal alignment on the caption viewport.
Alignment alignment {Alignment::LEFT}; Alignment alignment {Alignment::LEFT};
Size margins {3, 3}; Size margins {default_margins};
Caption(Widget* parent = nullptr) noexcept; using Widget::Widget;
~Caption() noexcept override;
virtual auto render_mode(Font::RenderMode) -> Caption*; // Set caption text rendering mode.
[[nodiscard]] virtual auto text() const noexcept -> const std::string&; virtual void render_mode(Font::RenderMode) = 0;
virtual auto text(const std::string&) -> Caption*; // Get caption text.
[[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0;
[[nodiscard]] auto size() const noexcept -> Size override; // Set caption text.
virtual void text(std::string) = 0;
}; };
} }

View File

@ -0,0 +1,37 @@
#ifndef BWIDGETS_CAPTION_IMPL_HPP
#define BWIDGETS_CAPTION_IMPL_HPP
#include <basic_widgets/w/base/widget_impl.hpp>
#include <basic_widgets/w/caption.hpp>
#include <basic_widgets/w/feat/font_handler_impl.hpp>
namespace bwidgets
{
class CaptionImpl : public virtual Caption,
public virtual FontHandlerImpl,
public virtual WidgetImpl
{
public:
CaptionImpl(Widget* p = nullptr) noexcept;
void render_mode(Font::RenderMode) override;
[[nodiscard]] auto size() const noexcept -> Size override;
[[nodiscard]] auto text() const noexcept -> std::string_view override;
void text(std::string) override;
protected:
Font::RenderMode _render_mode {Font::RenderMode::SHADED};
std::string _text;
std::shared_ptr<Texture> _text_texture {nullptr};
void _handle_font_change(const std::shared_ptr<Font>&) override;
void _handle_font_color_change(Color, Color)
override;
void _handle_geometry_change(const SDL_Rect&) override;
void _handle_renderer_change(const std::shared_ptr<Renderer>&) override;
void _handle_rendering() override;
void _handle_texture_update() override;
};
}
#endif

View File

@ -0,0 +1,31 @@
#ifndef BWIDGETS_EVENT_HANDLER_HPP
#define BWIDGETS_EVENT_HANDLER_HPP
#include <functional>
#include <SDL_events.h>
namespace bwidgets
{
class EventHandler
{
protected:
EventHandler() = default;
public:
using handler_t =
std::pair<SDL_EventType, std::function<void(const SDL_Event&)>>;
EventHandler(const EventHandler&) = delete;
EventHandler(EventHandler&&) = delete;
auto operator=(const EventHandler&) -> EventHandler& = delete;
auto operator=(EventHandler&&) -> EventHandler& = delete;
virtual ~EventHandler() = default;
// Pass the event to be handled.
virtual void handle_event(const SDL_Event&) = 0;
};
}
#endif

View File

@ -0,0 +1,30 @@
#ifndef BWIDGETS_EVENT_HANDLER_IMPL_HPP
#define BWIDGETS_EVENT_HANDLER_IMPL_HPP
#include <unordered_map>
#include <basic_widgets/w/feat/event_handler.hpp>
namespace bwidgets
{
class EventHandlerImpl : public virtual EventHandler
{
std::unordered_map<SDL_EventType, std::function<void(const SDL_Event&)>>
_event_handlers {};
public:
void handle_event(const SDL_Event&) override;
protected:
// Add an handler for a given event type. There can be only one handler by
// event type. Return true if handler is added successfuly. Return false
// if handler cannot be added because there's already an handler for that
// event type.
auto _add_event_handler(handler_t) -> bool;
// Remove the handler of a given event type. Return true with the removed
// handler on success, false otherwise.
auto _remove_event_handler(SDL_EventType) -> std::pair<handler_t, bool>;
};
}
#endif

View File

@ -1,26 +1,28 @@
#ifndef BWIDGETS_FOCUS_HANDLER_HPP #ifndef BWIDGETS_FOCUS_HANDLER_HPP
#define BWIDGETS_FOCUS_HANDLER_HPP #define BWIDGETS_FOCUS_HANDLER_HPP
#include <functional>
struct SDL_Rect; struct SDL_Rect;
namespace bwidgets namespace bwidgets
{ {
class FocusHandler class FocusHandler
{ {
protected:
const SDL_Rect* _focus_area = nullptr;
bool _has_focus = false;
virtual void _handle_focus_change(bool) = 0;
public: public:
virtual ~FocusHandler() = default; FocusHandler() = default;
FocusHandler(const FocusHandler&) = delete;
FocusHandler(FocusHandler&&) = delete;
auto operator=(FocusHandler&&) = delete;
auto operator=(const FocusHandler&) = delete;
virtual inline void focus(bool focus) final virtual ~FocusHandler() = default;
{ // set focus state.
_handle_focus_change(focus); virtual void focus(bool focus) = 0;
_has_focus = focus; // get focus state.
} virtual bool focus() = 0;
// set an handler for focus states changes.
virtual void focus_handler(std::function<void(bool)>) = 0;
}; };
} }

View File

@ -0,0 +1,31 @@
#ifndef BWIDGETS_FOCUS_HANDLER_IMPL_HPP
#define BWIDGETS_FOCUS_HANDLER_IMPL_HPP
#include <basic_widgets/w/feat/focus_handler.hpp>
namespace bwidgets
{
class FocusHandlerImpl : public virtual FocusHandler
{
bool _has_focus {false};
std::function<void(bool)> _focus_handler {[](auto) {}};
public:
void focus(const bool _focus) override
{
if (_has_focus != _focus) _focus_handler(_has_focus = _focus);
}
bool focus() override
{
return _has_focus;
}
void focus_handler(decltype(_focus_handler) handler) final
{
_focus_handler = std::move(handler);
}
};
}
#endif

View File

@ -7,54 +7,27 @@ namespace bwidgets
{ {
class FontHandler class FontHandler
{ {
protected:
Font* _font {nullptr};
Color _font_color_bg = default_font_color_bg;
Color _font_color_fg = default_font_color_fg;
Font::RenderMode _font_render_mode {Font::RenderMode::SHADED};
virtual void _handle_font_change(Font*) = 0;
virtual void _handle_font_color_change(const Color&, const Color&) = 0;
public: public:
inline static const Color default_font_color_bg {255, 255, 255, inline static const Color default_font_color_bg {255, 255, 255,
SDL_ALPHA_OPAQUE}; SDL_ALPHA_OPAQUE};
inline static const Color default_font_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE}; inline static const Color default_font_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
FontHandler(const FontHandler&) = delete;
FontHandler(FontHandler&&) = delete;
auto operator=(const FontHandler&) = delete;
auto operator=(FontHandler&&) = delete;
virtual ~FontHandler() noexcept = default; virtual ~FontHandler() noexcept = default;
virtual inline auto font(Font* f) -> FontHandler* final // Set the used font.
{ virtual void font(std::shared_ptr<Font> f) = 0;
if (f != nullptr // Set background color used for shaded text rendering
&& (f != _font || f->family_name != _font->family_name // mode.
|| f->height != _font->height || f->hinting() != _font->hinting() virtual void font_color_bg(Color c) = 0;
|| f->kerning() != _font->kerning() // Set foreground (glyphs) color.
|| f->outline() != _font->outline() virtual void font_color_fg(Color c) = 0;
|| f->style_name != _font->style_name))
{
_handle_font_change(f);
_font = f;
}
return this;
}
virtual inline auto font_color_bg(const Color& c) -> FontHandler* final protected:
{ FontHandler() noexcept = default;
if (c != _font_color_bg) {
_handle_font_color_change(_font_color_bg, c);
_font_color_bg = c;
}
return this;
}
virtual inline auto font_color_fg(const Color& c) -> FontHandler* final
{
if (c != _font_color_fg) {
_handle_font_color_change(c, _font_color_fg);
_font_color_fg = c;
}
return this;
}
}; };
} }

View File

@ -0,0 +1,54 @@
#ifndef BWIDGETS_FONT_HANDLER_IMPL_HPP
#define BWIDGETS_FONT_HANDLER_IMPL_HPP
#include <basic_widgets/w/feat/font_handler.hpp>
namespace bwidgets
{
class FontHandlerImpl : public virtual FontHandler
{
public:
void font(const std::shared_ptr<Font> f) final
{
if (f
&& (f != _font || f->family_name != _font->family_name
|| f->height != _font->height || f->hinting() != _font->hinting()
|| f->kerning() != _font->kerning()
|| f->outline() != _font->outline()
|| f->style_name != _font->style_name))
{
_handle_font_change(f);
_font = f;
}
}
void font_color_bg(const Color c) final
{
if (c != _font_color_bg) {
_handle_font_color_change(_font_color_fg, c);
_font_color_bg = c;
}
}
void font_color_fg(const Color c) final
{
if (c != _font_color_fg) {
_handle_font_color_change(c, _font_color_fg);
_font_color_fg = c;
}
}
protected:
std::shared_ptr<Font> _font;
Color _font_color_bg = default_font_color_bg;
Color _font_color_fg = default_font_color_fg;
Font::RenderMode _font_render_mode {Font::RenderMode::SHADED};
// Called on font changes.
virtual void _handle_font_change(const std::shared_ptr<Font>&) = 0;
// Called on font color changes.
virtual void _handle_font_color_change(Color, Color) = 0;
};
}
#endif

View File

@ -1,6 +1,7 @@
#ifndef BWIDGETS_KEYBOARD_HANDLER #ifndef BWIDGETS_KEYBOARD_HANDLER_HPP
#define BWIDGETS_KEYBOARD_HANDLER #define BWIDGETS_KEYBOARD_HANDLER_HPP
#include <basic_widgets/w/feat/event_handler.hpp>
#include <basic_widgets/w/feat/focus_handler.hpp> #include <basic_widgets/w/feat/focus_handler.hpp>
struct SDL_KeyboardEvent; struct SDL_KeyboardEvent;
@ -8,26 +9,22 @@ struct SDL_TextInputEvent;
namespace bwidgets namespace bwidgets
{ {
class KeyboardHandler : public virtual FocusHandler class KeyboardHandler : public virtual EventHandler,
public virtual FocusHandler
{ {
protected: protected:
virtual void _handle_key(const SDL_KeyboardEvent&) = 0; using EventHandler::EventHandler;
virtual void _handle_text_input(const SDL_TextInputEvent&) = 0;
public: public:
virtual inline auto handle_keyboard(const SDL_KeyboardEvent& ev) // Disable keyboard event handling.
-> KeyboardHandler* final virtual void disable_keyboard_handler() = 0;
{ // Enable keyboard event handling.
if (_has_focus) _handle_key(ev); virtual void enable_keyboard_handler() = 0;
return this; // Set the handler for key press events.
} virtual void key_handler(std::function<void(const SDL_KeyboardEvent&)>) = 0;
// Set the handler for text input events.
virtual inline auto handle_keyboard(const SDL_TextInputEvent& ev) virtual void
-> KeyboardHandler* final text_input_handler(std::function<void(const SDL_TextInputEvent&)>) = 0;
{
if (_has_focus) _handle_text_input(ev);
return this;
}
}; };
} }

View File

@ -0,0 +1,31 @@
#ifndef BWIDGETS_KEYBOARD_HANDLER_IMPL_HPP
#define BWIDGETS_KEYBOARD_HANDLER_IMPL_HPP
#include <basic_widgets/w/feat/event_handler_impl.hpp>
#include <basic_widgets/w/feat/focus_handler_impl.hpp>
#include <basic_widgets/w/feat/keyboard_handler.hpp>
namespace bwidgets
{
class KeyboardHandlerImpl : public virtual KeyboardHandler,
public virtual EventHandlerImpl,
public virtual FocusHandlerImpl
{
std::function<void(const SDL_KeyboardEvent&)> _key_handler {[](auto) {}};
std::function<void(const SDL_TextInputEvent&)> _input_handler {[](auto) {}};
public:
void disable_keyboard_handler() override;
void enable_keyboard_handler() override;
void key_handler(decltype(_key_handler) handler) override
{
_key_handler = std::move(handler);
}
void text_input_handler(decltype(_input_handler) handler) override
{
_input_handler = std::move(handler);
}
};
}
#endif

View File

@ -1,8 +1,7 @@
#ifndef BWIDGETS_MOUSE_HANDLER_HPP #ifndef BWIDGETS_MOUSE_HANDLER_HPP
#define BWIDGETS_MOUSE_HANDLER_HPP #define BWIDGETS_MOUSE_HANDLER_HPP
#include <functional> #include <basic_widgets/w/feat/event_handler.hpp>
#include <basic_widgets/w/feat/focus_handler.hpp> #include <basic_widgets/w/feat/focus_handler.hpp>
struct SDL_MouseButtonEvent; struct SDL_MouseButtonEvent;
@ -10,27 +9,28 @@ struct SDL_MouseMotionEvent;
namespace bwidgets namespace bwidgets
{ {
class MouseHandler : public virtual FocusHandler class MouseHandler : public virtual FocusHandler,
public virtual EventHandler
{ {
protected: protected:
const SDL_Rect* _click_area = nullptr; using EventHandler::EventHandler;
bool _is_hovered = false;
bool _is_pushed = false;
virtual void _handle_mouse_button(const SDL_MouseButtonEvent&,
const SDL_Rect&) {};
virtual void _handle_mouse_motion(const SDL_MouseMotionEvent&,
const SDL_Rect&) {};
virtual void _on_push(bool) {};
public: public:
std::function<void(const SDL_MouseButtonEvent&)> click_handler = nullptr; // Set click handler.
virtual void click_handler(std::function<void(const SDL_MouseButtonEvent&)>) = 0;
virtual auto handle_mouse(const SDL_MouseButtonEvent&, const SDL_Rect&) // Disable mouse event handling.
-> MouseHandler* final; virtual void disable_mouse_handler() = 0;
virtual auto handle_mouse(const SDL_MouseMotionEvent&, const SDL_Rect&) // Enable mouse event handling.
-> MouseHandler* final; virtual void enable_mouse_handler(const SDL_Rect&, const SDL_Rect&) = 0;
virtual auto push(bool) -> MouseHandler* final; // Called on hover state changes.
virtual void hover_handler(std::function<void(bool)>) = 0;
// Get current hover state.
[[nodiscard]] virtual bool hovered() const = 0;
// Set mouse motion handler.
virtual void
mouse_motion_handler(std::function<void(const SDL_MouseMotionEvent&)>) = 0;
// Get current push state (mouse button down)
[[nodiscard]] virtual bool pushed() const = 0;
}; };
} }

View File

@ -0,0 +1,59 @@
#ifndef BWIDGETS_MOUSE_HANDLER_IMPL_HPP
#define BWIDGETS_MOUSE_HANDLER_IMPL_HPP
#include <basic_widgets/w/feat/event_handler_impl.hpp>
#include <basic_widgets/w/feat/focus_handler_impl.hpp>
#include <basic_widgets/w/feat/mouse_handler.hpp>
namespace bwidgets
{
class MouseHandlerImpl : public virtual MouseHandler,
public virtual EventHandlerImpl,
public virtual FocusHandlerImpl
{
bool _hovered {false};
bool _pushed {false};
std::function<void(const SDL_MouseButtonEvent&)> _click_handler {[](auto) {}};
std::function<void(bool)> _hover_handler {[](auto) {}};
std::function<void(const SDL_MouseMotionEvent&)> _motion_handler {[](auto) {}};
public:
void click_handler(decltype(_click_handler) handler) override
{
_click_handler = std::move(handler);
}
void disable_mouse_handler() override;
void enable_mouse_handler(const SDL_Rect&, const SDL_Rect&) override;
[[nodiscard]] bool hovered() const override
{
return _hovered;
}
void hover_handler(decltype(_hover_handler) handler) override
{
_hover_handler = std::move(handler);
}
void mouse_motion_handler(decltype(_motion_handler) handler) override
{
_motion_handler = std::move(handler);
}
[[nodiscard]] bool pushed() const override
{
return _pushed;
}
protected:
using MouseHandler::MouseHandler;
// Called on push state changes.
virtual void _on_push(bool) {}
};
}
#endif

View File

@ -1,5 +1,5 @@
#ifndef BWIDGETS_TEXTURE_HANDLER #ifndef BWIDGETS_TEXTURE_HANDLER_HPP
#define BWIDGETS_TEXTURE_HANDLER #define BWIDGETS_TEXTURE_HANDLER_HPP
namespace bwidgets namespace bwidgets
{ {
@ -11,10 +11,17 @@ namespace bwidgets
class TextureHandler class TextureHandler
{ {
protected: protected:
TextureHandler() noexcept = default;
// (Re)render textures.
virtual void _handle_texture_update() = 0; virtual void _handle_texture_update() = 0;
public: public:
virtual ~TextureHandler() noexcept = default; TextureHandler(const TextureHandler&) = delete;
TextureHandler(TextureHandler&&) = delete;
virtual ~TextureHandler() noexcept = default;
auto operator=(const TextureHandler&) = delete;
auto operator=(TextureHandler&&) = delete;
}; };
} }

View File

@ -1,175 +1,26 @@
#ifndef BWIDGETS_NUMERIC_INPUT_HPP #ifndef BWIDGETS_NUMERIC_INPUT_HPP
#define BWIDGETS_NUMERIC_INPUT_HPP #define BWIDGETS_NUMERIC_INPUT_HPP
#include <limits>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/type/concepts.hpp> #include <basic_widgets/core/type/concepts.hpp>
#include <basic_widgets/w/base/input.hpp> #include <basic_widgets/w/base/input.hpp>
#include <basic_widgets/w/button.hpp>
#include <type_traits>
namespace bwidgets namespace bwidgets
{ {
template<Numeric T> template<Numeric T>
class NumericInput : public Input<T> class NumericInput : public virtual Input<T>
{ {
protected: protected:
Button _increment_button; using Input<T>::Input;
SDL_Rect _increment_button_area {};
Button _decrement_button;
SDL_Rect _decrement_button_area {};
std::pair<T, T> _value_range {std::numeric_limits<T>::lowest(),
std::numeric_limits<T>::max()};
void _handle_font_change(Font* f) override
{
_decrement_button.font(f);
_increment_button.font(f);
Input<T>::_handle_font_change(f);
}
void _handle_font_color_change(const Color& fg,
const Color& bg) override
{
Input<T>::_handle_font_color_change(fg, bg);
_decrement_button.font_color_bg(bg)->font_color_fg(fg);
_increment_button.font_color_bg(bg)->font_color_fg(fg);
}
void _handle_geometry_change(const SDL_Rect& vp) override
{
Input<T>::_handle_geometry_change(vp);
const int widest_button = _increment_button.size().w != 0
? _increment_button.size().w
: _decrement_button.size().w;
const int button_area_width = 2 * widest_button;
const int spacing = center_line(button_area_width, widest_button);
const int field_width = vp.w - 2 * button_area_width;
Widget::_widget_area.x = Widget::_widget_area.x + button_area_width;
Widget::_widget_area.w = field_width;
Input<T>::_input_caption.viewport(
rect_offset(rect_margin(Widget::_widget_area,
{Input<T>::border_width, Input<T>::border_width}),
vp));
_decrement_button_area = {spacing, Widget::_widget_area.y,
_decrement_button.size().w,
_decrement_button.size().h};
_increment_button_area = {vp.w - spacing - _increment_button.size().w,
Widget::_widget_area.y, _increment_button.size().w,
_increment_button.size().h};
_increment_button.viewport(rect_offset(_increment_button_area, vp));
_decrement_button.viewport(rect_offset(_decrement_button_area, vp));
}
void _handle_renderer_change(Renderer* r) override
{
Input<T>::_handle_renderer_change(r);
_decrement_button.renderer(r);
_increment_button.renderer(r);
}
void _handle_rendering() override
{
Input<T>::_handle_rendering();
_increment_button.render();
_decrement_button.render();
}
public: public:
// Button increment/decrement step.
T button_step = 1; T button_step = 1;
NumericInput(Widget* parent = nullptr) // Get allowed value range.
: Input<T>(parent), _increment_button(this), _decrement_button(this) [[nodiscard]] virtual auto value_range() const noexcept
{ -> const std::pair<T, T>& = 0;
Input<T>::_input_caption.alignment = Caption::Alignment::RIGHT; // Set allowed value range.
virtual void value_range(T min, T max) = 0;
Input<T>::input_min_width = 10; // NOLINT(readability-magic-numbers)
Input<T>::input_width_unit = '0';
_increment_button.text("+");
_increment_button.click_handler = [this](const SDL_MouseButtonEvent&) {
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&) {
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));
};
}
auto handle_event(const SDL_Event& ev) -> Widget* override
{
Input<T>::handle_event(ev);
_increment_button.handle_event(ev);
_decrement_button.handle_event(ev);
return this;
}
[[nodiscard]] auto is_valid_input(const std::string& input) const noexcept
-> bool override
{
bool valid = false;
if (input[0] >= '0' && input[0] <= '9') valid = true;
if constexpr (std::is_floating_point_v<T>)
if (input[0] == '.'
&& Input<T>::input_text().find('.') == std::string::npos)
valid = true;
if constexpr (std::is_signed_v<T>) {
std::string displayed_value = Input<T>::input_text();
if (input[0] == '-' && displayed_value.empty()) valid = true;
}
return valid;
}
[[nodiscard]] auto process_value(T x) const noexcept -> T override
{
T value = x;
if (x < _value_range.first) value = _value_range.first;
else if (x > _value_range.second) value = _value_range.second;
return value;
}
[[nodiscard]] inline auto size() const noexcept -> Size override
{
const auto base = Input<T>::size();
const auto btns_width = 4 * _increment_button.size().w;
return {base.w + btns_width, base.h};
}
[[nodiscard]] virtual auto value_range() const noexcept -> const std::pair<T, T>&
{
return _value_range;
}
virtual auto value_range(T min, T max) -> NumericInput<T>*
{
if (min > max) throw std::invalid_argument("min cannot be greater than max");
_value_range = {min, max};
Input<T>::value = process_value(Input<T>::value);
Input<T>::input_text(Input<T>::value_to_string(Input<T>::value));
return this;
}
}; };
} }

View File

@ -0,0 +1,196 @@
#ifndef BWIDGETS_NUMERIC_INPUT_IMPL_HPP
#define BWIDGETS_NUMERIC_INPUT_IMPL_HPP
#include <basic_widgets/w/base/input_impl.hpp>
#include <basic_widgets/w/numeric_input.hpp>
namespace bwidgets
{
template<Numeric T>
class NumericInputImpl : public virtual NumericInput<T>,
public virtual InputImpl<T>
{
using Self = NumericInputImpl;
public:
NumericInputImpl(Widget* parent = nullptr)
: InputImpl<T>(parent),
_increment_button(create_button(this)),
_decrement_button(create_button(this))
{
Self::_input_caption->alignment = Caption::Alignment::RIGHT;
Self::input_min_width = 10; // NOLINT(readability-magic-numbers)
Self::input_width_unit = '0';
// +/- 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);
if (_new_value < this->value) {
// if value - button_step is out of allowed range
// set value to the smallest allowed value.
if (std::abs(_value_range.first - this->value)
< std::abs(NumericInput<T>::button_step))
return _value_range.first;
return _new_value;
}
// if value + button_step exceeds maximal allowed value
// set value to max allowed value.
if (std::abs(_value_range.second - this->value)
< std::abs(NumericInput<T>::button_step))
return _value_range.second;
return _new_value;
}();
this->value = new_value;
this->input_text(this->value_to_string(new_value));
};
_increment_button->text("+");
_increment_button->click_handler(
[button_action](const SDL_MouseButtonEvent&) {
button_action([](const T a, const T b) { return a + b; });
});
_decrement_button->text("-");
_decrement_button->click_handler(
[button_action](const SDL_MouseButtonEvent&) {
button_action([](const T a, const T b) { return a - b; });
});
}
void handle_event(const SDL_Event& ev) override
{
InputImpl<T>::handle_event(ev);
_increment_button->handle_event(ev);
_decrement_button->handle_event(ev);
}
[[nodiscard]] auto is_valid_input(const std::string_view input) const noexcept
-> bool override
{
bool valid = [input, this]() {
// Allow 0-9 chars.
if (input[0] >= '0' && input[0] <= '9') return true;
// Allow one point for float types.
if constexpr (std::is_floating_point_v<T>)
if (input[0] == '.'
&& Self::input_text().find('.') == std::string::npos)
return true;
// 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;
}
return false;
}();
return valid;
}
[[nodiscard]] auto process_value(const T x) const noexcept -> T override
{
// If value is out of allowed range return either min or max allowed
// value.
T value = [x, this]() {
if (x < _value_range.first) return _value_range.first;
if (x > _value_range.second) return _value_range.second;
return x;
}();
return value;
}
[[nodiscard]] auto size() const noexcept -> Size override
{
const auto base = InputImpl<T>::size();
const auto btns_width = 4 * _increment_button->size().w;
return {base.w + btns_width, base.h};
}
[[nodiscard]] auto value_range() const noexcept
-> const std::pair<T, T>& override
{
return _value_range;
}
void value_range(const T min, const T max) override
{
if (min > max) throw std::invalid_argument("min cannot be greater than max");
_value_range = {min, max};
Self::value = process_value(Self::value);
Self::input_text(Self::value_to_string(Self::value));
}
protected:
std::unique_ptr<Button> _increment_button;
SDL_Rect _increment_button_area {};
std::unique_ptr<Button> _decrement_button;
SDL_Rect _decrement_button_area {};
std::pair<T, T> _value_range {std::numeric_limits<T>::lowest(),
std::numeric_limits<T>::max()};
void _handle_font_change(const std::shared_ptr<Font>& f) override
{
_decrement_button->font(f);
_increment_button->font(f);
InputImpl<T>::_handle_font_change(f);
}
void _handle_font_color_change(const Color fg, const Color bg) override
{
InputImpl<T>::_handle_font_color_change(fg, bg);
_decrement_button->font_color_bg(bg);
_decrement_button->font_color_fg(fg);
_increment_button->font_color_bg(bg);
_increment_button->font_color_fg(fg);
}
void _handle_geometry_change(const SDL_Rect& vp) override
{
InputImpl<T>::_handle_geometry_change(vp);
const int widest_button = _increment_button->size().w != 0
? _increment_button->size().w
: _decrement_button->size().w;
const int button_area_width = 2 * widest_button;
const int spacing = center_line(button_area_width, widest_button);
const int field_width = vp.w - 2 * button_area_width;
Self::_widget_area.x = Self::_widget_area.x + button_area_width;
Self::_widget_area.w = field_width;
Self::_input_caption->viewport(rect_offset(
rect_margin(Self::_widget_area, {Self::border_width, Self::border_width}),
vp));
_decrement_button_area = {spacing, Self::_widget_area.y,
_decrement_button->size().w,
_decrement_button->size().h};
_increment_button_area = {vp.w - spacing - _increment_button->size().w,
Self::_widget_area.y, _increment_button->size().w,
_increment_button->size().h};
_increment_button->viewport(rect_offset(_increment_button_area, vp));
_decrement_button->viewport(rect_offset(_decrement_button_area, vp));
}
void _handle_renderer_change(const std::shared_ptr<Renderer>& r) override
{
InputImpl<T>::_handle_renderer_change(r);
_decrement_button->renderer(r);
_increment_button->renderer(r);
}
void _handle_rendering() override
{
InputImpl<T>::_handle_rendering();
_increment_button->render();
_decrement_button->render();
}
};
}
#endif

View File

@ -0,0 +1,19 @@
#ifndef BWIDGETS_WIDGET_FACTORY_HPP
#define BWIDGETS_WIDGET_FACTORY_HPP
#include <basic_widgets/w/aligned_layout.hpp>
#include <basic_widgets/w/button.hpp>
#include <basic_widgets/w/caption.hpp>
#include <basic_widgets/w/numeric_input.hpp>
namespace bwidgets
{
auto create_button(Widget* p = nullptr) -> std::unique_ptr<Button>;
auto create_caption(Widget* p = nullptr) -> std::unique_ptr<Caption>;
auto create_horizontal_layout(Widget* p = nullptr) -> std::unique_ptr<AlignedLayout>;
auto create_input_float(Widget* p = nullptr) -> std::unique_ptr<NumericInput<float>>;
auto create_input_int(Widget* p = nullptr) -> std::unique_ptr<NumericInput<int>>;
auto create_vertical_layout(Widget* p = nullptr) -> std::unique_ptr<AlignedLayout>;
}
#endif

View File

@ -1,16 +1,19 @@
project('sdl2_basic_widgets', 'cpp', project('sdl2_basic_widgets', 'cpp',
version : '0.1pre', version : '0.9.0',
default_options : [ default_options : [
'b_lundef=false', 'b_lundef=false',
'b_sanitize=address,undefined', 'b_sanitize=undefined',
'cpp_std=c++20', 'cpp_std=c++20',
'd_ndebug=if-release',
'optimization=g', 'optimization=g',
'warning_level=3', 'warning_level=3',
], ],
license: 'EUPL-1.2') license: 'EUPL-1.2')
add_project_arguments('-pedantic', '-Winline', language: 'cpp') add_project_arguments('-Wconversion', '-pedantic', language: 'cpp')
if (get_option('buildtype').startswith('debug'))
add_project_arguments('-DBWIDGETS_DEBUG', language: 'cpp')
endif
sdl = [ sdl = [
dependency('sdl2', version: '>=2.0.5'), dependency('sdl2', version: '>=2.0.5'),
@ -25,12 +28,14 @@ libbasic_widgets = static_library('basic_widgets',
'src/core/font.cpp', 'src/core/font.cpp',
'src/core/renderer.cpp', 'src/core/renderer.cpp',
'src/core/texture.cpp', 'src/core/texture.cpp',
'src/w/aligned_layout.cpp', 'src/w/base/layout_impl.cpp',
'src/w/base/layout.cpp', 'src/w/base/widget_impl.cpp',
'src/w/base/widget.cpp', 'src/w/button_impl.cpp',
'src/w/button.cpp', 'src/w/caption_impl.cpp',
'src/w/caption.cpp', 'src/w/feat/event_handler_impl.cpp',
'src/w/feat/mouse_handler.cpp', 'src/w/feat/keyboard_handler_impl.cpp',
'src/w/feat/mouse_handler_impl.cpp',
'src/w/widget_factory.cpp',
dependencies : [sdl, fontconfig], dependencies : [sdl, fontconfig],
include_directories : pub_api, include_directories : pub_api,
install : true) install : true)
@ -42,24 +47,28 @@ libbasic_widgets_dep = declare_dependency(
executable('button_demo', executable('button_demo',
'examples/button_example.cpp', 'examples/button_example.cpp',
dependencies: [sdl],
include_directories : pub_api, include_directories : pub_api,
link_with : libbasic_widgets, link_with : libbasic_widgets,
install : false) install : false)
executable('caption_demo', executable('caption_demo',
'examples/caption_example.cpp', 'examples/caption_example.cpp',
dependencies: [sdl],
include_directories : pub_api, include_directories : pub_api,
link_with : libbasic_widgets, link_with : libbasic_widgets,
install : false) install : false)
executable('example_demo', executable('example_demo',
'examples/example_example.cpp', 'examples/example_example.cpp',
dependencies: [sdl],
include_directories : pub_api, include_directories : pub_api,
link_with : libbasic_widgets, link_with : libbasic_widgets,
install : false) install : false)
executable('input_demo', executable('input_demo',
'examples/input_example.cpp', 'examples/input_example.cpp',
dependencies: [sdl],
include_directories : pub_api, include_directories : pub_api,
link_with : libbasic_widgets, link_with : libbasic_widgets,
install : false) install : false)

View File

@ -2,7 +2,7 @@
#include <cmath> #include <cmath>
#include <vector> #include <vector>
#include <SDL2/SDL_render.h> #include <SDL_render.h>
#include <basic_widgets/core/draw.hpp> #include <basic_widgets/core/draw.hpp>
#include <basic_widgets/core/math.hpp> #include <basic_widgets/core/math.hpp>
@ -10,26 +10,27 @@
namespace bwidgets namespace bwidgets
{ {
auto aa(const Color& base_color, const int aa_pixels, const float d) noexcept auto aa(const Color base_color, const int aa_pixels, const float d) noexcept -> Color
-> Color
{ {
Color c(base_color); Color c = [aa_pixels, base_color, d]() -> Color {
if (aa_pixels == 0) { const auto [r, g, b, a] = base_color();
if (d > 0) c().a = 0; // AA disabled, returns fully opaque or fully transparent colors
} if (aa_pixels == 0) {
else { if (d > 0) return {r, g, b, 0};
return {r, g, b, a};
}
const auto factor = 1.0 - smoothstep<float>(d, (float)-aa_pixels, 0.); const auto factor = 1.0 - smoothstep<float>(d, (float)-aa_pixels, 0.);
c().a = (uint8_t)((float)c().a * factor); return {r, g, b, (uint8_t)((float)base_color().a * factor)};
} }();
return c; return c;
} }
auto filled_circle(const Color& c, const int resolution, Renderer* r, auto filled_circle(const Color c, const int resolution, const Renderer& r,
const int aa_pixels) -> Texture* const int aa_pixels) -> std::shared_ptr<Texture>
{ {
// clang-format off // clang-format off
auto* texture {new Texture( auto texture {std::make_shared<Texture>(
r, r,
SDL_PIXELFORMAT_RGBA32, SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STATIC, SDL_TEXTUREACCESS_STATIC,
@ -39,16 +40,15 @@ namespace bwidgets
const auto radius {resolution / 2}; const auto radius {resolution / 2};
const SDL_Point center {radius, radius}; const SDL_Point center {radius, radius};
set_pixels_color( set_pixels_color(texture.get(),
texture, [aa_pixels, c, center, radius](
[aa_pixels, c, center, radius](const SDL_Point& p, SDL_Point p, const SDL_PixelFormat& format) -> uint32_t {
const SDL_PixelFormat* format) -> uint32_t { const auto d_delta = distance(center, p) - (float)radius;
const auto d_delta = distance(center, p) - (float)radius; const auto aa_color = aa(c, aa_pixels, d_delta);
const auto aa_color = aa(c, aa_pixels, d_delta);
return SDL_MapRGBA(format, aa_color().r, aa_color().g, aa_color().b, return SDL_MapRGBA(&format, aa_color().r, aa_color().g,
aa_color().a); aa_color().b, aa_color().a);
}); });
texture->blend_mode(SDL_BLENDMODE_BLEND); texture->blend_mode(SDL_BLENDMODE_BLEND);
texture->scale_mode(SDL_ScaleModeNearest); texture->scale_mode(SDL_ScaleModeNearest);
@ -56,21 +56,21 @@ namespace bwidgets
} }
void set_pixels_color( void set_pixels_color(
Texture* t, Texture& t,
const std::function<uint32_t(const SDL_Point&, const SDL_PixelFormat*)>& const std::function<uint32_t(SDL_Point, const SDL_PixelFormat&)>& pixel_color)
pixel_color)
{ {
auto attr = t->attributes(); auto attr = t.attributes();
auto pitch = attr.w * attr.format->BytesPerPixel; auto pitch = attr.w * attr.format->BytesPerPixel;
std::vector<uint32_t> pixels; std::vector<uint32_t> pixels;
pixels.reserve(attr.h * (std::vector<uint32_t>::size_type)pitch); pixels.reserve(attr.h * (std::vector<uint32_t>::size_type)pitch);
// TODO parallel algo
for (auto y = 0; y < attr.h; y++) { for (auto y = 0; y < attr.h; y++) {
for (auto x = 0; x < attr.w; x++) { for (auto x = 0; x < attr.w; x++) {
pixels.emplace_back(pixel_color({x, y}, attr.format)); pixels.emplace_back(pixel_color({x, y}, *attr.format));
} }
} }
t->update(nullptr, (const void*)pixels.data(), pitch); t.update(nullptr, (const void*)pixels.data(), pitch);
} }
} }

View File

@ -1,161 +1,145 @@
#include <functional> #include <functional>
#include <memory>
#include <stack> #include <stack>
#include <stdexcept>
#include <fontconfig/fontconfig.h> #include <fontconfig/fontconfig.h>
#include <basic_widgets/core/error_helper.hpp>
#include <basic_widgets/core/font.hpp> #include <basic_widgets/core/font.hpp>
#include <basic_widgets/core/type/fc_error.hpp> #include <basic_widgets/core/type/fc_error.hpp>
#include <basic_widgets/core/type/sdl_error.hpp>
namespace bwidgets using namespace bwidgets;
const Color Font::default_color_bg {255, 255, 255, SDL_ALPHA_OPAQUE};
const Color Font::default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
Font::Font(TTF_Font* f)
: _data {ptr_or_throw<SDLError>(f)},
ascent {TTF_FontAscent(f)},
descent {TTF_FontDescent(f)},
faces {TTF_FontFaces(f)},
family_name {TTF_FontFaceFamilyName(f)},
fixed_width {TTF_FontFaceIsFixedWidth(f) != 0},
height {TTF_FontHeight(f)},
line_skip {TTF_FontLineSkip(f)},
style_name {TTF_FontFaceStyleName(f)}
{}
Font::Font(const std::string_view file, const int size)
: Font {TTF_OpenFont(file.data(), size)}
{}
auto Font::hinting() const noexcept -> Font::Hinting
{ {
Font::Font(TTF_Font* f) return static_cast<Hinting>(TTF_GetFontHinting(_data.get()));
: OpaqueStruct(f), }
ascent(TTF_FontAscent(f)),
descent(TTF_FontDescent(f)),
faces(TTF_FontFaces(f)),
family_name(TTF_FontFaceFamilyName(f)),
fixed_width(TTF_FontFaceIsFixedWidth(f) != 0),
height(TTF_FontHeight(f)),
line_skip(TTF_FontLineSkip(f)),
style_name(TTF_FontFaceStyleName(f))
{}
Font::Font(const std::string& file, const int size) auto Font::hinting(const Font::Hinting h) noexcept -> Font*
: Font(ptr_or_throw<FCError>(TTF_OpenFont(file.c_str(), size), __FILE__, {
__FUNCTION__, __LINE__, "failed to open font")) TTF_SetFontHinting(_data.get(), static_cast<int>(h));
{} return this;
}
Font::~Font() noexcept auto Font::kerning() const noexcept -> bool
{ {
TTF_CloseFont(_c_pod); return TTF_GetFontKerning(_data.get()) != 0;
} }
auto Font::hinting() const noexcept -> Font::Hinting auto Font::kerning(const bool allowed) noexcept -> Font*
{ {
return (Hinting)TTF_GetFontHinting(_c_pod); TTF_SetFontKerning(_data.get(), static_cast<int>(allowed));
} return this;
}
auto Font::hinting(const Font::Hinting h) noexcept -> Font* auto Font::outline() const noexcept -> int
{ {
TTF_SetFontHinting(_c_pod, (int)h); return TTF_GetFontOutline(_data.get());
return this; }
}
auto Font::kerning() const noexcept -> bool auto Font::outline(const int size) noexcept -> Font*
{ {
return TTF_GetFontKerning(_c_pod) != 0; TTF_SetFontOutline(_data.get(), size);
} return this;
}
auto Font::kerning(const bool allowed) noexcept -> Font*
{
TTF_SetFontKerning(_c_pod, static_cast<int>(allowed));
return this;
}
auto Font::outline() const noexcept -> int
{
return TTF_GetFontOutline(_c_pod);
}
auto Font::outline(const int size) noexcept -> Font*
{
TTF_SetFontOutline(_c_pod, size);
return this;
}
auto Font::render(const RenderMode m, const std::string& str, const Color& fg,
const Color& bg) -> SDL_Surface*
{
std::function<SDL_Surface*()> renderer;
const char* c_str = str.empty() ? " " : str.c_str();
auto Font::render(const RenderMode m, const std::string_view str, const Color fg,
const Color bg) -> std::unique_ptr<SDL_Surface, Deleter>
{
const char* const c_str = str.empty() ? " " : str.data();
auto renderer = [bg, c_str, fg, m, this]() -> std::function<SDL_Surface*()> {
switch (m) { switch (m) {
case RenderMode::BLENDED: case RenderMode::BLENDED:
renderer = [&fg, c_str, this]() { return [fg, c_str, this]() {
return TTF_RenderUTF8_Blended(_c_pod, c_str, fg()); return TTF_RenderUTF8_Blended(_data.get(), c_str, fg());
}; };
break;
case RenderMode::SHADED: case RenderMode::SHADED:
renderer = [&bg, &fg, c_str, this]() { return [bg, fg, c_str, this]() {
return TTF_RenderUTF8_Shaded(_c_pod, c_str, fg(), bg()); return TTF_RenderUTF8_Shaded(_data.get(), c_str, fg(), bg());
}; };
break;
case RenderMode::SOLID: case RenderMode::SOLID:
renderer = [&fg, c_str, this]() { return [fg, c_str, this]() {
return TTF_RenderUTF8_Solid(_c_pod, c_str, fg()); return TTF_RenderUTF8_Solid(_data.get(), c_str, fg());
}; };
break; default:
throw std::logic_error("missing switch case.");
} }
}();
return ptr_or_throw<SDLError>(renderer(), __FILE__, __FUNCTION__, __LINE__); return std::unique_ptr<SDL_Surface, Deleter>(ptr_or_throw<SDLError>(renderer()));
} }
auto Font::style() const noexcept -> uint8_t auto Font::style() const noexcept -> uint8_t
{ {
return TTF_GetFontStyle(_c_pod); return TTF_GetFontStyle(_data.get());
} }
auto Font::style(const uint8_t s) noexcept -> Font* auto Font::style(const uint8_t s) noexcept -> Font*
{ {
TTF_SetFontStyle(_c_pod, s); TTF_SetFontStyle(_data.get(), s);
return this; return this;
} }
auto Font::text_size(const std::string& str) const noexcept -> Size auto Font::text_size(const std::string_view str) const noexcept -> Size
{ {
Size s {}; Size s {};
TTF_SizeUTF8(_c_pod, str.c_str(), &s.w, &s.h); TTF_SizeUTF8(_data.get(), str.data(), &s.w, &s.h);
return s; return s;
} }
auto Font::find(const std::string& pat) -> std::string auto Font::find(const std::string_view pat) -> std::string
{ {
std::stack<std::function<void()>> cleaners; const auto conf = std::unique_ptr<FcConfig, Deleter>(
ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), "fontconfig init failed."));
FcConfig* conf = ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), __FILE__,
__FUNCTION__, __LINE__, "init failed"); const auto pattern = [&conf, pat]() {
cleaners.emplace([conf]() { FcConfigDestroy(conf); }); auto pattern = std::unique_ptr<FcPattern, Deleter>(ptr_or_throw<FCError>(
FcNameParse(reinterpret_cast<const FcChar8*>(pat.data())),
FcPattern* pattern = nullptr; "pattern parsing failed"));
try {
pattern = success_or_throw<FCError, FcBool>(
ptr_or_throw<FCError>(FcNameParse((FcChar8*)pat.c_str()), __FILE__, FcConfigSubstitute(conf.get(), pattern.get(), FcMatchPattern),
__FUNCTION__, __LINE__, "pattern parsing failed"); "FcConfigSubstitute failed", [](auto code) { return code == FcTrue; });
cleaners.emplace([pattern]() { FcPatternDestroy(pattern); }); return pattern;
}();
if (FcConfigSubstitute(conf, pattern, FcMatchPattern) == FcFalse)
throw FCError {__FILE__, __FUNCTION__, __LINE__, FcDefaultSubstitute(pattern.get());
"FcConfigSubstitute failed"};
} catch (const std::exception& e) { std::string file_path = [&conf, &pattern]() {
while (!cleaners.empty()) { FcResult res {};
cleaners.top()(); const auto font = std::unique_ptr<FcPattern, Deleter>(ptr_or_throw<FCError>(
cleaners.pop(); FcFontMatch(conf.get(), pattern.get(), &res), "no font found."));
}
throw e; if (FcChar8* file = nullptr;
} FcPatternGetString(font.get(), FC_FILE, 0, &file) == FcResultMatch)
{
FcDefaultSubstitute(pattern); // "I know what I'm doing"
// NOLINTNEXTLINE
std::string file_path; std::string _file_path {reinterpret_cast<char*>(file)};
FcResult res {}; return _file_path;
FcPattern* font = FcFontMatch(conf, pattern, &res); }
if (font != nullptr) { throw FCError("no font found.");
FcChar8* file = nullptr; }();
if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
file_path = (char*)file; return file_path;
}
FcPatternDestroy(font);
}
while (!cleaners.empty()) {
cleaners.top()();
cleaners.pop();
}
if (file_path.empty())
throw FCError {__FILE__, __FUNCTION__, __LINE__, "no font found"};
return file_path;
}
} }

View File

@ -3,47 +3,39 @@
using namespace bwidgets; using namespace bwidgets;
Renderer::Renderer(SDL_Renderer* r) : OpaqueStruct(r), info(_info(r)) {} Renderer::Renderer(SDL_Renderer* r) : _data {ptr_or_throw<SDLError>(r)}, info {_info(r)}
Renderer::Renderer(SDL_Window* w, const int index, const uint32_t flags = 0)
: Renderer(ptr_or_throw<SDLError>(SDL_CreateRenderer(w, index, flags), __FILE__,
__FUNCTION__, __LINE__))
{} {}
Renderer::~Renderer() noexcept Renderer::Renderer(SDL_Window* w, const int index, const uint32_t flags = 0)
{ : Renderer {SDL_CreateRenderer(w, index, flags)}
SDL_DestroyRenderer(_c_pod); {}
}
auto Renderer::blend_mode() -> SDL_BlendMode auto Renderer::blend_mode() -> SDL_BlendMode
{ {
SDL_BlendMode mode {}; SDL_BlendMode mode {};
SDLError::success_or_throw(SDL_GetRenderDrawBlendMode(_c_pod, &mode), __FILE__, success_or_throw<SDLError>(SDL_GetRenderDrawBlendMode(_data.get(), &mode));
__FUNCTION__, __LINE__);
return mode; return mode;
} }
auto Renderer::blend_mode(const SDL_BlendMode mode) -> Renderer* auto Renderer::blend_mode(const SDL_BlendMode mode) -> Renderer*
{ {
SDLError::success_or_throw(SDL_SetRenderDrawBlendMode(_c_pod, mode), __FILE__, success_or_throw<SDLError>(SDL_SetRenderDrawBlendMode(_data.get(), mode));
__FUNCTION__, __LINE__);
return this; return this;
} }
auto Renderer::clear() -> Renderer* auto Renderer::clear() -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderClear(_c_pod), __FILE__, __FUNCTION__, success_or_throw<SDLError>(SDL_RenderClear(_data.get()));
__LINE__);
return this; return this;
} }
auto Renderer::copy(Texture* t, const SDL_Rect* src, const SDL_Rect* dst) -> Renderer* auto Renderer::copy(const Texture& t, const SDL_Rect* const src,
const SDL_Rect* const dst) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderCopy(_c_pod, (*t)(), src, dst), __FILE__, success_or_throw<SDLError>(SDL_RenderCopy(_data.get(), t._data.get(), src, dst));
__FUNCTION__, __LINE__);
return this; return this;
} }
@ -51,55 +43,51 @@ auto Renderer::copy(Texture* t, const SDL_Rect* src, const SDL_Rect* dst) -> Ren
auto Renderer::draw_color() -> Color auto Renderer::draw_color() -> Color
{ {
Color c; Color c;
SDLError::success_or_throw( success_or_throw<SDLError>(
SDL_GetRenderDrawColor(_c_pod, &c().r, &c().g, &c().b, &c().a), __FILE__, SDL_GetRenderDrawColor(_data.get(), &c().r, &c().g, &c().b, &c().a));
__FUNCTION__, __LINE__);
return c; return c;
} }
auto Renderer::draw_color(const Color& c) -> Renderer* auto Renderer::draw_color(const Color c) -> Renderer*
{ {
SDLError::success_or_throw( success_or_throw<SDLError>(
SDL_SetRenderDrawColor(_c_pod, c().r, c().g, c().b, c().a), __FILE__, __FUNCTION__, SDL_SetRenderDrawColor(_data.get(), c().r, c().g, c().b, c().a));
__LINE__);
return this; return this;
} }
auto Renderer::draw_line(const SDL_Point& a, const SDL_Point& b) -> Renderer* auto Renderer::draw_line(const SDL_Point a, const SDL_Point b) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderDrawLine(_c_pod, a.x, a.y, b.x, b.y), __FILE__, success_or_throw<SDLError>(SDL_RenderDrawLine(_data.get(), a.x, a.y, b.x, b.y));
__FUNCTION__, __LINE__);
return this; return this;
} }
auto Renderer::draw_lines(const std::vector<SDL_Point>& pts) -> Renderer* auto Renderer::draw_lines(const std::span<SDL_Point> pts) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderDrawLines(_c_pod, pts.data(), (int)pts.size()), success_or_throw<SDLError>(
__FILE__, __FUNCTION__, __LINE__); SDL_RenderDrawLines(_data.get(), pts.data(), (int)pts.size()));
return this; return this;
} }
auto Renderer::draw_point(const SDL_Point& p) -> Renderer* auto Renderer::draw_point(const SDL_Point p) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderDrawPoint(_c_pod, p.x, p.y), __FILE__, success_or_throw<SDLError>(SDL_RenderDrawPoint(_data.get(), p.x, p.y));
__FUNCTION__, __LINE__);
return this; return this;
} }
auto Renderer::draw_points(const std::vector<SDL_Point>& pts) -> Renderer* auto Renderer::draw_points(const std::span<SDL_Point> pts) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderDrawPoints(_c_pod, pts.data(), (int)pts.size()), success_or_throw<SDLError>(
__FILE__, __FUNCTION__, __LINE__); SDL_RenderDrawPoints(_data.get(), pts.data(), (int)pts.size()));
return this; return this;
} }
auto Renderer::draw_rect(const SDL_Rect* r) -> Renderer* auto Renderer::draw_rect(const SDL_Rect* const r) -> Renderer*
{ {
auto vp = viewport(); auto vp = viewport();
// Has glitch at top-left and bottom-right corner. // Has glitch at top-left and bottom-right corner.
@ -107,37 +95,35 @@ auto Renderer::draw_rect(const SDL_Rect* r) -> Renderer*
// The second corner is missing a pixel. // The second corner is missing a pixel.
if (r != nullptr) if (r != nullptr)
viewport({vp.x + r->x, vp.y + r->y, r->w - 1, r->h - 1}); // crop extra pixel viewport({vp.x + r->x, vp.y + r->y, r->w - 1, r->h - 1}); // crop extra pixel
SDLError::success_or_throw(SDL_RenderDrawRect(_c_pod, nullptr), __FILE__, success_or_throw<SDLError>(SDL_RenderDrawRect(_data.get(), nullptr));
__FUNCTION__, __LINE__);
// add missing pixel. works sometimes… // add missing pixel. works sometimes…
if (r != nullptr) draw_point({r->w - 1, r->h - 1}); if (r != nullptr) draw_point({r->w - 1, r->h - 1});
else draw_point({vp.w - 1, vp.h - 1}); else draw_point({vp.w - 1, vp.h - 1});
viewport(vp); viewport(&vp);
return this; return this;
} }
auto Renderer::draw_rects(const std::vector<SDL_Rect>& rs) -> Renderer* auto Renderer::draw_rects(const std::span<SDL_Rect> rs) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderDrawRects(_c_pod, rs.data(), (int)rs.size()), success_or_throw<SDLError>(
__FILE__, __FUNCTION__, __LINE__); SDL_RenderDrawRects(_data.get(), rs.data(), (int)rs.size()));
return this; return this;
} }
auto Renderer::fill_rect(const SDL_Rect* r) -> Renderer* auto Renderer::fill_rect(const SDL_Rect* const r) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderFillRect(_c_pod, r), __FILE__, __FUNCTION__, success_or_throw<SDLError>(SDL_RenderFillRect(_data.get(), r));
__LINE__);
return this; return this;
} }
auto Renderer::fill_rects(const std::vector<SDL_Rect>& rs) -> Renderer* auto Renderer::fill_rects(const std::span<SDL_Rect> rs) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderFillRects(_c_pod, rs.data(), (int)rs.size()), success_or_throw<SDLError>(
__FILE__, __FUNCTION__, __LINE__); SDL_RenderFillRects(_data.get(), rs.data(), (int)rs.size()));
return this; return this;
} }
@ -145,29 +131,27 @@ auto Renderer::fill_rects(const std::vector<SDL_Rect>& rs) -> Renderer*
auto Renderer::output_size() -> Size auto Renderer::output_size() -> Size
{ {
Size s {}; Size s {};
SDLError::success_or_throw(SDL_GetRendererOutputSize(_c_pod, &s.w, &s.h), __FILE__, success_or_throw<SDLError>(SDL_GetRendererOutputSize(_data.get(), &s.w, &s.h));
__FUNCTION__, __LINE__);
return s; return s;
} }
void Renderer::present() noexcept void Renderer::present() noexcept
{ {
SDL_RenderPresent(_c_pod); SDL_RenderPresent(_data.get());
} }
auto Renderer::viewport() noexcept -> SDL_Rect auto Renderer::viewport() noexcept -> SDL_Rect
{ {
SDL_Rect vp; SDL_Rect vp;
SDL_RenderGetViewport(_c_pod, &vp); SDL_RenderGetViewport(_data.get(), &vp);
return vp; return vp;
} }
auto Renderer::viewport(const SDL_Rect* vp) -> Renderer* auto Renderer::viewport(const SDL_Rect* const vp) -> Renderer*
{ {
SDLError::success_or_throw(SDL_RenderSetViewport(_c_pod, vp), __FILE__, __FUNCTION__, success_or_throw<SDLError>(SDL_RenderSetViewport(_data.get(), vp));
__LINE__);
return this; return this;
} }

View File

@ -3,43 +3,36 @@
using namespace bwidgets; using namespace bwidgets;
Texture::Texture(SDL_Texture* t) : OpaqueStruct(t) Texture::Texture(SDL_Texture* t)
{ : _attributes(attributes(t)), _data {ptr_or_throw<SDLError>(t)}
_attributes = attributes(t);
}
Texture::Texture(Renderer* r, const SDL_PixelFormatEnum f, const SDL_TextureAccess a,
int w, int h)
: Texture(ptr_or_throw<SDLError>(SDL_CreateTexture((*r)(), f, a, w, h), __FILE__,
__FUNCTION__, __LINE__))
{} {}
Texture::Texture(Renderer* r, SDL_Surface* s) Texture::Texture(const Renderer& r, const SDL_PixelFormatEnum f,
: OpaqueStruct(ptr_or_throw<SDLError>(SDL_CreateTextureFromSurface((*r)(), s), const SDL_TextureAccess a, int w, int h)
__FILE__, __FUNCTION__, __LINE__)) : Texture {SDL_CreateTexture(r._data.get(), f, a, w, h)}
{ {}
_attributes = attributes(_c_pod);
} Texture::Texture(const Renderer& r, SDL_Surface* const s)
: Texture {SDL_CreateTextureFromSurface(r._data.get(), s)}
{}
Texture::~Texture() noexcept Texture::~Texture() noexcept
{ {
SDL_DestroyTexture(_c_pod); // Idk why but freeing the format in ~Attr gives memory errors (valgrind)
_c_pod = nullptr; SDL_FreeFormat(_attributes.format);
} }
auto Texture::alpha_mode() -> uint8_t auto Texture::alpha_mode() -> uint8_t
{ {
uint8_t mode = 0; uint8_t mode = 0;
SDLError::success_or_throw(SDL_GetTextureAlphaMod(_c_pod, &mode), __FILE__, success_or_throw<SDLError>(SDL_GetTextureAlphaMod(_data.get(), &mode));
__FUNCTION__, __LINE__);
return mode; return mode;
} }
auto Texture::alpha_mode(const uint8_t m) -> Texture* auto Texture::alpha_mode(const uint8_t m) -> Texture*
{ {
SDLError::success_or_throw(SDL_SetTextureAlphaMod(_c_pod, m), __FILE__, __FUNCTION__, success_or_throw<SDLError>(SDL_SetTextureAlphaMod(_data.get(), m));
__LINE__);
return this; return this;
} }
@ -47,16 +40,14 @@ auto Texture::alpha_mode(const uint8_t m) -> Texture*
auto Texture::blend_mode() -> SDL_BlendMode auto Texture::blend_mode() -> SDL_BlendMode
{ {
SDL_BlendMode mode {}; SDL_BlendMode mode {};
SDLError::success_or_throw(SDL_GetTextureBlendMode(_c_pod, &mode), __FILE__, success_or_throw<SDLError>(SDL_GetTextureBlendMode(_data.get(), &mode));
__FUNCTION__, __LINE__);
return mode; return mode;
} }
auto Texture::blend_mode(const SDL_BlendMode m) -> Texture* auto Texture::blend_mode(const SDL_BlendMode m) -> Texture*
{ {
SDLError::success_or_throw(SDL_SetTextureBlendMode(_c_pod, m), __FILE__, success_or_throw<SDLError>(SDL_SetTextureBlendMode(_data.get(), m));
__FUNCTION__, __LINE__);
return this; return this;
} }
@ -64,17 +55,15 @@ auto Texture::blend_mode(const SDL_BlendMode m) -> Texture*
auto Texture::color_mode() -> Color auto Texture::color_mode() -> Color
{ {
Color mode; Color mode;
SDLError::success_or_throw( success_or_throw<SDLError>(
SDL_GetTextureColorMod(_c_pod, &mode().r, &mode().g, &mode().b), __FILE__, SDL_GetTextureColorMod(_data.get(), &mode().r, &mode().g, &mode().b));
__FUNCTION__, __LINE__);
return mode; return mode;
} }
auto Texture::color_mode(const Color& m) -> Texture* auto Texture::color_mode(Color m) -> Texture*
{ {
SDLError::success_or_throw(SDL_SetTextureColorMod(_c_pod, m().r, m().g, m().b), success_or_throw<SDLError>(SDL_SetTextureColorMod(_data.get(), m().r, m().g, m().b));
__FILE__, __FUNCTION__, __LINE__);
return this; return this;
} }
@ -82,24 +71,22 @@ auto Texture::color_mode(const Color& m) -> Texture*
auto Texture::scale_mode() -> SDL_ScaleMode auto Texture::scale_mode() -> SDL_ScaleMode
{ {
SDL_ScaleMode mode {}; SDL_ScaleMode mode {};
SDLError::success_or_throw(SDL_GetTextureScaleMode(_c_pod, &mode), __FILE__, success_or_throw<SDLError>(SDL_GetTextureScaleMode(_data.get(), &mode));
__FUNCTION__, __LINE__);
return mode; return mode;
} }
auto Texture::scale_mode(const SDL_ScaleMode m) -> Texture* auto Texture::scale_mode(const SDL_ScaleMode m) -> Texture*
{ {
SDLError::success_or_throw(SDL_SetTextureScaleMode(_c_pod, m), __FILE__, success_or_throw<SDLError>(SDL_SetTextureScaleMode(_data.get(), m));
__FUNCTION__, __LINE__);
return this; return this;
} }
auto Texture::update(SDL_Rect* r, const void* pixels, const int pitch) -> Texture* auto Texture::update(const SDL_Rect* const r, const void* const pixels, const int pitch)
-> Texture*
{ {
SDLError::success_or_throw(SDL_UpdateTexture(_c_pod, r, pixels, pitch), __FILE__, success_or_throw<SDLError>(SDL_UpdateTexture(_data.get(), r, pixels, pitch));
__FUNCTION__, __LINE__);
return this; return this;
} }

View File

@ -1,57 +0,0 @@
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/aligned_layout.hpp>
using namespace bwidgets;
auto AlignedLayout::size() const noexcept -> Size
{
Size min_size {0, 0};
if (alignment == Alignment::HORIZONTAL) {
for (const auto* w : _widgets) {
if (w->size().w > min_size.w) min_size.w = w->size().w;
if (w->size().h > min_size.h) min_size.h = w->size().h;
}
return {(int)_widgets.size() * min_size.w
+ ((int)_widgets.size() + 1) * margins.w,
min_size.h + 2 * margins.h};
}
for (const auto* w : _widgets) {
if (w->size().w > min_size.w) min_size.w = w->size().w;
min_size.h += w->size().h;
}
return {min_size.w + 2 * margins.w,
min_size.h + ((int)_widgets.size() + 1) * margins.h};
}
void AlignedLayout::_update_layout(const SDL_Rect& vp)
{
if (alignment == Alignment::HORIZONTAL) {
int widget_width =
(vp.w - ((int)_widgets.size() + 1) * margins.w) / (int)_widgets.size();
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
auto* w = _widgets[i];
w->viewport({vp.x + margins.w + (int)i * (widget_width + margins.w),
vp.y + center_line(vp.h, w->size().h), widget_width,
w->size().h});
}
}
else {
int offset = 0;
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
auto* w = _widgets[i];
w->viewport({
vp.x,
vp.y + ((int)i + 1) * margins.h + offset,
vp.w,
w->size().h,
});
offset += w->size().h;
}
}
}

View File

@ -1,42 +0,0 @@
#include <basic_widgets/w/base/layout.hpp>
using namespace bwidgets;
Layout::~Layout()
{
for (auto* widget_ptr : _widgets) delete widget_ptr;
}
auto Layout::add_widget(Widget* widget_ptr) -> Layout*
{
widget_ptr->renderer(_renderer);
_widgets.emplace_back(widget_ptr);
_update_layout(_viewport);
return this;
}
void Layout::for_widgets(const std::function<void(Widget*)>& f)
{
for (auto* w : _widgets) f(w);
}
auto Layout::handle_event(const SDL_Event& ev) -> Layout*
{
for (auto* widget_ptr : _widgets) widget_ptr->handle_event(ev);
return this;
}
void Layout::_handle_geometry_change(const SDL_Rect& vp)
{
_update_layout(vp);
}
void Layout::_handle_renderer_change(Renderer* r)
{
for (auto* w : _widgets) w->renderer(r);
}
void Layout::_handle_rendering()
{
for (auto* widget_ptr : _widgets) widget_ptr->render();
}

View File

@ -0,0 +1,36 @@
#include <basic_widgets/w/base/layout_impl.hpp>
using namespace bwidgets;
void LayoutImpl::add_widget(std::unique_ptr<Widget> widget_ptr)
{
if (_renderer) widget_ptr->renderer(_renderer);
_widgets.emplace_back(std::move(widget_ptr));
_update_layout(_viewport);
}
void LayoutImpl::for_widgets(const std::function<void(Widget*)>& f)
{
for (const auto& w : _widgets) f(w.get());
}
void LayoutImpl::handle_event(const SDL_Event& ev)
{
EventHandlerImpl::handle_event(ev);
for (const auto& w : _widgets) w->handle_event(ev);
}
void LayoutImpl::_handle_geometry_change(const SDL_Rect& vp)
{
_update_layout(vp);
}
void LayoutImpl::_handle_renderer_change(const std::shared_ptr<Renderer>& r)
{
for (const auto& w : _widgets) w->renderer(r);
}
void LayoutImpl::_handle_rendering()
{
for (const auto& w : _widgets) w->render();
}

View File

@ -1,77 +0,0 @@
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <basic_widgets/w/base/widget.hpp>
#include <basic_widgets/w/feat/keyboard_handler.hpp>
#include <basic_widgets/w/feat/mouse_handler.hpp>
using namespace bwidgets;
auto Widget::handle_event(const SDL_Event& ev) -> Widget*
{
if (auto* handler = dynamic_cast<KeyboardHandler*>(this); handler) {
switch (ev.type) {
case SDL_KEYDOWN:
case SDL_KEYUP:
handler->handle_keyboard(ev.key);
break;
case SDL_TEXTINPUT:
handler->handle_keyboard(ev.text);
break;
}
}
if (auto* handler = dynamic_cast<MouseHandler*>(this); handler) {
switch (ev.type) {
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
handler->handle_mouse(ev.button, _viewport);
break;
case SDL_MOUSEMOTION:
handler->handle_mouse(ev.motion, _viewport);
break;
}
}
return this;
}
auto Widget::render() -> Widget*
{
if (_renderer == nullptr) return this;
#ifndef _NDEBUG
_renderer
->draw_color({
// NOLINTNEXTLINE(readability-magic-numbers)
{0, 255, 0, SDL_ALPHA_TRANSPARENT}
})
->draw_rect(nullptr);
#endif
_renderer->viewport(_viewport);
_handle_rendering();
return this;
}
auto Widget::renderer(Renderer* r) -> Widget*
{
if (r != _renderer) {
_handle_renderer_change(r);
_renderer = r;
}
return this;
}
auto Widget::viewport(const SDL_Rect& vp) -> Widget*
{
_handle_geometry_change(vp);
_viewport = vp;
return this;
}
auto Widget::viewport() const -> const SDL_Rect&
{
return _viewport;
}

View File

@ -0,0 +1,40 @@
#include <basic_widgets/w/base/widget_impl.hpp>
using namespace bwidgets;
void WidgetImpl::render()
{
if (!_renderer) return;
#ifdef BWIDGETS_DEBUG
// Render a debug outline of widget viewport.
_renderer
->draw_color({
// NOLINTNEXTLINE(readability-magic-numbers)
{0, 255, 0, SDL_ALPHA_OPAQUE}
})
->draw_rect(nullptr);
#endif
_renderer->viewport(&_viewport);
_handle_rendering();
}
void WidgetImpl::renderer(std::shared_ptr<Renderer> r)
{
if (r != _renderer) {
_handle_renderer_change(r);
_renderer = std::move(r);
}
}
void WidgetImpl::viewport(const SDL_Rect& vp)
{
_handle_geometry_change(vp);
_viewport = vp;
}
auto WidgetImpl::viewport() const -> const SDL_Rect&
{
return _viewport;
}

View File

@ -1,108 +0,0 @@
#include <cmath>
#include <SDL2/SDL_render.h>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/button.hpp>
using namespace bwidgets;
Button::Button(Widget* parent) noexcept : Widget(parent), _caption(this)
{
_focus_area = _click_area = &_widget_area;
_caption.alignment = Caption::Alignment::CENTER;
_caption.render_mode(Font::RenderMode::BLENDED);
border_gradient = [this](int len, int pos) -> Color {
const auto& end_color = _is_hovered ? color_bg_hover : color_bg;
const auto start_color = end_color / 2;
const auto factor = linear(pos, 0, len);
return lerp(start_color, end_color, factor);
};
border_gradient_pushed = [this](int len, int pos) -> Color {
const auto& end_color = _is_hovered ? color_bg_hover : color_bg;
const Color start_color = end_color / 1.5;
const auto factor = linear(pos, 0, len);
return lerp(start_color, end_color, factor);
};
}
auto Button::size() const noexcept -> Size
{
return _caption.size() + border_size * 2;
}
auto Button::text() const noexcept -> const std::string&
{
return _caption.text();
}
auto Button::text(const std::string& txt) -> Button*
{
_caption.text(txt);
_handle_geometry_change(_viewport);
return this;
}
void Button::_handle_font_change(Font* f)
{
_caption.font(f);
_handle_geometry_change(_viewport);
}
void Button::_handle_font_color_change(const Color& fg, const Color&)
{
_caption.font_color_fg(fg);
}
void Button::_handle_geometry_change(const SDL_Rect& vp)
{
const auto h = _caption.size().h + 2 * border_size.h;
_widget_area = {0, center_line(vp.h, _caption.size().h) - border_size.h, vp.w, h};
const auto txt_size = _caption.size();
_caption_area = {center_line(vp.w, txt_size.w), center_line(vp.h, txt_size.h),
txt_size.w, txt_size.h};
_caption.viewport(rect_offset(_caption_area, vp));
}
void Button::_handle_renderer_change(Renderer* r)
{
_caption.renderer(r);
}
void Button::_handle_rendering()
{
const Color& c = _is_hovered ? color_bg_hover : color_bg;
const auto& gradient = _is_pushed ? border_gradient_pushed : border_gradient;
auto x = 0;
auto y = 0;
const auto biggest = border_size.w > border_size.h ? border_size.w : border_size.h;
while (x < border_size.w || y < border_size.h) {
const auto max = x > y ? x : y;
const auto margin = Size({x, y});
_renderer->draw_color(gradient(biggest, max)())
->draw_rect(rect_margin(_widget_area, margin));
if (x < border_size.w) x++;
if (y < border_size.h) y++;
}
_renderer->draw_color(c())->fill_rect(rect_margin(_widget_area, border_size));
_caption.font_color_bg(c());
_caption.render();
}
void Button::_on_push(bool state)
{
SDL_Point offset {_viewport.x, _viewport.y};
if (state) {
offset.x += 1;
offset.y += 1;
}
_caption.viewport(rect_offset(_caption_area, offset));
}

102
src/w/button_impl.cpp Normal file
View File

@ -0,0 +1,102 @@
#include <cmath>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/button_impl.hpp>
#include <basic_widgets/w/widget_factory.hpp>
using namespace bwidgets;
ButtonImpl::ButtonImpl(Widget* parent) noexcept
: WidgetImpl {parent}, _caption {create_caption(this)}
{
enable_mouse_handler(_widget_area, _viewport);
_caption->alignment = Caption::Alignment::CENTER;
_caption->render_mode(Font::RenderMode::BLENDED);
border_gradient = [this](const int len, const int pos,
const float divider) -> Color {
const auto& end_color = hovered() ? color_bg_hover : color_bg;
return lerp(end_color / divider, end_color, linear(pos, 0, len));
};
}
auto ButtonImpl::size() const noexcept -> Size
{
return _caption->size() + border_size * 2;
}
auto ButtonImpl::text() const noexcept -> std::string_view
{
return _caption->text();
}
void ButtonImpl::text(std::string txt)
{
_caption->text(std::move(txt));
_handle_geometry_change(_viewport);
}
void ButtonImpl::_handle_font_change(const std::shared_ptr<Font>& f)
{
_caption->font(f);
_handle_geometry_change(_viewport);
}
void ButtonImpl::_handle_font_color_change(const Color fg, Color)
{
_caption->font_color_fg(fg);
}
void ButtonImpl::_handle_geometry_change(const SDL_Rect& vp)
{
const auto h = _caption->size().h + 2 * border_size.h;
_widget_area = {0, center_line(vp.h, _caption->size().h) - border_size.h, vp.w, h};
const auto txt_size = _caption->size();
_caption_area = {center_line(vp.w, txt_size.w), center_line(vp.h, txt_size.h),
txt_size.w, txt_size.h};
_caption->viewport(rect_offset(_caption_area, vp));
}
void ButtonImpl::_handle_renderer_change(const std::shared_ptr<Renderer>& r)
{
_caption->renderer(r);
}
void ButtonImpl::_handle_rendering()
{
const Color& c = hovered() ? color_bg_hover : color_bg;
const auto divider = pushed() ? 1.5F : 2.F;
auto x = 0;
auto y = 0;
const auto biggest_dimension =
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}));
if (x < border_size.w) x++;
if (y < border_size.h) y++;
}
_renderer->draw_color(c)->fill_rect(rect_margin(_widget_area, border_size));
_caption->font_color_bg(c);
_caption->render();
}
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};
}
return {_viewport.x, _viewport.y};
}();
_caption->viewport(rect_offset(_caption_area, offset));
}

View File

@ -1,121 +0,0 @@
#include <SDL2/SDL_assert.h>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/texture.hpp>
#include <basic_widgets/w/caption.hpp>
#include <basic_widgets/w/numeric_input.hpp>
using namespace bwidgets;
Caption::Caption(Widget* parent) noexcept : Widget(parent)
{
_font_color_bg = default_font_color_bg;
_font_color_fg = default_font_color_fg;
}
Caption::~Caption() noexcept
{
discard(_text_texture);
}
auto Caption::render_mode(Font::RenderMode m) -> Caption*
{
if (m != _render_mode) {
discard(_text_texture);
_render_mode = m;
}
return this;
}
auto Caption::size() const noexcept -> Size
{
if (_font == nullptr) return {0, 0};
const Size size = _font->text_size(_text);
return {size.w + 2 * margins.w, size.h + 2 * margins.h};
}
auto Caption::text() const noexcept -> const std::string&
{
return _text;
}
auto Caption::text(const std::string& t) -> Caption*
{
if (t != _text) {
discard(_text_texture);
_text = t;
}
return this;
}
void Caption::_handle_font_change(Font*)
{
discard(_text_texture);
}
void Caption::_handle_font_color_change(const Color& fg, const Color& bg)
{
if (fg != _font_color_fg
|| (bg != _font_color_bg && _font_render_mode == Font::RenderMode::SHADED))
{
discard(_text_texture);
_font_color_bg = bg;
}
}
void Caption::_handle_geometry_change(const SDL_Rect& vp)
{
if (vp.w != _viewport.w || vp.h != _viewport.h) {
_widget_area = rect_margin({0, 0, vp.w, vp.h}, margins);
}
}
void Caption::_handle_renderer_change(Renderer*)
{
discard(_text_texture);
}
void Caption::_handle_rendering()
{
if (_text_texture == nullptr) _handle_texture_update();
if (_render_mode == Font::RenderMode::SHADED) {
_renderer->draw_color(_font_color_bg)
->fill_rect({0, 0, _viewport.w, _viewport.h});
}
const Size size_dst {
(int)((float)_text_texture->attributes().w / (float)_text_texture->attributes().h
* (float)_widget_area.h),
_widget_area.h};
SDL_Rect texture_dst {margins.w, margins.h, size_dst.w, size_dst.h};
switch (alignment) {
case Alignment::CENTER:
texture_dst.x = center_line(_widget_area.w, texture_dst.w) + _widget_area.x;
break;
case Alignment::LEFT:
break;
case Alignment::RIGHT:
texture_dst.x = _widget_area.w - texture_dst.w - margins.w + _widget_area.x;
break;
}
_renderer->copy(_text_texture, nullptr, texture_dst);
}
void Caption::_handle_texture_update()
{
SDL_assert_release(_font != nullptr && _text_texture == nullptr); // NOLINT
SDL_Surface* s;
switch (_render_mode) {
case Font::RenderMode::SHADED:
s = _font->render(_render_mode, _text, _font_color_fg, _font_color_bg);
break;
default:
s = _font->render(_render_mode, _text, _font_color_fg);
break;
}
_text_texture = new Texture(_renderer, s);
SDL_FreeSurface(s);
}

124
src/w/caption_impl.cpp Normal file
View File

@ -0,0 +1,124 @@
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/texture.hpp>
#include <basic_widgets/w/caption_impl.hpp>
#include <basic_widgets/w/numeric_input.hpp>
using namespace bwidgets;
CaptionImpl::CaptionImpl(Widget* parent) noexcept : WidgetImpl {parent}
{
_font_color_bg = default_font_color_bg;
_font_color_fg = default_font_color_fg;
}
void CaptionImpl::render_mode(const Font::RenderMode m)
{
if (m != _render_mode) {
_text_texture.reset();
_render_mode = m;
}
}
auto CaptionImpl::size() const noexcept -> Size
{
if (!_font) return {0, 0};
const auto [w, h] = _font->text_size(_text);
return {w + 2 * margins.w, h + 2 * margins.h};
}
auto CaptionImpl::text() const noexcept -> std::string_view
{
return _text;
}
void CaptionImpl::text(std::string t)
{
if (t != _text) {
_text_texture.reset();
_text = std::move(t);
}
}
void CaptionImpl::_handle_font_change(const std::shared_ptr<Font>&)
{
_text_texture.reset();
}
void CaptionImpl::_handle_font_color_change(const Color fg, const Color bg)
{
if (fg != _font_color_fg) {
_text_texture.reset();
_font_color_fg = fg;
}
if (bg != _font_color_bg && _font_render_mode == Font::RenderMode::SHADED) {
_text_texture.reset();
_font_color_bg = fg;
}
}
void CaptionImpl::_handle_geometry_change(const SDL_Rect& vp)
{
if (vp.w != _viewport.w) {
_widget_area = rect_margin({0, 0, vp.w, vp.h}, margins);
}
else if (vp.h != _viewport.h) {
_widget_area = rect_margin({0, 0, vp.w, vp.h}, margins);
_text_texture.reset();
}
}
void CaptionImpl::_handle_renderer_change(const std::shared_ptr<Renderer>&)
{
_text_texture.reset();
}
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});
}
const Size size_dst {
(int)((float)_text_texture->attributes().w / (float)_text_texture->attributes().h
* (float)_widget_area.h),
_widget_area.h};
const auto texture_dst = [size_dst, this]() -> SDL_Rect {
switch (alignment) {
case Alignment::CENTER:
return {center_line(_widget_area.w, size_dst.w) + _widget_area.x,
margins.h, size_dst.w, size_dst.h};
case Alignment::LEFT:
return {margins.w, margins.h, size_dst.w, size_dst.h};
case Alignment::RIGHT:
return {_widget_area.w - size_dst.w - margins.w + _widget_area.x,
margins.h, size_dst.w, size_dst.h};
default:
throw std::logic_error("missing switch case.");
}
}();
_renderer->copy(*_text_texture, nullptr, texture_dst);
}
void CaptionImpl::_handle_texture_update()
{
if (!_font) return;
_text_texture.reset();
auto s = [this]() {
switch (_render_mode) {
case Font::RenderMode::SHADED:
return _font->render(_render_mode, _text, _font_color_fg,
_font_color_bg);
default:
return _font->render(_render_mode, _text, _font_color_fg);
}
}();
_text_texture = std::make_shared<Texture>(*_renderer, s.get());
}

View File

@ -0,0 +1,32 @@
#include <basic_widgets/w/feat/event_handler_impl.hpp>
using namespace bwidgets;
void EventHandlerImpl::handle_event(const SDL_Event& ev)
{
auto type = static_cast<SDL_EventType>(ev.type);
if (!_event_handlers.contains(type)) return;
_event_handlers[type](ev);
}
auto EventHandlerImpl::_add_event_handler(handler_t event_handler) -> bool
{
if (_event_handlers.contains(event_handler.first)) return false;
_event_handlers.emplace(std::move(event_handler));
return true;
}
auto EventHandlerImpl::_remove_event_handler(const SDL_EventType ev_type)
-> std::pair<handler_t, bool>
{
handler_t handler;
auto pos = _event_handlers.find(ev_type);
if (pos == _event_handlers.end()) return {handler, false};
handler = {ev_type, _event_handlers[ev_type]};
_event_handlers.erase(pos);
return {handler, true};
}

View File

@ -0,0 +1,23 @@
#include <basic_widgets/w/feat/keyboard_handler_impl.hpp>
using namespace bwidgets;
void KeyboardHandlerImpl::disable_keyboard_handler()
{
_remove_event_handler(SDL_KEYDOWN);
_remove_event_handler(SDL_KEYUP);
_remove_event_handler(SDL_TEXTINPUT);
}
void KeyboardHandlerImpl::enable_keyboard_handler()
{
const auto keyboard_handler = [this](const SDL_Event& ev) {
if (focus()) _key_handler(ev.key);
};
_add_event_handler({SDL_KEYDOWN, keyboard_handler});
_add_event_handler({SDL_KEYUP, keyboard_handler});
_add_event_handler({SDL_TEXTINPUT, [this](const SDL_Event& ev) {
if (focus()) _input_handler(ev.text);
}});
}

View File

@ -1,60 +0,0 @@
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_rect.h>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/feat/mouse_handler.hpp>
using namespace bwidgets;
auto MouseHandler::handle_mouse(const SDL_MouseButtonEvent& ev, const SDL_Rect& orig)
-> MouseHandler*
{
if (_click_area == nullptr) return this;
const SDL_Point p {ev.x, ev.y};
const SDL_Rect vp {rect_offset(*_click_area, orig)};
if (ev.type == SDL_MOUSEBUTTONDOWN) {
if (SDL_PointInRect(&p, &vp) == SDL_TRUE) {
push(true);
_handle_mouse_button(ev, vp);
}
else focus(false);
}
else {
if (_is_pushed) {
if (SDL_PointInRect(&p, &vp) == SDL_TRUE) {
focus(true);
if (click_handler != nullptr) click_handler(ev);
}
push(false);
_handle_mouse_button(ev, vp);
}
}
return this;
}
auto MouseHandler::handle_mouse(const SDL_MouseMotionEvent& ev, const SDL_Rect& orig)
-> MouseHandler*
{
if (_click_area == nullptr) return this;
const SDL_Point p {ev.x, ev.y};
const SDL_Rect vp {rect_offset(*_click_area, orig)};
_is_hovered = SDL_PointInRect(&p, &vp) != 0;
if (_is_hovered) _handle_mouse_motion(ev, orig);
return this;
}
auto MouseHandler::push(bool state) -> MouseHandler*
{
_on_push(state);
_is_pushed = state;
return this;
}

View File

@ -0,0 +1,48 @@
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
using namespace bwidgets;
void MouseHandlerImpl::disable_mouse_handler()
{
_remove_event_handler(SDL_MOUSEBUTTONDOWN);
_remove_event_handler(SDL_MOUSEBUTTONUP);
_remove_event_handler(SDL_MOUSEMOTION);
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void MouseHandlerImpl::enable_mouse_handler(const SDL_Rect& rel_area,
const SDL_Rect& origin)
{
const auto mouse_in_rect = [&rel_area, &origin](const SDL_Point p) {
const auto area = rect_offset(rel_area, origin);
return SDL_PointInRect(&p, &area) == SDL_TRUE;
};
_add_event_handler({SDL_MOUSEBUTTONDOWN, [this, mouse_in_rect](const SDL_Event& ev) {
if (!mouse_in_rect({ev.button.x, ev.button.y})) {
focus(false);
return;
}
_on_push(_pushed = true);
}});
_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);
}
}
}});
_add_event_handler({SDL_MOUSEMOTION, [this, mouse_in_rect](const SDL_Event& ev) {
if (!mouse_in_rect({ev.motion.x, ev.motion.y})) {
if (_hovered) _hover_handler(_hovered = false);
return;
}
if (!_hovered) _hover_handler(_hovered = true);
_motion_handler(ev.motion);
}});
}

37
src/w/widget_factory.cpp Normal file
View File

@ -0,0 +1,37 @@
#include <basic_widgets/w/aligned_layout_impl.hpp>
#include <basic_widgets/w/button_impl.hpp>
#include <basic_widgets/w/caption_impl.hpp>
#include <basic_widgets/w/numeric_input_impl.hpp>
#include <basic_widgets/w/widget_factory.hpp>
auto bwidgets::create_horizontal_layout(Widget* parent) -> std::unique_ptr<AlignedLayout>
{
return std::unique_ptr<AlignedLayout>(
new AlignedLayoutImpl<LayoutAlignment::HORIZONTAL>(parent));
}
auto bwidgets::create_vertical_layout(Widget* parent) -> std::unique_ptr<AlignedLayout>
{
return std::unique_ptr<AlignedLayout>(
new AlignedLayoutImpl<LayoutAlignment::VERTICAL>(parent));
}
auto bwidgets::create_button(Widget* parent) -> std::unique_ptr<Button>
{
return std::unique_ptr<Button>(new ButtonImpl(parent));
}
auto bwidgets::create_caption(Widget* parent) -> std::unique_ptr<Caption>
{
return std::unique_ptr<Caption>(new CaptionImpl(parent));
}
auto bwidgets::create_input_float(Widget* parent) -> std::unique_ptr<NumericInput<float>>
{
return std::unique_ptr<NumericInput<float>>(new NumericInputImpl<float>(parent));
}
auto bwidgets::create_input_int(Widget* parent) -> std::unique_ptr<NumericInput<int>>
{
return std::unique_ptr<NumericInput<int>>(new NumericInputImpl<int>(parent));
}