Compare commits

..

1 commit

Author SHA1 Message Date
Andrea Blankenstijn c07dbfe8ff rewrite how events are held 2021-08-16 20:28:31 +02:00
65 changed files with 1347 additions and 1761 deletions

View file

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

123
README.md
View file

@ -1,125 +1,4 @@
# sdl2_basic_widgets
A (very) basic and incomplete widget library for SDL applications.
A (very) basic 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/widget_factory.hpp>
#include <basic_widgets/w/button.hpp>
#include "run.hpp"
using bwidgets::Button;
auto main() -> int
{
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");
});
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("+");
});
return 0;
}

View file

@ -1,16 +1,17 @@
#include <basic_widgets/w/widget_factory.hpp>
#include <basic_widgets/w/caption.hpp>
#include "run.hpp"
using bwidgets::Caption;
auto main() -> int
{
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)
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)
return 0;
}

View file

@ -3,11 +3,9 @@
auto main() -> int
{
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)
});
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)
});
}

View file

@ -5,14 +5,14 @@
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/w/base/widget_impl.hpp>
#include <basic_widgets/w/base/widget.hpp>
using bwidgets::Color;
using bwidgets::rect_margin;
using bwidgets::Size;
using bwidgets::WidgetImpl;
using bwidgets::Widget;
class Example final : public WidgetImpl
class Example final : public Widget
{
void _handle_geometry_change(const SDL_Rect& vp) noexcept override
{
@ -27,7 +27,8 @@ class Example final : public WidgetImpl
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};
// NOLINTNEXTLINE(readability-magic-numbers)
const Color base_color {r, g, b, 255};
const int border = 10;
for (auto i = 0; i < border; i += 3) {
@ -49,7 +50,12 @@ public:
unsigned int cycle_g {3500}; // NOLINT(readability-magic-numbers)
unsigned int cycle_b {3500}; // NOLINT(readability-magic-numbers)
using WidgetImpl::WidgetImpl;
using Widget::Widget;
Example(Example&&) = delete;
Example(const Example&) = delete;
auto operator=(Example&&) = delete;
auto operator=(const Example&) = delete;
~Example() noexcept override = default;
[[nodiscard]] auto size() const noexcept -> Size override
{

View file

@ -1,15 +1,15 @@
#include <basic_widgets/w/widget_factory.hpp>
#include <basic_widgets/w/numeric_input.hpp>
#include "run.hpp"
using bwidgets::NumericInput;
auto main() -> int
{
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)
});
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)
});
return 0;
}

View file

@ -3,16 +3,23 @@
#include <functional>
#include <iostream>
#include <memory>
#include <stack>
#include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/w/widget_factory.hpp>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <basic_widgets/core/error_helper.hpp>
#include <basic_widgets/core/font.hpp>
#include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/w/aligned_layout.hpp>
#include <basic_widgets/w/base/widget.hpp>
template<typename T>
concept WidgetType = std::derived_from<T, bwidgets::Widget>;
template<WidgetType W>
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)
{
@ -27,30 +34,31 @@ void run_example(
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>(
auto win = bwidgets::OpaqueStruct<SDL_Window>(
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)));
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_UTILITY)),
[](auto ptr) noexcept { SDL_DestroyWindow(ptr); });
auto renderer =
std::make_shared<bwidgets::Renderer>(win.get(), -1, SDL_RENDERER_ACCELERATED);
std::make_shared<bwidgets::Renderer>(win(), -1, SDL_RENDERER_ACCELERATED);
renderer->blend_mode(SDL_BLENDMODE_BLEND);
auto layout = bwidgets::create_horizontal_layout();
bwidgets::AlignedLayout<bwidgets::LayoutAlignment::HORIZONTAL> layout;
for (auto x = 0; x < w; x++) {
auto col = bwidgets::create_vertical_layout();
auto col = std::make_unique<
bwidgets::AlignedLayout<bwidgets::LayoutAlignment::VERTICAL>>();
for (auto y = 0; y < h; y++) {
auto widget = factory();
auto widget = std::make_unique<W>();
setup(widget.get(), font, x, y);
col->add_widget(std::move(widget));
}
layout->add_widget(std::move(col));
layout.add_widget(std::move(col));
}
layout->renderer(renderer);
layout->viewport({0, 0, size_init.w, size_init.h});
layout.renderer(renderer);
layout.viewport({0, 0, size_init.w, size_init.h});
bool quit {false};
while (!quit) {
@ -63,18 +71,18 @@ void run_example(
case SDL_WINDOWEVENT:
if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
const auto size = renderer->output_size();
layout->viewport({0, 0, size.w, size.h});
layout.viewport({0, 0, size.w, size.h});
}
break;
}
layout->handle_event(ev);
layout.handle_event(ev);
}
// NOLINTNEXTLINE(readability-magic-numbers)
renderer->draw_color({50, 60, 70, SDL_ALPHA_OPAQUE});
renderer->clear();
layout->render();
layout.render();
renderer->present();
}
} catch (const std::exception& e) {

View file

@ -1,6 +1,7 @@
#ifndef BWIDGETS_DRAW_HPP
#define BWIDGETS_DRAW_HPP
#include <cstdint>
#include <functional>
#include <memory>
@ -13,21 +14,11 @@ namespace bwidgets
class Renderer;
class Texture;
// 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&)>&);
[[nodiscard]] auto aa(Color, int, float) noexcept -> Color;
[[nodiscard]] auto filled_circle(Color, int resolution, Renderer*, int aa_pixels = 3)
-> std::shared_ptr<Texture>;
void set_pixels_color(Texture*,
std::function<uint32_t(SDL_Point, const SDL_PixelFormat*)>);
}
#endif

View file

@ -1,19 +1,13 @@
#ifndef BWIDGETS_ERROR_HELPER_HPP
#define BWIDGETS_ERROR_HELPER_HPP
#include <functional>
#ifndef BWIDGETS_ERROR_HELPER
#define BWIDGETS_ERROR_HELPER
#include <basic_widgets/core/type/exception.hpp>
#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*
inline auto ptr_or_throw(T* ptr, const std::string& w = "unknown") -> T*
{
std::string what;
if constexpr (std::is_same_v<E, SDLError>)
@ -24,21 +18,16 @@ namespace bwidgets
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
T code, const std::string& w = "unknown",
std::function<bool(T)> success = [](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};
std::string what {w == "unknown" ? SDL_GetError() : w};
if (code < 0) throw E(what);
}
else if (!success(code)) throw E(w.data());
else if (success(code)) throw E(w);
return code;
}

View file

@ -4,19 +4,16 @@
#include <memory>
#include <string>
#include <SDL_ttf.h>
#include <SDL2/SDL_ttf.h>
#include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/core/type/opaque_struct.hpp>
#include <basic_widgets/core/type/size.hpp>
namespace bwidgets
{
// Wrap TTF_Font from SDL_ttf and provide a font finding function.
class Font final
class Font final : OpaqueStruct<TTF_Font>::Wrapper
{
const std::unique_ptr<TTF_Font, Deleter> _data;
public:
enum struct Hinting
{
@ -55,8 +52,7 @@ namespace bwidgets
const std::string style_name;
explicit Font(TTF_Font*);
// Load font at given path and with given size.
Font(std::string_view, int);
Font(const std::string&, int);
Font(const Font&) = delete;
Font(Font&&) = delete;
~Font() noexcept = default;
@ -70,15 +66,13 @@ namespace bwidgets
auto kerning(bool) noexcept -> Font*;
[[nodiscard]] auto outline() const noexcept -> int;
auto outline(int) noexcept -> Font*;
auto render(RenderMode, std::string_view, Color fg = default_color_fg,
Color bg = default_color_bg)
-> std::unique_ptr<SDL_Surface, Deleter>;
auto render(RenderMode, const std::string&, Color fg = default_color_fg,
Color bg = default_color_bg) -> SDL_Surface*;
[[nodiscard]] auto style() const noexcept -> uint8_t;
auto style(uint8_t) noexcept -> Font*;
[[nodiscard]] auto text_size(std::string_view) const noexcept -> Size;
[[nodiscard]] auto text_size(const std::string&) const noexcept -> Size;
// Get file path of the font best matching a give fontconfig pattern.
[[nodiscard]] static auto find(std::string_view) -> std::string;
[[nodiscard]] static auto find(const std::string&) -> std::string;
};
}

View file

@ -4,39 +4,34 @@
#include <algorithm>
#include <cmath>
#include <SDL_rect.h>
#include <SDL2/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
{
// 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
[[nodiscard]] inline auto center_line(int available_len, int used_len) noexcept -> int
{
return (available_len - used_len) / 2;
}
// 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
[[nodiscard]] inline auto distance_sqrd(SDL_Point a, SDL_Point b) noexcept
-> float
{
return float(a.x - b.x) * float(a.x - b.x)
+ float(a.y - b.y) * float(a.y - b.y);
// NOLINTNEXTLINE(bugprone-narrowing-conversions)
return (a.x - b.x) * (a.x - b.x)
+ (a.y - b.y) * (a.y - b.y);
}
// Get distance of two points on a 2D plan.
[[nodiscard]] inline auto distance(const SDL_Point a, const SDL_Point b) noexcept -> float
[[nodiscard]] inline auto distance(SDL_Point a, 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]] inline auto lerp(const Color a, const Color b, const F x, const bool op_alpha=false, const bool op_color=true) noexcept -> Color
[[nodiscard]] inline auto lerp(Color a, Color b, F x, bool op_alpha=false, bool op_color=true) noexcept -> Color
{
return {{
op_color ? (uint8_t)std::lerp(a().r, b().r, x) : a().r,
@ -46,48 +41,42 @@ namespace bwidgets
}};
}
// Reverse lerp.
template<Numeric N>
[[nodiscard]] inline auto linear(const N x, const N a, const N b) noexcept -> float
[[nodiscard]] inline auto linear(N x, N a, N b) noexcept -> float
{
return (float)(x - a) / (float)(b - a);
}
// 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
{
const SDL_Point top_left {inner.x, inner.y};
const SDL_Point bottom_right {inner.x + inner.w, inner.y + inner.h};
SDL_Point top_left {inner.x, inner.y};
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);
}
// 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
[[nodiscard]] inline auto rect_margin(const SDL_Rect& r, Size margin) noexcept
-> SDL_Rect
{
return {r.x + margin.w, r.y + margin.h, r.w - 2 * margin.w, r.h - 2 * margin.h};
}
// 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
[[nodiscard]] inline auto rect_offset(const SDL_Rect& r, SDL_Point offset) noexcept
-> SDL_Rect
{
return {r.x + offset.x, r.y + offset.y, r.w, r.h};
}
// 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]] inline auto smoothstep(const N x, const N a, const N b) noexcept -> float
[[nodiscard]] inline auto smoothstep(N x, N a, 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,27 @@
#ifndef BWIDGETS_RENDERER_HPP
#define BWIDGETS_RENDERER_HPP
#include <cstdint>
#include <memory>
#include <span>
#include <vector>
#include <SDL_render.h>
#include <SDL2/SDL_render.h>
#include <basic_widgets/core/error_helper.hpp>
#include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/core/type/opaque_struct.hpp>
#include <basic_widgets/core/type/sdl_error.hpp>
#include <basic_widgets/core/type/size.hpp>
namespace bwidgets
{
class Texture;
// Wrap some of SDL's 2D accelerated rendering API.
class Renderer final
class Renderer final : OpaqueStruct<SDL_Renderer>::Wrapper
{
friend Texture;
const std::unique_ptr<SDL_Renderer, Deleter> _data;
static auto _info(SDL_Renderer* r) -> SDL_RendererInfo
{
SDL_RendererInfo info;
@ -45,7 +45,7 @@ namespace bwidgets
[[nodiscard]] auto blend_mode() -> SDL_BlendMode;
auto blend_mode(SDL_BlendMode) -> Renderer*;
auto clear() -> Renderer*;
auto copy(const 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(Color) -> Renderer*;
auto draw_line(SDL_Point, SDL_Point) -> Renderer*;
@ -61,33 +61,33 @@ namespace bwidgets
[[nodiscard]] auto viewport() noexcept -> SDL_Rect;
auto viewport(const SDL_Rect*) -> Renderer*;
auto copy(const 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);
}
auto copy(const 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);
}
auto copy(const 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);
}
auto draw_rect(SDL_Rect&& r)
auto draw_rect(const SDL_Rect& r)
{
const auto rect = r;
return draw_rect(&rect);
}
auto fill_rect(SDL_Rect&& r)
auto fill_rect(const SDL_Rect& r)
{
const auto rect = r;
return fill_rect(&rect);
}
auto viewport(SDL_Rect&& vp)
auto viewport(const SDL_Rect& vp)
{
const auto v = vp;
return viewport(&v);

View file

@ -1,21 +1,22 @@
#ifndef BWIDGETS_TEXTURE_HPP
#define BWIDGETS_TEXTURE_HPP
#include <cstdint>
#include <memory>
#include <SDL_pixels.h>
#include <SDL_render.h>
#include <SDL2/SDL_pixels.h>
#include <SDL2/SDL_render.h>
#include <basic_widgets/core/error_helper.hpp>
#include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/core/type/opaque_struct.hpp>
#include <basic_widgets/core/type/sdl_error.hpp>
namespace bwidgets
{
class Renderer;
// Wrap most of the texture functions of SDL 2D rendering API.
class Texture final
class Texture final : OpaqueStruct<SDL_Texture>::Wrapper
{
friend Renderer;
@ -25,15 +26,12 @@ namespace bwidgets
SDL_PixelFormat* format;
SDL_TextureAccess access;
int w, h;
};
const Attr _attributes;
const std::unique_ptr<SDL_Texture, Deleter> _data;
} _attributes {};
public:
explicit Texture(SDL_Texture*);
Texture(const Renderer&, SDL_PixelFormatEnum, SDL_TextureAccess, int, int);
Texture(const Renderer&, SDL_Surface*);
Texture(Renderer*, SDL_PixelFormatEnum, SDL_TextureAccess, int, int);
Texture(Renderer*, SDL_Surface*);
Texture(const Texture&) = delete;
Texture(Texture&&) = delete;
~Texture() noexcept;
@ -49,14 +47,14 @@ namespace bwidgets
auto color_mode(Color) -> Texture*;
[[nodiscard]] auto scale_mode() -> SDL_ScaleMode;
auto scale_mode(SDL_ScaleMode) -> Texture*;
auto update(const SDL_Rect*, const void*, int) -> Texture*;
auto update(SDL_Rect*, const void*, int) -> Texture*;
[[nodiscard]] const auto& attributes() const noexcept
{
return _attributes;
}
auto update(SDL_Rect&& r, const void* pix, int pitch)
auto update(const SDL_Rect& r, const void* pix, int pitch)
{
SDL_Rect rect = r;
update(&rect, pix, pitch);

View file

@ -2,16 +2,16 @@
#define BWIDGETS_COLOR_HPP
#include <cmath>
#include <cstdint>
#include <functional>
#include <stdexcept>
#include <SDL_pixels.h>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_pixels.h>
#include <basic_widgets/core/type/concepts.hpp>
namespace bwidgets
{
// Wrap SDL_Color to provide operator overloads.
class Color final
{
public:
@ -24,28 +24,23 @@ namespace bwidgets
_operate_on_colors {op_on_colors}
{}
Color(SDL_Color c = {}, bool op_on_alpha = false,
bool op_on_colors = true) noexcept
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
[[nodiscard]] auto alpha() const noexcept -> Color
{
return {sdl_type, _operate_on_colors, state};
return {sdl_type, _operate_on_colors, true};
}
// Enable/disable operations on color channels.Enabled by default on
// construction.
[[nodiscard]] auto colors(const bool state = true) const noexcept -> Color
[[nodiscard]] auto colors() const noexcept -> Color
{
return {sdl_type, state, _operate_on_alpha};
return {sdl_type, true, _operate_on_alpha};
}
// Get reference to the wrapped SDL native object.
[[nodiscard]] auto& operator()() noexcept
{
return sdl_type;
@ -56,33 +51,28 @@ namespace bwidgets
return sdl_type;
}
// Add operand to enabled channels.
template<Numeric N>
[[nodiscard]] auto operator+(const N operand) const noexcept
[[nodiscard]] auto operator+(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
[[nodiscard]] auto operator-(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
[[nodiscard]] auto operator*(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
[[nodiscard]] auto operator/(N operand) const noexcept
{
if (operand == 0) throw std::domain_error("division by zero.");
SDL_assert_release(operand != 0); // NOLINT
return _operate<N>(operand, [](N a, N b) { return a / b; });
}
@ -95,18 +85,14 @@ namespace bwidgets
return *this;
}
// Compare for equality of the enabled channel values between two Color
// instances.
[[nodiscard]] auto operator==(const Color other) const noexcept
[[nodiscard]] auto operator==(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
[[nodiscard]] auto operator!=(Color other) const noexcept
{
return (sdl_type.r != other().r || sdl_type.g != other().g
|| sdl_type.b != other().b)
@ -119,25 +105,21 @@ namespace bwidgets
bool _operate_on_colors;
template<Numeric N>
auto _operate(const N operand, const std::function<N(N, N)>& operator_) const
auto _operate(N operand, std::function<N(N, N)> operator_) const noexcept
{
const auto overunderflow_check = [](auto x) {
if (x > (uint8_t)x) throw std::overflow_error("uint8_t overflow.");
if (x < (uint8_t)x) throw std::underflow_error("uint8_t underflow.");
};
Color c(sdl_type, _operate_on_alpha, _operate_on_colors);
if (_operate_on_alpha) {
const auto a = std::round(operator_(c().a, operand));
overunderflow_check(a);
auto a = std::round(operator_(c().a, operand));
SDL_assert_release(a == (uint8_t)a); // NOLINT
c().a = (uint8_t)a;
}
if (_operate_on_colors) {
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);
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);
c().r = (uint8_t)r;
c().g = (uint8_t)g;
c().b = (uint8_t)b;

View file

@ -1,27 +1,25 @@
#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

@ -1,51 +0,0 @@
#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,20 +1,20 @@
#ifndef BWIDGETS_EXCEPTION_HPP
#define BWIDGETS_EXCEPTION_HPP
#include <any>
#include <functional>
#include <map>
#include <stdexcept>
#include <basic_widgets/core/type/concepts.hpp>
namespace bwidgets
{
// Base type for custom runtime errors.
class BaseException : std::runtime_error
struct BaseException : std::runtime_error
{
protected:
using runtime_error::runtime_error;
};
// T derives from BaseException.
template<typename T>
concept Exception = std::derived_from<T, BaseException>;
}

View file

@ -5,7 +5,6 @@
namespace bwidgets
{
// Exception type for fontconfig errors
struct FCError final : BaseException
{
using BaseException::BaseException;

View file

@ -0,0 +1,50 @@
#ifndef BWIDGETS_OPAQUE_STRUCT_HPP
#define BWIDGETS_OPAQUE_STRUCT_HPP
#include <functional>
namespace bwidgets
{
template<typename T>
class OpaqueStruct
{
using Deleter = std::function<void(T*)>;
const Deleter _deleter;
T* _c_pod;
public:
OpaqueStruct(T* ptr, Deleter d) : _deleter {std::move(d)}, _c_pod {ptr} {}
OpaqueStruct(const OpaqueStruct&) = delete;
OpaqueStruct(OpaqueStruct&&) = delete;
virtual ~OpaqueStruct() noexcept
{
try {
_deleter(_c_pod);
} catch (...) {
}
};
[[nodiscard]] auto* operator()() const
{
return _c_pod;
}
auto operator=(const OpaqueStruct&) = delete;
auto operator=(OpaqueStruct&&) = delete;
class Wrapper
{
protected:
OpaqueStruct _data;
public:
Wrapper(T* ptr, Deleter d) : _data(ptr, std::move(d)) {}
};
};
}
#endif

View file

@ -3,9 +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
{
using BaseException::BaseException;

View file

@ -5,34 +5,30 @@
namespace bwidgets
{
// Represent size of a 2D surface.
struct Size
{
int w;
int h;
};
// Addition the dimensions of two Size objects.
[[nodiscard]] inline auto operator+(const Size a, const Size b) noexcept -> Size
[[nodiscard]] inline auto operator+(Size a, 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
[[nodiscard]] inline auto operator-(Size a, 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
[[nodiscard]] inline auto operator*(Size a, 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
[[nodiscard]] inline auto operator*(N a, Size b) noexcept -> Size
{
return b * a;
}

View file

@ -0,0 +1,45 @@
#ifndef BWIDGETS_MOUSE_HANDLER_IMPL_HPP
#define BWIDGETS_MOUSE_HANDLER_IMPL_HPP
#include <basic_widgets/w/feat/mouse_handler.hpp>
namespace bwidgets
{
class MouseHandlerImpl : public MouseHandler
{
private:
const SDL_Rect* _area {nullptr};
bool _hovered {false};
bool _pushed {false};
std::function<void(const SDL_MouseButtonEvent&)> _click_handler;
std::function<void(const SDL_MouseMotionEvent&)> _motion_handler;
protected:
MouseHandlerImpl() = default;
public:
void
click_handler(std::function<void(const SDL_MouseButtonEvent&)> handler) override
{
_click_handler = std::move(handler);
}
void disable_mouse_handler() override;
void enable_mouse_handler(const SDL_Rect*) override;
[[nodiscard]] bool hovered() const override
{
return _hovered;
}
void mouse_motion_handler(
std::function<void(const SDL_MouseMotionEvent&)> handler) override
{
_motion_handler = std::move(handler);
}
[[nodiscard]] bool pushed() const override
{
return _pushed;
}
};
}
#endif

View file

@ -1,6 +1,7 @@
#ifndef BWIDGETS_ALIGNED_LAYOUT_HPP
#define BWIDGETS_ALIGNED_LAYOUT_HPP
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/base/layout.hpp>
namespace bwidgets
@ -11,11 +12,71 @@ namespace bwidgets
VERTICAL
};
// Align vertically or horizontally widgets.
class AlignedLayout : public virtual Layout
template<LayoutAlignment alignment>
class AlignedLayout final : public Layout
{
private:
void _update_layout(const SDL_Rect& vp) override
{
if constexpr (alignment == LayoutAlignment::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++) {
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 {
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;
}
}
}
public:
using Layout::Layout;
AlignedLayout(Widget* p = nullptr) noexcept : Layout(p) {}
AlignedLayout(const AlignedLayout&) = delete;
AlignedLayout(AlignedLayout&&) = delete;
~AlignedLayout() override = default;
auto operator=(const AlignedLayout&) = delete;
auto operator=(AlignedLayout&&) = delete;
[[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};
}
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};
}
};
}

View file

@ -1,77 +0,0 @@
#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,23 +1,122 @@
#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/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>
#include <basic_widgets/w/caption.hpp>
#include <basic_widgets/w/feat/keyboard_handler_impl.hpp>
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
namespace bwidgets
{
template<typename T>
class Input : public virtual Widget,
public virtual FontHandler,
public virtual KeyboardHandler,
public virtual MouseHandler
class Input : public Widget,
public FontHandler,
public KeyboardHandlerImpl,
public MouseHandlerImpl
{
protected:
using Widget::Widget;
Caption _input_caption;
Input(Widget* parent = nullptr) : Widget {parent}, _input_caption {this}
{
FocusHandlerImpl::focus_handler([this](bool focus) {
if (focus) {
SDL_StartTextInput();
}
// focus loss.
else if (FocusHandlerImpl::focus()) {
value = value_from_string(input_text());
input_text(value_to_string(value));
SDL_StopTextInput();
_input_caption.text(value_to_string(value));
}
});
KeyboardHandlerImpl::key_handler([this](const SDL_KeyboardEvent& key) {
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());
FocusHandlerImpl::focus(false);
break;
}
}
});
KeyboardHandlerImpl::text_input_handler(
[this](const SDL_TextInputEvent& input) {
if (is_valid_input(input.text)) {
input_text(input_text() + input.text);
}
});
KeyboardHandlerImpl::enable_keyboard_handler();
MouseHandlerImpl::enable_mouse_handler(&(Input::_widget_area),
&(Input::_viewport));
}
void _handle_font_change(const std::shared_ptr<Font>& f) override
{
_input_caption.font(f);
_handle_geometry_change(_viewport);
}
void _handle_font_color_change(Color fg, 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_renderer_change(const std::shared_ptr<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 =
FocusHandlerImpl::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 (hovered() || FocusHandlerImpl::focus())
_input_caption.font_color_bg(color_bg_focused);
else _input_caption.font_color_bg(color_bg);
_input_caption.render();
}
public:
static const int default_border_width = 3;
@ -28,30 +127,102 @@ namespace bwidgets
static const int default_min_width = 1;
int border_width = default_border_width;
Color color_bg_default = default_color_bg;
Color color_bg = 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 used as unit for text length computation.
char input_width_unit = 'W';
T value {};
// 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;
Input(const Input&) = delete;
Input(Input&&) = delete;
~Input() override = default;
auto operator=(const Input&) = delete;
auto operator=(Input&&) = delete;
// 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;
virtual auto color_fg(Color c) -> Input<T>*
{
_input_caption.font_color_fg(c);
return this;
}
[[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]] 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 type T.");
return process_value(v);
}
[[nodiscard]] auto value_to_string(T value) noexcept
{
std::string s;
if constexpr (std::is_same_v<std::string,
T> || std::convertible_to<T, std::string>)
s = std::move(value);
else if constexpr (CanToString<T>) {
std::stringstream ss;
ss << std::fixed << std::setprecision(float_precision) << value;
s = std::move(ss).str();
}
else if constexpr (Printable<T>) {
std::stringstream ss;
ss << value;
s = std::move(ss).str();
}
else
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
static_assert((bool)sizeof(T) && false,
"value cannot be converted to string.");
return s;
}
};
}

View file

@ -1,231 +0,0 @@
#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,26 +1,48 @@
#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 Layout : public virtual Widget
class Renderer;
}
namespace bwidgets
{
class Layout : public Widget
{
protected:
using Widget::Widget;
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;
virtual void _update_layout(const SDL_Rect&) = 0;
public:
static inline const Size default_margins {8, 8};
static const Size default_margins;
// Margins between layout widgets.
Size margins = default_margins;
// 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;
using Widget::Widget;
Layout(const Layout&) = delete;
Layout(Layout&&) = delete;
~Layout() override = default;
auto operator=(const Layout&) = delete;
auto operator=(Layout&&) = delete;
void handle_event(const SDL_Event&) override;
[[nodiscard]] auto size() const noexcept -> Size override = 0;
virtual auto add_widget(std::unique_ptr<Widget>) -> Layout*;
virtual void for_widgets(const std::function<void(Widget*)>&);
};
}

View file

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

@ -5,7 +5,7 @@
#include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/core/type/size.hpp>
#include <basic_widgets/w/feat/event_handler.hpp>
#include <basic_widgets/w/feat/event_handler_impl.hpp>
union SDL_Event;
@ -13,26 +13,28 @@ struct SDL_Renderer;
namespace bwidgets
{
class Widget : public virtual EventHandler
class Widget : public virtual EventHandlerImpl
{
protected:
std::shared_ptr<Renderer> _renderer;
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(const std::shared_ptr<Renderer>&) {}
virtual void _handle_rendering() = 0;
public:
// Parent widget for bidirectional message passing between widgets.
// Currently not used by this lib.
Widget* parent;
explicit Widget(Widget* p = nullptr) noexcept : parent {p} {}
// 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;
[[nodiscard]] virtual auto size() const noexcept -> Size = 0;
virtual auto render() -> Widget* final;
virtual auto renderer(std::shared_ptr<Renderer>) -> Widget* final;
virtual auto viewport(const SDL_Rect&) -> Widget* final;
[[nodiscard]] virtual auto viewport() const -> const SDL_Rect& final;
};
}

View file

@ -1,35 +0,0 @@
#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,36 +1,57 @@
#ifndef BWIDGETS_BUTTON_HPP
#define BWIDGETS_BUTTON_HPP
#include <functional>
#include <string>
#include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/w/base/widget.hpp>
#include <basic_widgets/w/feat/font_handler.hpp>
#include <basic_widgets/w/feat/mouse_handler.hpp>
#include <basic_widgets/w/caption.hpp>
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
namespace bwidgets
{
class Button : public virtual Widget,
public virtual FontHandler,
public virtual MouseHandler
public virtual MouseHandlerImpl
{
protected:
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;
public:
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};
static inline const Color default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
static const Color default_color_bg;
static const Color default_color_bg_hover;
static const Color default_color_fg;
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;
using Widget::Widget;
using EventHandlerImpl::handle_event;
using FocusHandlerImpl::focus;
using FocusHandlerImpl::focus_handler;
// Get button text (label).
[[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0;
// Set button text
virtual void text(std::string) = 0;
Button(Widget* parent = nullptr) noexcept;
Button(const Button&) = delete;
Button(Button&&) = delete;
~Button() override = default;
auto operator=(const Button&) = delete;
auto operator=(Button&&) = delete;
[[nodiscard]] auto size() const noexcept -> Size override;
[[nodiscard]] virtual auto text() const noexcept -> const std::string&;
virtual auto text(const std::string&) -> Button*;
};
}

View file

@ -1,40 +0,0 @@
#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,6 +1,8 @@
#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>
@ -9,10 +11,23 @@ struct SDL_Surface;
namespace bwidgets
{
class Caption : public virtual Widget,
public virtual FontHandler,
public virtual TextureHandler
class Caption : public Widget,
public FontHandler,
public TextureHandler
{
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;
public:
enum struct Alignment
{
@ -20,22 +35,26 @@ 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 {default_margins};
Size margins {3, 3};
using Widget::Widget;
Caption(Widget* parent = nullptr) noexcept;
Caption(const Caption&) = delete;
Caption(Caption&&) = delete;
~Caption() override = default;
auto operator=(const Caption&) = delete;
auto operator=(Caption&&) = delete;
// 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;
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;
};
}

View file

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

@ -2,6 +2,7 @@
#define BWIDGETS_EVENT_HANDLER_HPP
#include <functional>
#include <utility>
#include <SDL_events.h>
@ -9,13 +10,14 @@ namespace bwidgets
{
class EventHandler
{
protected:
EventHandler() = default;
public:
using handler_t =
std::pair<SDL_EventType, std::function<void(const SDL_Event&)>>;
protected:
EventHandler() = default;
public:
EventHandler(const EventHandler&) = delete;
EventHandler(EventHandler&&) = delete;
auto operator=(const EventHandler&) -> EventHandler& = delete;
@ -23,7 +25,6 @@ namespace bwidgets
virtual ~EventHandler() = default;
// Pass the event to be handled.
virtual void handle_event(const SDL_Event&) = 0;
};
}

View file

@ -12,18 +12,12 @@ namespace bwidgets
std::unordered_map<SDL_EventType, std::function<void(const SDL_Event&)>>
_event_handlers {};
protected:
auto _add_event_handler(handler_t) -> bool;
auto _remove_event_handler(SDL_EventType) -> std::pair<handler_t, bool>;
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>;
};
}

View file

@ -17,11 +17,8 @@ namespace bwidgets
auto operator=(const FocusHandler&) = delete;
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

@ -11,9 +11,10 @@ namespace bwidgets
std::function<void(bool)> _focus_handler {[](auto) {}};
public:
void focus(const bool _focus) override
void focus(bool focus_) override
{
if (_has_focus != _focus) _focus_handler(_has_focus = _focus);
_focus_handler(focus_);
_has_focus = focus_;
}
bool focus() override

View file

@ -1,33 +1,67 @@
#ifndef BWIDGETS_FONT_HANDLER_HPP
#define BWIDGETS_FONT_HANDLER_HPP
#include <memory>
#include <basic_widgets/core/font.hpp>
namespace bwidgets
{
class FontHandler
{
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};
virtual void _handle_font_change(const std::shared_ptr<Font>&) = 0;
virtual void _handle_font_color_change(Color, 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() noexcept = default;
FontHandler(const FontHandler&) = delete;
FontHandler(FontHandler&&) = delete;
auto operator=(const FontHandler&) = delete;
auto operator=(FontHandler&&) = delete;
virtual ~FontHandler() noexcept = default;
// 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 auto font(std::shared_ptr<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;
}
protected:
FontHandler() noexcept = default;
virtual auto font_color_bg(Color c) -> FontHandler* final
{
if (c != _font_color_bg) {
_handle_font_color_change(_font_color_bg, c);
_font_color_bg = c;
}
return this;
}
virtual auto font_color_fg(Color c) -> FontHandler* final
{
if (c != _font_color_fg) {
_handle_font_color_change(c, _font_color_fg);
_font_color_fg = c;
}
return this;
}
};
}

View file

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

@ -16,13 +16,9 @@ namespace bwidgets
using EventHandler::EventHandler;
public:
// 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

@ -7,9 +7,9 @@
namespace bwidgets
{
class KeyboardHandlerImpl : public virtual KeyboardHandler,
public virtual EventHandlerImpl,
public virtual FocusHandlerImpl
class KeyboardHandlerImpl : public KeyboardHandler,
virtual public EventHandlerImpl,
virtual public FocusHandlerImpl
{
std::function<void(const SDL_KeyboardEvent&)> _key_handler {[](auto) {}};
std::function<void(const SDL_TextInputEvent&)> _input_handler {[](auto) {}};

View file

@ -9,27 +9,21 @@ struct SDL_MouseMotionEvent;
namespace bwidgets
{
class MouseHandler : public virtual FocusHandler,
public virtual EventHandler
class MouseHandler : virtual public FocusHandler,
virtual public EventHandler
{
protected:
using EventHandler::EventHandler;
virtual void _on_push(bool) = 0;
public:
// 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.
virtual void enable_mouse_handler(const SDL_Rect*, const SDL_Rect*) = 0;
[[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

@ -7,38 +7,39 @@
namespace bwidgets
{
class MouseHandlerImpl : public virtual MouseHandler,
public virtual EventHandlerImpl,
public virtual FocusHandlerImpl
class MouseHandlerImpl : public MouseHandler,
virtual public EventHandlerImpl,
virtual public FocusHandlerImpl
{
private:
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) {}};
protected:
using MouseHandler::MouseHandler;
void _on_push(bool) override {}
public:
void click_handler(decltype(_click_handler) handler) override
void
click_handler(std::function<void(const SDL_MouseButtonEvent&)> handler) override
{
_click_handler = std::move(handler);
}
void disable_mouse_handler() override;
void enable_mouse_handler(const SDL_Rect&, const SDL_Rect&) 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
void mouse_motion_handler(
std::function<void(const SDL_MouseMotionEvent&)> handler) override
{
_motion_handler = std::move(handler);
}
@ -47,12 +48,6 @@ namespace bwidgets
{
return _pushed;
}
protected:
using MouseHandler::MouseHandler;
// Called on push state changes.
virtual void _on_push(bool) {}
};
}

View file

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

View file

@ -1,26 +1,178 @@
#ifndef BWIDGETS_NUMERIC_INPUT_HPP
#define BWIDGETS_NUMERIC_INPUT_HPP
#include <limits>
#include <type_traits>
#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>
namespace bwidgets
{
template<Numeric T>
class NumericInput : public virtual Input<T>
class NumericInput : public Input<T>
{
protected:
using Input<T>::Input;
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(const std::shared_ptr<Font>& f) override
{
_decrement_button.font(f);
_increment_button.font(f);
Input<T>::_handle_font_change(f);
}
void _handle_font_color_change(Color fg, 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(const std::shared_ptr<Renderer>& r) override
{
Input<T>::_handle_renderer_change(r);
_decrement_button.renderer(r);
_increment_button.renderer(r);
}
void _handle_rendering() override
{
Input<T>::_handle_rendering();
_increment_button.render();
_decrement_button.render();
}
public:
// Button increment/decrement step.
T button_step = 1;
// 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;
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));
});
}
NumericInput(const NumericInput&) = delete;
NumericInput(NumericInput&&) = delete;
~NumericInput() override = default;
auto operator=(const NumericInput&) = delete;
auto operator=(NumericInput&&) = delete;
void handle_event(const SDL_Event& ev) override
{
Input<T>::handle_event(ev);
_increment_button.handle_event(ev);
_decrement_button.handle_event(ev);
}
[[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]] auto size() const noexcept -> Size override
{
const auto base = Input<T>::size();
const auto btns_width = 4 * _increment_button.size().w;
return {base.w + btns_width, base.h};
}
[[nodiscard]] virtual auto value_range() const noexcept -> const std::pair<T, T>&
{
return _value_range;
}
virtual auto value_range(T min, T max) -> NumericInput<T>*
{
if (min > max) throw std::invalid_argument("min cannot be greater than max");
_value_range = {min, max};
Input<T>::value = process_value(Input<T>::value);
Input<T>::input_text(Input<T>::value_to_string(Input<T>::value));
return this;
}
};
}

View file

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

@ -1,19 +0,0 @@
#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,15 +1,15 @@
project('sdl2_basic_widgets', 'cpp',
version : '0.9.0',
version : '0.1pre',
default_options : [
'b_lundef=false',
'b_sanitize=undefined',
# 'b_lundef=false',
# 'b_sanitize=address,undefined',
'cpp_std=c++20',
'optimization=g',
'warning_level=3',
],
license: 'EUPL-1.2')
add_project_arguments('-Wconversion', '-pedantic', language: 'cpp')
add_project_arguments('-pedantic', language: 'cpp')
if (get_option('buildtype').startswith('debug'))
add_project_arguments('-DBWIDGETS_DEBUG', language: 'cpp')
@ -28,14 +28,13 @@ libbasic_widgets = static_library('basic_widgets',
'src/core/font.cpp',
'src/core/renderer.cpp',
'src/core/texture.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/base/layout.cpp',
'src/w/base/widget.cpp',
'src/w/button.cpp',
'src/w/caption.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)

View file

@ -2,7 +2,7 @@
#include <cmath>
#include <vector>
#include <SDL_render.h>
#include <SDL2/SDL_render.h>
#include <basic_widgets/core/draw.hpp>
#include <basic_widgets/core/math.hpp>
@ -10,24 +10,22 @@
namespace bwidgets
{
auto aa(const Color base_color, const int aa_pixels, const float d) noexcept -> Color
auto aa(Color base_color, const int aa_pixels, const float d) noexcept -> Color
{
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};
}
Color c(base_color);
if (aa_pixels == 0) {
if (d > 0) c().a = 0;
}
else {
const auto factor = 1.0 - smoothstep<float>(d, (float)-aa_pixels, 0.);
return {r, g, b, (uint8_t)((float)base_color().a * factor)};
}();
c().a = (uint8_t)((float)c().a * factor);
}
return c;
}
auto filled_circle(const Color c, const int resolution, const Renderer& r,
const int aa_pixels) -> std::shared_ptr<Texture>
auto filled_circle(Color c, const int resolution, Renderer* r, const int aa_pixels)
-> std::shared_ptr<Texture>
{
// clang-format off
auto texture {std::make_shared<Texture>(
@ -42,11 +40,11 @@ namespace bwidgets
set_pixels_color(texture.get(),
[aa_pixels, c, center, radius](
SDL_Point p, const SDL_PixelFormat& format) -> uint32_t {
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,
return SDL_MapRGBA(format, aa_color().r, aa_color().g,
aa_color().b, aa_color().a);
});
texture->blend_mode(SDL_BLENDMODE_BLEND);
@ -56,21 +54,19 @@ namespace bwidgets
}
void set_pixels_color(
Texture& t,
const std::function<uint32_t(SDL_Point, const SDL_PixelFormat&)>& pixel_color)
Texture* t, 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,13 +1,12 @@
#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>
using namespace bwidgets;
@ -15,7 +14,7 @@ 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)},
: Wrapper {ptr_or_throw<SDLError>(f), [f](TTF_Font*) noexcept { TTF_CloseFont(f); }},
ascent {TTF_FontAscent(f)},
descent {TTF_FontDescent(f)},
faces {TTF_FontFaces(f)},
@ -26,120 +25,134 @@ Font::Font(TTF_Font* f)
style_name {TTF_FontFaceStyleName(f)}
{}
Font::Font(const std::string_view file, const int size)
: Font {TTF_OpenFont(file.data(), size)}
Font::Font(const std::string& file, int size) : Font {TTF_OpenFont(file.c_str(), size)}
{}
auto Font::hinting() const noexcept -> Font::Hinting
{
return static_cast<Hinting>(TTF_GetFontHinting(_data.get()));
return (Hinting)TTF_GetFontHinting(_data());
}
auto Font::hinting(const Font::Hinting h) noexcept -> Font*
{
TTF_SetFontHinting(_data.get(), static_cast<int>(h));
TTF_SetFontHinting(_data(), (int)h);
return this;
}
auto Font::kerning() const noexcept -> bool
{
return TTF_GetFontKerning(_data.get()) != 0;
return TTF_GetFontKerning(_data()) != 0;
}
auto Font::kerning(const bool allowed) noexcept -> Font*
{
TTF_SetFontKerning(_data.get(), static_cast<int>(allowed));
TTF_SetFontKerning(_data(), static_cast<int>(allowed));
return this;
}
auto Font::outline() const noexcept -> int
{
return TTF_GetFontOutline(_data.get());
return TTF_GetFontOutline(_data());
}
auto Font::outline(const int size) noexcept -> Font*
{
TTF_SetFontOutline(_data.get(), size);
TTF_SetFontOutline(_data(), 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>
auto Font::render(const RenderMode m, const std::string& str, Color fg, Color bg)
-> SDL_Surface*
{
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:
return [fg, c_str, this]() {
return TTF_RenderUTF8_Blended(_data.get(), c_str, fg());
};
case RenderMode::SHADED:
return [bg, fg, c_str, this]() {
return TTF_RenderUTF8_Shaded(_data.get(), c_str, fg(), bg());
};
case RenderMode::SOLID:
return [fg, c_str, this]() {
return TTF_RenderUTF8_Solid(_data.get(), c_str, fg());
};
default:
throw std::logic_error("missing switch case.");
}
}();
std::function<SDL_Surface*()> renderer;
const char* c_str = str.empty() ? " " : str.c_str();
return std::unique_ptr<SDL_Surface, Deleter>(ptr_or_throw<SDLError>(renderer()));
switch (m) {
case RenderMode::BLENDED:
renderer = [&fg, c_str, this]() {
return TTF_RenderUTF8_Blended(_data(), c_str, fg());
};
break;
case RenderMode::SHADED:
renderer = [&bg, &fg, c_str, this]() {
return TTF_RenderUTF8_Shaded(_data(), c_str, fg(), bg());
};
break;
case RenderMode::SOLID:
renderer = [&fg, c_str, this]() {
return TTF_RenderUTF8_Solid(_data(), c_str, fg());
};
break;
}
return ptr_or_throw<SDLError>(renderer());
}
auto Font::style() const noexcept -> uint8_t
{
return TTF_GetFontStyle(_data.get());
return TTF_GetFontStyle(_data());
}
auto Font::style(const uint8_t s) noexcept -> Font*
{
TTF_SetFontStyle(_data.get(), s);
TTF_SetFontStyle(_data(), s);
return this;
}
auto Font::text_size(const std::string_view str) const noexcept -> Size
auto Font::text_size(const std::string& str) const noexcept -> Size
{
Size s {};
TTF_SizeUTF8(_data.get(), str.data(), &s.w, &s.h);
TTF_SizeUTF8(_data(), str.c_str(), &s.w, &s.h);
return s;
}
auto Font::find(const std::string_view pat) -> std::string
auto Font::find(const std::string& pat) -> std::string
{
const auto conf = std::unique_ptr<FcConfig, Deleter>(
ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), "fontconfig init failed."));
std::stack<std::function<void()>> cleaners;
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"));
FcConfig* conf = ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), "init failed");
cleaners.emplace([conf]() { FcConfigDestroy(conf); });
success_or_throw<FCError, FcBool>(
FcConfigSubstitute(conf.get(), pattern.get(), FcMatchPattern),
"FcConfigSubstitute failed", [](auto code) { return code == FcTrue; });
return pattern;
}();
FcPattern* pattern = nullptr;
try {
pattern = ptr_or_throw<FCError>(FcNameParse((FcChar8*)pat.c_str()),
"pattern parsing failed");
cleaners.emplace([pattern]() { FcPatternDestroy(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;
// Don't check for error because it looks like it returns an error code when used
// as the actual code is. But neverless things doesn't work well without calling
// it.
/* success_or_throw<FCError, FcBool>( */
FcConfigSubstitute(conf, pattern,
FcMatchPattern); //, "FcConfigSubstitute failed",
/* [](auto code) { return code == FcFalse; }); */
} catch (const FCError& e) {
while (!cleaners.empty()) {
cleaners.top()();
cleaners.pop();
}
throw FCError("no font found.");
}();
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 {"no font found"};
return file_path;
}

View file

@ -3,39 +3,42 @@
using namespace bwidgets;
Renderer::Renderer(SDL_Renderer* r) : _data {ptr_or_throw<SDLError>(r)}, info {_info(r)}
Renderer::Renderer(SDL_Renderer* r)
: Wrapper {ptr_or_throw<SDLError>(r),
[r](SDL_Renderer*) noexcept { SDL_DestroyRenderer(r); }},
info {_info(r)}
{}
Renderer::Renderer(SDL_Window* w, const int index, const uint32_t flags = 0)
: Renderer {SDL_CreateRenderer(w, index, flags)}
: Renderer {ptr_or_throw<SDLError>(SDL_CreateRenderer(w, index, flags))}
{}
auto Renderer::blend_mode() -> SDL_BlendMode
{
SDL_BlendMode mode {};
success_or_throw<SDLError>(SDL_GetRenderDrawBlendMode(_data.get(), &mode));
success_or_throw<SDLError>(SDL_GetRenderDrawBlendMode(_data(), &mode));
return mode;
}
auto Renderer::blend_mode(const SDL_BlendMode mode) -> Renderer*
{
success_or_throw<SDLError>(SDL_SetRenderDrawBlendMode(_data.get(), mode));
success_or_throw<SDLError>(SDL_SetRenderDrawBlendMode(_data(), mode));
return this;
}
auto Renderer::clear() -> Renderer*
{
success_or_throw<SDLError>(SDL_RenderClear(_data.get()));
success_or_throw<SDLError>(SDL_RenderClear(_data()));
return this;
}
auto Renderer::copy(const Texture& t, const SDL_Rect* const src,
const SDL_Rect* const dst) -> Renderer*
auto Renderer::copy(const Texture* t, const SDL_Rect* src, const SDL_Rect* dst)
-> Renderer*
{
success_or_throw<SDLError>(SDL_RenderCopy(_data.get(), t._data.get(), src, dst));
success_or_throw<SDLError>(SDL_RenderCopy(_data(), t->_data(), src, dst));
return this;
}
@ -44,50 +47,50 @@ auto Renderer::draw_color() -> Color
{
Color c;
success_or_throw<SDLError>(
SDL_GetRenderDrawColor(_data.get(), &c().r, &c().g, &c().b, &c().a));
SDL_GetRenderDrawColor(_data(), &c().r, &c().g, &c().b, &c().a));
return c;
}
auto Renderer::draw_color(const Color c) -> Renderer*
auto Renderer::draw_color(Color c) -> Renderer*
{
success_or_throw<SDLError>(
SDL_SetRenderDrawColor(_data.get(), c().r, c().g, c().b, c().a));
SDL_SetRenderDrawColor(_data(), 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(SDL_Point a, SDL_Point b) -> Renderer*
{
success_or_throw<SDLError>(SDL_RenderDrawLine(_data.get(), a.x, a.y, b.x, b.y));
success_or_throw<SDLError>(SDL_RenderDrawLine(_data(), a.x, a.y, b.x, b.y));
return this;
}
auto Renderer::draw_lines(const std::span<SDL_Point> pts) -> Renderer*
auto Renderer::draw_lines(std::span<SDL_Point> pts) -> Renderer*
{
success_or_throw<SDLError>(
SDL_RenderDrawLines(_data.get(), pts.data(), (int)pts.size()));
SDL_RenderDrawLines(_data(), pts.data(), (int)pts.size()));
return this;
}
auto Renderer::draw_point(const SDL_Point p) -> Renderer*
auto Renderer::draw_point(SDL_Point p) -> Renderer*
{
success_or_throw<SDLError>(SDL_RenderDrawPoint(_data.get(), p.x, p.y));
success_or_throw<SDLError>(SDL_RenderDrawPoint(_data(), p.x, p.y));
return this;
}
auto Renderer::draw_points(const std::span<SDL_Point> pts) -> Renderer*
auto Renderer::draw_points(std::span<SDL_Point> pts) -> Renderer*
{
success_or_throw<SDLError>(
SDL_RenderDrawPoints(_data.get(), pts.data(), (int)pts.size()));
SDL_RenderDrawPoints(_data(), pts.data(), (int)pts.size()));
return this;
}
auto Renderer::draw_rect(const SDL_Rect* const r) -> Renderer*
auto Renderer::draw_rect(const SDL_Rect* r) -> Renderer*
{
auto vp = viewport();
// Has glitch at top-left and bottom-right corner.
@ -95,35 +98,33 @@ auto Renderer::draw_rect(const SDL_Rect* const 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
success_or_throw<SDLError>(SDL_RenderDrawRect(_data.get(), nullptr));
success_or_throw<SDLError>(SDL_RenderDrawRect(_data(), 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::span<SDL_Rect> rs) -> Renderer*
auto Renderer::draw_rects(std::span<SDL_Rect> rs) -> Renderer*
{
success_or_throw<SDLError>(
SDL_RenderDrawRects(_data.get(), rs.data(), (int)rs.size()));
success_or_throw<SDLError>(SDL_RenderDrawRects(_data(), rs.data(), (int)rs.size()));
return this;
}
auto Renderer::fill_rect(const SDL_Rect* const r) -> Renderer*
auto Renderer::fill_rect(const SDL_Rect* r) -> Renderer*
{
success_or_throw<SDLError>(SDL_RenderFillRect(_data.get(), r));
success_or_throw<SDLError>(SDL_RenderFillRect(_data(), r));
return this;
}
auto Renderer::fill_rects(const std::span<SDL_Rect> rs) -> Renderer*
auto Renderer::fill_rects(std::span<SDL_Rect> rs) -> Renderer*
{
success_or_throw<SDLError>(
SDL_RenderFillRects(_data.get(), rs.data(), (int)rs.size()));
success_or_throw<SDLError>(SDL_RenderFillRects(_data(), rs.data(), (int)rs.size()));
return this;
}
@ -131,27 +132,27 @@ auto Renderer::fill_rects(const std::span<SDL_Rect> rs) -> Renderer*
auto Renderer::output_size() -> Size
{
Size s {};
success_or_throw<SDLError>(SDL_GetRendererOutputSize(_data.get(), &s.w, &s.h));
success_or_throw<SDLError>(SDL_GetRendererOutputSize(_data(), &s.w, &s.h));
return s;
}
void Renderer::present() noexcept
{
SDL_RenderPresent(_data.get());
SDL_RenderPresent(_data());
}
auto Renderer::viewport() noexcept -> SDL_Rect
{
SDL_Rect vp;
SDL_RenderGetViewport(_data.get(), &vp);
SDL_RenderGetViewport(_data(), &vp);
return vp;
}
auto Renderer::viewport(const SDL_Rect* const vp) -> Renderer*
auto Renderer::viewport(const SDL_Rect* vp) -> Renderer*
{
success_or_throw<SDLError>(SDL_RenderSetViewport(_data.get(), vp));
success_or_throw<SDLError>(SDL_RenderSetViewport(_data(), vp));
return this;
}

View file

@ -4,17 +4,22 @@
using namespace bwidgets;
Texture::Texture(SDL_Texture* t)
: _attributes(attributes(t)), _data {ptr_or_throw<SDLError>(t)}
: Wrapper {ptr_or_throw<SDLError>(t),
[t](SDL_Texture*) noexcept { SDL_DestroyTexture(t); }}
{
_attributes = attributes(t);
}
Texture::Texture(Renderer* r, const SDL_PixelFormatEnum f, const SDL_TextureAccess a,
int w, int h)
: Texture {SDL_CreateTexture(r->_data(), f, a, w, h)}
{}
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(Renderer* r, SDL_Surface* s)
: Texture {SDL_CreateTextureFromSurface(r->_data(), s)}
{
_attributes = attributes(_data());
}
Texture::~Texture() noexcept
{
@ -25,14 +30,14 @@ Texture::~Texture() noexcept
auto Texture::alpha_mode() -> uint8_t
{
uint8_t mode = 0;
success_or_throw<SDLError>(SDL_GetTextureAlphaMod(_data.get(), &mode));
success_or_throw<SDLError>(SDL_GetTextureAlphaMod(_data(), &mode));
return mode;
}
auto Texture::alpha_mode(const uint8_t m) -> Texture*
{
success_or_throw<SDLError>(SDL_SetTextureAlphaMod(_data.get(), m));
success_or_throw<SDLError>(SDL_SetTextureAlphaMod(_data(), m));
return this;
}
@ -40,14 +45,14 @@ auto Texture::alpha_mode(const uint8_t m) -> Texture*
auto Texture::blend_mode() -> SDL_BlendMode
{
SDL_BlendMode mode {};
success_or_throw<SDLError>(SDL_GetTextureBlendMode(_data.get(), &mode));
success_or_throw<SDLError>(SDL_GetTextureBlendMode(_data(), &mode));
return mode;
}
auto Texture::blend_mode(const SDL_BlendMode m) -> Texture*
{
success_or_throw<SDLError>(SDL_SetTextureBlendMode(_data.get(), m));
success_or_throw<SDLError>(SDL_SetTextureBlendMode(_data(), m));
return this;
}
@ -56,14 +61,14 @@ auto Texture::color_mode() -> Color
{
Color mode;
success_or_throw<SDLError>(
SDL_GetTextureColorMod(_data.get(), &mode().r, &mode().g, &mode().b));
SDL_GetTextureColorMod(_data(), &mode().r, &mode().g, &mode().b));
return mode;
}
auto Texture::color_mode(Color m) -> Texture*
{
success_or_throw<SDLError>(SDL_SetTextureColorMod(_data.get(), m().r, m().g, m().b));
success_or_throw<SDLError>(SDL_SetTextureColorMod(_data(), m().r, m().g, m().b));
return this;
}
@ -71,22 +76,21 @@ auto Texture::color_mode(Color m) -> Texture*
auto Texture::scale_mode() -> SDL_ScaleMode
{
SDL_ScaleMode mode {};
success_or_throw<SDLError>(SDL_GetTextureScaleMode(_data.get(), &mode));
success_or_throw<SDLError>(SDL_GetTextureScaleMode(_data(), &mode));
return mode;
}
auto Texture::scale_mode(const SDL_ScaleMode m) -> Texture*
{
success_or_throw<SDLError>(SDL_SetTextureScaleMode(_data.get(), m));
success_or_throw<SDLError>(SDL_SetTextureScaleMode(_data(), m));
return this;
}
auto Texture::update(const SDL_Rect* const r, const void* const pixels, const int pitch)
-> Texture*
auto Texture::update(SDL_Rect* r, const void* pixels, const int pitch) -> Texture*
{
success_or_throw<SDLError>(SDL_UpdateTexture(_data.get(), r, pixels, pitch));
success_or_throw<SDLError>(SDL_UpdateTexture(_data(), r, pixels, pitch));
return this;
}

39
src/w/base/layout.cpp Normal file
View file

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

View file

@ -1,36 +0,0 @@
#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,13 +1,12 @@
#include <basic_widgets/w/base/widget_impl.hpp>
#include <basic_widgets/w/base/widget.hpp>
using namespace bwidgets;
void WidgetImpl::render()
auto Widget::render() -> Widget*
{
if (!_renderer) return;
if (_renderer == nullptr) return this;
#ifdef BWIDGETS_DEBUG
// Render a debug outline of widget viewport.
_renderer
->draw_color({
// NOLINTNEXTLINE(readability-magic-numbers)
@ -16,25 +15,29 @@ void WidgetImpl::render()
->draw_rect(nullptr);
#endif
_renderer->viewport(&_viewport);
_renderer->viewport(_viewport);
_handle_rendering();
return this;
}
void WidgetImpl::renderer(std::shared_ptr<Renderer> r)
auto Widget::renderer(std::shared_ptr<Renderer> r) -> Widget*
{
if (r != _renderer) {
_handle_renderer_change(r);
_renderer = std::move(r);
_renderer = r;
}
return this;
}
void WidgetImpl::viewport(const SDL_Rect& vp)
auto Widget::viewport(const SDL_Rect& vp) -> Widget*
{
_handle_geometry_change(vp);
_viewport = vp;
return this;
}
auto WidgetImpl::viewport() const -> const SDL_Rect&
auto Widget::viewport() const -> const SDL_Rect&
{
return _viewport;
}

105
src/w/button.cpp Normal file
View file

@ -0,0 +1,105 @@
#include <cmath>
#include <SDL2/SDL_render.h>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/button.hpp>
using namespace bwidgets;
const Color Button::default_color_bg {150, 150, 150, SDL_ALPHA_OPAQUE};
const Color Button::default_color_bg_hover {175, 175, 175, SDL_ALPHA_OPAQUE};
const Color Button::default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
Button::Button(Widget* parent) noexcept : Widget {parent}, _caption {this}
{
enable_mouse_handler(&_widget_area, &_viewport);
_caption.alignment = Caption::Alignment::CENTER;
_caption.render_mode(Font::RenderMode::BLENDED);
border_gradient = [this](int len, int pos, float divider) -> Color {
const auto& end_color = hovered() ? color_bg_hover : color_bg;
const auto start_color = end_color / divider;
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(const std::shared_ptr<Font>& f)
{
_caption.font(f);
_handle_geometry_change(_viewport);
}
void Button::_handle_font_color_change(Color fg, 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(const std::shared_ptr<Renderer>& r)
{
_caption.renderer(r);
}
void Button::_handle_rendering()
{
Color c = hovered() ? color_bg_hover : color_bg;
const auto divider = pushed() ? 1.5 : 2;
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(border_gradient(biggest, max, divider))
->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));
}

View file

@ -1,102 +0,0 @@
#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));
}

116
src/w/caption.cpp Normal file
View file

@ -0,0 +1,116 @@
#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;
}
auto Caption::render_mode(Font::RenderMode m) -> Caption*
{
if (m != _render_mode) {
_text_texture.reset();
_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) {
_text_texture.reset();
_text = t;
}
return this;
}
void Caption::_handle_font_change(const std::shared_ptr<Font>&)
{
_text_texture.reset();
}
void Caption::_handle_font_color_change(Color fg, Color bg)
{
if (fg != _font_color_fg
|| (bg != _font_color_bg && _font_render_mode == Font::RenderMode::SHADED))
{
_text_texture.reset();
_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(const std::shared_ptr<Renderer>&)
{
_text_texture.reset();
}
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.get(), nullptr, texture_dst);
}
void Caption::_handle_texture_update()
{
SDL_assert_release(_font != nullptr && _text_texture == nullptr); // NOLINT
SDL_Surface* s {nullptr};
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 = std::make_shared<Texture>(_renderer.get(), s);
SDL_FreeSurface(s);
}

View file

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

@ -14,11 +14,11 @@ 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));
_event_handlers.emplace(event_handler);
return true;
}
auto EventHandlerImpl::_remove_event_handler(const SDL_EventType ev_type)
auto EventHandlerImpl::_remove_event_handler(SDL_EventType ev_type)
-> std::pair<handler_t, bool>
{
handler_t handler;

View file

@ -12,12 +12,12 @@ void KeyboardHandlerImpl::disable_keyboard_handler()
void KeyboardHandlerImpl::enable_keyboard_handler()
{
const auto keyboard_handler = [this](const SDL_Event& ev) {
if (focus()) _key_handler(ev.key);
if (FocusHandlerImpl::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);
if (FocusHandlerImpl::focus()) _input_handler(ev.text);
}});
}

View file

@ -5,44 +5,46 @@ using namespace bwidgets;
void MouseHandlerImpl::disable_mouse_handler()
{
_remove_event_handler(SDL_MOUSEBUTTONDOWN);
_remove_event_handler(SDL_MOUSEBUTTONUP);
EventHandlerImpl::_remove_event_handler(SDL_MOUSEBUTTONDOWN);
EventHandlerImpl::_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)
void MouseHandlerImpl::enable_mouse_handler(const SDL_Rect* area, const SDL_Rect* vp)
{
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;
};
if (!area || !vp) throw std::logic_error("ptr parameters cannot be null.");
_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);
_add_event_handler({SDL_MOUSEBUTTONDOWN, [this, area, vp](const SDL_Event& ev) {
auto absolute_area = rect_offset(*area, *vp);
SDL_Point p = {ev.button.x, ev.button.y};
if (SDL_PointInRect(&p, &absolute_area) == SDL_FALSE) {
FocusHandlerImpl::focus(false);
return;
}
_on_push(_pushed = true);
_on_push(true);
_pushed = true;
}});
_add_event_handler({SDL_MOUSEBUTTONUP, [this, mouse_in_rect](const SDL_Event& ev) {
_add_event_handler({SDL_MOUSEBUTTONUP, [this, area, vp](const SDL_Event& ev) {
auto absolute_area = rect_offset(*area, *vp);
SDL_Point p = {ev.button.x, ev.button.y};
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);
}
_on_push(false);
_pushed = false;
}
if (SDL_PointInRect(&p, &absolute_area) == SDL_FALSE) return;
FocusHandlerImpl::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);
_add_event_handler({SDL_MOUSEMOTION, [this, area, vp](const SDL_Event& ev) {
auto absolute_area = rect_offset(*area, *vp);
SDL_Point p = {ev.motion.x, ev.motion.y};
if (SDL_PointInRect(&p, &absolute_area) == SDL_FALSE) {
_hovered = false;
return;
}
if (!_hovered) _hover_handler(_hovered = true);
_hovered = true;
_motion_handler(ev.motion);
}});
}

View file

@ -1,37 +0,0 @@
#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));
}