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
IncludeBlocks: Regroup
IncludeCategories:
- Regex: '<[[:alnum:].]+>'
Priority: -10
- Regex: '^<fontconfig/'
- Regex: '^<fontconfig'
Priority: -5
- Regex: '^<SDL2/'
- Regex: '^<SDL'
Priority: -5
- Regex: '^<basic_widgets/'
Priority: 0
- Regex: '<[[:alnum:]._]+>'
Priority: -10
- Regex: '^".*"$'
Priority: 5
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

123
README.md
View File

@ -1,4 +1,125 @@
# sdl2_basic_widgets
A (very) basic widget library for SDL applications.
A (very) basic and incomplete widget library for SDL applications.
## Build
The build dependencies are SDL2, SDL_ttf and fontconfig dev libs
and headers. Meson is used as build system, so you'll need it too.
As the code use some C++20 features, you'll need a recent compiler
which support that version of the standard.
Then, inside the project root directory, run the following commands:
```ShellSession
$ meson build
$ meson compile -C build -j0
```
After that you'll have in the build directory the static library and the
example programs.
## API overview
The library API is divided in two main parts:
- the core, which provides some basic wrapping of
SDL, SDL_ttf and fontconfig functionalities.
- the widget API built on top of the core.
### Core
The main classes of the core are:
- [Font](inc/basic_widgets/core/font.hpp):
wraps TTF_Font object with the most common operations
of SDL_ttf. Also provide a helper function to
search system fonts by using fontconfig.
- [Renderer](inc/basic_widgets/core/renderer.hpp):
wraps SDL_Renderer object with common operation from
the SDL 2D accelerated rendering API.
- [Texture](inc/basic_widgets/core/texture.hpp):
wraps SDL_Texture object with common operations
to manipulate it.
Apart from the SDL API wrappers, some generic utility functions and
data types are defined:
- [Color](inc/basic_widgets/core/type/color.hpp):
wraps SDL_Color object and adds convenience operators
overloads to operate on all color channels and/or
alpha channel.
- [draw.hpp](inc/basic_widgets/core/draw.hpp):
2D drawing utilities. Currently, provides a couple of basic utility
functions for software rendered textures: circle rendering
and an antialising algorithm.
- [math.hpp](inc/basic_widgets/core/math.hpp): mostly provides
frequently used formulas to work with rectangles and points
on a 2D coordinate system.
- [concepts.hpp](inc/basic_widgets/core/type/concepts.hpp):
a few concept definitions for used for generic programming.
- [Size](inc/basic_widgets/core/type/size.hpp): representation
of a 2D size. There are also a few operator overloads to
operate on Size objects.
## Widgets
Widgets are classes that implements the common widget interface and
optionally one or more "handler" interfaces. Handlers are interfaces
for event or external resources handling.
These interfaces are:
- [Widget](inc/basic_widgets/w/base/widget.hpp): the common base
interface of every widget.
- [EventHandler](inc/basic_widgets/w/feat/event_handler.hpp): base
interface for event handlers.
- [FocusHandler](inc/basic_widgets/w/feat/focus_handler.hpp): base
interface for handler having a focus notion.
- [KeyboardHandler](inc/basic_widgets/w/feat/keyboard_handler.hpp):
interface for key and text input events handling.
- [MouseHandler](inc/basic_widgets/w/feat/mouse_handler.hpp):
interface for mouse events handling. Handle click and focus state
changes and also introduce a notion of "pushed" state.
- [FontHandler](inc/basic_widgets/w/feat/font_handler.hpp): interface
for widgets using a font. Declares methods to set the widget font and
set background and text color used for text rendering.
- [TextureHandler](inc/basic_widgets/w/feat/texture_handler.hpp): it exists
but I'm thinking now that it shouldn't.
Based on these interfaces, there's implementation for a few basic widgets.
Currently, the following ones are defined:
- [Button](inc/basic_widgets/w/button.hpp): a push button.
- [Caption](inc/basic_widgets/w/caption.hpp): a caption
that displays a rendered string. No multiline support.
- [Input<T>](inc/basic_widgets/w/base/input.hpp): base
class for widgets using an input field.
- [NumericInput<Numeric>](inc/basic_widgets/w/numeric_input.hpp) an
input widget for numeric values with two buttons to increase/decrease
the hold value.
There are also "containers" widgets which are widget that hold a collection of
widgets and manage their geometry:
- [Layout](inc/basic_widgets/w/base/layout.hpp): abstract class for container widgets.
- [AlignedLayout](inc/basic_widgets/w/aligned_layout.hpp): layout that is done by distributing
widgets on a horizontal or vertical row.
To create widget objects, the corresponding function from [widget_factory.hpp](inc/basic_widgets/w/widget_factory.hpp)
should be used. It's done this way for interface-implementation sake.
## Source code organization overview
The header and source directory hierarchies is organized in the following way:
- core: C→C++ wrappers and commonly used generic functions.
- core/type: basic data types and concepts.
- w: the widget library
- w/base: the abstract widget classes.
- w/feat: handlers (optional widget features)
Widget code intent to separate public API from implementation details. For that
a Widget has two classes, a pure virtual one (Widget) which is the public API and another
class (WidgetImpl) that implements the interface of the first one. Unless an access
to implementation details is required, the interface (pure virtual) type should
be used.

View File

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

View File

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

View File

@ -3,9 +3,11 @@
auto main() -> int
{
run_example<Example>([](auto* w, auto*, auto x, auto y) {
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)
});
run_example<Example>(
[]() { return std::make_unique<Example>(); },
[](auto w, auto, auto x, auto y) {
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
#define BWIDGETS_EXAMPLES_EXAMPLE_WIDGET
#include <SDL2/SDL_timer.h>
#include <chrono>
#include <basic_widgets/core/math.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::rect_margin;
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
{
_widget_area = {2, 2, vp.w - 4, vp.h - 4};
@ -22,12 +21,13 @@ private:
void _handle_rendering() override
{
const auto now = SDL_GetTicks();
const uint8_t r = 255 * (now % cycle_r / (float)cycle_r); // NOLINT
const uint8_t g = 255 * (now % cycle_g / (float)cycle_g); // NOLINT
const uint8_t b = 255 * (now % cycle_b / (float)cycle_b); // NOLINT
// NOLINTNEXTLINE(readability-magic-numbers)
const Color base_color {r, g, b, 255};
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch())
.count();
const uint8_t r = 255 * (now % cycle_r / (float)cycle_r); // NOLINT
const uint8_t g = 255 * (now % cycle_g / (float)cycle_g); // NOLINT
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;
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_b {3500}; // NOLINT(readability-magic-numbers)
using WidgetImpl::WidgetImpl;
[[nodiscard]] auto size() const noexcept -> Size override
{
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"
using bwidgets::NumericInput;
auto main() -> int
{
run_example<NumericInput<float>>([](auto* w, auto* f, auto, auto) {
w->font(f);
w->button_step = 0.5; // NOLINT(readability-magic-numbers)
w->value_range(-3.14, 8.5); // NOLINT(readability-magic-numbers)
});
run_example<bwidgets::NumericInput<float>>(
[]() { return bwidgets::create_input_float(); },
[](auto w, auto f, auto, auto) {
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;
}

View File

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

View File

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

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
#define BWIDGETS_FONT_HPP
#include <memory>
#include <string>
#include <SDL2/SDL_ttf.h>
#include <SDL_ttf.h>
#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>
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
{
LIGHT = TTF_HINTING_LIGHT,
@ -37,8 +42,8 @@ namespace bwidgets
UNDERLINE = TTF_STYLE_UNDERLINE
};
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};
static const Color default_color_bg;
static const Color default_color_fg;
const int ascent;
const int descent;
@ -49,12 +54,15 @@ namespace bwidgets
const int line_skip;
const std::string style_name;
Font(TTF_Font*);
Font(const std::string&, int);
explicit Font(TTF_Font*);
// Load font at given path and with given size.
Font(std::string_view, int);
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;
auto hinting(Hinting) noexcept -> Font*;
@ -62,13 +70,15 @@ namespace bwidgets
auto kerning(bool) noexcept -> Font*;
[[nodiscard]] auto outline() const noexcept -> int;
auto outline(int) noexcept -> Font*;
auto render(RenderMode, const std::string&, const Color& fg = default_color_fg,
const Color& bg = default_color_bg) -> SDL_Surface*;
auto render(RenderMode, std::string_view, Color fg = default_color_fg,
Color bg = default_color_bg)
-> std::unique_ptr<SDL_Surface, Deleter>;
[[nodiscard]] auto style() const noexcept -> uint8_t;
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 <cmath>
#include <SDL2/SDL_rect.h>
#include <SDL_rect.h>
#include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/concepts.hpp>
#include <basic_widgets/core/type/size.hpp>
namespace bwidgets
{
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
[[nodiscard]] static inline auto center_line(int available_len, int used_len) noexcept -> int
// Get the start coordinate offset required to center a ligne with a length used_len on another
// 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;
}
[[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
{
// NOLINTNEXTLINE(bugprone-narrowing-conversions)
return (a.x - b.x) * (a.x - b.x)
+ (a.y - b.y) * (a.y - b.y);
return float(a.x - b.x) * float(a.x - b.x)
+ float(a.y - b.y) * float(a.y - b.y);
}
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
[[nodiscard]] static inline auto distance(const SDL_Point& a, const SDL_Point& b) noexcept -> float
// Get distance of two points on a 2D plan.
[[nodiscard]] inline auto distance(const SDL_Point a, const SDL_Point b) noexcept -> float
{
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>
[[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 {{
op_color ? (uint8_t)std::lerp(a().r, b().r, x) : a().r,
@ -43,45 +46,48 @@ namespace bwidgets
}};
}
// Reverse lerp.
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);
}
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
[[nodiscard]] static inline auto rect_in_rect(const SDL_Rect& outer, const SDL_Rect& inner) noexcept
// Check if a rectangle is completly inside of another rectangle.
[[nodiscard]] inline auto rect_in_rect(const SDL_Rect& outer, const SDL_Rect& inner) noexcept
-> bool
{
SDL_Point top_left {inner.x, inner.y};
SDL_Point bottom_right {inner.x + inner.w, inner.y + inner.h};
const SDL_Point top_left {inner.x, inner.y};
const SDL_Point bottom_right {inner.x + inner.w, inner.y + inner.h};
return (SDL_PointInRect(&top_left, &outer) == SDL_TRUE)
&& (SDL_PointInRect(&bottom_right, &outer) == SDL_TRUE);
}
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
[[nodiscard]] static inline auto rect_margin(const SDL_Rect& r, const Size& margin) noexcept
// Get the rectangle inside r leaving margins of given size between inner and outer rectangle.
[[nodiscard]] inline auto rect_margin(const SDL_Rect& r, const Size margin) noexcept
-> SDL_Rect
{
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
{
return {r.x + offset.x, r.y + offset.y, r.w, r.h};
}
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
[[nodiscard]] static inline auto rect_offset(const SDL_Rect& r, const SDL_Rect& offset) noexcept
// Commodity function to use another rectangle as a size for the offset to be added to r.
[[nodiscard]] inline auto rect_offset(const SDL_Rect& r, const SDL_Rect& offset) noexcept
-> SDL_Rect
{
return rect_offset(r, SDL_Point {offset.x, offset.y});
}
// Standard smoothstep algorithm.
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);
return 3 * x_norm * x_norm - 2 * x_norm * x_norm * x_norm;

View File

@ -1,27 +1,31 @@
#ifndef BWIDGETS_RENDERER_HPP
#define BWIDGETS_RENDERER_HPP
#include <cstdint>
#include <vector>
#include <memory>
#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/opaque_struct.hpp>
#include <basic_widgets/core/type/sdl_error.hpp>
#include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/core/type/size.hpp>
namespace bwidgets
{
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;
SDLError::success_or_throw(SDL_GetRendererInfo(r, &info), __FILE__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_GetRendererInfo(r, &info));
return info;
}
@ -29,59 +33,61 @@ namespace bwidgets
public:
const SDL_RendererInfo info;
Renderer(SDL_Renderer*);
explicit Renderer(SDL_Renderer*);
Renderer(SDL_Window*, int, uint32_t);
Renderer(const Renderer&) = delete;
~Renderer() noexcept override;
Renderer(Renderer&&) = delete;
~Renderer() noexcept = default;
auto operator=(const Renderer&) = delete;
auto operator=(Renderer&&) = delete;
[[nodiscard]] auto blend_mode() -> SDL_BlendMode;
auto blend_mode(SDL_BlendMode) -> 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;
auto draw_color(const Color&) -> Renderer*;
auto draw_line(const SDL_Point&, const SDL_Point&) -> Renderer*;
auto draw_lines(const std::vector<SDL_Point>&) -> Renderer*;
auto draw_point(const SDL_Point&) -> Renderer*;
auto draw_points(const std::vector<SDL_Point>&) -> Renderer*;
auto draw_color(Color) -> Renderer*;
auto draw_line(SDL_Point, SDL_Point) -> Renderer*;
auto draw_lines(std::span<SDL_Point>) -> Renderer*;
auto draw_point(SDL_Point) -> Renderer*;
auto draw_points(std::span<SDL_Point>) -> 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_rects(const std::vector<SDL_Rect>&) -> Renderer*;
auto fill_rects(std::span<SDL_Rect>) -> Renderer*;
[[nodiscard]] auto output_size() -> Size;
void present() noexcept;
[[nodiscard]] auto viewport() noexcept -> SDL_Rect;
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;
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;
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 d = dst;
return copy(t, &s, &d);
}
inline auto* draw_rect(const SDL_Rect& r)
auto draw_rect(SDL_Rect&& r)
{
const auto rect = r;
return draw_rect(&rect);
}
inline auto* fill_rect(const SDL_Rect& r)
auto fill_rect(SDL_Rect&& r)
{
const auto rect = r;
return fill_rect(&rect);
}
inline auto* viewport(const SDL_Rect& vp)
auto viewport(SDL_Rect&& vp)
{
const auto v = vp;
return viewport(&v);

View File

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

View File

@ -1,136 +1,149 @@
#ifndef BWIDGETS_COLOR_HPP
#define BWIDGETS_COLOR_HPP
#include <cstdint>
#include <cmath>
#include <functional>
#include <stdexcept>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_pixels.h>
#include <SDL_pixels.h>
#include <basic_widgets/core/type/concepts.hpp>
namespace bwidgets
{
// Wrap SDL_Color to provide operator overloads.
class Color final
{
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;
}
[[nodiscard]] inline const auto& operator()() const noexcept
[[nodiscard]] const auto& operator()() const noexcept
{
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:
bool _operate_on_alpha;
bool _operate_on_colors;
template<Numeric N>
inline auto
_operate(const N& operand,
const std::function<N(const N&, const N&)>& operator_) const noexcept
auto _operate(const N operand, const std::function<N(N, N)>& operator_) const
{
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);
if (_operate_on_alpha) {
auto a = std::round(operator_(c().a, operand));
SDL_assert_release(a == (uint8_t)a); // NOLINT
const auto a = std::round(operator_(c().a, operand));
overunderflow_check(a);
c().a = (uint8_t)a;
}
if (_operate_on_colors) {
auto r = std::round(operator_(c().r, operand));
auto g = std::round(operator_(c().g, operand));
auto b = std::round(operator_(c().b, operand));
// NOLINTNEXTLINE
SDL_assert_release(r == (uint8_t)r && g == (uint8_t)g
&& b == (uint8_t)b);
const auto r = std::round(operator_(c().r, operand));
const auto g = std::round(operator_(c().g, operand));
const auto b = std::round(operator_(c().b, operand));
for (const auto x : {r, g, b}) overunderflow_check(x);
c().r = (uint8_t)r;
c().g = (uint8_t)g;
c().b = (uint8_t)b;
}
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_
#define BWIDGETS_CONCEPTS_HPP_
#ifndef BWIDGETS_CONCEPTS_HPP
#define BWIDGETS_CONCEPTS_HPP
#include <ostream>
#include <string>
#include <type_traits>
namespace bwidgets
{
// T can be converted to string with to_string.
template<typename T>
concept CanToString = requires(T value)
{
std::to_string(value);
};
// T is a floating point type.
template<typename T>
concept FloatingPoint = std::is_floating_point_v<T>;
// T can be any numeric type.
template<typename T>
concept Numeric = std::is_arithmetic_v<T>;
// T can be converted to string by using ostream.
template<typename T>
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
#define BWIDGETS_EXCEPTION_HPP
#include <any>
#include <exception>
#include <functional>
#include <map>
#include <stdexcept>
#include <basic_widgets/core/type/concepts.hpp>
namespace bwidgets
{
struct BaseException : std::exception
// Base type for custom runtime errors.
class BaseException : std::runtime_error
{
const char* file;
const char* func;
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)
{}
protected:
using runtime_error::runtime_error;
};
// T derives from BaseException.
template<typename T>
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

View File

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

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>
extern "C" {
auto SDL_GetError() -> const char*;
}
namespace bwidgets
{
// Custom exception type for SDL errors.
struct SDLError final : BaseException
{
SDLError(const char* file, const char* func, const int l,
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;
}
using BaseException::BaseException;
};
}

View File

@ -5,30 +5,37 @@
namespace bwidgets
{
// Represent size of a 2D surface.
struct Size
{
int w;
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

View File

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

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
#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/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/mouse_handler.hpp>
@ -20,104 +11,13 @@ namespace bwidgets
{
template<typename T>
class Input : public Widget,
public FontHandler,
public KeyboardHandler,
public MouseHandler
class Input : public virtual Widget,
public virtual FontHandler,
public virtual KeyboardHandler,
public virtual MouseHandler
{
protected:
Caption _input_caption;
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);
}
}
using Widget::Widget;
public:
static const int default_border_width = 3;
@ -128,96 +28,30 @@ namespace bwidgets
static const int default_min_width = 1;
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;
int float_precision = default_float_precision;
int input_min_width = default_min_width;
char input_width_unit = 'W';
char input_width_unit = 'W'; // char used as unit for text length computation.
T value {};
virtual auto color_fg(const Color& c) -> Input<T>*
{
_input_caption.font_color_fg(c);
return this;
}
// Get the current displayed string.
[[nodiscard]] virtual auto input_text() const -> std::string_view = 0;
// Set displayed string
virtual void input_text(std::string) = 0;
virtual void input_text_color(Color) = 0;
// Check if a character is allowed to be 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&
{
return _input_caption.text();
}
virtual auto input_text(const std::string& txt) -> Input<T>*
{
_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;
}
// Process a value for instance to modify it before saving it.
[[nodiscard]] virtual auto process_value(T) const noexcept -> T = 0;
// Get string representation to be shown of the stored value.
[[nodiscard]] virtual auto value_from_string(std::string_view) const noexcept
-> T = 0;
// 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;
};
}

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
#define BWIDGETS_LAYOUT_HPP
#include <functional>
#include <vector>
#include <basic_widgets/core/type/size.hpp>
#include <basic_widgets/w/base/widget.hpp>
namespace bwidgets
{
class Renderer;
}
namespace bwidgets
{
class Layout : public Widget
class Layout : public virtual Widget
{
protected:
std::vector<Widget*> _widgets;
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;
using Widget::Widget;
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;
~Layout() override;
auto handle_event(const SDL_Event&) -> Layout* override;
[[nodiscard]] auto size() const noexcept -> Size override = 0;
virtual auto add_widget(Widget*) -> Layout*;
virtual void for_widgets(const std::function<void(Widget*)>&);
// Add widget to the layout
virtual void add_widget(std::unique_ptr<Widget>) = 0;
// Apply a function to every layout widget.
virtual void for_widgets(const std::function<void(Widget*)>&) = 0;
};
}

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
#define BWIDGETS_WIDGET_HPP
#include <memory>
#include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/core/type/size.hpp>
#include <basic_widgets/w/feat/event_handler.hpp>
union SDL_Event;
@ -10,30 +13,26 @@ struct SDL_Renderer;
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:
// Parent widget for bidirectional message passing between widgets.
// Currently not used by this lib.
Widget* parent;
Widget(Widget* p = nullptr) noexcept : parent(p) {}
explicit Widget(Widget* p = nullptr) noexcept : parent {p} {}
virtual ~Widget() = default;
virtual auto handle_event(const SDL_Event&) -> Widget*;
[[nodiscard]] virtual auto size() const noexcept -> Size = 0;
virtual auto render() -> Widget* final;
virtual auto renderer(Renderer*) -> Widget* final;
virtual auto viewport(const SDL_Rect&) -> Widget* final;
[[nodiscard]] virtual auto viewport() const -> const SDL_Rect& final;
// Render widget on current renderer target.
virtual void render() = 0;
// Set widget renderer.
virtual void renderer(std::shared_ptr<Renderer>) = 0;
// Get prefered or minimal widget size.
[[nodiscard]] virtual auto size() const noexcept -> Size = 0;
// Set the viewport delimiting the widget rendering area and the origin
// 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
#define BWIDGETS_BUTTON_HPP
#include <functional>
#include <string>
#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>
namespace bwidgets
{
class Button : public Widget,
public FontHandler,
public MouseHandler
class Button : public virtual Widget,
public virtual FontHandler,
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:
inline static 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 {150, 150, 150, SDL_ALPHA_OPAQUE};
static inline const Color default_color_bg_hover {175, 175, 175,
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)> border_gradient_pushed;
Size border_size {3, 3};
Color color_bg = default_color_bg;
Color color_bg_hover = default_color_bg_hover;
std::function<Color(int, int, float)> border_gradient;
Size border_size {3, 3};
Color color_bg = default_color_bg;
Color color_bg_hover = default_color_bg_hover;
Button(Widget* parent = nullptr) noexcept;
using Widget::Widget;
[[nodiscard]] auto size() const noexcept -> Size override;
[[nodiscard]] virtual auto text() const noexcept -> const std::string&;
virtual auto text(const std::string&) -> Button*;
// Get button text (label).
[[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0;
// Set button text
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
#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/feat/font_handler.hpp>
#include <basic_widgets/w/feat/texture_handler.hpp>
@ -11,22 +9,10 @@ struct SDL_Surface;
namespace bwidgets
{
class Caption : public Widget,
public FontHandler,
public TextureHandler
class Caption : public virtual Widget,
public virtual FontHandler,
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:
enum struct Alignment
{
@ -34,22 +20,22 @@ namespace bwidgets
LEFT,
RIGHT
};
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 Size default_margins = {3, 3};
// Text horizontal alignment on the caption viewport.
Alignment alignment {Alignment::LEFT};
Size margins {3, 3};
Size margins {default_margins};
Caption(Widget* parent = nullptr) noexcept;
~Caption() noexcept override;
using Widget::Widget;
virtual auto render_mode(Font::RenderMode) -> Caption*;
[[nodiscard]] virtual auto text() const noexcept -> const std::string&;
virtual auto text(const std::string&) -> Caption*;
[[nodiscard]] auto size() const noexcept -> Size override;
// Set caption text rendering mode.
virtual void render_mode(Font::RenderMode) = 0;
// Get caption text.
[[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0;
// 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
#define BWIDGETS_FOCUS_HANDLER_HPP
#include <functional>
struct SDL_Rect;
namespace bwidgets
{
class FocusHandler
{
protected:
const SDL_Rect* _focus_area = nullptr;
bool _has_focus = false;
virtual void _handle_focus_change(bool) = 0;
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
{
_handle_focus_change(focus);
_has_focus = focus;
}
virtual ~FocusHandler() = default;
// set focus state.
virtual void focus(bool focus) = 0;
// 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
{
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:
inline static const Color default_font_color_bg {255, 255, 255,
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 inline auto font(Font* f) -> FontHandler* final
{
if (f != nullptr
&& (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;
}
return this;
}
// Set the used font.
virtual void font(std::shared_ptr<Font> f) = 0;
// Set background color used for shaded text rendering
// mode.
virtual void font_color_bg(Color c) = 0;
// Set foreground (glyphs) color.
virtual void font_color_fg(Color c) = 0;
virtual inline auto font_color_bg(const Color& c) -> FontHandler* final
{
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;
}
protected:
FontHandler() noexcept = default;
};
}

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

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
#define BWIDGETS_MOUSE_HANDLER_HPP
#include <functional>
#include <basic_widgets/w/feat/event_handler.hpp>
#include <basic_widgets/w/feat/focus_handler.hpp>
struct SDL_MouseButtonEvent;
@ -10,27 +9,28 @@ struct SDL_MouseMotionEvent;
namespace bwidgets
{
class MouseHandler : public virtual FocusHandler
class MouseHandler : public virtual FocusHandler,
public virtual EventHandler
{
protected:
const SDL_Rect* _click_area = nullptr;
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) {};
using EventHandler::EventHandler;
public:
std::function<void(const SDL_MouseButtonEvent&)> click_handler = nullptr;
virtual auto handle_mouse(const SDL_MouseButtonEvent&, const SDL_Rect&)
-> MouseHandler* final;
virtual auto handle_mouse(const SDL_MouseMotionEvent&, const SDL_Rect&)
-> MouseHandler* final;
virtual auto push(bool) -> MouseHandler* final;
// Set click handler.
virtual void click_handler(std::function<void(const SDL_MouseButtonEvent&)>) = 0;
// Disable mouse event handling.
virtual void disable_mouse_handler() = 0;
// Enable mouse event handling.
virtual void enable_mouse_handler(const SDL_Rect&, const SDL_Rect&) = 0;
// 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
#define BWIDGETS_TEXTURE_HANDLER
#ifndef BWIDGETS_TEXTURE_HANDLER_HPP
#define BWIDGETS_TEXTURE_HANDLER_HPP
namespace bwidgets
{
@ -11,10 +11,17 @@ namespace bwidgets
class TextureHandler
{
protected:
TextureHandler() noexcept = default;
// (Re)render textures.
virtual void _handle_texture_update() = 0;
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
#define BWIDGETS_NUMERIC_INPUT_HPP
#include <limits>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/type/concepts.hpp>
#include <basic_widgets/w/base/input.hpp>
#include <basic_widgets/w/button.hpp>
#include <type_traits>
namespace bwidgets
{
template<Numeric T>
class NumericInput : public Input<T>
class NumericInput : public virtual Input<T>
{
protected:
Button _increment_button;
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();
}
using Input<T>::Input;
public:
// Button increment/decrement step.
T button_step = 1;
NumericInput(Widget* parent = nullptr)
: Input<T>(parent), _increment_button(this), _decrement_button(this)
{
Input<T>::_input_caption.alignment = Caption::Alignment::RIGHT;
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;
}
// Get allowed value range.
[[nodiscard]] virtual auto value_range() const noexcept
-> const std::pair<T, T>& = 0;
// Set allowed value range.
virtual void value_range(T min, T max) = 0;
};
}

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',
version : '0.1pre',
version : '0.9.0',
default_options : [
'b_lundef=false',
'b_sanitize=address,undefined',
'b_sanitize=undefined',
'cpp_std=c++20',
'd_ndebug=if-release',
'optimization=g',
'warning_level=3',
],
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 = [
dependency('sdl2', version: '>=2.0.5'),
@ -25,12 +28,14 @@ libbasic_widgets = static_library('basic_widgets',
'src/core/font.cpp',
'src/core/renderer.cpp',
'src/core/texture.cpp',
'src/w/aligned_layout.cpp',
'src/w/base/layout.cpp',
'src/w/base/widget.cpp',
'src/w/button.cpp',
'src/w/caption.cpp',
'src/w/feat/mouse_handler.cpp',
'src/w/base/layout_impl.cpp',
'src/w/base/widget_impl.cpp',
'src/w/button_impl.cpp',
'src/w/caption_impl.cpp',
'src/w/feat/event_handler_impl.cpp',
'src/w/feat/keyboard_handler_impl.cpp',
'src/w/feat/mouse_handler_impl.cpp',
'src/w/widget_factory.cpp',
dependencies : [sdl, fontconfig],
include_directories : pub_api,
install : true)
@ -42,24 +47,28 @@ libbasic_widgets_dep = declare_dependency(
executable('button_demo',
'examples/button_example.cpp',
dependencies: [sdl],
include_directories : pub_api,
link_with : libbasic_widgets,
install : false)
executable('caption_demo',
'examples/caption_example.cpp',
dependencies: [sdl],
include_directories : pub_api,
link_with : libbasic_widgets,
install : false)
executable('example_demo',
'examples/example_example.cpp',
dependencies: [sdl],
include_directories : pub_api,
link_with : libbasic_widgets,
install : false)
executable('input_demo',
'examples/input_example.cpp',
dependencies: [sdl],
include_directories : pub_api,
link_with : libbasic_widgets,
install : false)

View File

@ -2,7 +2,7 @@
#include <cmath>
#include <vector>
#include <SDL2/SDL_render.h>
#include <SDL_render.h>
#include <basic_widgets/core/draw.hpp>
#include <basic_widgets/core/math.hpp>
@ -10,26 +10,27 @@
namespace bwidgets
{
auto aa(const Color& base_color, const int aa_pixels, const float d) noexcept
-> Color
auto aa(const Color base_color, const int aa_pixels, const float d) noexcept -> Color
{
Color c(base_color);
if (aa_pixels == 0) {
if (d > 0) c().a = 0;
}
else {
Color c = [aa_pixels, base_color, d]() -> Color {
const auto [r, g, b, a] = base_color();
// AA disabled, returns fully opaque or fully transparent colors
if (aa_pixels == 0) {
if (d > 0) return {r, g, b, 0};
return {r, g, b, a};
}
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;
}
auto filled_circle(const Color& c, const int resolution, Renderer* r,
const int aa_pixels) -> Texture*
auto filled_circle(const Color c, const int resolution, const Renderer& r,
const int aa_pixels) -> std::shared_ptr<Texture>
{
// clang-format off
auto* texture {new Texture(
auto texture {std::make_shared<Texture>(
r,
SDL_PIXELFORMAT_RGBA32,
SDL_TEXTUREACCESS_STATIC,
@ -39,16 +40,15 @@ namespace bwidgets
const auto radius {resolution / 2};
const SDL_Point center {radius, radius};
set_pixels_color(
texture,
[aa_pixels, c, center, radius](const SDL_Point& p,
const SDL_PixelFormat* format) -> uint32_t {
const auto d_delta = distance(center, p) - (float)radius;
const auto aa_color = aa(c, aa_pixels, d_delta);
set_pixels_color(texture.get(),
[aa_pixels, c, center, radius](
SDL_Point p, const SDL_PixelFormat& format) -> uint32_t {
const auto d_delta = distance(center, p) - (float)radius;
const auto aa_color = aa(c, aa_pixels, d_delta);
return SDL_MapRGBA(format, aa_color().r, aa_color().g, aa_color().b,
aa_color().a);
});
return SDL_MapRGBA(&format, aa_color().r, aa_color().g,
aa_color().b, aa_color().a);
});
texture->blend_mode(SDL_BLENDMODE_BLEND);
texture->scale_mode(SDL_ScaleModeNearest);
@ -56,21 +56,21 @@ namespace bwidgets
}
void set_pixels_color(
Texture* t,
const std::function<uint32_t(const SDL_Point&, const SDL_PixelFormat*)>&
pixel_color)
Texture& t,
const std::function<uint32_t(SDL_Point, const SDL_PixelFormat&)>& pixel_color)
{
auto attr = t->attributes();
auto attr = t.attributes();
auto pitch = attr.w * attr.format->BytesPerPixel;
std::vector<uint32_t> pixels;
pixels.reserve(attr.h * (std::vector<uint32_t>::size_type)pitch);
// TODO parallel algo
for (auto y = 0; y < attr.h; y++) {
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 <memory>
#include <stack>
#include <stdexcept>
#include <fontconfig/fontconfig.h>
#include <basic_widgets/core/error_helper.hpp>
#include <basic_widgets/core/font.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)
: 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))
{}
return static_cast<Hinting>(TTF_GetFontHinting(_data.get()));
}
Font::Font(const std::string& file, const int size)
: Font(ptr_or_throw<FCError>(TTF_OpenFont(file.c_str(), size), __FILE__,
__FUNCTION__, __LINE__, "failed to open font"))
{}
auto Font::hinting(const Font::Hinting h) noexcept -> Font*
{
TTF_SetFontHinting(_data.get(), static_cast<int>(h));
return this;
}
Font::~Font() noexcept
{
TTF_CloseFont(_c_pod);
}
auto Font::kerning() const noexcept -> bool
{
return TTF_GetFontKerning(_data.get()) != 0;
}
auto Font::hinting() const noexcept -> Font::Hinting
{
return (Hinting)TTF_GetFontHinting(_c_pod);
}
auto Font::kerning(const bool allowed) noexcept -> Font*
{
TTF_SetFontKerning(_data.get(), static_cast<int>(allowed));
return this;
}
auto Font::hinting(const Font::Hinting h) noexcept -> Font*
{
TTF_SetFontHinting(_c_pod, (int)h);
return this;
}
auto Font::outline() const noexcept -> int
{
return TTF_GetFontOutline(_data.get());
}
auto Font::kerning() const noexcept -> bool
{
return TTF_GetFontKerning(_c_pod) != 0;
}
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::outline(const int size) noexcept -> Font*
{
TTF_SetFontOutline(_data.get(), size);
return this;
}
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) {
case RenderMode::BLENDED:
renderer = [&fg, c_str, this]() {
return TTF_RenderUTF8_Blended(_c_pod, c_str, fg());
return [fg, c_str, this]() {
return TTF_RenderUTF8_Blended(_data.get(), c_str, fg());
};
break;
case RenderMode::SHADED:
renderer = [&bg, &fg, c_str, this]() {
return TTF_RenderUTF8_Shaded(_c_pod, c_str, fg(), bg());
return [bg, fg, c_str, this]() {
return TTF_RenderUTF8_Shaded(_data.get(), c_str, fg(), bg());
};
break;
case RenderMode::SOLID:
renderer = [&fg, c_str, this]() {
return TTF_RenderUTF8_Solid(_c_pod, c_str, fg());
return [fg, c_str, this]() {
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__);
}
auto Font::style() const noexcept -> uint8_t
{
return TTF_GetFontStyle(_c_pod);
}
auto Font::style(const uint8_t s) noexcept -> Font*
{
TTF_SetFontStyle(_c_pod, s);
return this;
}
auto Font::text_size(const std::string& str) const noexcept -> Size
{
Size s {};
TTF_SizeUTF8(_c_pod, str.c_str(), &s.w, &s.h);
return s;
}
auto Font::find(const std::string& pat) -> std::string
{
std::stack<std::function<void()>> cleaners;
FcConfig* conf = ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), __FILE__,
__FUNCTION__, __LINE__, "init failed");
cleaners.emplace([conf]() { FcConfigDestroy(conf); });
FcPattern* pattern = nullptr;
try {
pattern =
ptr_or_throw<FCError>(FcNameParse((FcChar8*)pat.c_str()), __FILE__,
__FUNCTION__, __LINE__, "pattern parsing failed");
cleaners.emplace([pattern]() { FcPatternDestroy(pattern); });
if (FcConfigSubstitute(conf, pattern, FcMatchPattern) == FcFalse)
throw FCError {__FILE__, __FUNCTION__, __LINE__,
"FcConfigSubstitute failed"};
} catch (const std::exception& e) {
while (!cleaners.empty()) {
cleaners.top()();
cleaners.pop();
}
throw e;
}
FcDefaultSubstitute(pattern);
std::string file_path;
FcResult res {};
FcPattern* font = FcFontMatch(conf, pattern, &res);
if (font != nullptr) {
FcChar8* file = nullptr;
if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
file_path = (char*)file;
}
FcPatternDestroy(font);
}
while (!cleaners.empty()) {
cleaners.top()();
cleaners.pop();
}
if (file_path.empty())
throw FCError {__FILE__, __FUNCTION__, __LINE__, "no font found"};
return file_path;
}
return std::unique_ptr<SDL_Surface, Deleter>(ptr_or_throw<SDLError>(renderer()));
}
auto Font::style() const noexcept -> uint8_t
{
return TTF_GetFontStyle(_data.get());
}
auto Font::style(const uint8_t s) noexcept -> Font*
{
TTF_SetFontStyle(_data.get(), s);
return this;
}
auto Font::text_size(const std::string_view str) const noexcept -> Size
{
Size s {};
TTF_SizeUTF8(_data.get(), str.data(), &s.w, &s.h);
return s;
}
auto Font::find(const std::string_view pat) -> std::string
{
const auto conf = std::unique_ptr<FcConfig, Deleter>(
ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), "fontconfig init failed."));
const auto pattern = [&conf, pat]() {
auto pattern = std::unique_ptr<FcPattern, Deleter>(ptr_or_throw<FCError>(
FcNameParse(reinterpret_cast<const FcChar8*>(pat.data())),
"pattern parsing failed"));
success_or_throw<FCError, FcBool>(
FcConfigSubstitute(conf.get(), pattern.get(), FcMatchPattern),
"FcConfigSubstitute failed", [](auto code) { return code == FcTrue; });
return pattern;
}();
FcDefaultSubstitute(pattern.get());
std::string file_path = [&conf, &pattern]() {
FcResult res {};
const auto font = std::unique_ptr<FcPattern, Deleter>(ptr_or_throw<FCError>(
FcFontMatch(conf.get(), pattern.get(), &res), "no font found."));
if (FcChar8* file = nullptr;
FcPatternGetString(font.get(), FC_FILE, 0, &file) == FcResultMatch)
{
// "I know what I'm doing"
// NOLINTNEXTLINE
std::string _file_path {reinterpret_cast<char*>(file)};
return _file_path;
}
throw FCError("no font found.");
}();
return file_path;
}

View File

@ -3,47 +3,39 @@
using namespace bwidgets;
Renderer::Renderer(SDL_Renderer* r) : OpaqueStruct(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(SDL_Renderer* r) : _data {ptr_or_throw<SDLError>(r)}, info {_info(r)}
{}
Renderer::~Renderer() noexcept
{
SDL_DestroyRenderer(_c_pod);
}
Renderer::Renderer(SDL_Window* w, const int index, const uint32_t flags = 0)
: Renderer {SDL_CreateRenderer(w, index, flags)}
{}
auto Renderer::blend_mode() -> SDL_BlendMode
{
SDL_BlendMode mode {};
SDLError::success_or_throw(SDL_GetRenderDrawBlendMode(_c_pod, &mode), __FILE__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_GetRenderDrawBlendMode(_data.get(), &mode));
return mode;
}
auto Renderer::blend_mode(const SDL_BlendMode mode) -> Renderer*
{
SDLError::success_or_throw(SDL_SetRenderDrawBlendMode(_c_pod, mode), __FILE__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_SetRenderDrawBlendMode(_data.get(), mode));
return this;
}
auto Renderer::clear() -> Renderer*
{
SDLError::success_or_throw(SDL_RenderClear(_c_pod), __FILE__, __FUNCTION__,
__LINE__);
success_or_throw<SDLError>(SDL_RenderClear(_data.get()));
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__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_RenderCopy(_data.get(), t._data.get(), src, dst));
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
{
Color c;
SDLError::success_or_throw(
SDL_GetRenderDrawColor(_c_pod, &c().r, &c().g, &c().b, &c().a), __FILE__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(
SDL_GetRenderDrawColor(_data.get(), &c().r, &c().g, &c().b, &c().a));
return c;
}
auto Renderer::draw_color(const Color& c) -> Renderer*
auto Renderer::draw_color(const Color c) -> Renderer*
{
SDLError::success_or_throw(
SDL_SetRenderDrawColor(_c_pod, c().r, c().g, c().b, c().a), __FILE__, __FUNCTION__,
__LINE__);
success_or_throw<SDLError>(
SDL_SetRenderDrawColor(_data.get(), c().r, c().g, c().b, c().a));
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__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_RenderDrawLine(_data.get(), a.x, a.y, b.x, b.y));
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()),
__FILE__, __FUNCTION__, __LINE__);
success_or_throw<SDLError>(
SDL_RenderDrawLines(_data.get(), pts.data(), (int)pts.size()));
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__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_RenderDrawPoint(_data.get(), p.x, p.y));
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()),
__FILE__, __FUNCTION__, __LINE__);
success_or_throw<SDLError>(
SDL_RenderDrawPoints(_data.get(), pts.data(), (int)pts.size()));
return this;
}
auto Renderer::draw_rect(const SDL_Rect* r) -> Renderer*
auto Renderer::draw_rect(const SDL_Rect* const r) -> Renderer*
{
auto vp = viewport();
// 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.
if (r != nullptr)
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__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_RenderDrawRect(_data.get(), nullptr));
// add missing pixel. works sometimes…
if (r != nullptr) draw_point({r->w - 1, r->h - 1});
else draw_point({vp.w - 1, vp.h - 1});
viewport(vp);
viewport(&vp);
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()),
__FILE__, __FUNCTION__, __LINE__);
success_or_throw<SDLError>(
SDL_RenderDrawRects(_data.get(), rs.data(), (int)rs.size()));
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__,
__LINE__);
success_or_throw<SDLError>(SDL_RenderFillRect(_data.get(), r));
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()),
__FILE__, __FUNCTION__, __LINE__);
success_or_throw<SDLError>(
SDL_RenderFillRects(_data.get(), rs.data(), (int)rs.size()));
return this;
}
@ -145,29 +131,27 @@ auto Renderer::fill_rects(const std::vector<SDL_Rect>& rs) -> Renderer*
auto Renderer::output_size() -> Size
{
Size s {};
SDLError::success_or_throw(SDL_GetRendererOutputSize(_c_pod, &s.w, &s.h), __FILE__,
__FUNCTION__, __LINE__);
success_or_throw<SDLError>(SDL_GetRendererOutputSize(_data.get(), &s.w, &s.h));
return s;
}
void Renderer::present() noexcept
{
SDL_RenderPresent(_c_pod);
SDL_RenderPresent(_data.get());
}
auto Renderer::viewport() noexcept -> SDL_Rect
{
SDL_Rect vp;
SDL_RenderGetViewport(_c_pod, &vp);
SDL_RenderGetViewport(_data.get(), &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__,
__LINE__);
success_or_throw<SDLError>(SDL_RenderSetViewport(_data.get(), vp));
return this;
}

View File

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