Merge branch 'wip'
This commit is contained in:
commit
bac66748ec
|
@ -34,14 +34,14 @@ EmptyLineBeforeAccessModifier: Always
|
||||||
FixNamespaceComments: false
|
FixNamespaceComments: false
|
||||||
IncludeBlocks: Regroup
|
IncludeBlocks: Regroup
|
||||||
IncludeCategories:
|
IncludeCategories:
|
||||||
- Regex: '<[[:alnum:].]+>'
|
- Regex: '^<fontconfig'
|
||||||
Priority: -10
|
|
||||||
- Regex: '^<fontconfig/'
|
|
||||||
Priority: -5
|
Priority: -5
|
||||||
- Regex: '^<SDL2/'
|
- Regex: '^<SDL'
|
||||||
Priority: -5
|
Priority: -5
|
||||||
- Regex: '^<basic_widgets/'
|
- Regex: '^<basic_widgets/'
|
||||||
Priority: 0
|
Priority: 0
|
||||||
|
- Regex: '<[[:alnum:]._]+>'
|
||||||
|
Priority: -10
|
||||||
- Regex: '^".*"$'
|
- Regex: '^".*"$'
|
||||||
Priority: 5
|
Priority: 5
|
||||||
IndentCaseLabels: true
|
IndentCaseLabels: true
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
Checks: 'bugprone-*,cert-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-misc-non-private-member-variables-in-classes,-bugprone-easily-swappable-parameters,-readability-braces-around-statements,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-named-parameter,-modernize-use-trailing-return-type'
|
Checks: 'bugprone-*,cert-*,cppcoreguidelines-*,misc-*,modernize-*,performance-*,portability-*,readability-*,-misc-non-private-member-variables-in-classes,-bugprone-easily-swappable-parameters,-readability-braces-around-statements,-cppcoreguidelines-non-private-member-variables-in-classes,-readability-named-parameter,-modernize-use-trailing-return-type,-cppcoreguidelines-avoid-magic-numbers'
|
||||||
FormatStyle: file
|
FormatStyle: file
|
||||||
|
|
123
README.md
123
README.md
|
@ -1,4 +1,125 @@
|
||||||
# sdl2_basic_widgets
|
# sdl2_basic_widgets
|
||||||
|
|
||||||
A (very) basic widget library for SDL applications.
|
A (very) basic and incomplete widget library for SDL applications.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
The build dependencies are SDL2, SDL_ttf and fontconfig dev libs
|
||||||
|
and headers. Meson is used as build system, so you'll need it too.
|
||||||
|
|
||||||
|
As the code use some C++20 features, you'll need a recent compiler
|
||||||
|
which support that version of the standard.
|
||||||
|
|
||||||
|
Then, inside the project root directory, run the following commands:
|
||||||
|
|
||||||
|
```ShellSession
|
||||||
|
$ meson build
|
||||||
|
$ meson compile -C build -j0
|
||||||
|
```
|
||||||
|
|
||||||
|
After that you'll have in the build directory the static library and the
|
||||||
|
example programs.
|
||||||
|
|
||||||
|
## API overview
|
||||||
|
|
||||||
|
The library API is divided in two main parts:
|
||||||
|
- the core, which provides some basic wrapping of
|
||||||
|
SDL, SDL_ttf and fontconfig functionalities.
|
||||||
|
- the widget API built on top of the core.
|
||||||
|
|
||||||
|
### Core
|
||||||
|
|
||||||
|
The main classes of the core are:
|
||||||
|
|
||||||
|
- [Font](inc/basic_widgets/core/font.hpp):
|
||||||
|
wraps TTF_Font object with the most common operations
|
||||||
|
of SDL_ttf. Also provide a helper function to
|
||||||
|
search system fonts by using fontconfig.
|
||||||
|
- [Renderer](inc/basic_widgets/core/renderer.hpp):
|
||||||
|
wraps SDL_Renderer object with common operation from
|
||||||
|
the SDL 2D accelerated rendering API.
|
||||||
|
- [Texture](inc/basic_widgets/core/texture.hpp):
|
||||||
|
wraps SDL_Texture object with common operations
|
||||||
|
to manipulate it.
|
||||||
|
|
||||||
|
Apart from the SDL API wrappers, some generic utility functions and
|
||||||
|
data types are defined:
|
||||||
|
|
||||||
|
- [Color](inc/basic_widgets/core/type/color.hpp):
|
||||||
|
wraps SDL_Color object and adds convenience operators
|
||||||
|
overloads to operate on all color channels and/or
|
||||||
|
alpha channel.
|
||||||
|
- [draw.hpp](inc/basic_widgets/core/draw.hpp):
|
||||||
|
2D drawing utilities. Currently, provides a couple of basic utility
|
||||||
|
functions for software rendered textures: circle rendering
|
||||||
|
and an antialising algorithm.
|
||||||
|
- [math.hpp](inc/basic_widgets/core/math.hpp): mostly provides
|
||||||
|
frequently used formulas to work with rectangles and points
|
||||||
|
on a 2D coordinate system.
|
||||||
|
- [concepts.hpp](inc/basic_widgets/core/type/concepts.hpp):
|
||||||
|
a few concept definitions for used for generic programming.
|
||||||
|
- [Size](inc/basic_widgets/core/type/size.hpp): representation
|
||||||
|
of a 2D size. There are also a few operator overloads to
|
||||||
|
operate on Size objects.
|
||||||
|
|
||||||
|
## Widgets
|
||||||
|
|
||||||
|
Widgets are classes that implements the common widget interface and
|
||||||
|
optionally one or more "handler" interfaces. Handlers are interfaces
|
||||||
|
for event or external resources handling.
|
||||||
|
|
||||||
|
These interfaces are:
|
||||||
|
|
||||||
|
- [Widget](inc/basic_widgets/w/base/widget.hpp): the common base
|
||||||
|
interface of every widget.
|
||||||
|
- [EventHandler](inc/basic_widgets/w/feat/event_handler.hpp): base
|
||||||
|
interface for event handlers.
|
||||||
|
- [FocusHandler](inc/basic_widgets/w/feat/focus_handler.hpp): base
|
||||||
|
interface for handler having a focus notion.
|
||||||
|
- [KeyboardHandler](inc/basic_widgets/w/feat/keyboard_handler.hpp):
|
||||||
|
interface for key and text input events handling.
|
||||||
|
- [MouseHandler](inc/basic_widgets/w/feat/mouse_handler.hpp):
|
||||||
|
interface for mouse events handling. Handle click and focus state
|
||||||
|
changes and also introduce a notion of "pushed" state.
|
||||||
|
- [FontHandler](inc/basic_widgets/w/feat/font_handler.hpp): interface
|
||||||
|
for widgets using a font. Declares methods to set the widget font and
|
||||||
|
set background and text color used for text rendering.
|
||||||
|
- [TextureHandler](inc/basic_widgets/w/feat/texture_handler.hpp): it exists
|
||||||
|
but I'm thinking now that it shouldn't.
|
||||||
|
|
||||||
|
Based on these interfaces, there's implementation for a few basic widgets.
|
||||||
|
Currently, the following ones are defined:
|
||||||
|
|
||||||
|
- [Button](inc/basic_widgets/w/button.hpp): a push button.
|
||||||
|
- [Caption](inc/basic_widgets/w/caption.hpp): a caption
|
||||||
|
that displays a rendered string. No multiline support.
|
||||||
|
- [Input<T>](inc/basic_widgets/w/base/input.hpp): base
|
||||||
|
class for widgets using an input field.
|
||||||
|
- [NumericInput<Numeric>](inc/basic_widgets/w/numeric_input.hpp) an
|
||||||
|
input widget for numeric values with two buttons to increase/decrease
|
||||||
|
the hold value.
|
||||||
|
|
||||||
|
There are also "containers" widgets which are widget that hold a collection of
|
||||||
|
widgets and manage their geometry:
|
||||||
|
|
||||||
|
- [Layout](inc/basic_widgets/w/base/layout.hpp): abstract class for container widgets.
|
||||||
|
- [AlignedLayout](inc/basic_widgets/w/aligned_layout.hpp): layout that is done by distributing
|
||||||
|
widgets on a horizontal or vertical row.
|
||||||
|
|
||||||
|
To create widget objects, the corresponding function from [widget_factory.hpp](inc/basic_widgets/w/widget_factory.hpp)
|
||||||
|
should be used. It's done this way for interface-implementation sake.
|
||||||
|
|
||||||
|
## Source code organization overview
|
||||||
|
|
||||||
|
The header and source directory hierarchies is organized in the following way:
|
||||||
|
|
||||||
|
- core: C→C++ wrappers and commonly used generic functions.
|
||||||
|
- core/type: basic data types and concepts.
|
||||||
|
- w: the widget library
|
||||||
|
- w/base: the abstract widget classes.
|
||||||
|
- w/feat: handlers (optional widget features)
|
||||||
|
|
||||||
|
Widget code intent to separate public API from implementation details. For that
|
||||||
|
a Widget has two classes, a pure virtual one (Widget) which is the public API and another
|
||||||
|
class (WidgetImpl) that implements the interface of the first one. Unless an access
|
||||||
|
to implementation details is required, the interface (pure virtual) type should
|
||||||
|
be used.
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <basic_widgets/w/button.hpp>
|
#include <basic_widgets/w/widget_factory.hpp>
|
||||||
|
|
||||||
#include "run.hpp"
|
#include "run.hpp"
|
||||||
|
|
||||||
using bwidgets::Button;
|
|
||||||
|
|
||||||
auto main() -> int
|
auto main() -> int
|
||||||
{
|
{
|
||||||
run_example<Button>([](auto* w, auto* f, auto x, auto y) {
|
run_example<bwidgets::Button>(
|
||||||
w->click_handler = [x, y](const SDL_MouseButtonEvent&) {
|
[]() { return bwidgets::create_button(); },
|
||||||
std::cout << "button(" << x << ',' << y << "):click!" << std::endl;
|
[](auto w, auto f, auto x, auto y) {
|
||||||
};
|
w->click_handler([x, y](const SDL_MouseButtonEvent&) {
|
||||||
w->font(f);
|
std::cout << "button(" << x << ',' << y << "):click!" << std::endl;
|
||||||
w->text("+");
|
});
|
||||||
});
|
w->font(f);
|
||||||
|
w->text("click me");
|
||||||
|
});
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
#include <basic_widgets/w/caption.hpp>
|
#include <basic_widgets/w/widget_factory.hpp>
|
||||||
|
|
||||||
#include "run.hpp"
|
#include "run.hpp"
|
||||||
|
|
||||||
using bwidgets::Caption;
|
|
||||||
|
|
||||||
auto main() -> int
|
auto main() -> int
|
||||||
{
|
{
|
||||||
run_example<Caption>(
|
run_example<bwidgets::Caption>([]() { return bwidgets::create_caption(); },
|
||||||
[](auto* w, auto* f, auto, auto) {
|
[](auto w, auto f, auto, auto) {
|
||||||
w->alignment = Caption::Alignment::CENTER;
|
w->alignment =
|
||||||
w->text("¡jello!");
|
bwidgets::Caption::Alignment::CENTER;
|
||||||
w->font(f);
|
w->text("¡jello!");
|
||||||
},
|
w->font(f);
|
||||||
4, 8); // NOLINT(readability-magic-numbers)
|
},
|
||||||
|
4, 8); // NOLINT(readability-magic-numbers)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,11 @@
|
||||||
|
|
||||||
auto main() -> int
|
auto main() -> int
|
||||||
{
|
{
|
||||||
run_example<Example>([](auto* w, auto*, auto x, auto y) {
|
run_example<Example>(
|
||||||
w->cycle_r = (x + 1) * 3000; // NOLINT(readability-magic-numbers)
|
[]() { return std::make_unique<Example>(); },
|
||||||
w->cycle_b = (y + 1) * 3000; // NOLINT(readability-magic-numbers)
|
[](auto w, auto, auto x, auto y) {
|
||||||
w->cycle_b = (1 + x + y) * (y + 1) * 400; // NOLINT(readability-magic-numbers)
|
w->cycle_r = (x + 1) * 3000; // NOLINT(readability-magic-numbers)
|
||||||
});
|
w->cycle_b = (y + 1) * 3000; // NOLINT(readability-magic-numbers)
|
||||||
|
w->cycle_b = (1 + x + y) * (y + 1) * 400; // NOLINT(readability-magic-numbers)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,19 @@
|
||||||
#ifndef BWIDGETS_EXAMPLES_EXAMPLE_WIDGET
|
#ifndef BWIDGETS_EXAMPLES_EXAMPLE_WIDGET
|
||||||
#define BWIDGETS_EXAMPLES_EXAMPLE_WIDGET
|
#define BWIDGETS_EXAMPLES_EXAMPLE_WIDGET
|
||||||
|
|
||||||
#include <SDL2/SDL_timer.h>
|
#include <chrono>
|
||||||
|
|
||||||
#include <basic_widgets/core/math.hpp>
|
#include <basic_widgets/core/math.hpp>
|
||||||
#include <basic_widgets/core/renderer.hpp>
|
#include <basic_widgets/core/renderer.hpp>
|
||||||
#include <basic_widgets/w/base/widget.hpp>
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
||||||
|
|
||||||
using bwidgets::Color;
|
using bwidgets::Color;
|
||||||
using bwidgets::rect_margin;
|
using bwidgets::rect_margin;
|
||||||
using bwidgets::Size;
|
using bwidgets::Size;
|
||||||
using bwidgets::Widget;
|
using bwidgets::WidgetImpl;
|
||||||
|
|
||||||
class Example final : public Widget
|
class Example final : public WidgetImpl
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
void _handle_geometry_change(const SDL_Rect& vp) noexcept override
|
void _handle_geometry_change(const SDL_Rect& vp) noexcept override
|
||||||
{
|
{
|
||||||
_widget_area = {2, 2, vp.w - 4, vp.h - 4};
|
_widget_area = {2, 2, vp.w - 4, vp.h - 4};
|
||||||
|
@ -22,12 +21,13 @@ private:
|
||||||
|
|
||||||
void _handle_rendering() override
|
void _handle_rendering() override
|
||||||
{
|
{
|
||||||
const auto now = SDL_GetTicks();
|
const auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
const uint8_t r = 255 * (now % cycle_r / (float)cycle_r); // NOLINT
|
std::chrono::steady_clock::now().time_since_epoch())
|
||||||
const uint8_t g = 255 * (now % cycle_g / (float)cycle_g); // NOLINT
|
.count();
|
||||||
const uint8_t b = 255 * (now % cycle_b / (float)cycle_b); // NOLINT
|
const uint8_t r = 255 * (now % cycle_r / (float)cycle_r); // NOLINT
|
||||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
const uint8_t g = 255 * (now % cycle_g / (float)cycle_g); // NOLINT
|
||||||
const Color base_color {r, g, b, 255};
|
const uint8_t b = 255 * (now % cycle_b / (float)cycle_b); // NOLINT
|
||||||
|
const Color base_color {r, g, b, SDL_ALPHA_OPAQUE};
|
||||||
|
|
||||||
const int border = 10;
|
const int border = 10;
|
||||||
for (auto i = 0; i < border; i += 3) {
|
for (auto i = 0; i < border; i += 3) {
|
||||||
|
@ -49,6 +49,8 @@ public:
|
||||||
unsigned int cycle_g {3500}; // NOLINT(readability-magic-numbers)
|
unsigned int cycle_g {3500}; // NOLINT(readability-magic-numbers)
|
||||||
unsigned int cycle_b {3500}; // NOLINT(readability-magic-numbers)
|
unsigned int cycle_b {3500}; // NOLINT(readability-magic-numbers)
|
||||||
|
|
||||||
|
using WidgetImpl::WidgetImpl;
|
||||||
|
|
||||||
[[nodiscard]] auto size() const noexcept -> Size override
|
[[nodiscard]] auto size() const noexcept -> Size override
|
||||||
{
|
{
|
||||||
return {128, 64}; // NOLINT(readability-magic-numbers)
|
return {128, 64}; // NOLINT(readability-magic-numbers)
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
#include <basic_widgets/w/numeric_input.hpp>
|
#include <basic_widgets/w/widget_factory.hpp>
|
||||||
|
|
||||||
#include "run.hpp"
|
#include "run.hpp"
|
||||||
|
|
||||||
using bwidgets::NumericInput;
|
|
||||||
|
|
||||||
auto main() -> int
|
auto main() -> int
|
||||||
{
|
{
|
||||||
run_example<NumericInput<float>>([](auto* w, auto* f, auto, auto) {
|
run_example<bwidgets::NumericInput<float>>(
|
||||||
w->font(f);
|
[]() { return bwidgets::create_input_float(); },
|
||||||
w->button_step = 0.5; // NOLINT(readability-magic-numbers)
|
[](auto w, auto f, auto, auto) {
|
||||||
w->value_range(-3.14, 8.5); // NOLINT(readability-magic-numbers)
|
w->font(f);
|
||||||
});
|
w->button_step = 0.5; // NOLINT(readability-magic-numbers)
|
||||||
|
w->value_range(-3.14, 8.5); // NOLINT(readability-magic-numbers)
|
||||||
|
});
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,58 +3,50 @@
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <stack>
|
|
||||||
|
|
||||||
#include <SDL2/SDL.h>
|
#include <basic_widgets/core/type/deleter.hpp>
|
||||||
#include <SDL2/SDL_ttf.h>
|
#include <basic_widgets/w/widget_factory.hpp>
|
||||||
|
|
||||||
#include <basic_widgets/core/font.hpp>
|
|
||||||
#include <basic_widgets/core/renderer.hpp>
|
|
||||||
#include <basic_widgets/w/aligned_layout.hpp>
|
|
||||||
#include <basic_widgets/w/base/widget.hpp>
|
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept WidgetType = std::derived_from<T, bwidgets::Widget>;
|
concept WidgetType = std::derived_from<T, bwidgets::Widget>;
|
||||||
|
|
||||||
template<WidgetType W>
|
template<WidgetType W>
|
||||||
void run_example(const std::function<void(W*, bwidgets::Font*, int, int)>& setup,
|
void run_example(
|
||||||
int w = 3, int h = 3)
|
std::function<std::unique_ptr<W>()> factory,
|
||||||
|
std::function<void(W*, const std::shared_ptr<bwidgets::Font>, int, int)> setup,
|
||||||
|
int w = 3, int h = 3)
|
||||||
{
|
{
|
||||||
std::stack<std::function<void()>> cleaners;
|
std::atexit([]() {
|
||||||
|
TTF_Quit();
|
||||||
|
SDL_Quit();
|
||||||
|
});
|
||||||
try {
|
try {
|
||||||
bwidgets::SDLError::success_or_throw(SDL_Init(SDL_INIT_VIDEO), __FILE__,
|
bwidgets::success_or_throw<bwidgets::SDLError>(SDL_Init(SDL_INIT_VIDEO));
|
||||||
__FUNCTION__, __LINE__);
|
bwidgets::success_or_throw<bwidgets::SDLError>(TTF_Init());
|
||||||
cleaners.emplace([]() { SDL_Quit(); });
|
|
||||||
bwidgets::SDLError::success_or_throw(TTF_Init(), __FILE__, __FUNCTION__,
|
|
||||||
__LINE__);
|
|
||||||
cleaners.emplace([]() { TTF_Quit(); });
|
|
||||||
|
|
||||||
const bwidgets::Size size_init {854, 480};
|
const bwidgets::Size size_init {854, 480};
|
||||||
auto* font {new bwidgets::Font(bwidgets::Font::find("Monospace"),
|
auto font =
|
||||||
16)}; // NOLINT(readability-magic-numbers)
|
std::make_shared<bwidgets::Font>(bwidgets::Font::find("Monospace"),
|
||||||
cleaners.emplace([font]() { delete font; });
|
16); // NOLINT(readability-magic-numbers)
|
||||||
auto* win {SDL_CreateWindow("basic_widgets example", SDL_WINDOWPOS_CENTERED,
|
|
||||||
SDL_WINDOWPOS_CENTERED, size_init.w, size_init.h,
|
auto win = std::unique_ptr<SDL_Window, bwidgets::Deleter>(
|
||||||
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE
|
bwidgets::ptr_or_throw<bwidgets::SDLError>(SDL_CreateWindow(
|
||||||
| SDL_WINDOW_UTILITY)};
|
"basic_widgets example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
|
||||||
cleaners.emplace([win]() { SDL_DestroyWindow(win); });
|
size_init.w, size_init.h,
|
||||||
auto* renderer {new bwidgets::Renderer(win, -1, SDL_RENDERER_ACCELERATED)};
|
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_UTILITY)));
|
||||||
cleaners.emplace([renderer]() { delete renderer; });
|
|
||||||
|
auto renderer =
|
||||||
|
std::make_shared<bwidgets::Renderer>(win.get(), -1, SDL_RENDERER_ACCELERATED);
|
||||||
renderer->blend_mode(SDL_BLENDMODE_BLEND);
|
renderer->blend_mode(SDL_BLENDMODE_BLEND);
|
||||||
|
|
||||||
auto* layout {
|
auto layout = bwidgets::create_horizontal_layout();
|
||||||
new bwidgets::AlignedLayout(bwidgets::AlignedLayout::Alignment::HORIZONTAL)};
|
|
||||||
cleaners.emplace([layout]() { delete layout; });
|
|
||||||
|
|
||||||
for (auto x = 0; x < w; x++) {
|
for (auto x = 0; x < w; x++) {
|
||||||
auto* col =
|
auto col = bwidgets::create_vertical_layout();
|
||||||
new bwidgets::AlignedLayout(bwidgets::AlignedLayout::Alignment::VERTICAL);
|
|
||||||
for (auto y = 0; y < h; y++) {
|
for (auto y = 0; y < h; y++) {
|
||||||
auto* widget = new W();
|
auto widget = factory();
|
||||||
setup(widget, font, x, y);
|
setup(widget.get(), font, x, y);
|
||||||
col->add_widget(widget);
|
col->add_widget(std::move(widget));
|
||||||
}
|
}
|
||||||
layout->add_widget(col);
|
layout->add_widget(std::move(col));
|
||||||
}
|
}
|
||||||
|
|
||||||
layout->renderer(renderer);
|
layout->renderer(renderer);
|
||||||
|
@ -85,11 +77,8 @@ void run_example(const std::function<void(W*, bwidgets::Font*, int, int)>& setup
|
||||||
layout->render();
|
layout->render();
|
||||||
renderer->present();
|
renderer->present();
|
||||||
}
|
}
|
||||||
while (!cleaners.empty()) cleaners.top()(), cleaners.pop();
|
} catch (const std::exception& e) {
|
||||||
} catch (const bwidgets::SDLError& e) {
|
std::cerr << "Error: " << e.what() << std::endl;
|
||||||
std::cerr << "Error: " << e.file << ":" << e.func << ":" << e.line << ": "
|
|
||||||
<< e.what << std::endl;
|
|
||||||
while (!cleaners.empty()) cleaners.top()(), cleaners.pop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#ifndef BWIDGETS_DRAW_HPP
|
#ifndef BWIDGETS_DRAW_HPP
|
||||||
#define BWIDGETS_DRAW_HPP
|
#define BWIDGETS_DRAW_HPP
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
struct SDL_PixelFormat;
|
struct SDL_PixelFormat;
|
||||||
struct SDL_Point;
|
struct SDL_Point;
|
||||||
|
@ -13,12 +13,21 @@ namespace bwidgets
|
||||||
class Renderer;
|
class Renderer;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
[[nodiscard]] auto aa(const Color&, int, float) noexcept -> Color;
|
// Add transparency to color base_color relative to the distance d from
|
||||||
[[nodiscard]] auto filled_circle(const Color&, int resolution, Renderer*,
|
// a limit to produce an AntiAliasing effect.
|
||||||
int aa_pixels = 3) -> Texture*;
|
// d >= 0 → full transparency;
|
||||||
void set_pixels_color(
|
// −(aa_pixels) <= d < 0 → smoothsteped transparency gradient;
|
||||||
Texture*,
|
// d < −(aa_pixels) → fully opaque.
|
||||||
const std::function<uint32_t(const SDL_Point&, const SDL_PixelFormat*)>&);
|
[[nodiscard]] auto aa(Color base_color, int aa_pixels, float d) noexcept -> Color;
|
||||||
|
// Render a filled circle texture of color c and diameter resolution with
|
||||||
|
// aa_pixels used for antialiasing.
|
||||||
|
[[nodiscard]] auto filled_circle(Color c, int resolution, const Renderer&,
|
||||||
|
int aa_pixels = 3) -> std::shared_ptr<Texture>;
|
||||||
|
// Set the color of every pixels of a Texture using the passed function to compute
|
||||||
|
// pixel color.
|
||||||
|
void
|
||||||
|
set_pixels_color(Texture*,
|
||||||
|
const std::function<uint32_t(SDL_Point, const SDL_PixelFormat&)>&);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
#ifndef BWIDGETS_ERROR_HELPER_HPP
|
||||||
|
#define BWIDGETS_ERROR_HELPER_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <basic_widgets/core/type/sdl_error.hpp>
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
auto SDL_GetError() -> const char*;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
// Throw a E exception with message w if ptr is null. Otherwise return ptr.
|
||||||
|
template<Exception E, typename T>
|
||||||
|
inline auto ptr_or_throw(T* const ptr, const std::string_view w = "unknown") -> T*
|
||||||
|
{
|
||||||
|
std::string what;
|
||||||
|
if constexpr (std::is_same_v<E, SDLError>)
|
||||||
|
what = w == "unknown" ? SDL_GetError() : w;
|
||||||
|
else what = w;
|
||||||
|
|
||||||
|
if (ptr == nullptr) throw E(what);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check a return code with the success function passed. If success return false
|
||||||
|
// throw an E Exception with message w.
|
||||||
|
template<Exception E, typename T>
|
||||||
|
inline auto success_or_throw(
|
||||||
|
const T code, const std::string_view w = "unknown",
|
||||||
|
const std::function<bool(T)>& success = [](const T code) { return code == 0; })
|
||||||
|
-> T
|
||||||
|
{
|
||||||
|
// If exception type is SDLError and no value or unknown was passed for w, get
|
||||||
|
// the last error message using SDL facilities.
|
||||||
|
if constexpr (std::is_same_v<E, SDLError>) {
|
||||||
|
const std::string what {w == "unknown" ? SDL_GetError() : w};
|
||||||
|
if (code < 0) throw E(what);
|
||||||
|
}
|
||||||
|
else if (!success(code)) throw E(w.data());
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,18 +1,23 @@
|
||||||
#ifndef BWIDGETS_FONT_HPP
|
#ifndef BWIDGETS_FONT_HPP
|
||||||
#define BWIDGETS_FONT_HPP
|
#define BWIDGETS_FONT_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <SDL2/SDL_ttf.h>
|
#include <SDL_ttf.h>
|
||||||
|
|
||||||
#include <basic_widgets/core/type/color.hpp>
|
#include <basic_widgets/core/type/color.hpp>
|
||||||
#include <basic_widgets/core/type/opaque_struct.hpp>
|
#include <basic_widgets/core/type/deleter.hpp>
|
||||||
#include <basic_widgets/core/type/size.hpp>
|
#include <basic_widgets/core/type/size.hpp>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
struct Font final : OpaqueStruct<TTF_Font>
|
// Wrap TTF_Font from SDL_ttf and provide a font finding function.
|
||||||
|
class Font final
|
||||||
{
|
{
|
||||||
|
const std::unique_ptr<TTF_Font, Deleter> _data;
|
||||||
|
|
||||||
|
public:
|
||||||
enum struct Hinting
|
enum struct Hinting
|
||||||
{
|
{
|
||||||
LIGHT = TTF_HINTING_LIGHT,
|
LIGHT = TTF_HINTING_LIGHT,
|
||||||
|
@ -37,8 +42,8 @@ namespace bwidgets
|
||||||
UNDERLINE = TTF_STYLE_UNDERLINE
|
UNDERLINE = TTF_STYLE_UNDERLINE
|
||||||
};
|
};
|
||||||
|
|
||||||
inline static const Color default_color_bg {255, 255, 255, SDL_ALPHA_OPAQUE};
|
static const Color default_color_bg;
|
||||||
inline static const Color default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
|
static const Color default_color_fg;
|
||||||
|
|
||||||
const int ascent;
|
const int ascent;
|
||||||
const int descent;
|
const int descent;
|
||||||
|
@ -49,12 +54,15 @@ namespace bwidgets
|
||||||
const int line_skip;
|
const int line_skip;
|
||||||
const std::string style_name;
|
const std::string style_name;
|
||||||
|
|
||||||
Font(TTF_Font*);
|
explicit Font(TTF_Font*);
|
||||||
Font(const std::string&, int);
|
// Load font at given path and with given size.
|
||||||
|
Font(std::string_view, int);
|
||||||
Font(const Font&) = delete;
|
Font(const Font&) = delete;
|
||||||
~Font() noexcept override;
|
Font(Font&&) = delete;
|
||||||
|
~Font() noexcept = default;
|
||||||
|
|
||||||
auto operator=(const Font&) -> Font& = delete;
|
auto operator=(const Font&) = delete;
|
||||||
|
auto operator=(Font&&) = delete;
|
||||||
|
|
||||||
[[nodiscard]] auto hinting() const noexcept -> Hinting;
|
[[nodiscard]] auto hinting() const noexcept -> Hinting;
|
||||||
auto hinting(Hinting) noexcept -> Font*;
|
auto hinting(Hinting) noexcept -> Font*;
|
||||||
|
@ -62,13 +70,15 @@ namespace bwidgets
|
||||||
auto kerning(bool) noexcept -> Font*;
|
auto kerning(bool) noexcept -> Font*;
|
||||||
[[nodiscard]] auto outline() const noexcept -> int;
|
[[nodiscard]] auto outline() const noexcept -> int;
|
||||||
auto outline(int) noexcept -> Font*;
|
auto outline(int) noexcept -> Font*;
|
||||||
auto render(RenderMode, const std::string&, const Color& fg = default_color_fg,
|
auto render(RenderMode, std::string_view, Color fg = default_color_fg,
|
||||||
const Color& bg = default_color_bg) -> SDL_Surface*;
|
Color bg = default_color_bg)
|
||||||
|
-> std::unique_ptr<SDL_Surface, Deleter>;
|
||||||
[[nodiscard]] auto style() const noexcept -> uint8_t;
|
[[nodiscard]] auto style() const noexcept -> uint8_t;
|
||||||
auto style(uint8_t) noexcept -> Font*;
|
auto style(uint8_t) noexcept -> Font*;
|
||||||
[[nodiscard]] auto text_size(const std::string&) const noexcept -> Size;
|
[[nodiscard]] auto text_size(std::string_view) const noexcept -> Size;
|
||||||
|
|
||||||
[[nodiscard]] static auto find(const std::string&) -> std::string;
|
// Get file path of the font best matching a give fontconfig pattern.
|
||||||
|
[[nodiscard]] static auto find(std::string_view) -> std::string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,36 +4,39 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
#include <SDL2/SDL_rect.h>
|
#include <SDL_rect.h>
|
||||||
|
|
||||||
#include <basic_widgets/core/type/color.hpp>
|
#include <basic_widgets/core/type/color.hpp>
|
||||||
#include <basic_widgets/core/type/concepts.hpp>
|
|
||||||
#include <basic_widgets/core/type/size.hpp>
|
#include <basic_widgets/core/type/size.hpp>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
|
// Get the start coordinate offset required to center a ligne with a length used_len on another
|
||||||
[[nodiscard]] static inline auto center_line(int available_len, int used_len) noexcept -> int
|
// line with a length available_len.
|
||||||
|
[[nodiscard]] inline auto center_line(const int available_len, const int used_len) noexcept -> int
|
||||||
{
|
{
|
||||||
return (available_len - used_len) / 2;
|
return (available_len - used_len) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static inline auto distance_sqrd(const SDL_Point& a, const SDL_Point& b) noexcept
|
// Get the squared distance of two points on a 2D plan. This avoid computing square root
|
||||||
|
// when for instance we need to compare two lenght but knowing the actual lenght is not
|
||||||
|
// needed.
|
||||||
|
[[nodiscard]] inline auto distance_sqrd(const SDL_Point a, const SDL_Point b) noexcept
|
||||||
-> float
|
-> float
|
||||||
{
|
{
|
||||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions)
|
return float(a.x - b.x) * float(a.x - b.x)
|
||||||
return (a.x - b.x) * (a.x - b.x)
|
+ float(a.y - b.y) * float(a.y - b.y);
|
||||||
+ (a.y - b.y) * (a.y - b.y);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
|
// Get distance of two points on a 2D plan.
|
||||||
[[nodiscard]] static inline auto distance(const SDL_Point& a, const SDL_Point& b) noexcept -> float
|
[[nodiscard]] inline auto distance(const SDL_Point a, const SDL_Point b) noexcept -> float
|
||||||
{
|
{
|
||||||
return std::sqrt(distance_sqrd(a, b));
|
return std::sqrt(distance_sqrd(a, b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Like std::lerp but for use with Color object. lerp function is applied only to enabled channels.
|
||||||
template<FloatingPoint F>
|
template<FloatingPoint F>
|
||||||
[[nodiscard]] static inline auto lerp(const Color& a, const Color& b, F x, bool op_alpha=false, bool op_color=true) noexcept -> Color
|
[[nodiscard]] inline auto lerp(const Color a, const Color b, const F x, const bool op_alpha=false, const bool op_color=true) noexcept -> Color
|
||||||
{
|
{
|
||||||
return {{
|
return {{
|
||||||
op_color ? (uint8_t)std::lerp(a().r, b().r, x) : a().r,
|
op_color ? (uint8_t)std::lerp(a().r, b().r, x) : a().r,
|
||||||
|
@ -43,45 +46,48 @@ namespace bwidgets
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reverse lerp.
|
||||||
template<Numeric N>
|
template<Numeric N>
|
||||||
[[nodiscard]] static inline auto linear(N x, N a, N b) noexcept -> float
|
[[nodiscard]] inline auto linear(const N x, const N a, const N b) noexcept -> float
|
||||||
{
|
{
|
||||||
return (float)(x - a) / (float)(b - a);
|
return (float)(x - a) / (float)(b - a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
|
// Check if a rectangle is completly inside of another rectangle.
|
||||||
[[nodiscard]] static inline auto rect_in_rect(const SDL_Rect& outer, const SDL_Rect& inner) noexcept
|
[[nodiscard]] inline auto rect_in_rect(const SDL_Rect& outer, const SDL_Rect& inner) noexcept
|
||||||
-> bool
|
-> bool
|
||||||
{
|
{
|
||||||
SDL_Point top_left {inner.x, inner.y};
|
const SDL_Point top_left {inner.x, inner.y};
|
||||||
SDL_Point bottom_right {inner.x + inner.w, inner.y + inner.h};
|
const SDL_Point bottom_right {inner.x + inner.w, inner.y + inner.h};
|
||||||
|
|
||||||
return (SDL_PointInRect(&top_left, &outer) == SDL_TRUE)
|
return (SDL_PointInRect(&top_left, &outer) == SDL_TRUE)
|
||||||
&& (SDL_PointInRect(&bottom_right, &outer) == SDL_TRUE);
|
&& (SDL_PointInRect(&bottom_right, &outer) == SDL_TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
|
// Get the rectangle inside r leaving margins of given size between inner and outer rectangle.
|
||||||
[[nodiscard]] static inline auto rect_margin(const SDL_Rect& r, const Size& margin) noexcept
|
[[nodiscard]] inline auto rect_margin(const SDL_Rect& r, const Size margin) noexcept
|
||||||
-> SDL_Rect
|
-> SDL_Rect
|
||||||
{
|
{
|
||||||
return {r.x + margin.w, r.y + margin.h, r.w - 2 * margin.w, r.h - 2 * margin.h};
|
return {r.x + margin.w, r.y + margin.h, r.w - 2 * margin.w, r.h - 2 * margin.h};
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static inline auto rect_offset(const SDL_Rect& r, const SDL_Point& offset) noexcept
|
// Get the rectangle obtained from adding offset to its origin coordinate.
|
||||||
|
[[nodiscard]] inline auto rect_offset(const SDL_Rect& r, const SDL_Point offset) noexcept
|
||||||
-> SDL_Rect
|
-> SDL_Rect
|
||||||
{
|
{
|
||||||
return {r.x + offset.x, r.y + offset.y, r.w, r.h};
|
return {r.x + offset.x, r.y + offset.y, r.w, r.h};
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOLINTNEXTLINE(clang-diagnostic-unused-function)
|
// Commodity function to use another rectangle as a size for the offset to be added to r.
|
||||||
[[nodiscard]] static inline auto rect_offset(const SDL_Rect& r, const SDL_Rect& offset) noexcept
|
[[nodiscard]] inline auto rect_offset(const SDL_Rect& r, const SDL_Rect& offset) noexcept
|
||||||
-> SDL_Rect
|
-> SDL_Rect
|
||||||
{
|
{
|
||||||
return rect_offset(r, SDL_Point {offset.x, offset.y});
|
return rect_offset(r, SDL_Point {offset.x, offset.y});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Standard smoothstep algorithm.
|
||||||
template<Numeric N>
|
template<Numeric N>
|
||||||
[[nodiscard]] static inline auto smoothstep(N x, N a, N b) noexcept -> float
|
[[nodiscard]] inline auto smoothstep(const N x, const N a, const N b) noexcept -> float
|
||||||
{
|
{
|
||||||
const float x_norm = linear(std::clamp<float>(x, a, b), a, b);
|
const float x_norm = linear(std::clamp<float>(x, a, b), a, b);
|
||||||
return 3 * x_norm * x_norm - 2 * x_norm * x_norm * x_norm;
|
return 3 * x_norm * x_norm - 2 * x_norm * x_norm * x_norm;
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
#ifndef BWIDGETS_RENDERER_HPP
|
#ifndef BWIDGETS_RENDERER_HPP
|
||||||
#define BWIDGETS_RENDERER_HPP
|
#define BWIDGETS_RENDERER_HPP
|
||||||
|
|
||||||
#include <cstdint>
|
#include <memory>
|
||||||
#include <vector>
|
#include <span>
|
||||||
|
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL_render.h>
|
||||||
|
|
||||||
|
#include <basic_widgets/core/error_helper.hpp>
|
||||||
#include <basic_widgets/core/type/color.hpp>
|
#include <basic_widgets/core/type/color.hpp>
|
||||||
#include <basic_widgets/core/type/opaque_struct.hpp>
|
#include <basic_widgets/core/type/deleter.hpp>
|
||||||
#include <basic_widgets/core/type/sdl_error.hpp>
|
|
||||||
#include <basic_widgets/core/type/size.hpp>
|
#include <basic_widgets/core/type/size.hpp>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
class Renderer final : public OpaqueStruct<SDL_Renderer>
|
// Wrap some of SDL's 2D accelerated rendering API.
|
||||||
|
class Renderer final
|
||||||
{
|
{
|
||||||
static inline auto _info(SDL_Renderer* r) -> SDL_RendererInfo
|
friend Texture;
|
||||||
|
|
||||||
|
const std::unique_ptr<SDL_Renderer, Deleter> _data;
|
||||||
|
|
||||||
|
static auto _info(SDL_Renderer* r) -> SDL_RendererInfo
|
||||||
{
|
{
|
||||||
SDL_RendererInfo info;
|
SDL_RendererInfo info;
|
||||||
SDLError::success_or_throw(SDL_GetRendererInfo(r, &info), __FILE__,
|
success_or_throw<SDLError>(SDL_GetRendererInfo(r, &info));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
@ -29,59 +33,61 @@ namespace bwidgets
|
||||||
public:
|
public:
|
||||||
const SDL_RendererInfo info;
|
const SDL_RendererInfo info;
|
||||||
|
|
||||||
Renderer(SDL_Renderer*);
|
explicit Renderer(SDL_Renderer*);
|
||||||
Renderer(SDL_Window*, int, uint32_t);
|
Renderer(SDL_Window*, int, uint32_t);
|
||||||
Renderer(const Renderer&) = delete;
|
Renderer(const Renderer&) = delete;
|
||||||
~Renderer() noexcept override;
|
Renderer(Renderer&&) = delete;
|
||||||
|
~Renderer() noexcept = default;
|
||||||
|
|
||||||
auto operator=(const Renderer&) = delete;
|
auto operator=(const Renderer&) = delete;
|
||||||
|
auto operator=(Renderer&&) = delete;
|
||||||
|
|
||||||
[[nodiscard]] auto blend_mode() -> SDL_BlendMode;
|
[[nodiscard]] auto blend_mode() -> SDL_BlendMode;
|
||||||
auto blend_mode(SDL_BlendMode) -> Renderer*;
|
auto blend_mode(SDL_BlendMode) -> Renderer*;
|
||||||
auto clear() -> Renderer*;
|
auto clear() -> Renderer*;
|
||||||
auto copy(Texture*, const SDL_Rect*, const SDL_Rect*) -> Renderer*;
|
auto copy(const Texture&, const SDL_Rect*, const SDL_Rect*) -> Renderer*;
|
||||||
[[nodiscard]] auto draw_color() -> Color;
|
[[nodiscard]] auto draw_color() -> Color;
|
||||||
auto draw_color(const Color&) -> Renderer*;
|
auto draw_color(Color) -> Renderer*;
|
||||||
auto draw_line(const SDL_Point&, const SDL_Point&) -> Renderer*;
|
auto draw_line(SDL_Point, SDL_Point) -> Renderer*;
|
||||||
auto draw_lines(const std::vector<SDL_Point>&) -> Renderer*;
|
auto draw_lines(std::span<SDL_Point>) -> Renderer*;
|
||||||
auto draw_point(const SDL_Point&) -> Renderer*;
|
auto draw_point(SDL_Point) -> Renderer*;
|
||||||
auto draw_points(const std::vector<SDL_Point>&) -> Renderer*;
|
auto draw_points(std::span<SDL_Point>) -> Renderer*;
|
||||||
auto draw_rect(const SDL_Rect*) -> Renderer*;
|
auto draw_rect(const SDL_Rect*) -> Renderer*;
|
||||||
auto draw_rects(const std::vector<SDL_Rect>&) -> Renderer*;
|
auto draw_rects(std::span<SDL_Rect>) -> Renderer*;
|
||||||
auto fill_rect(const SDL_Rect*) -> Renderer*;
|
auto fill_rect(const SDL_Rect*) -> Renderer*;
|
||||||
auto fill_rects(const std::vector<SDL_Rect>&) -> Renderer*;
|
auto fill_rects(std::span<SDL_Rect>) -> Renderer*;
|
||||||
[[nodiscard]] auto output_size() -> Size;
|
[[nodiscard]] auto output_size() -> Size;
|
||||||
void present() noexcept;
|
void present() noexcept;
|
||||||
[[nodiscard]] auto viewport() noexcept -> SDL_Rect;
|
[[nodiscard]] auto viewport() noexcept -> SDL_Rect;
|
||||||
auto viewport(const SDL_Rect*) -> Renderer*;
|
auto viewport(const SDL_Rect*) -> Renderer*;
|
||||||
|
|
||||||
inline auto* copy(Texture* t, const SDL_Rect* src, const SDL_Rect& dst)
|
auto copy(const Texture& t, const SDL_Rect* src, const SDL_Rect& dst)
|
||||||
{
|
{
|
||||||
const auto d = dst;
|
const auto d = dst;
|
||||||
return copy(t, src, &d);
|
return copy(t, src, &d);
|
||||||
}
|
}
|
||||||
inline auto* copy(Texture* t, const SDL_Rect& src, const SDL_Rect* dst)
|
auto copy(const Texture& t, const SDL_Rect& src, const SDL_Rect* dst)
|
||||||
{
|
{
|
||||||
const auto s = src;
|
const auto s = src;
|
||||||
return copy(t, &s, dst);
|
return copy(t, &s, dst);
|
||||||
}
|
}
|
||||||
inline auto* copy(Texture* t, const SDL_Rect& src, const SDL_Rect& dst)
|
auto copy(const Texture& t, const SDL_Rect& src, const SDL_Rect& dst)
|
||||||
{
|
{
|
||||||
const auto s = src;
|
const auto s = src;
|
||||||
const auto d = dst;
|
const auto d = dst;
|
||||||
return copy(t, &s, &d);
|
return copy(t, &s, &d);
|
||||||
}
|
}
|
||||||
inline auto* draw_rect(const SDL_Rect& r)
|
auto draw_rect(SDL_Rect&& r)
|
||||||
{
|
{
|
||||||
const auto rect = r;
|
const auto rect = r;
|
||||||
return draw_rect(&rect);
|
return draw_rect(&rect);
|
||||||
}
|
}
|
||||||
inline auto* fill_rect(const SDL_Rect& r)
|
auto fill_rect(SDL_Rect&& r)
|
||||||
{
|
{
|
||||||
const auto rect = r;
|
const auto rect = r;
|
||||||
return fill_rect(&rect);
|
return fill_rect(&rect);
|
||||||
}
|
}
|
||||||
inline auto* viewport(const SDL_Rect& vp)
|
auto viewport(SDL_Rect&& vp)
|
||||||
{
|
{
|
||||||
const auto v = vp;
|
const auto v = vp;
|
||||||
return viewport(&v);
|
return viewport(&v);
|
||||||
|
|
|
@ -1,54 +1,62 @@
|
||||||
#ifndef BWIDGETS_TEXTURE_HPP
|
#ifndef BWIDGETS_TEXTURE_HPP
|
||||||
#define BWIDGETS_TEXTURE_HPP
|
#define BWIDGETS_TEXTURE_HPP
|
||||||
|
|
||||||
#include <cstdint>
|
#include <memory>
|
||||||
|
|
||||||
#include <SDL2/SDL_pixels.h>
|
#include <SDL_pixels.h>
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL_render.h>
|
||||||
|
|
||||||
|
#include <basic_widgets/core/error_helper.hpp>
|
||||||
#include <basic_widgets/core/type/color.hpp>
|
#include <basic_widgets/core/type/color.hpp>
|
||||||
#include <basic_widgets/core/type/opaque_struct.hpp>
|
#include <basic_widgets/core/type/deleter.hpp>
|
||||||
#include <basic_widgets/core/type/sdl_error.hpp>
|
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class Renderer;
|
class Renderer;
|
||||||
|
|
||||||
class Texture final : public OpaqueStruct<SDL_Texture>
|
// Wrap most of the texture functions of SDL 2D rendering API.
|
||||||
|
class Texture final
|
||||||
{
|
{
|
||||||
struct Attr
|
friend Renderer;
|
||||||
|
|
||||||
|
struct Attr final
|
||||||
{
|
{
|
||||||
uint32_t format_raw;
|
uint32_t format_raw;
|
||||||
SDL_PixelFormat* format;
|
SDL_PixelFormat* format;
|
||||||
SDL_TextureAccess access;
|
SDL_TextureAccess access;
|
||||||
int w, h;
|
int w, h;
|
||||||
} _attributes {};
|
};
|
||||||
|
|
||||||
|
const Attr _attributes;
|
||||||
|
const std::unique_ptr<SDL_Texture, Deleter> _data;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Texture(SDL_Texture*);
|
explicit Texture(SDL_Texture*);
|
||||||
Texture(Renderer* r, SDL_PixelFormatEnum f, SDL_TextureAccess a, int w, int h);
|
Texture(const Renderer&, SDL_PixelFormatEnum, SDL_TextureAccess, int, int);
|
||||||
Texture(Renderer*, SDL_Surface*);
|
Texture(const Renderer&, SDL_Surface*);
|
||||||
Texture(const Texture&) = delete;
|
Texture(const Texture&) = delete;
|
||||||
~Texture() noexcept override;
|
Texture(Texture&&) = delete;
|
||||||
|
~Texture() noexcept;
|
||||||
|
|
||||||
auto operator=(const Texture&) = delete;
|
auto operator=(const Texture&) = delete;
|
||||||
|
auto operator=(Texture&&) = delete;
|
||||||
|
|
||||||
[[nodiscard]] auto alpha_mode() -> uint8_t;
|
[[nodiscard]] auto alpha_mode() -> uint8_t;
|
||||||
auto alpha_mode(uint8_t) -> Texture*;
|
auto alpha_mode(uint8_t) -> Texture*;
|
||||||
[[nodiscard]] auto blend_mode() -> SDL_BlendMode;
|
[[nodiscard]] auto blend_mode() -> SDL_BlendMode;
|
||||||
auto blend_mode(SDL_BlendMode) -> Texture*;
|
auto blend_mode(SDL_BlendMode) -> Texture*;
|
||||||
[[nodiscard]] auto color_mode() -> Color;
|
[[nodiscard]] auto color_mode() -> Color;
|
||||||
auto color_mode(const Color&) -> Texture*;
|
auto color_mode(Color) -> Texture*;
|
||||||
[[nodiscard]] auto scale_mode() -> SDL_ScaleMode;
|
[[nodiscard]] auto scale_mode() -> SDL_ScaleMode;
|
||||||
auto scale_mode(SDL_ScaleMode) -> Texture*;
|
auto scale_mode(SDL_ScaleMode) -> Texture*;
|
||||||
auto update(SDL_Rect*, const void*, int) -> Texture*;
|
auto update(const SDL_Rect*, const void*, int) -> Texture*;
|
||||||
|
|
||||||
[[nodiscard]] inline const auto& attributes() const noexcept
|
[[nodiscard]] const auto& attributes() const noexcept
|
||||||
{
|
{
|
||||||
return _attributes;
|
return _attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline auto update(const SDL_Rect& r, const void* pix, int pitch)
|
auto update(SDL_Rect&& r, const void* pix, int pitch)
|
||||||
{
|
{
|
||||||
SDL_Rect rect = r;
|
SDL_Rect rect = r;
|
||||||
update(&rect, pix, pitch);
|
update(&rect, pix, pitch);
|
||||||
|
@ -56,15 +64,14 @@ namespace bwidgets
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] static inline auto attributes(SDL_Texture* t)
|
[[nodiscard]] static auto attributes(SDL_Texture* t)
|
||||||
{
|
{
|
||||||
Attr attr {};
|
Attr attr {};
|
||||||
SDLError::success_or_throw(SDL_QueryTexture(t, &attr.format_raw,
|
success_or_throw<SDLError>(
|
||||||
(int*)&attr.access, &attr.w,
|
SDL_QueryTexture(t, &attr.format_raw,
|
||||||
&attr.h),
|
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
|
||||||
__FILE__, __FUNCTION__, __LINE__);
|
(int*)&attr.access, &attr.w, &attr.h));
|
||||||
attr.format = SDLError::ptr_or_throw(SDL_AllocFormat(attr.format_raw),
|
attr.format = ptr_or_throw<SDLError>(SDL_AllocFormat(attr.format_raw));
|
||||||
__FILE__, __FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return attr;
|
return attr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,136 +1,149 @@
|
||||||
#ifndef BWIDGETS_COLOR_HPP
|
#ifndef BWIDGETS_COLOR_HPP
|
||||||
#define BWIDGETS_COLOR_HPP
|
#define BWIDGETS_COLOR_HPP
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cmath>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <SDL2/SDL_assert.h>
|
#include <SDL_pixels.h>
|
||||||
#include <SDL2/SDL_pixels.h>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/type/concepts.hpp>
|
#include <basic_widgets/core/type/concepts.hpp>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
|
// Wrap SDL_Color to provide operator overloads.
|
||||||
class Color final
|
class Color final
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
[[nodiscard]] inline auto& operator()() noexcept
|
SDL_Color sdl_type;
|
||||||
|
|
||||||
|
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a, bool op_on_alpha = false,
|
||||||
|
bool op_on_colors = true) noexcept
|
||||||
|
: sdl_type {r, g, b, a},
|
||||||
|
_operate_on_alpha {op_on_alpha},
|
||||||
|
_operate_on_colors {op_on_colors}
|
||||||
|
{}
|
||||||
|
Color(SDL_Color c = {}, bool op_on_alpha = false,
|
||||||
|
bool op_on_colors = true) noexcept
|
||||||
|
: sdl_type(c), _operate_on_alpha(op_on_alpha), _operate_on_colors(op_on_colors)
|
||||||
|
{}
|
||||||
|
Color(const Color&) noexcept = default;
|
||||||
|
Color(Color&&) noexcept = default;
|
||||||
|
~Color() = default;
|
||||||
|
|
||||||
|
// Enable/disable operations on alpha channel. Disabled by default on
|
||||||
|
// construction.
|
||||||
|
[[nodiscard]] auto alpha(const bool state = true) const noexcept -> Color
|
||||||
|
{
|
||||||
|
return {sdl_type, _operate_on_colors, state};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable/disable operations on color channels.Enabled by default on
|
||||||
|
// construction.
|
||||||
|
[[nodiscard]] auto colors(const bool state = true) const noexcept -> Color
|
||||||
|
{
|
||||||
|
return {sdl_type, state, _operate_on_alpha};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get reference to the wrapped SDL native object.
|
||||||
|
[[nodiscard]] auto& operator()() noexcept
|
||||||
{
|
{
|
||||||
return sdl_type;
|
return sdl_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] inline const auto& operator()() const noexcept
|
[[nodiscard]] const auto& operator()() const noexcept
|
||||||
{
|
{
|
||||||
return sdl_type;
|
return sdl_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add operand to enabled channels.
|
||||||
|
template<Numeric N>
|
||||||
|
[[nodiscard]] auto operator+(const N operand) const noexcept
|
||||||
|
{
|
||||||
|
return _operate(operand, [](N a, N b) { return a + b; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Substract operand from enabled channels.
|
||||||
|
template<Numeric N>
|
||||||
|
[[nodiscard]] auto operator-(const N operand) const noexcept
|
||||||
|
{
|
||||||
|
return _operate(operand, [](N a, N b) { return a - b; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply enabled channel values by opererand.
|
||||||
|
template<Numeric N>
|
||||||
|
[[nodiscard]] auto operator*(const N operand) const noexcept
|
||||||
|
{
|
||||||
|
return _operate(operand, [](N a, N b) { return a * b; });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Divide enabled channel values by operand.
|
||||||
|
template<Numeric N>
|
||||||
|
[[nodiscard]] auto operator/(const N operand) const
|
||||||
|
{
|
||||||
|
if (operand == 0) throw std::domain_error("division by zero.");
|
||||||
|
|
||||||
|
return _operate<N>(operand, [](N a, N b) { return a / b; });
|
||||||
|
}
|
||||||
|
|
||||||
|
auto operator=(const Color& c) noexcept -> Color& = default;
|
||||||
|
auto operator=(Color&&) noexcept -> Color& = default;
|
||||||
|
|
||||||
|
auto& operator=(SDL_Color c) noexcept
|
||||||
|
{
|
||||||
|
sdl_type = c;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare for equality of the enabled channel values between two Color
|
||||||
|
// instances.
|
||||||
|
[[nodiscard]] auto operator==(const Color other) const noexcept
|
||||||
|
{
|
||||||
|
return (_operate_on_colors && sdl_type.r == other().r
|
||||||
|
&& sdl_type.g == other().g && sdl_type.b == other().b)
|
||||||
|
|| (_operate_on_alpha && sdl_type.a == other().a);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare for inequality of the enabled channel values between two Color
|
||||||
|
// instances.
|
||||||
|
[[nodiscard]] auto operator!=(const Color other) const noexcept
|
||||||
|
{
|
||||||
|
return (sdl_type.r != other().r || sdl_type.g != other().g
|
||||||
|
|| sdl_type.b != other().b)
|
||||||
|
&& ((_operate_on_alpha && sdl_type.a != other().a)
|
||||||
|
|| !_operate_on_alpha);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool _operate_on_alpha;
|
bool _operate_on_alpha;
|
||||||
bool _operate_on_colors;
|
bool _operate_on_colors;
|
||||||
|
|
||||||
template<Numeric N>
|
template<Numeric N>
|
||||||
inline auto
|
auto _operate(const N operand, const std::function<N(N, N)>& operator_) const
|
||||||
_operate(const N& operand,
|
|
||||||
const std::function<N(const N&, const N&)>& operator_) const noexcept
|
|
||||||
{
|
{
|
||||||
|
const auto overunderflow_check = [](auto x) {
|
||||||
|
if (x > (uint8_t)x) throw std::overflow_error("uint8_t overflow.");
|
||||||
|
if (x < (uint8_t)x) throw std::underflow_error("uint8_t underflow.");
|
||||||
|
};
|
||||||
|
|
||||||
Color c(sdl_type, _operate_on_alpha, _operate_on_colors);
|
Color c(sdl_type, _operate_on_alpha, _operate_on_colors);
|
||||||
if (_operate_on_alpha) {
|
if (_operate_on_alpha) {
|
||||||
auto a = std::round(operator_(c().a, operand));
|
const auto a = std::round(operator_(c().a, operand));
|
||||||
SDL_assert_release(a == (uint8_t)a); // NOLINT
|
overunderflow_check(a);
|
||||||
c().a = (uint8_t)a;
|
c().a = (uint8_t)a;
|
||||||
}
|
}
|
||||||
if (_operate_on_colors) {
|
if (_operate_on_colors) {
|
||||||
auto r = std::round(operator_(c().r, operand));
|
const auto r = std::round(operator_(c().r, operand));
|
||||||
auto g = std::round(operator_(c().g, operand));
|
const auto g = std::round(operator_(c().g, operand));
|
||||||
auto b = std::round(operator_(c().b, operand));
|
const auto b = std::round(operator_(c().b, operand));
|
||||||
// NOLINTNEXTLINE
|
for (const auto x : {r, g, b}) overunderflow_check(x);
|
||||||
SDL_assert_release(r == (uint8_t)r && g == (uint8_t)g
|
|
||||||
&& b == (uint8_t)b);
|
|
||||||
c().r = (uint8_t)r;
|
c().r = (uint8_t)r;
|
||||||
c().g = (uint8_t)g;
|
c().g = (uint8_t)g;
|
||||||
c().b = (uint8_t)b;
|
c().b = (uint8_t)b;
|
||||||
}
|
}
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
|
||||||
SDL_Color sdl_type;
|
|
||||||
|
|
||||||
inline Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a,
|
|
||||||
bool op_on_alpha = false, bool op_on_colors = true) noexcept
|
|
||||||
: _operate_on_alpha(op_on_alpha),
|
|
||||||
_operate_on_colors(op_on_colors),
|
|
||||||
sdl_type({r, g, b, a})
|
|
||||||
{}
|
|
||||||
inline Color(const SDL_Color& c = {}, bool op_on_alpha = false,
|
|
||||||
bool op_on_colors = true) noexcept
|
|
||||||
: _operate_on_alpha(op_on_alpha), _operate_on_colors(op_on_colors), sdl_type(c)
|
|
||||||
{}
|
|
||||||
inline Color(const Color&) noexcept = default;
|
|
||||||
|
|
||||||
[[nodiscard]] inline auto alpha() const noexcept -> Color
|
|
||||||
{
|
|
||||||
return {sdl_type, _operate_on_colors, true};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] inline auto colors() const noexcept -> Color
|
|
||||||
{
|
|
||||||
return {sdl_type, true, _operate_on_alpha};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Numeric N>
|
|
||||||
[[nodiscard]] inline auto operator+(const N& operand) const noexcept
|
|
||||||
{
|
|
||||||
return _operate(operand, [](const N& a, const N& b) { return a + b; });
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Numeric N>
|
|
||||||
[[nodiscard]] inline auto operator-(const N& operand) const noexcept
|
|
||||||
{
|
|
||||||
return _operate(operand, [](const N& a, const N& b) { return a - b; });
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Numeric N>
|
|
||||||
[[nodiscard]] inline auto operator*(const N& operand) const noexcept
|
|
||||||
{
|
|
||||||
return _operate(operand, [](const N& a, const N& b) { return a * b; });
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Numeric N>
|
|
||||||
[[nodiscard]] inline auto operator/(const N& operand) const noexcept
|
|
||||||
{
|
|
||||||
SDL_assert(operand != 0); // NOLINT
|
|
||||||
return _operate<N>(operand, [](const N& a, const N& b) { return a / b; });
|
|
||||||
}
|
|
||||||
|
|
||||||
inline auto& operator=(const Color& c) noexcept
|
|
||||||
{
|
|
||||||
if (this != &c) {
|
|
||||||
sdl_type = c.sdl_type;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline auto& operator=(const SDL_Color& c) noexcept
|
|
||||||
{
|
|
||||||
sdl_type = c;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] inline auto operator==(const Color& other) const noexcept
|
|
||||||
{
|
|
||||||
return (_operate_on_colors && sdl_type.r == other().r
|
|
||||||
&& sdl_type.g == other().g && sdl_type.b == other().b)
|
|
||||||
|| (_operate_on_alpha && sdl_type.a == other().a);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] inline auto operator!=(const Color& other) const noexcept
|
|
||||||
{
|
|
||||||
return (sdl_type.r != other().r || sdl_type.g != other().g
|
|
||||||
|| sdl_type.b != other().b)
|
|
||||||
&& ((_operate_on_alpha && sdl_type.a != other().a)
|
|
||||||
|| !_operate_on_alpha);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
#ifndef BWIDGETS_CONCEPTS_HPP_
|
#ifndef BWIDGETS_CONCEPTS_HPP
|
||||||
#define BWIDGETS_CONCEPTS_HPP_
|
#define BWIDGETS_CONCEPTS_HPP
|
||||||
|
|
||||||
#include <ostream>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
|
// T can be converted to string with to_string.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept CanToString = requires(T value)
|
concept CanToString = requires(T value)
|
||||||
{
|
{
|
||||||
std::to_string(value);
|
std::to_string(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// T is a floating point type.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept FloatingPoint = std::is_floating_point_v<T>;
|
concept FloatingPoint = std::is_floating_point_v<T>;
|
||||||
|
|
||||||
|
// T can be any numeric type.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept Numeric = std::is_arithmetic_v<T>;
|
concept Numeric = std::is_arithmetic_v<T>;
|
||||||
|
|
||||||
|
// T can be converted to string by using ostream.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept Printable = requires(T value)
|
concept Printable = requires(T value)
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
#ifndef BWIDGETS_OPAQUE_STRUCT_HPP
|
||||||
|
#define BWIDGETS_OPAQUE_STRUCT_HPP
|
||||||
|
|
||||||
|
#include <fontconfig/fontconfig.h>
|
||||||
|
#include <SDL_render.h>
|
||||||
|
#include <SDL_surface.h>
|
||||||
|
#include <SDL_ttf.h>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
// Deleter type for fancy pointers.
|
||||||
|
struct Deleter
|
||||||
|
{
|
||||||
|
void operator()(FcConfig* ptr)
|
||||||
|
{
|
||||||
|
FcConfigDestroy(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(FcPattern* ptr)
|
||||||
|
{
|
||||||
|
FcPatternDestroy(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(SDL_Renderer* ptr)
|
||||||
|
{
|
||||||
|
SDL_DestroyRenderer(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(SDL_Surface* ptr)
|
||||||
|
{
|
||||||
|
SDL_FreeSurface(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(SDL_Texture* ptr)
|
||||||
|
{
|
||||||
|
SDL_DestroyTexture(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(SDL_Window* ptr)
|
||||||
|
{
|
||||||
|
SDL_DestroyWindow(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator()(TTF_Font* ptr)
|
||||||
|
{
|
||||||
|
TTF_CloseFont(ptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,47 +1,22 @@
|
||||||
#ifndef BWIDGETS_EXCEPTION_HPP
|
#ifndef BWIDGETS_EXCEPTION_HPP
|
||||||
#define BWIDGETS_EXCEPTION_HPP
|
#define BWIDGETS_EXCEPTION_HPP
|
||||||
|
|
||||||
#include <any>
|
#include <stdexcept>
|
||||||
#include <exception>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/type/concepts.hpp>
|
#include <basic_widgets/core/type/concepts.hpp>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
struct BaseException : std::exception
|
// Base type for custom runtime errors.
|
||||||
|
class BaseException : std::runtime_error
|
||||||
{
|
{
|
||||||
const char* file;
|
protected:
|
||||||
const char* func;
|
using runtime_error::runtime_error;
|
||||||
const int line;
|
|
||||||
const char* what;
|
|
||||||
|
|
||||||
BaseException(const char* file, const char* func, int l, const char* w) noexcept
|
|
||||||
: file(file), func(func), line(l), what(w)
|
|
||||||
{}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// T derives from BaseException.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
concept Exception = std::derived_from<T, BaseException>;
|
concept Exception = std::derived_from<T, BaseException>;
|
||||||
|
|
||||||
template<Exception E, typename T>
|
|
||||||
static inline auto ptr_or_throw(T* ptr, const char* file, const char* func, int l,
|
|
||||||
const char* w = nullptr) -> T*
|
|
||||||
{
|
|
||||||
if (ptr == nullptr) throw E(file, func, l, w);
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Exception E, typename T>
|
|
||||||
static inline auto success_or_throw(
|
|
||||||
T code, const char* file, const char* func, int l, const char* w = nullptr,
|
|
||||||
std::function<bool(const T&)> success = [](const T& code) { return code == 0; })
|
|
||||||
-> T
|
|
||||||
{
|
|
||||||
if (!success(code)) throw E(file, func, l, w);
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -5,11 +5,10 @@
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
|
// Exception type for fontconfig errors
|
||||||
struct FCError final : BaseException
|
struct FCError final : BaseException
|
||||||
{
|
{
|
||||||
FCError(const char* file, const char* func, int l, const char* w = nullptr)
|
using BaseException::BaseException;
|
||||||
: BaseException(file, func, l, w)
|
|
||||||
{}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
#ifndef BWIDGETS_OPAQUE_STRUCT_HPP
|
|
||||||
#define BWIDGETS_OPAQUE_STRUCT_HPP
|
|
||||||
|
|
||||||
namespace bwidgets
|
|
||||||
{
|
|
||||||
template<typename T>
|
|
||||||
class OpaqueStruct
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
T* _c_pod;
|
|
||||||
|
|
||||||
public:
|
|
||||||
OpaqueStruct(T* ptr = nullptr) : _c_pod(ptr) {}
|
|
||||||
virtual ~OpaqueStruct<T>() noexcept = default;
|
|
||||||
|
|
||||||
// NOLINTNEXTLINE(modernize-use-trailing-return-type)
|
|
||||||
[[nodiscard]] inline auto& operator()()
|
|
||||||
{
|
|
||||||
return _c_pod;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
template<typename... Ts>
|
|
||||||
void discard(Ts*&... ptrs)
|
|
||||||
{
|
|
||||||
((delete ptrs), ...);
|
|
||||||
((ptrs = nullptr), ...);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
|
@ -3,40 +3,12 @@
|
||||||
|
|
||||||
#include <basic_widgets/core/type/exception.hpp>
|
#include <basic_widgets/core/type/exception.hpp>
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
auto SDL_GetError() -> const char*;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
|
// Custom exception type for SDL errors.
|
||||||
struct SDLError final : BaseException
|
struct SDLError final : BaseException
|
||||||
{
|
{
|
||||||
SDLError(const char* file, const char* func, const int l,
|
using BaseException::BaseException;
|
||||||
const char* w = nullptr) noexcept
|
|
||||||
: BaseException(file, func, l, w)
|
|
||||||
{}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
[[nodiscard]] static inline auto ptr_or_throw(T* ptr, const char* file,
|
|
||||||
const char* func, int l,
|
|
||||||
const char* w = nullptr) -> T*
|
|
||||||
{
|
|
||||||
if (!ptr) {
|
|
||||||
if (!w) w = SDL_GetError();
|
|
||||||
throw SDLError(file, func, l, w);
|
|
||||||
}
|
|
||||||
return ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline auto success_or_throw(int code, const char* file, const char* func,
|
|
||||||
int l, const char* w = nullptr) -> int
|
|
||||||
{
|
|
||||||
if (code < 0) {
|
|
||||||
if (w == nullptr) w = SDL_GetError();
|
|
||||||
throw SDLError(file, func, l, w);
|
|
||||||
}
|
|
||||||
return code;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,30 +5,37 @@
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
|
// Represent size of a 2D surface.
|
||||||
struct Size
|
struct Size
|
||||||
{
|
{
|
||||||
int w;
|
int w;
|
||||||
int h;
|
int h;
|
||||||
|
|
||||||
[[nodiscard]] inline auto operator-() const noexcept -> Size
|
|
||||||
{
|
|
||||||
return {-w, -h};
|
|
||||||
}
|
|
||||||
[[nodiscard]] inline auto operator+(const Size& s) const noexcept -> Size
|
|
||||||
{
|
|
||||||
return {w + s.w, h + s.h};
|
|
||||||
}
|
|
||||||
[[nodiscard]] inline auto operator-(const Size& s) const noexcept -> Size
|
|
||||||
{
|
|
||||||
return {w - s.w, h - s.h};
|
|
||||||
}
|
|
||||||
|
|
||||||
template<Numeric N>
|
|
||||||
[[nodiscard]] inline auto operator*(N a) const noexcept -> Size
|
|
||||||
{
|
|
||||||
return {a * w, a * h};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Addition the dimensions of two Size objects.
|
||||||
|
[[nodiscard]] inline auto operator+(const Size a, const Size b) noexcept -> Size
|
||||||
|
{
|
||||||
|
return {a.w + b.w, a.h + b.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Substract dimensions of a Size object from another.
|
||||||
|
[[nodiscard]] inline auto operator-(const Size a, const Size b) noexcept -> Size
|
||||||
|
{
|
||||||
|
return {a.w - b.w, a.h - b.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiply dimensions of a Size instance by a number.
|
||||||
|
template<Numeric N>
|
||||||
|
[[nodiscard]] inline auto operator*(const Size a, const N b) noexcept -> Size
|
||||||
|
{
|
||||||
|
return {a.w * b, a.h * b};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<Numeric N>
|
||||||
|
[[nodiscard]] inline auto operator*(const N a, const Size b) noexcept -> Size
|
||||||
|
{
|
||||||
|
return b * a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -5,21 +5,17 @@
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class AlignedLayout final : public Layout
|
enum struct LayoutAlignment
|
||||||
{
|
{
|
||||||
private:
|
HORIZONTAL,
|
||||||
void _update_layout(const SDL_Rect&) override;
|
VERTICAL
|
||||||
|
};
|
||||||
|
|
||||||
|
// Align vertically or horizontally widgets.
|
||||||
|
class AlignedLayout : public virtual Layout
|
||||||
|
{
|
||||||
public:
|
public:
|
||||||
enum struct Alignment
|
using Layout::Layout;
|
||||||
{
|
|
||||||
HORIZONTAL,
|
|
||||||
VERTICAL
|
|
||||||
} alignment;
|
|
||||||
|
|
||||||
AlignedLayout(Alignment align) noexcept : alignment(align) {}
|
|
||||||
|
|
||||||
[[nodiscard]] auto size() const noexcept -> Size override;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
#ifndef BWIDGETS_ALIGNED_LAYOUT_IMPL_HPP
|
||||||
|
#define BWIDGETS_ALIGNED_LAYOUT_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/core/math.hpp>
|
||||||
|
#include <basic_widgets/w/aligned_layout.hpp>
|
||||||
|
#include <basic_widgets/w/base/layout_impl.hpp>
|
||||||
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
template<LayoutAlignment alignment>
|
||||||
|
class AlignedLayoutImpl final : public virtual AlignedLayout,
|
||||||
|
public virtual LayoutImpl,
|
||||||
|
public virtual WidgetImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using LayoutImpl::LayoutImpl;
|
||||||
|
|
||||||
|
// Return smallest usable size.
|
||||||
|
[[nodiscard]] auto size() const noexcept -> Size override
|
||||||
|
{
|
||||||
|
Size min_size {0, 0};
|
||||||
|
|
||||||
|
if constexpr (alignment == LayoutAlignment::HORIZONTAL) {
|
||||||
|
for (const auto& w : _widgets) {
|
||||||
|
if (w->size().w > min_size.w) min_size.w = w->size().w;
|
||||||
|
if (w->size().h > min_size.h) min_size.h = w->size().h;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {(int)_widgets.size() * min_size.w
|
||||||
|
+ ((int)_widgets.size() + 1) * margins.w,
|
||||||
|
min_size.h + 2 * margins.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical
|
||||||
|
for (const auto& w : _widgets) {
|
||||||
|
if (w->size().w > min_size.w) min_size.w = w->size().w;
|
||||||
|
|
||||||
|
min_size.h += w->size().h;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {min_size.w + 2 * margins.w,
|
||||||
|
min_size.h + ((int)_widgets.size() + 1) * margins.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _update_layout(const SDL_Rect& vp) override
|
||||||
|
{
|
||||||
|
if constexpr (alignment == LayoutAlignment::HORIZONTAL) {
|
||||||
|
const int widget_width =
|
||||||
|
(vp.w - ((int)_widgets.size() + 1) * margins.w) / (int)_widgets.size();
|
||||||
|
|
||||||
|
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
|
||||||
|
const auto& w {_widgets[i]};
|
||||||
|
w->viewport({vp.x + margins.w + (int)i * (widget_width + margins.w),
|
||||||
|
vp.y + center_line(vp.h, w->size().h), widget_width,
|
||||||
|
w->size().h});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else { // Vertical
|
||||||
|
int offset = 0;
|
||||||
|
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
|
||||||
|
const auto& w {_widgets[i]};
|
||||||
|
w->viewport({
|
||||||
|
vp.x,
|
||||||
|
vp.y + ((int)i + 1) * margins.h + offset,
|
||||||
|
vp.w,
|
||||||
|
w->size().h,
|
||||||
|
});
|
||||||
|
offset += w->size().h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,18 +1,9 @@
|
||||||
#ifndef BWIDGETS_INPUT_HPP
|
#ifndef BWIDGETS_INPUT_HPP
|
||||||
#define BWIDGETS_INPUT_HPP
|
#define BWIDGETS_INPUT_HPP
|
||||||
|
|
||||||
#include <cmath>
|
|
||||||
#include <exception>
|
|
||||||
#include <iomanip>
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include <SDL2/SDL_events.h>
|
|
||||||
#include <SDL2/SDL_keyboard.h>
|
|
||||||
#include <SDL2/SDL_render.h>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/math.hpp>
|
|
||||||
#include <basic_widgets/core/type/concepts.hpp>
|
#include <basic_widgets/core/type/concepts.hpp>
|
||||||
#include <basic_widgets/w/caption.hpp>
|
#include <basic_widgets/w/base/widget.hpp>
|
||||||
|
#include <basic_widgets/w/feat/font_handler.hpp>
|
||||||
#include <basic_widgets/w/feat/keyboard_handler.hpp>
|
#include <basic_widgets/w/feat/keyboard_handler.hpp>
|
||||||
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
||||||
|
|
||||||
|
@ -20,104 +11,13 @@ namespace bwidgets
|
||||||
{
|
{
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class Input : public Widget,
|
class Input : public virtual Widget,
|
||||||
public FontHandler,
|
public virtual FontHandler,
|
||||||
public KeyboardHandler,
|
public virtual KeyboardHandler,
|
||||||
public MouseHandler
|
public virtual MouseHandler
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
Caption _input_caption;
|
using Widget::Widget;
|
||||||
|
|
||||||
Input(Widget* parent = nullptr) : Widget(parent), _input_caption(this)
|
|
||||||
{
|
|
||||||
FocusHandler::_focus_area = &_widget_area;
|
|
||||||
MouseHandler::_click_area = &_widget_area;
|
|
||||||
_input_caption.text(value_to_string(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_focus_change(bool focus) override
|
|
||||||
{
|
|
||||||
if (focus) {
|
|
||||||
SDL_StartTextInput();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
value = value_from_string(input_text());
|
|
||||||
input_text(value_to_string(value));
|
|
||||||
SDL_StopTextInput();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_font_change(Font* f) override
|
|
||||||
{
|
|
||||||
_input_caption.font(f);
|
|
||||||
_handle_geometry_change(_viewport);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_font_color_change(const Color& fg,
|
|
||||||
const Color& bg) override
|
|
||||||
{
|
|
||||||
_input_caption.font_color_bg(bg)->font_color_fg(fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_geometry_change(const SDL_Rect& vp) override
|
|
||||||
{
|
|
||||||
const auto input_h = _input_caption.size().h + 2 * border_width;
|
|
||||||
_widget_area = {0, center_line(vp.h, input_h), vp.w, input_h};
|
|
||||||
|
|
||||||
_input_caption.viewport(rect_offset(
|
|
||||||
{rect_margin(_widget_area, {border_width, border_width})}, vp));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_key(const SDL_KeyboardEvent& key) override
|
|
||||||
{
|
|
||||||
if (key.type == SDL_KEYDOWN) {
|
|
||||||
switch (key.keysym.sym) {
|
|
||||||
case SDLK_BACKSPACE: {
|
|
||||||
std::string txt = input_text();
|
|
||||||
if (txt.length() > 0) {
|
|
||||||
txt.pop_back();
|
|
||||||
input_text(txt);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SDLK_RETURN:
|
|
||||||
case SDLK_RETURN2: // what is return2 btw?
|
|
||||||
case SDLK_KP_ENTER:
|
|
||||||
value = value_from_string(input_text());
|
|
||||||
focus(false);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_renderer_change(Renderer* r) override
|
|
||||||
{
|
|
||||||
_input_caption.renderer(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_rendering() override
|
|
||||||
{
|
|
||||||
for (int i = border_width - 1; i >= 0; i--) {
|
|
||||||
const auto factor = linear(i, 0, border_width);
|
|
||||||
const auto& color_end = _has_focus ? color_bg_focused : color_bg;
|
|
||||||
const auto color_start = color_end / 2;
|
|
||||||
_renderer->draw_color(lerp(color_start, color_end, factor))
|
|
||||||
->draw_rect(rect_margin(_widget_area, {i, i}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MouseHandler::_is_hovered || FocusHandler::_has_focus)
|
|
||||||
_input_caption.font_color_bg(color_bg_focused);
|
|
||||||
else _input_caption.font_color_bg(color_bg);
|
|
||||||
|
|
||||||
_input_caption.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_text_input(const SDL_TextInputEvent& input) override
|
|
||||||
{
|
|
||||||
if (is_valid_input(input.text)) {
|
|
||||||
input_text(input_text() + input.text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static const int default_border_width = 3;
|
static const int default_border_width = 3;
|
||||||
|
@ -128,96 +28,30 @@ namespace bwidgets
|
||||||
static const int default_min_width = 1;
|
static const int default_min_width = 1;
|
||||||
|
|
||||||
int border_width = default_border_width;
|
int border_width = default_border_width;
|
||||||
Color color_bg = default_color_bg;
|
Color color_bg_default = default_color_bg;
|
||||||
Color color_bg_focused = default_color_bg_focused;
|
Color color_bg_focused = default_color_bg_focused;
|
||||||
int float_precision = default_float_precision;
|
int float_precision = default_float_precision;
|
||||||
int input_min_width = default_min_width;
|
int input_min_width = default_min_width;
|
||||||
char input_width_unit = 'W';
|
char input_width_unit = 'W'; // char used as unit for text length computation.
|
||||||
T value {};
|
T value {};
|
||||||
|
|
||||||
virtual auto color_fg(const Color& c) -> Input<T>*
|
// Get the current displayed string.
|
||||||
{
|
[[nodiscard]] virtual auto input_text() const -> std::string_view = 0;
|
||||||
_input_caption.font_color_fg(c);
|
// Set displayed string
|
||||||
return this;
|
virtual void input_text(std::string) = 0;
|
||||||
}
|
virtual void input_text_color(Color) = 0;
|
||||||
|
// Check if a character is allowed to be inputted. String are used because we
|
||||||
|
// could have multibytes characters to represent using 8bits chars.
|
||||||
|
[[nodiscard]] virtual auto is_valid_input(std::string_view) const noexcept
|
||||||
|
-> bool = 0;
|
||||||
|
|
||||||
[[nodiscard]] virtual auto input_text() const -> const std::string&
|
// Process a value for instance to modify it before saving it.
|
||||||
{
|
[[nodiscard]] virtual auto process_value(T) const noexcept -> T = 0;
|
||||||
return _input_caption.text();
|
// Get string representation to be shown of the stored value.
|
||||||
}
|
[[nodiscard]] virtual auto value_from_string(std::string_view) const noexcept
|
||||||
|
-> T = 0;
|
||||||
virtual auto input_text(const std::string& txt) -> Input<T>*
|
// Parse a string to get its representation in T type of input value memory.
|
||||||
{
|
[[nodiscard]] virtual auto value_to_string(T) const noexcept -> std::string = 0;
|
||||||
_input_caption.text(txt);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] virtual auto is_valid_input(const std::string&) const noexcept
|
|
||||||
-> bool
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] virtual auto process_value(T x) const noexcept -> T
|
|
||||||
{
|
|
||||||
return x;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] inline auto size() const noexcept -> Size override
|
|
||||||
{
|
|
||||||
if (_font == nullptr) return {0, 0};
|
|
||||||
|
|
||||||
return {
|
|
||||||
_font->text_size(std::string(input_min_width, input_width_unit)).w
|
|
||||||
+ 2 * border_width,
|
|
||||||
_font->line_skip + 4 * border_width // _why_ 4 and not 2?…
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto value_from_string(std::string s) noexcept
|
|
||||||
{
|
|
||||||
T v;
|
|
||||||
|
|
||||||
if constexpr (std::is_arithmetic_v<T>) {
|
|
||||||
s = s.length() > 0 ? s : "0";
|
|
||||||
if constexpr (std::is_floating_point_v<T>) v = (T)std::stold(s);
|
|
||||||
else if constexpr (std::is_integral_v<T>) v = (T)std::stoll(s);
|
|
||||||
}
|
|
||||||
else if constexpr (std::is_same_v<
|
|
||||||
T, std::string> || std::convertible_to<std::string, T>)
|
|
||||||
v = s;
|
|
||||||
else
|
|
||||||
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
|
|
||||||
static_assert((bool)sizeof(T) && false,
|
|
||||||
"string cannot be converted to v type T.");
|
|
||||||
|
|
||||||
return process_value(v);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto value_to_string(T value) noexcept
|
|
||||||
{
|
|
||||||
std::string s;
|
|
||||||
|
|
||||||
if constexpr (std::is_same_v<std::string,
|
|
||||||
T> || std::convertible_to<T, std::string>)
|
|
||||||
s = std::move(value);
|
|
||||||
else if constexpr (CanToString<T>) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << std::fixed << std::setprecision(float_precision) << value;
|
|
||||||
s = std::move(ss).str();
|
|
||||||
}
|
|
||||||
else if constexpr (Printable<T>) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << value;
|
|
||||||
s = std::move(ss).str();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
|
|
||||||
static_assert((bool)sizeof(T) && false,
|
|
||||||
"value cannot be converted to string.");
|
|
||||||
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,231 @@
|
||||||
|
#ifndef BWIDGETS_INPUT_IMPL_HPP
|
||||||
|
#define BWIDGETS_INPUT_IMPL_HPP
|
||||||
|
|
||||||
|
#include <iomanip>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <basic_widgets/core/math.hpp>
|
||||||
|
#include <basic_widgets/w/base/input.hpp>
|
||||||
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
||||||
|
#include <basic_widgets/w/caption.hpp>
|
||||||
|
#include <basic_widgets/w/feat/font_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/feat/keyboard_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/widget_factory.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
template<typename T>
|
||||||
|
class InputImpl : public virtual Input<T>,
|
||||||
|
public virtual FontHandlerImpl,
|
||||||
|
public virtual KeyboardHandlerImpl,
|
||||||
|
public virtual MouseHandlerImpl,
|
||||||
|
public virtual WidgetImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
[[nodiscard]] auto input_text() const -> std::string_view override
|
||||||
|
{
|
||||||
|
return _input_caption->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
void input_text(std::string txt) override
|
||||||
|
{
|
||||||
|
_input_caption->text(std::move(txt));
|
||||||
|
}
|
||||||
|
|
||||||
|
void input_text_color(const Color c) override
|
||||||
|
{
|
||||||
|
_input_caption->font_color_fg(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto is_valid_input(const std::string_view) const noexcept
|
||||||
|
-> bool override
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto process_value(const T x) const noexcept -> T override
|
||||||
|
{
|
||||||
|
return x;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto size() const noexcept -> Size override
|
||||||
|
{
|
||||||
|
if (!_font) return {0, 0};
|
||||||
|
|
||||||
|
return {
|
||||||
|
_font
|
||||||
|
->text_size(
|
||||||
|
std::string(InputImpl::input_min_width, InputImpl::input_width_unit))
|
||||||
|
.w
|
||||||
|
+ 2 * InputImpl::border_width,
|
||||||
|
_font->line_skip + 4 * InputImpl::border_width // _why_ 4 and not 2?…
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto value_from_string(const std::string_view s) const noexcept
|
||||||
|
-> T override
|
||||||
|
{
|
||||||
|
const T v = [s]() {
|
||||||
|
if constexpr (std::is_arithmetic_v<T>) {
|
||||||
|
const auto _s = s.length() > 0 ? s : "0";
|
||||||
|
if constexpr (std::is_floating_point_v<T>)
|
||||||
|
return (T)std::stold(_s.data());
|
||||||
|
else if constexpr (std::is_integral_v<T>)
|
||||||
|
return (T)std::stoll(_s.data());
|
||||||
|
}
|
||||||
|
else if constexpr (std::is_same_v<
|
||||||
|
T,
|
||||||
|
std::string> || std::convertible_to<std::string, T>)
|
||||||
|
return std::string(s);
|
||||||
|
else
|
||||||
|
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
|
||||||
|
static_assert((bool)sizeof(T) && false,
|
||||||
|
"string cannot be converted to type T.");
|
||||||
|
}();
|
||||||
|
|
||||||
|
return process_value(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto value_to_string(const T value) const noexcept
|
||||||
|
-> std::string override
|
||||||
|
{
|
||||||
|
std::string s = [value, this]() {
|
||||||
|
if constexpr (std::is_same_v<std::string,
|
||||||
|
T> || std::convertible_to<T, std::string>)
|
||||||
|
return std::move(value);
|
||||||
|
else if constexpr (CanToString<T>) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::fixed << std::setprecision(InputImpl::float_precision)
|
||||||
|
<< value;
|
||||||
|
return std::move(ss).str();
|
||||||
|
}
|
||||||
|
else if constexpr (Printable<T>) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << value;
|
||||||
|
return std::move(ss).str();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// NOLINTNEXTLINE(readability-simplify-boolean-expr)
|
||||||
|
static_assert((bool)sizeof(T) && false,
|
||||||
|
"value cannot be converted to string.");
|
||||||
|
}();
|
||||||
|
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Caption> _input_caption;
|
||||||
|
|
||||||
|
InputImpl(Widget* parent = nullptr)
|
||||||
|
: WidgetImpl {parent}, _input_caption {create_caption(this)}
|
||||||
|
{
|
||||||
|
// Start text input on focus. Stop it on focus loss and save the inputted
|
||||||
|
// value.
|
||||||
|
focus_handler([this](const bool focus) {
|
||||||
|
if (focus) {
|
||||||
|
_input_caption->font_color_bg(InputImpl::color_bg_focused);
|
||||||
|
SDL_StartTextInput();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_StopTextInput();
|
||||||
|
_input_caption->font_color_bg(InputImpl::color_bg_default);
|
||||||
|
|
||||||
|
InputImpl::value = value_from_string(input_text());
|
||||||
|
input_text(value_to_string(InputImpl::value));
|
||||||
|
_input_caption->text(value_to_string(InputImpl::value));
|
||||||
|
});
|
||||||
|
|
||||||
|
key_handler([this](const SDL_KeyboardEvent& key) {
|
||||||
|
if (key.type == SDL_KEYDOWN) {
|
||||||
|
switch (key.keysym.sym) {
|
||||||
|
// Discard inputted value on escape key.
|
||||||
|
case SDLK_ESCAPE: {
|
||||||
|
focus(false);
|
||||||
|
input_text(value_to_string(InputImpl::value));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SDLK_BACKSPACE: {
|
||||||
|
const auto& txt = input_text();
|
||||||
|
if (const auto len = txt.length(); len > 0) {
|
||||||
|
input_text(std::string(txt.substr(0, len - 1)));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Save inputted value and unfocus the widget on enter key.
|
||||||
|
case SDLK_RETURN:
|
||||||
|
case SDLK_RETURN2: // what is return2 btw?
|
||||||
|
case SDLK_KP_ENTER:
|
||||||
|
focus(false);
|
||||||
|
InputImpl::value = value_from_string(input_text());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Just keep the string representation of input in the input field caption
|
||||||
|
// text but don't save the parsed value until some other event
|
||||||
|
// (Esc/Enter/focus loss)
|
||||||
|
text_input_handler([this](const SDL_TextInputEvent& input) {
|
||||||
|
if (is_valid_input(input.text)) {
|
||||||
|
input_text(std::string(input_text()) + input.text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
enable_keyboard_handler();
|
||||||
|
|
||||||
|
// Highlight input field on mouse hover.
|
||||||
|
hover_handler([this](const bool _hover) {
|
||||||
|
if (_hover) _input_caption->font_color_bg(InputImpl::color_bg_focused);
|
||||||
|
else _input_caption->font_color_bg(InputImpl::color_bg_default);
|
||||||
|
});
|
||||||
|
enable_mouse_handler(_widget_area, _viewport);
|
||||||
|
|
||||||
|
_input_caption->font_color_bg(InputImpl::color_bg_default);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_font_change(const std::shared_ptr<Font>& f) override
|
||||||
|
{
|
||||||
|
_input_caption->font(f);
|
||||||
|
_handle_geometry_change(_viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_font_color_change(const Color fg, const Color bg) override
|
||||||
|
{
|
||||||
|
_input_caption->font_color_bg(bg);
|
||||||
|
_input_caption->font_color_fg(fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_geometry_change(const SDL_Rect& vp) override
|
||||||
|
{
|
||||||
|
const auto input_h = _input_caption->size().h + 2 * InputImpl::border_width;
|
||||||
|
_widget_area = {0, center_line(vp.h, input_h), vp.w, input_h};
|
||||||
|
|
||||||
|
_input_caption->viewport(
|
||||||
|
rect_offset({rect_margin(_widget_area, {InputImpl::border_width,
|
||||||
|
InputImpl::border_width})},
|
||||||
|
vp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_renderer_change(const std::shared_ptr<Renderer>& r) override
|
||||||
|
{
|
||||||
|
_input_caption->renderer(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_rendering() override
|
||||||
|
{
|
||||||
|
const auto& color_end =
|
||||||
|
focus() ? InputImpl::color_bg_focused : InputImpl::color_bg_default;
|
||||||
|
|
||||||
|
const auto color_start = color_end / 2;
|
||||||
|
// Render input borders.
|
||||||
|
for (int i = InputImpl::border_width - 1; i >= 0; i--) {
|
||||||
|
const auto factor = linear(i, 0, InputImpl::border_width);
|
||||||
|
_renderer->draw_color(lerp(color_start, color_end, factor))
|
||||||
|
->draw_rect(rect_margin(_widget_area, {i, i}));
|
||||||
|
}
|
||||||
|
|
||||||
|
_input_caption->render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,42 +1,26 @@
|
||||||
#ifndef BWIDGETS_LAYOUT_HPP
|
#ifndef BWIDGETS_LAYOUT_HPP
|
||||||
#define BWIDGETS_LAYOUT_HPP
|
#define BWIDGETS_LAYOUT_HPP
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/type/size.hpp>
|
#include <basic_widgets/core/type/size.hpp>
|
||||||
#include <basic_widgets/w/base/widget.hpp>
|
#include <basic_widgets/w/base/widget.hpp>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class Renderer;
|
class Layout : public virtual Widget
|
||||||
}
|
|
||||||
|
|
||||||
namespace bwidgets
|
|
||||||
{
|
|
||||||
class Layout : public Widget
|
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
std::vector<Widget*> _widgets;
|
using Widget::Widget;
|
||||||
|
|
||||||
void _handle_geometry_change(const SDL_Rect&) override;
|
|
||||||
void _handle_renderer_change(Renderer*) override;
|
|
||||||
void _handle_rendering() override;
|
|
||||||
|
|
||||||
virtual void _update_layout(const SDL_Rect&) = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline static const Size default_margins {8, 8};
|
static inline const Size default_margins {8, 8};
|
||||||
|
|
||||||
|
// Margins between layout widgets.
|
||||||
Size margins = default_margins;
|
Size margins = default_margins;
|
||||||
|
|
||||||
~Layout() override;
|
// Add widget to the layout
|
||||||
|
virtual void add_widget(std::unique_ptr<Widget>) = 0;
|
||||||
auto handle_event(const SDL_Event&) -> Layout* override;
|
// Apply a function to every layout widget.
|
||||||
[[nodiscard]] auto size() const noexcept -> Size override = 0;
|
virtual void for_widgets(const std::function<void(Widget*)>&) = 0;
|
||||||
|
|
||||||
virtual auto add_widget(Widget*) -> Layout*;
|
|
||||||
virtual void for_widgets(const std::function<void(Widget*)>&);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
#ifndef BWIDGETS_LAYOUT_IMPL_HPP
|
||||||
|
#define BWIDGETS_LAYOUT_IMPL_HPP
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <basic_widgets/w/base/layout.hpp>
|
||||||
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class Renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class LayoutImpl : public virtual Layout,
|
||||||
|
public virtual WidgetImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using WidgetImpl::WidgetImpl;
|
||||||
|
|
||||||
|
void add_widget(std::unique_ptr<Widget>) override;
|
||||||
|
void for_widgets(const std::function<void(Widget*)>&) override;
|
||||||
|
void handle_event(const SDL_Event&) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<std::unique_ptr<Widget>> _widgets;
|
||||||
|
|
||||||
|
void _handle_geometry_change(const SDL_Rect&) override;
|
||||||
|
void _handle_renderer_change(const std::shared_ptr<Renderer>&) override;
|
||||||
|
void _handle_rendering() override;
|
||||||
|
|
||||||
|
// Compute and set widget geometry.
|
||||||
|
virtual void _update_layout(const SDL_Rect&) = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,8 +1,11 @@
|
||||||
#ifndef BWIDGETS_WIDGET_HPP
|
#ifndef BWIDGETS_WIDGET_HPP
|
||||||
#define BWIDGETS_WIDGET_HPP
|
#define BWIDGETS_WIDGET_HPP
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include <basic_widgets/core/renderer.hpp>
|
#include <basic_widgets/core/renderer.hpp>
|
||||||
#include <basic_widgets/core/type/size.hpp>
|
#include <basic_widgets/core/type/size.hpp>
|
||||||
|
#include <basic_widgets/w/feat/event_handler.hpp>
|
||||||
|
|
||||||
union SDL_Event;
|
union SDL_Event;
|
||||||
|
|
||||||
|
@ -10,30 +13,26 @@ struct SDL_Renderer;
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class Widget
|
class Widget : public virtual EventHandler
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
Renderer* _renderer {nullptr};
|
|
||||||
SDL_Rect _viewport {0, 0, 0, 0};
|
|
||||||
SDL_Rect _widget_area {0, 0, 0, 0};
|
|
||||||
|
|
||||||
virtual void _handle_geometry_change(const SDL_Rect&) = 0;
|
|
||||||
virtual void _handle_renderer_change(Renderer*) {}
|
|
||||||
virtual void _handle_rendering() = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// Parent widget for bidirectional message passing between widgets.
|
||||||
|
// Currently not used by this lib.
|
||||||
Widget* parent;
|
Widget* parent;
|
||||||
|
|
||||||
Widget(Widget* p = nullptr) noexcept : parent(p) {}
|
explicit Widget(Widget* p = nullptr) noexcept : parent {p} {}
|
||||||
|
|
||||||
virtual ~Widget() = default;
|
// Render widget on current renderer target.
|
||||||
|
virtual void render() = 0;
|
||||||
virtual auto handle_event(const SDL_Event&) -> Widget*;
|
// Set widget renderer.
|
||||||
[[nodiscard]] virtual auto size() const noexcept -> Size = 0;
|
virtual void renderer(std::shared_ptr<Renderer>) = 0;
|
||||||
virtual auto render() -> Widget* final;
|
// Get prefered or minimal widget size.
|
||||||
virtual auto renderer(Renderer*) -> Widget* final;
|
[[nodiscard]] virtual auto size() const noexcept -> Size = 0;
|
||||||
virtual auto viewport(const SDL_Rect&) -> Widget* final;
|
// Set the viewport delimiting the widget rendering area and the origin
|
||||||
[[nodiscard]] virtual auto viewport() const -> const SDL_Rect& final;
|
// for relative coordinates.
|
||||||
|
virtual void viewport(const SDL_Rect&) = 0;
|
||||||
|
// Get current viewport.
|
||||||
|
[[nodiscard]] virtual auto viewport() const -> const SDL_Rect& = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
#ifndef BWIDGETS_WIDGET_IMPL_HPP
|
||||||
|
#define BWIDGETS_WIDGET_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/base/widget.hpp>
|
||||||
|
#include <basic_widgets/w/feat/event_handler_impl.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class WidgetImpl : public virtual Widget,
|
||||||
|
public virtual EventHandlerImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void render() override;
|
||||||
|
void renderer(std::shared_ptr<Renderer>) override;
|
||||||
|
void viewport(const SDL_Rect&) override;
|
||||||
|
[[nodiscard]] auto viewport() const -> const SDL_Rect& override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<Renderer> _renderer;
|
||||||
|
SDL_Rect _viewport {0, 0, 0, 0};
|
||||||
|
// Area actually used by the widget.
|
||||||
|
SDL_Rect _widget_area {0, 0, 0, 0};
|
||||||
|
|
||||||
|
using Widget::Widget;
|
||||||
|
|
||||||
|
// Called on viewport change.
|
||||||
|
virtual void _handle_geometry_change(const SDL_Rect&) = 0;
|
||||||
|
// Called on renderer change.
|
||||||
|
virtual void _handle_renderer_change(const std::shared_ptr<Renderer>&) {}
|
||||||
|
// Called on rendering.
|
||||||
|
virtual void _handle_rendering() = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,50 +1,36 @@
|
||||||
#ifndef BWIDGETS_BUTTON_HPP
|
#ifndef BWIDGETS_BUTTON_HPP
|
||||||
#define BWIDGETS_BUTTON_HPP
|
#define BWIDGETS_BUTTON_HPP
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <basic_widgets/core/type/color.hpp>
|
#include <basic_widgets/core/type/color.hpp>
|
||||||
#include <basic_widgets/w/caption.hpp>
|
#include <basic_widgets/w/base/widget.hpp>
|
||||||
|
#include <basic_widgets/w/feat/font_handler.hpp>
|
||||||
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class Button : public Widget,
|
class Button : public virtual Widget,
|
||||||
public FontHandler,
|
public virtual FontHandler,
|
||||||
public MouseHandler
|
public virtual MouseHandler
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
Caption _caption;
|
|
||||||
SDL_Rect _caption_area {};
|
|
||||||
Color _color_foreground = default_color_fg;
|
|
||||||
|
|
||||||
void _handle_focus_change(bool) override {}
|
|
||||||
void _handle_font_change(Font*) override;
|
|
||||||
void _handle_font_color_change(const Color&, const Color&) override;
|
|
||||||
void _handle_geometry_change(const SDL_Rect&) override;
|
|
||||||
void _handle_renderer_change(Renderer*) override;
|
|
||||||
void _handle_rendering() override;
|
|
||||||
void _on_push(bool) override;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline static const Color default_color_bg {150, 150, 150, SDL_ALPHA_OPAQUE};
|
static inline const Color default_color_bg {150, 150, 150, SDL_ALPHA_OPAQUE};
|
||||||
inline static const Color default_color_bg_hover {175, 175, 175,
|
static inline const Color default_color_bg_hover {175, 175, 175,
|
||||||
SDL_ALPHA_OPAQUE};
|
SDL_ALPHA_OPAQUE};
|
||||||
inline static const Color default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
|
static inline const Color default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
|
||||||
|
|
||||||
std::function<Color(int, int)> border_gradient;
|
std::function<Color(int, int, float)> border_gradient;
|
||||||
std::function<Color(int, int)> border_gradient_pushed;
|
Size border_size {3, 3};
|
||||||
Size border_size {3, 3};
|
Color color_bg = default_color_bg;
|
||||||
Color color_bg = default_color_bg;
|
Color color_bg_hover = default_color_bg_hover;
|
||||||
Color color_bg_hover = default_color_bg_hover;
|
|
||||||
|
|
||||||
Button(Widget* parent = nullptr) noexcept;
|
using Widget::Widget;
|
||||||
|
|
||||||
[[nodiscard]] auto size() const noexcept -> Size override;
|
// Get button text (label).
|
||||||
|
[[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0;
|
||||||
[[nodiscard]] virtual auto text() const noexcept -> const std::string&;
|
// Set button text
|
||||||
virtual auto text(const std::string&) -> Button*;
|
virtual void text(std::string) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
#ifndef BWIDGETS_BUTTON_IMPL_HPP
|
||||||
|
#define BWIDGETS_BUTTON_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
||||||
|
#include <basic_widgets/w/button.hpp>
|
||||||
|
#include <basic_widgets/w/caption.hpp>
|
||||||
|
#include <basic_widgets/w/feat/font_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class ButtonImpl : public virtual Button,
|
||||||
|
public virtual FontHandlerImpl,
|
||||||
|
public virtual MouseHandlerImpl,
|
||||||
|
public virtual WidgetImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ButtonImpl(Widget* parent = nullptr) noexcept;
|
||||||
|
|
||||||
|
// Smallest valid button size.
|
||||||
|
[[nodiscard]] auto size() const noexcept -> Size override;
|
||||||
|
[[nodiscard]] auto text() const noexcept -> std::string_view override;
|
||||||
|
void text(std::string) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Caption> _caption;
|
||||||
|
SDL_Rect _caption_area {};
|
||||||
|
Color _color_foreground = default_color_fg;
|
||||||
|
|
||||||
|
void _handle_font_change(const std::shared_ptr<Font>&) override;
|
||||||
|
void _handle_font_color_change(Color, Color)
|
||||||
|
override;
|
||||||
|
void _handle_geometry_change(const SDL_Rect&) override;
|
||||||
|
void _handle_renderer_change(const std::shared_ptr<Renderer>&) override;
|
||||||
|
void _handle_rendering() override;
|
||||||
|
void _on_push(bool) override;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,8 +1,6 @@
|
||||||
#ifndef BWIDGETS_CAPTION_HPP
|
#ifndef BWIDGETS_CAPTION_HPP
|
||||||
#define BWIDGETS_CAPTION_HPP
|
#define BWIDGETS_CAPTION_HPP
|
||||||
|
|
||||||
#include <basic_widgets/core/texture.hpp>
|
|
||||||
#include <basic_widgets/core/type/size.hpp>
|
|
||||||
#include <basic_widgets/w/base/widget.hpp>
|
#include <basic_widgets/w/base/widget.hpp>
|
||||||
#include <basic_widgets/w/feat/font_handler.hpp>
|
#include <basic_widgets/w/feat/font_handler.hpp>
|
||||||
#include <basic_widgets/w/feat/texture_handler.hpp>
|
#include <basic_widgets/w/feat/texture_handler.hpp>
|
||||||
|
@ -11,22 +9,10 @@ struct SDL_Surface;
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class Caption : public Widget,
|
class Caption : public virtual Widget,
|
||||||
public FontHandler,
|
public virtual FontHandler,
|
||||||
public TextureHandler
|
public virtual TextureHandler
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
Font::RenderMode _render_mode {Font::RenderMode::SHADED};
|
|
||||||
std::string _text;
|
|
||||||
Texture* _text_texture {nullptr};
|
|
||||||
|
|
||||||
void _handle_font_change(Font*) override;
|
|
||||||
void _handle_font_color_change(const Color&, const Color&) override;
|
|
||||||
void _handle_geometry_change(const SDL_Rect&) override;
|
|
||||||
void _handle_renderer_change(Renderer*) override;
|
|
||||||
void _handle_rendering() override;
|
|
||||||
void _handle_texture_update() override;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum struct Alignment
|
enum struct Alignment
|
||||||
{
|
{
|
||||||
|
@ -34,22 +20,22 @@ namespace bwidgets
|
||||||
LEFT,
|
LEFT,
|
||||||
RIGHT
|
RIGHT
|
||||||
};
|
};
|
||||||
|
|
||||||
inline static const Color default_color_bg = {255, 255, 255, SDL_ALPHA_OPAQUE};
|
inline static const Color default_color_bg = {255, 255, 255, SDL_ALPHA_OPAQUE};
|
||||||
inline static const Color default_color_fg = {0, 0, 0, SDL_ALPHA_OPAQUE};
|
inline static const Color default_color_fg = {0, 0, 0, SDL_ALPHA_OPAQUE};
|
||||||
inline static const Size default_margins = {3, 3};
|
inline static const Size default_margins = {3, 3};
|
||||||
|
|
||||||
|
// Text horizontal alignment on the caption viewport.
|
||||||
Alignment alignment {Alignment::LEFT};
|
Alignment alignment {Alignment::LEFT};
|
||||||
Size margins {3, 3};
|
Size margins {default_margins};
|
||||||
|
|
||||||
Caption(Widget* parent = nullptr) noexcept;
|
using Widget::Widget;
|
||||||
~Caption() noexcept override;
|
|
||||||
|
|
||||||
virtual auto render_mode(Font::RenderMode) -> Caption*;
|
// Set caption text rendering mode.
|
||||||
[[nodiscard]] virtual auto text() const noexcept -> const std::string&;
|
virtual void render_mode(Font::RenderMode) = 0;
|
||||||
virtual auto text(const std::string&) -> Caption*;
|
// Get caption text.
|
||||||
|
[[nodiscard]] virtual auto text() const noexcept -> std::string_view = 0;
|
||||||
[[nodiscard]] auto size() const noexcept -> Size override;
|
// Set caption text.
|
||||||
|
virtual void text(std::string) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
#ifndef BWIDGETS_CAPTION_IMPL_HPP
|
||||||
|
#define BWIDGETS_CAPTION_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
||||||
|
#include <basic_widgets/w/caption.hpp>
|
||||||
|
#include <basic_widgets/w/feat/font_handler_impl.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class CaptionImpl : public virtual Caption,
|
||||||
|
public virtual FontHandlerImpl,
|
||||||
|
public virtual WidgetImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CaptionImpl(Widget* p = nullptr) noexcept;
|
||||||
|
|
||||||
|
void render_mode(Font::RenderMode) override;
|
||||||
|
[[nodiscard]] auto size() const noexcept -> Size override;
|
||||||
|
[[nodiscard]] auto text() const noexcept -> std::string_view override;
|
||||||
|
void text(std::string) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Font::RenderMode _render_mode {Font::RenderMode::SHADED};
|
||||||
|
std::string _text;
|
||||||
|
std::shared_ptr<Texture> _text_texture {nullptr};
|
||||||
|
|
||||||
|
void _handle_font_change(const std::shared_ptr<Font>&) override;
|
||||||
|
void _handle_font_color_change(Color, Color)
|
||||||
|
override;
|
||||||
|
void _handle_geometry_change(const SDL_Rect&) override;
|
||||||
|
void _handle_renderer_change(const std::shared_ptr<Renderer>&) override;
|
||||||
|
void _handle_rendering() override;
|
||||||
|
void _handle_texture_update() override;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef BWIDGETS_EVENT_HANDLER_HPP
|
||||||
|
#define BWIDGETS_EVENT_HANDLER_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
#include <SDL_events.h>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class EventHandler
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
EventHandler() = default;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using handler_t =
|
||||||
|
std::pair<SDL_EventType, std::function<void(const SDL_Event&)>>;
|
||||||
|
|
||||||
|
EventHandler(const EventHandler&) = delete;
|
||||||
|
EventHandler(EventHandler&&) = delete;
|
||||||
|
auto operator=(const EventHandler&) -> EventHandler& = delete;
|
||||||
|
auto operator=(EventHandler&&) -> EventHandler& = delete;
|
||||||
|
|
||||||
|
virtual ~EventHandler() = default;
|
||||||
|
|
||||||
|
// Pass the event to be handled.
|
||||||
|
virtual void handle_event(const SDL_Event&) = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef BWIDGETS_EVENT_HANDLER_IMPL_HPP
|
||||||
|
#define BWIDGETS_EVENT_HANDLER_IMPL_HPP
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <basic_widgets/w/feat/event_handler.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class EventHandlerImpl : public virtual EventHandler
|
||||||
|
{
|
||||||
|
std::unordered_map<SDL_EventType, std::function<void(const SDL_Event&)>>
|
||||||
|
_event_handlers {};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void handle_event(const SDL_Event&) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Add an handler for a given event type. There can be only one handler by
|
||||||
|
// event type. Return true if handler is added successfuly. Return false
|
||||||
|
// if handler cannot be added because there's already an handler for that
|
||||||
|
// event type.
|
||||||
|
auto _add_event_handler(handler_t) -> bool;
|
||||||
|
// Remove the handler of a given event type. Return true with the removed
|
||||||
|
// handler on success, false otherwise.
|
||||||
|
auto _remove_event_handler(SDL_EventType) -> std::pair<handler_t, bool>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,26 +1,28 @@
|
||||||
#ifndef BWIDGETS_FOCUS_HANDLER_HPP
|
#ifndef BWIDGETS_FOCUS_HANDLER_HPP
|
||||||
#define BWIDGETS_FOCUS_HANDLER_HPP
|
#define BWIDGETS_FOCUS_HANDLER_HPP
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
struct SDL_Rect;
|
struct SDL_Rect;
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class FocusHandler
|
class FocusHandler
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
const SDL_Rect* _focus_area = nullptr;
|
|
||||||
bool _has_focus = false;
|
|
||||||
|
|
||||||
virtual void _handle_focus_change(bool) = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~FocusHandler() = default;
|
FocusHandler() = default;
|
||||||
|
FocusHandler(const FocusHandler&) = delete;
|
||||||
|
FocusHandler(FocusHandler&&) = delete;
|
||||||
|
auto operator=(FocusHandler&&) = delete;
|
||||||
|
auto operator=(const FocusHandler&) = delete;
|
||||||
|
|
||||||
virtual inline void focus(bool focus) final
|
virtual ~FocusHandler() = default;
|
||||||
{
|
// set focus state.
|
||||||
_handle_focus_change(focus);
|
virtual void focus(bool focus) = 0;
|
||||||
_has_focus = focus;
|
// get focus state.
|
||||||
}
|
virtual bool focus() = 0;
|
||||||
|
// set an handler for focus states changes.
|
||||||
|
virtual void focus_handler(std::function<void(bool)>) = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef BWIDGETS_FOCUS_HANDLER_IMPL_HPP
|
||||||
|
#define BWIDGETS_FOCUS_HANDLER_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/feat/focus_handler.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class FocusHandlerImpl : public virtual FocusHandler
|
||||||
|
{
|
||||||
|
bool _has_focus {false};
|
||||||
|
std::function<void(bool)> _focus_handler {[](auto) {}};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void focus(const bool _focus) override
|
||||||
|
{
|
||||||
|
if (_has_focus != _focus) _focus_handler(_has_focus = _focus);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool focus() override
|
||||||
|
{
|
||||||
|
return _has_focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
void focus_handler(decltype(_focus_handler) handler) final
|
||||||
|
{
|
||||||
|
_focus_handler = std::move(handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -7,54 +7,27 @@ namespace bwidgets
|
||||||
{
|
{
|
||||||
class FontHandler
|
class FontHandler
|
||||||
{
|
{
|
||||||
protected:
|
|
||||||
Font* _font {nullptr};
|
|
||||||
Color _font_color_bg = default_font_color_bg;
|
|
||||||
Color _font_color_fg = default_font_color_fg;
|
|
||||||
Font::RenderMode _font_render_mode {Font::RenderMode::SHADED};
|
|
||||||
|
|
||||||
virtual void _handle_font_change(Font*) = 0;
|
|
||||||
virtual void _handle_font_color_change(const Color&, const Color&) = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
inline static const Color default_font_color_bg {255, 255, 255,
|
inline static const Color default_font_color_bg {255, 255, 255,
|
||||||
SDL_ALPHA_OPAQUE};
|
SDL_ALPHA_OPAQUE};
|
||||||
inline static const Color default_font_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
|
inline static const Color default_font_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
|
||||||
|
|
||||||
|
FontHandler(const FontHandler&) = delete;
|
||||||
|
FontHandler(FontHandler&&) = delete;
|
||||||
|
auto operator=(const FontHandler&) = delete;
|
||||||
|
auto operator=(FontHandler&&) = delete;
|
||||||
virtual ~FontHandler() noexcept = default;
|
virtual ~FontHandler() noexcept = default;
|
||||||
|
|
||||||
virtual inline auto font(Font* f) -> FontHandler* final
|
// Set the used font.
|
||||||
{
|
virtual void font(std::shared_ptr<Font> f) = 0;
|
||||||
if (f != nullptr
|
// Set background color used for shaded text rendering
|
||||||
&& (f != _font || f->family_name != _font->family_name
|
// mode.
|
||||||
|| f->height != _font->height || f->hinting() != _font->hinting()
|
virtual void font_color_bg(Color c) = 0;
|
||||||
|| f->kerning() != _font->kerning()
|
// Set foreground (glyphs) color.
|
||||||
|| f->outline() != _font->outline()
|
virtual void font_color_fg(Color c) = 0;
|
||||||
|| f->style_name != _font->style_name))
|
|
||||||
{
|
|
||||||
_handle_font_change(f);
|
|
||||||
_font = f;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual inline auto font_color_bg(const Color& c) -> FontHandler* final
|
protected:
|
||||||
{
|
FontHandler() noexcept = default;
|
||||||
if (c != _font_color_bg) {
|
|
||||||
_handle_font_color_change(_font_color_bg, c);
|
|
||||||
_font_color_bg = c;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual inline auto font_color_fg(const Color& c) -> FontHandler* final
|
|
||||||
{
|
|
||||||
if (c != _font_color_fg) {
|
|
||||||
_handle_font_color_change(c, _font_color_fg);
|
|
||||||
_font_color_fg = c;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
#ifndef BWIDGETS_FONT_HANDLER_IMPL_HPP
|
||||||
|
#define BWIDGETS_FONT_HANDLER_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/feat/font_handler.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class FontHandlerImpl : public virtual FontHandler
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void font(const std::shared_ptr<Font> f) final
|
||||||
|
{
|
||||||
|
if (f
|
||||||
|
&& (f != _font || f->family_name != _font->family_name
|
||||||
|
|| f->height != _font->height || f->hinting() != _font->hinting()
|
||||||
|
|| f->kerning() != _font->kerning()
|
||||||
|
|| f->outline() != _font->outline()
|
||||||
|
|| f->style_name != _font->style_name))
|
||||||
|
{
|
||||||
|
_handle_font_change(f);
|
||||||
|
_font = f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void font_color_bg(const Color c) final
|
||||||
|
{
|
||||||
|
if (c != _font_color_bg) {
|
||||||
|
_handle_font_color_change(_font_color_fg, c);
|
||||||
|
_font_color_bg = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void font_color_fg(const Color c) final
|
||||||
|
{
|
||||||
|
if (c != _font_color_fg) {
|
||||||
|
_handle_font_color_change(c, _font_color_fg);
|
||||||
|
_font_color_fg = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::shared_ptr<Font> _font;
|
||||||
|
Color _font_color_bg = default_font_color_bg;
|
||||||
|
Color _font_color_fg = default_font_color_fg;
|
||||||
|
Font::RenderMode _font_render_mode {Font::RenderMode::SHADED};
|
||||||
|
|
||||||
|
// Called on font changes.
|
||||||
|
virtual void _handle_font_change(const std::shared_ptr<Font>&) = 0;
|
||||||
|
// Called on font color changes.
|
||||||
|
virtual void _handle_font_color_change(Color, Color) = 0;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef BWIDGETS_KEYBOARD_HANDLER
|
#ifndef BWIDGETS_KEYBOARD_HANDLER_HPP
|
||||||
#define BWIDGETS_KEYBOARD_HANDLER
|
#define BWIDGETS_KEYBOARD_HANDLER_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/feat/event_handler.hpp>
|
||||||
#include <basic_widgets/w/feat/focus_handler.hpp>
|
#include <basic_widgets/w/feat/focus_handler.hpp>
|
||||||
|
|
||||||
struct SDL_KeyboardEvent;
|
struct SDL_KeyboardEvent;
|
||||||
|
@ -8,26 +9,22 @@ struct SDL_TextInputEvent;
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class KeyboardHandler : public virtual FocusHandler
|
class KeyboardHandler : public virtual EventHandler,
|
||||||
|
public virtual FocusHandler
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
virtual void _handle_key(const SDL_KeyboardEvent&) = 0;
|
using EventHandler::EventHandler;
|
||||||
virtual void _handle_text_input(const SDL_TextInputEvent&) = 0;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual inline auto handle_keyboard(const SDL_KeyboardEvent& ev)
|
// Disable keyboard event handling.
|
||||||
-> KeyboardHandler* final
|
virtual void disable_keyboard_handler() = 0;
|
||||||
{
|
// Enable keyboard event handling.
|
||||||
if (_has_focus) _handle_key(ev);
|
virtual void enable_keyboard_handler() = 0;
|
||||||
return this;
|
// Set the handler for key press events.
|
||||||
}
|
virtual void key_handler(std::function<void(const SDL_KeyboardEvent&)>) = 0;
|
||||||
|
// Set the handler for text input events.
|
||||||
virtual inline auto handle_keyboard(const SDL_TextInputEvent& ev)
|
virtual void
|
||||||
-> KeyboardHandler* final
|
text_input_handler(std::function<void(const SDL_TextInputEvent&)>) = 0;
|
||||||
{
|
|
||||||
if (_has_focus) _handle_text_input(ev);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
#ifndef BWIDGETS_KEYBOARD_HANDLER_IMPL_HPP
|
||||||
|
#define BWIDGETS_KEYBOARD_HANDLER_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/feat/event_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/feat/focus_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/feat/keyboard_handler.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class KeyboardHandlerImpl : public virtual KeyboardHandler,
|
||||||
|
public virtual EventHandlerImpl,
|
||||||
|
public virtual FocusHandlerImpl
|
||||||
|
{
|
||||||
|
std::function<void(const SDL_KeyboardEvent&)> _key_handler {[](auto) {}};
|
||||||
|
std::function<void(const SDL_TextInputEvent&)> _input_handler {[](auto) {}};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void disable_keyboard_handler() override;
|
||||||
|
void enable_keyboard_handler() override;
|
||||||
|
void key_handler(decltype(_key_handler) handler) override
|
||||||
|
{
|
||||||
|
_key_handler = std::move(handler);
|
||||||
|
}
|
||||||
|
void text_input_handler(decltype(_input_handler) handler) override
|
||||||
|
{
|
||||||
|
_input_handler = std::move(handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,8 +1,7 @@
|
||||||
#ifndef BWIDGETS_MOUSE_HANDLER_HPP
|
#ifndef BWIDGETS_MOUSE_HANDLER_HPP
|
||||||
#define BWIDGETS_MOUSE_HANDLER_HPP
|
#define BWIDGETS_MOUSE_HANDLER_HPP
|
||||||
|
|
||||||
#include <functional>
|
#include <basic_widgets/w/feat/event_handler.hpp>
|
||||||
|
|
||||||
#include <basic_widgets/w/feat/focus_handler.hpp>
|
#include <basic_widgets/w/feat/focus_handler.hpp>
|
||||||
|
|
||||||
struct SDL_MouseButtonEvent;
|
struct SDL_MouseButtonEvent;
|
||||||
|
@ -10,27 +9,28 @@ struct SDL_MouseMotionEvent;
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
class MouseHandler : public virtual FocusHandler
|
class MouseHandler : public virtual FocusHandler,
|
||||||
|
public virtual EventHandler
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
const SDL_Rect* _click_area = nullptr;
|
using EventHandler::EventHandler;
|
||||||
bool _is_hovered = false;
|
|
||||||
bool _is_pushed = false;
|
|
||||||
|
|
||||||
virtual void _handle_mouse_button(const SDL_MouseButtonEvent&,
|
|
||||||
const SDL_Rect&) {};
|
|
||||||
virtual void _handle_mouse_motion(const SDL_MouseMotionEvent&,
|
|
||||||
const SDL_Rect&) {};
|
|
||||||
virtual void _on_push(bool) {};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::function<void(const SDL_MouseButtonEvent&)> click_handler = nullptr;
|
// Set click handler.
|
||||||
|
virtual void click_handler(std::function<void(const SDL_MouseButtonEvent&)>) = 0;
|
||||||
virtual auto handle_mouse(const SDL_MouseButtonEvent&, const SDL_Rect&)
|
// Disable mouse event handling.
|
||||||
-> MouseHandler* final;
|
virtual void disable_mouse_handler() = 0;
|
||||||
virtual auto handle_mouse(const SDL_MouseMotionEvent&, const SDL_Rect&)
|
// Enable mouse event handling.
|
||||||
-> MouseHandler* final;
|
virtual void enable_mouse_handler(const SDL_Rect&, const SDL_Rect&) = 0;
|
||||||
virtual auto push(bool) -> MouseHandler* final;
|
// Called on hover state changes.
|
||||||
|
virtual void hover_handler(std::function<void(bool)>) = 0;
|
||||||
|
// Get current hover state.
|
||||||
|
[[nodiscard]] virtual bool hovered() const = 0;
|
||||||
|
// Set mouse motion handler.
|
||||||
|
virtual void
|
||||||
|
mouse_motion_handler(std::function<void(const SDL_MouseMotionEvent&)>) = 0;
|
||||||
|
// Get current push state (mouse button down)
|
||||||
|
[[nodiscard]] virtual bool pushed() const = 0;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
#ifndef BWIDGETS_MOUSE_HANDLER_IMPL_HPP
|
||||||
|
#define BWIDGETS_MOUSE_HANDLER_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/feat/event_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/feat/focus_handler_impl.hpp>
|
||||||
|
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
class MouseHandlerImpl : public virtual MouseHandler,
|
||||||
|
public virtual EventHandlerImpl,
|
||||||
|
public virtual FocusHandlerImpl
|
||||||
|
{
|
||||||
|
bool _hovered {false};
|
||||||
|
bool _pushed {false};
|
||||||
|
|
||||||
|
std::function<void(const SDL_MouseButtonEvent&)> _click_handler {[](auto) {}};
|
||||||
|
std::function<void(bool)> _hover_handler {[](auto) {}};
|
||||||
|
std::function<void(const SDL_MouseMotionEvent&)> _motion_handler {[](auto) {}};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void click_handler(decltype(_click_handler) handler) override
|
||||||
|
{
|
||||||
|
_click_handler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void disable_mouse_handler() override;
|
||||||
|
|
||||||
|
void enable_mouse_handler(const SDL_Rect&, const SDL_Rect&) override;
|
||||||
|
|
||||||
|
[[nodiscard]] bool hovered() const override
|
||||||
|
{
|
||||||
|
return _hovered;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hover_handler(decltype(_hover_handler) handler) override
|
||||||
|
{
|
||||||
|
_hover_handler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void mouse_motion_handler(decltype(_motion_handler) handler) override
|
||||||
|
{
|
||||||
|
_motion_handler = std::move(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool pushed() const override
|
||||||
|
{
|
||||||
|
return _pushed;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
using MouseHandler::MouseHandler;
|
||||||
|
|
||||||
|
// Called on push state changes.
|
||||||
|
virtual void _on_push(bool) {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -1,5 +1,5 @@
|
||||||
#ifndef BWIDGETS_TEXTURE_HANDLER
|
#ifndef BWIDGETS_TEXTURE_HANDLER_HPP
|
||||||
#define BWIDGETS_TEXTURE_HANDLER
|
#define BWIDGETS_TEXTURE_HANDLER_HPP
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
|
@ -11,10 +11,17 @@ namespace bwidgets
|
||||||
class TextureHandler
|
class TextureHandler
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
TextureHandler() noexcept = default;
|
||||||
|
// (Re)render textures.
|
||||||
virtual void _handle_texture_update() = 0;
|
virtual void _handle_texture_update() = 0;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~TextureHandler() noexcept = default;
|
TextureHandler(const TextureHandler&) = delete;
|
||||||
|
TextureHandler(TextureHandler&&) = delete;
|
||||||
|
virtual ~TextureHandler() noexcept = default;
|
||||||
|
|
||||||
|
auto operator=(const TextureHandler&) = delete;
|
||||||
|
auto operator=(TextureHandler&&) = delete;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,175 +1,26 @@
|
||||||
#ifndef BWIDGETS_NUMERIC_INPUT_HPP
|
#ifndef BWIDGETS_NUMERIC_INPUT_HPP
|
||||||
#define BWIDGETS_NUMERIC_INPUT_HPP
|
#define BWIDGETS_NUMERIC_INPUT_HPP
|
||||||
|
|
||||||
#include <limits>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/math.hpp>
|
|
||||||
#include <basic_widgets/core/type/concepts.hpp>
|
#include <basic_widgets/core/type/concepts.hpp>
|
||||||
#include <basic_widgets/w/base/input.hpp>
|
#include <basic_widgets/w/base/input.hpp>
|
||||||
#include <basic_widgets/w/button.hpp>
|
|
||||||
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
template<Numeric T>
|
template<Numeric T>
|
||||||
class NumericInput : public Input<T>
|
class NumericInput : public virtual Input<T>
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
Button _increment_button;
|
using Input<T>::Input;
|
||||||
SDL_Rect _increment_button_area {};
|
|
||||||
Button _decrement_button;
|
|
||||||
SDL_Rect _decrement_button_area {};
|
|
||||||
std::pair<T, T> _value_range {std::numeric_limits<T>::lowest(),
|
|
||||||
std::numeric_limits<T>::max()};
|
|
||||||
|
|
||||||
void _handle_font_change(Font* f) override
|
|
||||||
{
|
|
||||||
_decrement_button.font(f);
|
|
||||||
_increment_button.font(f);
|
|
||||||
Input<T>::_handle_font_change(f);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_font_color_change(const Color& fg,
|
|
||||||
const Color& bg) override
|
|
||||||
{
|
|
||||||
Input<T>::_handle_font_color_change(fg, bg);
|
|
||||||
_decrement_button.font_color_bg(bg)->font_color_fg(fg);
|
|
||||||
_increment_button.font_color_bg(bg)->font_color_fg(fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_geometry_change(const SDL_Rect& vp) override
|
|
||||||
{
|
|
||||||
Input<T>::_handle_geometry_change(vp);
|
|
||||||
|
|
||||||
const int widest_button = _increment_button.size().w != 0
|
|
||||||
? _increment_button.size().w
|
|
||||||
: _decrement_button.size().w;
|
|
||||||
const int button_area_width = 2 * widest_button;
|
|
||||||
const int spacing = center_line(button_area_width, widest_button);
|
|
||||||
const int field_width = vp.w - 2 * button_area_width;
|
|
||||||
|
|
||||||
Widget::_widget_area.x = Widget::_widget_area.x + button_area_width;
|
|
||||||
Widget::_widget_area.w = field_width;
|
|
||||||
|
|
||||||
Input<T>::_input_caption.viewport(
|
|
||||||
rect_offset(rect_margin(Widget::_widget_area,
|
|
||||||
{Input<T>::border_width, Input<T>::border_width}),
|
|
||||||
vp));
|
|
||||||
_decrement_button_area = {spacing, Widget::_widget_area.y,
|
|
||||||
_decrement_button.size().w,
|
|
||||||
_decrement_button.size().h};
|
|
||||||
|
|
||||||
_increment_button_area = {vp.w - spacing - _increment_button.size().w,
|
|
||||||
Widget::_widget_area.y, _increment_button.size().w,
|
|
||||||
_increment_button.size().h};
|
|
||||||
|
|
||||||
_increment_button.viewport(rect_offset(_increment_button_area, vp));
|
|
||||||
_decrement_button.viewport(rect_offset(_decrement_button_area, vp));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_renderer_change(Renderer* r) override
|
|
||||||
{
|
|
||||||
Input<T>::_handle_renderer_change(r);
|
|
||||||
_decrement_button.renderer(r);
|
|
||||||
_increment_button.renderer(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handle_rendering() override
|
|
||||||
{
|
|
||||||
Input<T>::_handle_rendering();
|
|
||||||
_increment_button.render();
|
|
||||||
_decrement_button.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
// Button increment/decrement step.
|
||||||
T button_step = 1;
|
T button_step = 1;
|
||||||
|
|
||||||
NumericInput(Widget* parent = nullptr)
|
// Get allowed value range.
|
||||||
: Input<T>(parent), _increment_button(this), _decrement_button(this)
|
[[nodiscard]] virtual auto value_range() const noexcept
|
||||||
{
|
-> const std::pair<T, T>& = 0;
|
||||||
Input<T>::_input_caption.alignment = Caption::Alignment::RIGHT;
|
// Set allowed value range.
|
||||||
|
virtual void value_range(T min, T max) = 0;
|
||||||
Input<T>::input_min_width = 10; // NOLINT(readability-magic-numbers)
|
|
||||||
Input<T>::input_width_unit = '0';
|
|
||||||
|
|
||||||
_increment_button.text("+");
|
|
||||||
|
|
||||||
_increment_button.click_handler = [this](const SDL_MouseButtonEvent&) {
|
|
||||||
T new_value = this->value + button_step;
|
|
||||||
if (_value_range.second - this->value < button_step)
|
|
||||||
new_value = _value_range.second;
|
|
||||||
|
|
||||||
this->value = new_value;
|
|
||||||
this->input_text(this->value_to_string(new_value));
|
|
||||||
};
|
|
||||||
|
|
||||||
_decrement_button.text("-");
|
|
||||||
|
|
||||||
_decrement_button.click_handler = [this](const SDL_MouseButtonEvent&) {
|
|
||||||
T new_value = this->value - button_step;
|
|
||||||
if (this->value - _value_range.first < button_step)
|
|
||||||
new_value = _value_range.first;
|
|
||||||
|
|
||||||
this->value = new_value;
|
|
||||||
this->input_text(this->value_to_string(new_value));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto handle_event(const SDL_Event& ev) -> Widget* override
|
|
||||||
{
|
|
||||||
Input<T>::handle_event(ev);
|
|
||||||
_increment_button.handle_event(ev);
|
|
||||||
_decrement_button.handle_event(ev);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto is_valid_input(const std::string& input) const noexcept
|
|
||||||
-> bool override
|
|
||||||
{
|
|
||||||
bool valid = false;
|
|
||||||
|
|
||||||
if (input[0] >= '0' && input[0] <= '9') valid = true;
|
|
||||||
if constexpr (std::is_floating_point_v<T>)
|
|
||||||
if (input[0] == '.'
|
|
||||||
&& Input<T>::input_text().find('.') == std::string::npos)
|
|
||||||
valid = true;
|
|
||||||
if constexpr (std::is_signed_v<T>) {
|
|
||||||
std::string displayed_value = Input<T>::input_text();
|
|
||||||
if (input[0] == '-' && displayed_value.empty()) valid = true;
|
|
||||||
}
|
|
||||||
return valid;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] auto process_value(T x) const noexcept -> T override
|
|
||||||
{
|
|
||||||
T value = x;
|
|
||||||
if (x < _value_range.first) value = _value_range.first;
|
|
||||||
else if (x > _value_range.second) value = _value_range.second;
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] inline auto size() const noexcept -> Size override
|
|
||||||
{
|
|
||||||
const auto base = Input<T>::size();
|
|
||||||
const auto btns_width = 4 * _increment_button.size().w;
|
|
||||||
return {base.w + btns_width, base.h};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] virtual auto value_range() const noexcept -> const std::pair<T, T>&
|
|
||||||
{
|
|
||||||
return _value_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual auto value_range(T min, T max) -> NumericInput<T>*
|
|
||||||
{
|
|
||||||
if (min > max) throw std::invalid_argument("min cannot be greater than max");
|
|
||||||
_value_range = {min, max};
|
|
||||||
Input<T>::value = process_value(Input<T>::value);
|
|
||||||
Input<T>::input_text(Input<T>::value_to_string(Input<T>::value));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,196 @@
|
||||||
|
#ifndef BWIDGETS_NUMERIC_INPUT_IMPL_HPP
|
||||||
|
#define BWIDGETS_NUMERIC_INPUT_IMPL_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/base/input_impl.hpp>
|
||||||
|
#include <basic_widgets/w/numeric_input.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
template<Numeric T>
|
||||||
|
class NumericInputImpl : public virtual NumericInput<T>,
|
||||||
|
public virtual InputImpl<T>
|
||||||
|
{
|
||||||
|
using Self = NumericInputImpl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
NumericInputImpl(Widget* parent = nullptr)
|
||||||
|
: InputImpl<T>(parent),
|
||||||
|
_increment_button(create_button(this)),
|
||||||
|
_decrement_button(create_button(this))
|
||||||
|
{
|
||||||
|
Self::_input_caption->alignment = Caption::Alignment::RIGHT;
|
||||||
|
|
||||||
|
Self::input_min_width = 10; // NOLINT(readability-magic-numbers)
|
||||||
|
Self::input_width_unit = '0';
|
||||||
|
|
||||||
|
// +/- buttons click handler. op is the operation (+/-) to apply on current
|
||||||
|
// value.
|
||||||
|
const auto button_action = [this](const std::function<T(T, T)> op) {
|
||||||
|
const T new_value = [this, &op]() {
|
||||||
|
auto _new_value = op(this->value, NumericInput<T>::button_step);
|
||||||
|
if (_new_value < this->value) {
|
||||||
|
// if value - button_step is out of allowed range
|
||||||
|
// set value to the smallest allowed value.
|
||||||
|
if (std::abs(_value_range.first - this->value)
|
||||||
|
< std::abs(NumericInput<T>::button_step))
|
||||||
|
return _value_range.first;
|
||||||
|
return _new_value;
|
||||||
|
}
|
||||||
|
// if value + button_step exceeds maximal allowed value
|
||||||
|
// set value to max allowed value.
|
||||||
|
if (std::abs(_value_range.second - this->value)
|
||||||
|
< std::abs(NumericInput<T>::button_step))
|
||||||
|
return _value_range.second;
|
||||||
|
return _new_value;
|
||||||
|
}();
|
||||||
|
|
||||||
|
this->value = new_value;
|
||||||
|
this->input_text(this->value_to_string(new_value));
|
||||||
|
};
|
||||||
|
|
||||||
|
_increment_button->text("+");
|
||||||
|
_increment_button->click_handler(
|
||||||
|
[button_action](const SDL_MouseButtonEvent&) {
|
||||||
|
button_action([](const T a, const T b) { return a + b; });
|
||||||
|
});
|
||||||
|
|
||||||
|
_decrement_button->text("-");
|
||||||
|
|
||||||
|
_decrement_button->click_handler(
|
||||||
|
[button_action](const SDL_MouseButtonEvent&) {
|
||||||
|
button_action([](const T a, const T b) { return a - b; });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handle_event(const SDL_Event& ev) override
|
||||||
|
{
|
||||||
|
InputImpl<T>::handle_event(ev);
|
||||||
|
_increment_button->handle_event(ev);
|
||||||
|
_decrement_button->handle_event(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto is_valid_input(const std::string_view input) const noexcept
|
||||||
|
-> bool override
|
||||||
|
{
|
||||||
|
bool valid = [input, this]() {
|
||||||
|
// Allow 0-9 chars.
|
||||||
|
if (input[0] >= '0' && input[0] <= '9') return true;
|
||||||
|
// Allow one point for float types.
|
||||||
|
if constexpr (std::is_floating_point_v<T>)
|
||||||
|
if (input[0] == '.'
|
||||||
|
&& Self::input_text().find('.') == std::string::npos)
|
||||||
|
return true;
|
||||||
|
// Allow "-" as first input char for signed types.
|
||||||
|
if constexpr (std::is_signed_v<T>) {
|
||||||
|
std::string_view displayed_value = Self::input_text();
|
||||||
|
if (input[0] == '-' && displayed_value.empty()) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}();
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto process_value(const T x) const noexcept -> T override
|
||||||
|
{
|
||||||
|
// If value is out of allowed range return either min or max allowed
|
||||||
|
// value.
|
||||||
|
T value = [x, this]() {
|
||||||
|
if (x < _value_range.first) return _value_range.first;
|
||||||
|
if (x > _value_range.second) return _value_range.second;
|
||||||
|
return x;
|
||||||
|
}();
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto size() const noexcept -> Size override
|
||||||
|
{
|
||||||
|
const auto base = InputImpl<T>::size();
|
||||||
|
const auto btns_width = 4 * _increment_button->size().w;
|
||||||
|
return {base.w + btns_width, base.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] auto value_range() const noexcept
|
||||||
|
-> const std::pair<T, T>& override
|
||||||
|
{
|
||||||
|
return _value_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
void value_range(const T min, const T max) override
|
||||||
|
{
|
||||||
|
if (min > max) throw std::invalid_argument("min cannot be greater than max");
|
||||||
|
_value_range = {min, max};
|
||||||
|
Self::value = process_value(Self::value);
|
||||||
|
Self::input_text(Self::value_to_string(Self::value));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<Button> _increment_button;
|
||||||
|
SDL_Rect _increment_button_area {};
|
||||||
|
std::unique_ptr<Button> _decrement_button;
|
||||||
|
SDL_Rect _decrement_button_area {};
|
||||||
|
std::pair<T, T> _value_range {std::numeric_limits<T>::lowest(),
|
||||||
|
std::numeric_limits<T>::max()};
|
||||||
|
|
||||||
|
void _handle_font_change(const std::shared_ptr<Font>& f) override
|
||||||
|
{
|
||||||
|
_decrement_button->font(f);
|
||||||
|
_increment_button->font(f);
|
||||||
|
InputImpl<T>::_handle_font_change(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_font_color_change(const Color fg, const Color bg) override
|
||||||
|
{
|
||||||
|
InputImpl<T>::_handle_font_color_change(fg, bg);
|
||||||
|
_decrement_button->font_color_bg(bg);
|
||||||
|
_decrement_button->font_color_fg(fg);
|
||||||
|
_increment_button->font_color_bg(bg);
|
||||||
|
_increment_button->font_color_fg(fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_geometry_change(const SDL_Rect& vp) override
|
||||||
|
{
|
||||||
|
InputImpl<T>::_handle_geometry_change(vp);
|
||||||
|
|
||||||
|
const int widest_button = _increment_button->size().w != 0
|
||||||
|
? _increment_button->size().w
|
||||||
|
: _decrement_button->size().w;
|
||||||
|
const int button_area_width = 2 * widest_button;
|
||||||
|
const int spacing = center_line(button_area_width, widest_button);
|
||||||
|
const int field_width = vp.w - 2 * button_area_width;
|
||||||
|
|
||||||
|
Self::_widget_area.x = Self::_widget_area.x + button_area_width;
|
||||||
|
Self::_widget_area.w = field_width;
|
||||||
|
|
||||||
|
Self::_input_caption->viewport(rect_offset(
|
||||||
|
rect_margin(Self::_widget_area, {Self::border_width, Self::border_width}),
|
||||||
|
vp));
|
||||||
|
_decrement_button_area = {spacing, Self::_widget_area.y,
|
||||||
|
_decrement_button->size().w,
|
||||||
|
_decrement_button->size().h};
|
||||||
|
|
||||||
|
_increment_button_area = {vp.w - spacing - _increment_button->size().w,
|
||||||
|
Self::_widget_area.y, _increment_button->size().w,
|
||||||
|
_increment_button->size().h};
|
||||||
|
|
||||||
|
_increment_button->viewport(rect_offset(_increment_button_area, vp));
|
||||||
|
_decrement_button->viewport(rect_offset(_decrement_button_area, vp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_renderer_change(const std::shared_ptr<Renderer>& r) override
|
||||||
|
{
|
||||||
|
InputImpl<T>::_handle_renderer_change(r);
|
||||||
|
_decrement_button->renderer(r);
|
||||||
|
_increment_button->renderer(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handle_rendering() override
|
||||||
|
{
|
||||||
|
InputImpl<T>::_handle_rendering();
|
||||||
|
_increment_button->render();
|
||||||
|
_decrement_button->render();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,19 @@
|
||||||
|
#ifndef BWIDGETS_WIDGET_FACTORY_HPP
|
||||||
|
#define BWIDGETS_WIDGET_FACTORY_HPP
|
||||||
|
|
||||||
|
#include <basic_widgets/w/aligned_layout.hpp>
|
||||||
|
#include <basic_widgets/w/button.hpp>
|
||||||
|
#include <basic_widgets/w/caption.hpp>
|
||||||
|
#include <basic_widgets/w/numeric_input.hpp>
|
||||||
|
|
||||||
|
namespace bwidgets
|
||||||
|
{
|
||||||
|
auto create_button(Widget* p = nullptr) -> std::unique_ptr<Button>;
|
||||||
|
auto create_caption(Widget* p = nullptr) -> std::unique_ptr<Caption>;
|
||||||
|
auto create_horizontal_layout(Widget* p = nullptr) -> std::unique_ptr<AlignedLayout>;
|
||||||
|
auto create_input_float(Widget* p = nullptr) -> std::unique_ptr<NumericInput<float>>;
|
||||||
|
auto create_input_int(Widget* p = nullptr) -> std::unique_ptr<NumericInput<int>>;
|
||||||
|
auto create_vertical_layout(Widget* p = nullptr) -> std::unique_ptr<AlignedLayout>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
29
meson.build
29
meson.build
|
@ -1,16 +1,19 @@
|
||||||
project('sdl2_basic_widgets', 'cpp',
|
project('sdl2_basic_widgets', 'cpp',
|
||||||
version : '0.1pre',
|
version : '0.9.0',
|
||||||
default_options : [
|
default_options : [
|
||||||
'b_lundef=false',
|
'b_lundef=false',
|
||||||
'b_sanitize=address,undefined',
|
'b_sanitize=undefined',
|
||||||
'cpp_std=c++20',
|
'cpp_std=c++20',
|
||||||
'd_ndebug=if-release',
|
|
||||||
'optimization=g',
|
'optimization=g',
|
||||||
'warning_level=3',
|
'warning_level=3',
|
||||||
],
|
],
|
||||||
license: 'EUPL-1.2')
|
license: 'EUPL-1.2')
|
||||||
|
|
||||||
add_project_arguments('-pedantic', '-Winline', language: 'cpp')
|
add_project_arguments('-Wconversion', '-pedantic', language: 'cpp')
|
||||||
|
|
||||||
|
if (get_option('buildtype').startswith('debug'))
|
||||||
|
add_project_arguments('-DBWIDGETS_DEBUG', language: 'cpp')
|
||||||
|
endif
|
||||||
|
|
||||||
sdl = [
|
sdl = [
|
||||||
dependency('sdl2', version: '>=2.0.5'),
|
dependency('sdl2', version: '>=2.0.5'),
|
||||||
|
@ -25,12 +28,14 @@ libbasic_widgets = static_library('basic_widgets',
|
||||||
'src/core/font.cpp',
|
'src/core/font.cpp',
|
||||||
'src/core/renderer.cpp',
|
'src/core/renderer.cpp',
|
||||||
'src/core/texture.cpp',
|
'src/core/texture.cpp',
|
||||||
'src/w/aligned_layout.cpp',
|
'src/w/base/layout_impl.cpp',
|
||||||
'src/w/base/layout.cpp',
|
'src/w/base/widget_impl.cpp',
|
||||||
'src/w/base/widget.cpp',
|
'src/w/button_impl.cpp',
|
||||||
'src/w/button.cpp',
|
'src/w/caption_impl.cpp',
|
||||||
'src/w/caption.cpp',
|
'src/w/feat/event_handler_impl.cpp',
|
||||||
'src/w/feat/mouse_handler.cpp',
|
'src/w/feat/keyboard_handler_impl.cpp',
|
||||||
|
'src/w/feat/mouse_handler_impl.cpp',
|
||||||
|
'src/w/widget_factory.cpp',
|
||||||
dependencies : [sdl, fontconfig],
|
dependencies : [sdl, fontconfig],
|
||||||
include_directories : pub_api,
|
include_directories : pub_api,
|
||||||
install : true)
|
install : true)
|
||||||
|
@ -42,24 +47,28 @@ libbasic_widgets_dep = declare_dependency(
|
||||||
|
|
||||||
executable('button_demo',
|
executable('button_demo',
|
||||||
'examples/button_example.cpp',
|
'examples/button_example.cpp',
|
||||||
|
dependencies: [sdl],
|
||||||
include_directories : pub_api,
|
include_directories : pub_api,
|
||||||
link_with : libbasic_widgets,
|
link_with : libbasic_widgets,
|
||||||
install : false)
|
install : false)
|
||||||
|
|
||||||
executable('caption_demo',
|
executable('caption_demo',
|
||||||
'examples/caption_example.cpp',
|
'examples/caption_example.cpp',
|
||||||
|
dependencies: [sdl],
|
||||||
include_directories : pub_api,
|
include_directories : pub_api,
|
||||||
link_with : libbasic_widgets,
|
link_with : libbasic_widgets,
|
||||||
install : false)
|
install : false)
|
||||||
|
|
||||||
executable('example_demo',
|
executable('example_demo',
|
||||||
'examples/example_example.cpp',
|
'examples/example_example.cpp',
|
||||||
|
dependencies: [sdl],
|
||||||
include_directories : pub_api,
|
include_directories : pub_api,
|
||||||
link_with : libbasic_widgets,
|
link_with : libbasic_widgets,
|
||||||
install : false)
|
install : false)
|
||||||
|
|
||||||
executable('input_demo',
|
executable('input_demo',
|
||||||
'examples/input_example.cpp',
|
'examples/input_example.cpp',
|
||||||
|
dependencies: [sdl],
|
||||||
include_directories : pub_api,
|
include_directories : pub_api,
|
||||||
link_with : libbasic_widgets,
|
link_with : libbasic_widgets,
|
||||||
install : false)
|
install : false)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <SDL2/SDL_render.h>
|
#include <SDL_render.h>
|
||||||
|
|
||||||
#include <basic_widgets/core/draw.hpp>
|
#include <basic_widgets/core/draw.hpp>
|
||||||
#include <basic_widgets/core/math.hpp>
|
#include <basic_widgets/core/math.hpp>
|
||||||
|
@ -10,26 +10,27 @@
|
||||||
|
|
||||||
namespace bwidgets
|
namespace bwidgets
|
||||||
{
|
{
|
||||||
auto aa(const Color& base_color, const int aa_pixels, const float d) noexcept
|
auto aa(const Color base_color, const int aa_pixels, const float d) noexcept -> Color
|
||||||
-> Color
|
|
||||||
{
|
{
|
||||||
Color c(base_color);
|
Color c = [aa_pixels, base_color, d]() -> Color {
|
||||||
if (aa_pixels == 0) {
|
const auto [r, g, b, a] = base_color();
|
||||||
if (d > 0) c().a = 0;
|
// AA disabled, returns fully opaque or fully transparent colors
|
||||||
}
|
if (aa_pixels == 0) {
|
||||||
else {
|
if (d > 0) return {r, g, b, 0};
|
||||||
|
return {r, g, b, a};
|
||||||
|
}
|
||||||
const auto factor = 1.0 - smoothstep<float>(d, (float)-aa_pixels, 0.);
|
const auto factor = 1.0 - smoothstep<float>(d, (float)-aa_pixels, 0.);
|
||||||
c().a = (uint8_t)((float)c().a * factor);
|
return {r, g, b, (uint8_t)((float)base_color().a * factor)};
|
||||||
}
|
}();
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto filled_circle(const Color& c, const int resolution, Renderer* r,
|
auto filled_circle(const Color c, const int resolution, const Renderer& r,
|
||||||
const int aa_pixels) -> Texture*
|
const int aa_pixels) -> std::shared_ptr<Texture>
|
||||||
{
|
{
|
||||||
// clang-format off
|
// clang-format off
|
||||||
auto* texture {new Texture(
|
auto texture {std::make_shared<Texture>(
|
||||||
r,
|
r,
|
||||||
SDL_PIXELFORMAT_RGBA32,
|
SDL_PIXELFORMAT_RGBA32,
|
||||||
SDL_TEXTUREACCESS_STATIC,
|
SDL_TEXTUREACCESS_STATIC,
|
||||||
|
@ -39,16 +40,15 @@ namespace bwidgets
|
||||||
const auto radius {resolution / 2};
|
const auto radius {resolution / 2};
|
||||||
const SDL_Point center {radius, radius};
|
const SDL_Point center {radius, radius};
|
||||||
|
|
||||||
set_pixels_color(
|
set_pixels_color(texture.get(),
|
||||||
texture,
|
[aa_pixels, c, center, radius](
|
||||||
[aa_pixels, c, center, radius](const SDL_Point& p,
|
SDL_Point p, const SDL_PixelFormat& format) -> uint32_t {
|
||||||
const SDL_PixelFormat* format) -> uint32_t {
|
const auto d_delta = distance(center, p) - (float)radius;
|
||||||
const auto d_delta = distance(center, p) - (float)radius;
|
const auto aa_color = aa(c, aa_pixels, d_delta);
|
||||||
const auto aa_color = aa(c, aa_pixels, d_delta);
|
|
||||||
|
|
||||||
return SDL_MapRGBA(format, aa_color().r, aa_color().g, aa_color().b,
|
return SDL_MapRGBA(&format, aa_color().r, aa_color().g,
|
||||||
aa_color().a);
|
aa_color().b, aa_color().a);
|
||||||
});
|
});
|
||||||
texture->blend_mode(SDL_BLENDMODE_BLEND);
|
texture->blend_mode(SDL_BLENDMODE_BLEND);
|
||||||
texture->scale_mode(SDL_ScaleModeNearest);
|
texture->scale_mode(SDL_ScaleModeNearest);
|
||||||
|
|
||||||
|
@ -56,21 +56,21 @@ namespace bwidgets
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_pixels_color(
|
void set_pixels_color(
|
||||||
Texture* t,
|
Texture& t,
|
||||||
const std::function<uint32_t(const SDL_Point&, const SDL_PixelFormat*)>&
|
const std::function<uint32_t(SDL_Point, const SDL_PixelFormat&)>& pixel_color)
|
||||||
pixel_color)
|
|
||||||
{
|
{
|
||||||
auto attr = t->attributes();
|
auto attr = t.attributes();
|
||||||
auto pitch = attr.w * attr.format->BytesPerPixel;
|
auto pitch = attr.w * attr.format->BytesPerPixel;
|
||||||
|
|
||||||
std::vector<uint32_t> pixels;
|
std::vector<uint32_t> pixels;
|
||||||
pixels.reserve(attr.h * (std::vector<uint32_t>::size_type)pitch);
|
pixels.reserve(attr.h * (std::vector<uint32_t>::size_type)pitch);
|
||||||
|
// TODO parallel algo
|
||||||
for (auto y = 0; y < attr.h; y++) {
|
for (auto y = 0; y < attr.h; y++) {
|
||||||
for (auto x = 0; x < attr.w; x++) {
|
for (auto x = 0; x < attr.w; x++) {
|
||||||
pixels.emplace_back(pixel_color({x, y}, attr.format));
|
pixels.emplace_back(pixel_color({x, y}, *attr.format));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
t->update(nullptr, (const void*)pixels.data(), pitch);
|
t.update(nullptr, (const void*)pixels.data(), pitch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,161 +1,145 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include <fontconfig/fontconfig.h>
|
#include <fontconfig/fontconfig.h>
|
||||||
|
|
||||||
|
#include <basic_widgets/core/error_helper.hpp>
|
||||||
#include <basic_widgets/core/font.hpp>
|
#include <basic_widgets/core/font.hpp>
|
||||||
#include <basic_widgets/core/type/fc_error.hpp>
|
#include <basic_widgets/core/type/fc_error.hpp>
|
||||||
#include <basic_widgets/core/type/sdl_error.hpp>
|
|
||||||
|
|
||||||
namespace bwidgets
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
const Color Font::default_color_bg {255, 255, 255, SDL_ALPHA_OPAQUE};
|
||||||
|
const Color Font::default_color_fg {0, 0, 0, SDL_ALPHA_OPAQUE};
|
||||||
|
|
||||||
|
Font::Font(TTF_Font* f)
|
||||||
|
: _data {ptr_or_throw<SDLError>(f)},
|
||||||
|
ascent {TTF_FontAscent(f)},
|
||||||
|
descent {TTF_FontDescent(f)},
|
||||||
|
faces {TTF_FontFaces(f)},
|
||||||
|
family_name {TTF_FontFaceFamilyName(f)},
|
||||||
|
fixed_width {TTF_FontFaceIsFixedWidth(f) != 0},
|
||||||
|
height {TTF_FontHeight(f)},
|
||||||
|
line_skip {TTF_FontLineSkip(f)},
|
||||||
|
style_name {TTF_FontFaceStyleName(f)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
Font::Font(const std::string_view file, const int size)
|
||||||
|
: Font {TTF_OpenFont(file.data(), size)}
|
||||||
|
{}
|
||||||
|
|
||||||
|
auto Font::hinting() const noexcept -> Font::Hinting
|
||||||
{
|
{
|
||||||
Font::Font(TTF_Font* f)
|
return static_cast<Hinting>(TTF_GetFontHinting(_data.get()));
|
||||||
: OpaqueStruct(f),
|
}
|
||||||
ascent(TTF_FontAscent(f)),
|
|
||||||
descent(TTF_FontDescent(f)),
|
|
||||||
faces(TTF_FontFaces(f)),
|
|
||||||
family_name(TTF_FontFaceFamilyName(f)),
|
|
||||||
fixed_width(TTF_FontFaceIsFixedWidth(f) != 0),
|
|
||||||
height(TTF_FontHeight(f)),
|
|
||||||
line_skip(TTF_FontLineSkip(f)),
|
|
||||||
style_name(TTF_FontFaceStyleName(f))
|
|
||||||
{}
|
|
||||||
|
|
||||||
Font::Font(const std::string& file, const int size)
|
auto Font::hinting(const Font::Hinting h) noexcept -> Font*
|
||||||
: Font(ptr_or_throw<FCError>(TTF_OpenFont(file.c_str(), size), __FILE__,
|
{
|
||||||
__FUNCTION__, __LINE__, "failed to open font"))
|
TTF_SetFontHinting(_data.get(), static_cast<int>(h));
|
||||||
{}
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
Font::~Font() noexcept
|
auto Font::kerning() const noexcept -> bool
|
||||||
{
|
{
|
||||||
TTF_CloseFont(_c_pod);
|
return TTF_GetFontKerning(_data.get()) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Font::hinting() const noexcept -> Font::Hinting
|
auto Font::kerning(const bool allowed) noexcept -> Font*
|
||||||
{
|
{
|
||||||
return (Hinting)TTF_GetFontHinting(_c_pod);
|
TTF_SetFontKerning(_data.get(), static_cast<int>(allowed));
|
||||||
}
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
auto Font::hinting(const Font::Hinting h) noexcept -> Font*
|
auto Font::outline() const noexcept -> int
|
||||||
{
|
{
|
||||||
TTF_SetFontHinting(_c_pod, (int)h);
|
return TTF_GetFontOutline(_data.get());
|
||||||
return this;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
auto Font::kerning() const noexcept -> bool
|
auto Font::outline(const int size) noexcept -> Font*
|
||||||
{
|
{
|
||||||
return TTF_GetFontKerning(_c_pod) != 0;
|
TTF_SetFontOutline(_data.get(), size);
|
||||||
}
|
return this;
|
||||||
|
}
|
||||||
auto Font::kerning(const bool allowed) noexcept -> Font*
|
|
||||||
{
|
|
||||||
TTF_SetFontKerning(_c_pod, static_cast<int>(allowed));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Font::outline() const noexcept -> int
|
|
||||||
{
|
|
||||||
return TTF_GetFontOutline(_c_pod);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Font::outline(const int size) noexcept -> Font*
|
|
||||||
{
|
|
||||||
TTF_SetFontOutline(_c_pod, size);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Font::render(const RenderMode m, const std::string& str, const Color& fg,
|
|
||||||
const Color& bg) -> SDL_Surface*
|
|
||||||
{
|
|
||||||
std::function<SDL_Surface*()> renderer;
|
|
||||||
const char* c_str = str.empty() ? " " : str.c_str();
|
|
||||||
|
|
||||||
|
auto Font::render(const RenderMode m, const std::string_view str, const Color fg,
|
||||||
|
const Color bg) -> std::unique_ptr<SDL_Surface, Deleter>
|
||||||
|
{
|
||||||
|
const char* const c_str = str.empty() ? " " : str.data();
|
||||||
|
auto renderer = [bg, c_str, fg, m, this]() -> std::function<SDL_Surface*()> {
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case RenderMode::BLENDED:
|
case RenderMode::BLENDED:
|
||||||
renderer = [&fg, c_str, this]() {
|
return [fg, c_str, this]() {
|
||||||
return TTF_RenderUTF8_Blended(_c_pod, c_str, fg());
|
return TTF_RenderUTF8_Blended(_data.get(), c_str, fg());
|
||||||
};
|
};
|
||||||
break;
|
|
||||||
case RenderMode::SHADED:
|
case RenderMode::SHADED:
|
||||||
renderer = [&bg, &fg, c_str, this]() {
|
return [bg, fg, c_str, this]() {
|
||||||
return TTF_RenderUTF8_Shaded(_c_pod, c_str, fg(), bg());
|
return TTF_RenderUTF8_Shaded(_data.get(), c_str, fg(), bg());
|
||||||
};
|
};
|
||||||
break;
|
|
||||||
case RenderMode::SOLID:
|
case RenderMode::SOLID:
|
||||||
renderer = [&fg, c_str, this]() {
|
return [fg, c_str, this]() {
|
||||||
return TTF_RenderUTF8_Solid(_c_pod, c_str, fg());
|
return TTF_RenderUTF8_Solid(_data.get(), c_str, fg());
|
||||||
};
|
};
|
||||||
break;
|
default:
|
||||||
|
throw std::logic_error("missing switch case.");
|
||||||
}
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
return ptr_or_throw<SDLError>(renderer(), __FILE__, __FUNCTION__, __LINE__);
|
return std::unique_ptr<SDL_Surface, Deleter>(ptr_or_throw<SDLError>(renderer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Font::style() const noexcept -> uint8_t
|
auto Font::style() const noexcept -> uint8_t
|
||||||
{
|
{
|
||||||
return TTF_GetFontStyle(_c_pod);
|
return TTF_GetFontStyle(_data.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Font::style(const uint8_t s) noexcept -> Font*
|
auto Font::style(const uint8_t s) noexcept -> Font*
|
||||||
{
|
{
|
||||||
TTF_SetFontStyle(_c_pod, s);
|
TTF_SetFontStyle(_data.get(), s);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Font::text_size(const std::string& str) const noexcept -> Size
|
auto Font::text_size(const std::string_view str) const noexcept -> Size
|
||||||
{
|
{
|
||||||
Size s {};
|
Size s {};
|
||||||
TTF_SizeUTF8(_c_pod, str.c_str(), &s.w, &s.h);
|
TTF_SizeUTF8(_data.get(), str.data(), &s.w, &s.h);
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Font::find(const std::string& pat) -> std::string
|
auto Font::find(const std::string_view pat) -> std::string
|
||||||
{
|
{
|
||||||
std::stack<std::function<void()>> cleaners;
|
const auto conf = std::unique_ptr<FcConfig, Deleter>(
|
||||||
|
ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), "fontconfig init failed."));
|
||||||
FcConfig* conf = ptr_or_throw<FCError>(FcInitLoadConfigAndFonts(), __FILE__,
|
|
||||||
__FUNCTION__, __LINE__, "init failed");
|
const auto pattern = [&conf, pat]() {
|
||||||
cleaners.emplace([conf]() { FcConfigDestroy(conf); });
|
auto pattern = std::unique_ptr<FcPattern, Deleter>(ptr_or_throw<FCError>(
|
||||||
|
FcNameParse(reinterpret_cast<const FcChar8*>(pat.data())),
|
||||||
FcPattern* pattern = nullptr;
|
"pattern parsing failed"));
|
||||||
try {
|
|
||||||
pattern =
|
success_or_throw<FCError, FcBool>(
|
||||||
ptr_or_throw<FCError>(FcNameParse((FcChar8*)pat.c_str()), __FILE__,
|
FcConfigSubstitute(conf.get(), pattern.get(), FcMatchPattern),
|
||||||
__FUNCTION__, __LINE__, "pattern parsing failed");
|
"FcConfigSubstitute failed", [](auto code) { return code == FcTrue; });
|
||||||
cleaners.emplace([pattern]() { FcPatternDestroy(pattern); });
|
return pattern;
|
||||||
|
}();
|
||||||
if (FcConfigSubstitute(conf, pattern, FcMatchPattern) == FcFalse)
|
|
||||||
throw FCError {__FILE__, __FUNCTION__, __LINE__,
|
FcDefaultSubstitute(pattern.get());
|
||||||
"FcConfigSubstitute failed"};
|
|
||||||
} catch (const std::exception& e) {
|
std::string file_path = [&conf, &pattern]() {
|
||||||
while (!cleaners.empty()) {
|
FcResult res {};
|
||||||
cleaners.top()();
|
const auto font = std::unique_ptr<FcPattern, Deleter>(ptr_or_throw<FCError>(
|
||||||
cleaners.pop();
|
FcFontMatch(conf.get(), pattern.get(), &res), "no font found."));
|
||||||
}
|
|
||||||
throw e;
|
if (FcChar8* file = nullptr;
|
||||||
}
|
FcPatternGetString(font.get(), FC_FILE, 0, &file) == FcResultMatch)
|
||||||
|
{
|
||||||
FcDefaultSubstitute(pattern);
|
// "I know what I'm doing"
|
||||||
|
// NOLINTNEXTLINE
|
||||||
std::string file_path;
|
std::string _file_path {reinterpret_cast<char*>(file)};
|
||||||
FcResult res {};
|
return _file_path;
|
||||||
FcPattern* font = FcFontMatch(conf, pattern, &res);
|
}
|
||||||
if (font != nullptr) {
|
throw FCError("no font found.");
|
||||||
FcChar8* file = nullptr;
|
}();
|
||||||
if (FcPatternGetString(font, FC_FILE, 0, &file) == FcResultMatch) {
|
|
||||||
file_path = (char*)file;
|
return file_path;
|
||||||
}
|
|
||||||
FcPatternDestroy(font);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (!cleaners.empty()) {
|
|
||||||
cleaners.top()();
|
|
||||||
cleaners.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file_path.empty())
|
|
||||||
throw FCError {__FILE__, __FUNCTION__, __LINE__, "no font found"};
|
|
||||||
|
|
||||||
return file_path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,47 +3,39 @@
|
||||||
|
|
||||||
using namespace bwidgets;
|
using namespace bwidgets;
|
||||||
|
|
||||||
Renderer::Renderer(SDL_Renderer* r) : OpaqueStruct(r), info(_info(r)) {}
|
Renderer::Renderer(SDL_Renderer* r) : _data {ptr_or_throw<SDLError>(r)}, info {_info(r)}
|
||||||
|
|
||||||
Renderer::Renderer(SDL_Window* w, const int index, const uint32_t flags = 0)
|
|
||||||
: Renderer(ptr_or_throw<SDLError>(SDL_CreateRenderer(w, index, flags), __FILE__,
|
|
||||||
__FUNCTION__, __LINE__))
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Renderer::~Renderer() noexcept
|
Renderer::Renderer(SDL_Window* w, const int index, const uint32_t flags = 0)
|
||||||
{
|
: Renderer {SDL_CreateRenderer(w, index, flags)}
|
||||||
SDL_DestroyRenderer(_c_pod);
|
{}
|
||||||
}
|
|
||||||
|
|
||||||
auto Renderer::blend_mode() -> SDL_BlendMode
|
auto Renderer::blend_mode() -> SDL_BlendMode
|
||||||
{
|
{
|
||||||
SDL_BlendMode mode {};
|
SDL_BlendMode mode {};
|
||||||
SDLError::success_or_throw(SDL_GetRenderDrawBlendMode(_c_pod, &mode), __FILE__,
|
success_or_throw<SDLError>(SDL_GetRenderDrawBlendMode(_data.get(), &mode));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::blend_mode(const SDL_BlendMode mode) -> Renderer*
|
auto Renderer::blend_mode(const SDL_BlendMode mode) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_SetRenderDrawBlendMode(_c_pod, mode), __FILE__,
|
success_or_throw<SDLError>(SDL_SetRenderDrawBlendMode(_data.get(), mode));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::clear() -> Renderer*
|
auto Renderer::clear() -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderClear(_c_pod), __FILE__, __FUNCTION__,
|
success_or_throw<SDLError>(SDL_RenderClear(_data.get()));
|
||||||
__LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::copy(Texture* t, const SDL_Rect* src, const SDL_Rect* dst) -> Renderer*
|
auto Renderer::copy(const Texture& t, const SDL_Rect* const src,
|
||||||
|
const SDL_Rect* const dst) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderCopy(_c_pod, (*t)(), src, dst), __FILE__,
|
success_or_throw<SDLError>(SDL_RenderCopy(_data.get(), t._data.get(), src, dst));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -51,55 +43,51 @@ auto Renderer::copy(Texture* t, const SDL_Rect* src, const SDL_Rect* dst) -> Ren
|
||||||
auto Renderer::draw_color() -> Color
|
auto Renderer::draw_color() -> Color
|
||||||
{
|
{
|
||||||
Color c;
|
Color c;
|
||||||
SDLError::success_or_throw(
|
success_or_throw<SDLError>(
|
||||||
SDL_GetRenderDrawColor(_c_pod, &c().r, &c().g, &c().b, &c().a), __FILE__,
|
SDL_GetRenderDrawColor(_data.get(), &c().r, &c().g, &c().b, &c().a));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::draw_color(const Color& c) -> Renderer*
|
auto Renderer::draw_color(const Color c) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(
|
success_or_throw<SDLError>(
|
||||||
SDL_SetRenderDrawColor(_c_pod, c().r, c().g, c().b, c().a), __FILE__, __FUNCTION__,
|
SDL_SetRenderDrawColor(_data.get(), c().r, c().g, c().b, c().a));
|
||||||
__LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::draw_line(const SDL_Point& a, const SDL_Point& b) -> Renderer*
|
auto Renderer::draw_line(const SDL_Point a, const SDL_Point b) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderDrawLine(_c_pod, a.x, a.y, b.x, b.y), __FILE__,
|
success_or_throw<SDLError>(SDL_RenderDrawLine(_data.get(), a.x, a.y, b.x, b.y));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::draw_lines(const std::vector<SDL_Point>& pts) -> Renderer*
|
auto Renderer::draw_lines(const std::span<SDL_Point> pts) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderDrawLines(_c_pod, pts.data(), (int)pts.size()),
|
success_or_throw<SDLError>(
|
||||||
__FILE__, __FUNCTION__, __LINE__);
|
SDL_RenderDrawLines(_data.get(), pts.data(), (int)pts.size()));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::draw_point(const SDL_Point& p) -> Renderer*
|
auto Renderer::draw_point(const SDL_Point p) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderDrawPoint(_c_pod, p.x, p.y), __FILE__,
|
success_or_throw<SDLError>(SDL_RenderDrawPoint(_data.get(), p.x, p.y));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::draw_points(const std::vector<SDL_Point>& pts) -> Renderer*
|
auto Renderer::draw_points(const std::span<SDL_Point> pts) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderDrawPoints(_c_pod, pts.data(), (int)pts.size()),
|
success_or_throw<SDLError>(
|
||||||
__FILE__, __FUNCTION__, __LINE__);
|
SDL_RenderDrawPoints(_data.get(), pts.data(), (int)pts.size()));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::draw_rect(const SDL_Rect* r) -> Renderer*
|
auto Renderer::draw_rect(const SDL_Rect* const r) -> Renderer*
|
||||||
{
|
{
|
||||||
auto vp = viewport();
|
auto vp = viewport();
|
||||||
// Has glitch at top-left and bottom-right corner.
|
// Has glitch at top-left and bottom-right corner.
|
||||||
|
@ -107,37 +95,35 @@ auto Renderer::draw_rect(const SDL_Rect* r) -> Renderer*
|
||||||
// The second corner is missing a pixel.
|
// The second corner is missing a pixel.
|
||||||
if (r != nullptr)
|
if (r != nullptr)
|
||||||
viewport({vp.x + r->x, vp.y + r->y, r->w - 1, r->h - 1}); // crop extra pixel
|
viewport({vp.x + r->x, vp.y + r->y, r->w - 1, r->h - 1}); // crop extra pixel
|
||||||
SDLError::success_or_throw(SDL_RenderDrawRect(_c_pod, nullptr), __FILE__,
|
success_or_throw<SDLError>(SDL_RenderDrawRect(_data.get(), nullptr));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
// add missing pixel. works sometimes…
|
// add missing pixel. works sometimes…
|
||||||
if (r != nullptr) draw_point({r->w - 1, r->h - 1});
|
if (r != nullptr) draw_point({r->w - 1, r->h - 1});
|
||||||
else draw_point({vp.w - 1, vp.h - 1});
|
else draw_point({vp.w - 1, vp.h - 1});
|
||||||
|
|
||||||
viewport(vp);
|
viewport(&vp);
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::draw_rects(const std::vector<SDL_Rect>& rs) -> Renderer*
|
auto Renderer::draw_rects(const std::span<SDL_Rect> rs) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderDrawRects(_c_pod, rs.data(), (int)rs.size()),
|
success_or_throw<SDLError>(
|
||||||
__FILE__, __FUNCTION__, __LINE__);
|
SDL_RenderDrawRects(_data.get(), rs.data(), (int)rs.size()));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::fill_rect(const SDL_Rect* r) -> Renderer*
|
auto Renderer::fill_rect(const SDL_Rect* const r) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderFillRect(_c_pod, r), __FILE__, __FUNCTION__,
|
success_or_throw<SDLError>(SDL_RenderFillRect(_data.get(), r));
|
||||||
__LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::fill_rects(const std::vector<SDL_Rect>& rs) -> Renderer*
|
auto Renderer::fill_rects(const std::span<SDL_Rect> rs) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderFillRects(_c_pod, rs.data(), (int)rs.size()),
|
success_or_throw<SDLError>(
|
||||||
__FILE__, __FUNCTION__, __LINE__);
|
SDL_RenderFillRects(_data.get(), rs.data(), (int)rs.size()));
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -145,29 +131,27 @@ auto Renderer::fill_rects(const std::vector<SDL_Rect>& rs) -> Renderer*
|
||||||
auto Renderer::output_size() -> Size
|
auto Renderer::output_size() -> Size
|
||||||
{
|
{
|
||||||
Size s {};
|
Size s {};
|
||||||
SDLError::success_or_throw(SDL_GetRendererOutputSize(_c_pod, &s.w, &s.h), __FILE__,
|
success_or_throw<SDLError>(SDL_GetRendererOutputSize(_data.get(), &s.w, &s.h));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::present() noexcept
|
void Renderer::present() noexcept
|
||||||
{
|
{
|
||||||
SDL_RenderPresent(_c_pod);
|
SDL_RenderPresent(_data.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::viewport() noexcept -> SDL_Rect
|
auto Renderer::viewport() noexcept -> SDL_Rect
|
||||||
{
|
{
|
||||||
SDL_Rect vp;
|
SDL_Rect vp;
|
||||||
SDL_RenderGetViewport(_c_pod, &vp);
|
SDL_RenderGetViewport(_data.get(), &vp);
|
||||||
|
|
||||||
return vp;
|
return vp;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Renderer::viewport(const SDL_Rect* vp) -> Renderer*
|
auto Renderer::viewport(const SDL_Rect* const vp) -> Renderer*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_RenderSetViewport(_c_pod, vp), __FILE__, __FUNCTION__,
|
success_or_throw<SDLError>(SDL_RenderSetViewport(_data.get(), vp));
|
||||||
__LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,43 +3,36 @@
|
||||||
|
|
||||||
using namespace bwidgets;
|
using namespace bwidgets;
|
||||||
|
|
||||||
Texture::Texture(SDL_Texture* t) : OpaqueStruct(t)
|
Texture::Texture(SDL_Texture* t)
|
||||||
{
|
: _attributes(attributes(t)), _data {ptr_or_throw<SDLError>(t)}
|
||||||
_attributes = attributes(t);
|
|
||||||
}
|
|
||||||
|
|
||||||
Texture::Texture(Renderer* r, const SDL_PixelFormatEnum f, const SDL_TextureAccess a,
|
|
||||||
int w, int h)
|
|
||||||
: Texture(ptr_or_throw<SDLError>(SDL_CreateTexture((*r)(), f, a, w, h), __FILE__,
|
|
||||||
__FUNCTION__, __LINE__))
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Texture::Texture(Renderer* r, SDL_Surface* s)
|
Texture::Texture(const Renderer& r, const SDL_PixelFormatEnum f,
|
||||||
: OpaqueStruct(ptr_or_throw<SDLError>(SDL_CreateTextureFromSurface((*r)(), s),
|
const SDL_TextureAccess a, int w, int h)
|
||||||
__FILE__, __FUNCTION__, __LINE__))
|
: Texture {SDL_CreateTexture(r._data.get(), f, a, w, h)}
|
||||||
{
|
{}
|
||||||
_attributes = attributes(_c_pod);
|
|
||||||
}
|
Texture::Texture(const Renderer& r, SDL_Surface* const s)
|
||||||
|
: Texture {SDL_CreateTextureFromSurface(r._data.get(), s)}
|
||||||
|
{}
|
||||||
|
|
||||||
Texture::~Texture() noexcept
|
Texture::~Texture() noexcept
|
||||||
{
|
{
|
||||||
SDL_DestroyTexture(_c_pod);
|
// Idk why but freeing the format in ~Attr gives memory errors (valgrind)
|
||||||
_c_pod = nullptr;
|
SDL_FreeFormat(_attributes.format);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Texture::alpha_mode() -> uint8_t
|
auto Texture::alpha_mode() -> uint8_t
|
||||||
{
|
{
|
||||||
uint8_t mode = 0;
|
uint8_t mode = 0;
|
||||||
SDLError::success_or_throw(SDL_GetTextureAlphaMod(_c_pod, &mode), __FILE__,
|
success_or_throw<SDLError>(SDL_GetTextureAlphaMod(_data.get(), &mode));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Texture::alpha_mode(const uint8_t m) -> Texture*
|
auto Texture::alpha_mode(const uint8_t m) -> Texture*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_SetTextureAlphaMod(_c_pod, m), __FILE__, __FUNCTION__,
|
success_or_throw<SDLError>(SDL_SetTextureAlphaMod(_data.get(), m));
|
||||||
__LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -47,16 +40,14 @@ auto Texture::alpha_mode(const uint8_t m) -> Texture*
|
||||||
auto Texture::blend_mode() -> SDL_BlendMode
|
auto Texture::blend_mode() -> SDL_BlendMode
|
||||||
{
|
{
|
||||||
SDL_BlendMode mode {};
|
SDL_BlendMode mode {};
|
||||||
SDLError::success_or_throw(SDL_GetTextureBlendMode(_c_pod, &mode), __FILE__,
|
success_or_throw<SDLError>(SDL_GetTextureBlendMode(_data.get(), &mode));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Texture::blend_mode(const SDL_BlendMode m) -> Texture*
|
auto Texture::blend_mode(const SDL_BlendMode m) -> Texture*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_SetTextureBlendMode(_c_pod, m), __FILE__,
|
success_or_throw<SDLError>(SDL_SetTextureBlendMode(_data.get(), m));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -64,17 +55,15 @@ auto Texture::blend_mode(const SDL_BlendMode m) -> Texture*
|
||||||
auto Texture::color_mode() -> Color
|
auto Texture::color_mode() -> Color
|
||||||
{
|
{
|
||||||
Color mode;
|
Color mode;
|
||||||
SDLError::success_or_throw(
|
success_or_throw<SDLError>(
|
||||||
SDL_GetTextureColorMod(_c_pod, &mode().r, &mode().g, &mode().b), __FILE__,
|
SDL_GetTextureColorMod(_data.get(), &mode().r, &mode().g, &mode().b));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Texture::color_mode(const Color& m) -> Texture*
|
auto Texture::color_mode(Color m) -> Texture*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_SetTextureColorMod(_c_pod, m().r, m().g, m().b),
|
success_or_throw<SDLError>(SDL_SetTextureColorMod(_data.get(), m().r, m().g, m().b));
|
||||||
__FILE__, __FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -82,24 +71,22 @@ auto Texture::color_mode(const Color& m) -> Texture*
|
||||||
auto Texture::scale_mode() -> SDL_ScaleMode
|
auto Texture::scale_mode() -> SDL_ScaleMode
|
||||||
{
|
{
|
||||||
SDL_ScaleMode mode {};
|
SDL_ScaleMode mode {};
|
||||||
SDLError::success_or_throw(SDL_GetTextureScaleMode(_c_pod, &mode), __FILE__,
|
success_or_throw<SDLError>(SDL_GetTextureScaleMode(_data.get(), &mode));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return mode;
|
return mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Texture::scale_mode(const SDL_ScaleMode m) -> Texture*
|
auto Texture::scale_mode(const SDL_ScaleMode m) -> Texture*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_SetTextureScaleMode(_c_pod, m), __FILE__,
|
success_or_throw<SDLError>(SDL_SetTextureScaleMode(_data.get(), m));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Texture::update(SDL_Rect* r, const void* pixels, const int pitch) -> Texture*
|
auto Texture::update(const SDL_Rect* const r, const void* const pixels, const int pitch)
|
||||||
|
-> Texture*
|
||||||
{
|
{
|
||||||
SDLError::success_or_throw(SDL_UpdateTexture(_c_pod, r, pixels, pitch), __FILE__,
|
success_or_throw<SDLError>(SDL_UpdateTexture(_data.get(), r, pixels, pitch));
|
||||||
__FUNCTION__, __LINE__);
|
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
#include <basic_widgets/core/math.hpp>
|
|
||||||
#include <basic_widgets/w/aligned_layout.hpp>
|
|
||||||
|
|
||||||
using namespace bwidgets;
|
|
||||||
|
|
||||||
auto AlignedLayout::size() const noexcept -> Size
|
|
||||||
{
|
|
||||||
Size min_size {0, 0};
|
|
||||||
|
|
||||||
if (alignment == Alignment::HORIZONTAL) {
|
|
||||||
for (const auto* w : _widgets) {
|
|
||||||
if (w->size().w > min_size.w) min_size.w = w->size().w;
|
|
||||||
if (w->size().h > min_size.h) min_size.h = w->size().h;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {(int)_widgets.size() * min_size.w
|
|
||||||
+ ((int)_widgets.size() + 1) * margins.w,
|
|
||||||
min_size.h + 2 * margins.h};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const auto* w : _widgets) {
|
|
||||||
if (w->size().w > min_size.w) min_size.w = w->size().w;
|
|
||||||
|
|
||||||
min_size.h += w->size().h;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {min_size.w + 2 * margins.w,
|
|
||||||
min_size.h + ((int)_widgets.size() + 1) * margins.h};
|
|
||||||
}
|
|
||||||
|
|
||||||
void AlignedLayout::_update_layout(const SDL_Rect& vp)
|
|
||||||
{
|
|
||||||
if (alignment == Alignment::HORIZONTAL) {
|
|
||||||
int widget_width =
|
|
||||||
(vp.w - ((int)_widgets.size() + 1) * margins.w) / (int)_widgets.size();
|
|
||||||
|
|
||||||
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
|
|
||||||
auto* w = _widgets[i];
|
|
||||||
w->viewport({vp.x + margins.w + (int)i * (widget_width + margins.w),
|
|
||||||
vp.y + center_line(vp.h, w->size().h), widget_width,
|
|
||||||
w->size().h});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int offset = 0;
|
|
||||||
for (std::vector<Widget*>::size_type i = 0; i < _widgets.size(); i++) {
|
|
||||||
auto* w = _widgets[i];
|
|
||||||
w->viewport({
|
|
||||||
vp.x,
|
|
||||||
vp.y + ((int)i + 1) * margins.h + offset,
|
|
||||||
vp.w,
|
|
||||||
w->size().h,
|
|
||||||
});
|
|
||||||
offset += w->size().h;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
#include <basic_widgets/w/base/layout.hpp>
|
|
||||||
|
|
||||||
using namespace bwidgets;
|
|
||||||
|
|
||||||
Layout::~Layout()
|
|
||||||
{
|
|
||||||
for (auto* widget_ptr : _widgets) delete widget_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Layout::add_widget(Widget* widget_ptr) -> Layout*
|
|
||||||
{
|
|
||||||
widget_ptr->renderer(_renderer);
|
|
||||||
_widgets.emplace_back(widget_ptr);
|
|
||||||
_update_layout(_viewport);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Layout::for_widgets(const std::function<void(Widget*)>& f)
|
|
||||||
{
|
|
||||||
for (auto* w : _widgets) f(w);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Layout::handle_event(const SDL_Event& ev) -> Layout*
|
|
||||||
{
|
|
||||||
for (auto* widget_ptr : _widgets) widget_ptr->handle_event(ev);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Layout::_handle_geometry_change(const SDL_Rect& vp)
|
|
||||||
{
|
|
||||||
_update_layout(vp);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Layout::_handle_renderer_change(Renderer* r)
|
|
||||||
{
|
|
||||||
for (auto* w : _widgets) w->renderer(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Layout::_handle_rendering()
|
|
||||||
{
|
|
||||||
for (auto* widget_ptr : _widgets) widget_ptr->render();
|
|
||||||
}
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
#include <basic_widgets/w/base/layout_impl.hpp>
|
||||||
|
|
||||||
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
void LayoutImpl::add_widget(std::unique_ptr<Widget> widget_ptr)
|
||||||
|
{
|
||||||
|
if (_renderer) widget_ptr->renderer(_renderer);
|
||||||
|
_widgets.emplace_back(std::move(widget_ptr));
|
||||||
|
_update_layout(_viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutImpl::for_widgets(const std::function<void(Widget*)>& f)
|
||||||
|
{
|
||||||
|
for (const auto& w : _widgets) f(w.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutImpl::handle_event(const SDL_Event& ev)
|
||||||
|
{
|
||||||
|
EventHandlerImpl::handle_event(ev);
|
||||||
|
for (const auto& w : _widgets) w->handle_event(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutImpl::_handle_geometry_change(const SDL_Rect& vp)
|
||||||
|
{
|
||||||
|
_update_layout(vp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutImpl::_handle_renderer_change(const std::shared_ptr<Renderer>& r)
|
||||||
|
{
|
||||||
|
for (const auto& w : _widgets) w->renderer(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LayoutImpl::_handle_rendering()
|
||||||
|
{
|
||||||
|
for (const auto& w : _widgets) w->render();
|
||||||
|
}
|
|
@ -1,77 +0,0 @@
|
||||||
#include <SDL2/SDL_assert.h>
|
|
||||||
#include <SDL2/SDL_events.h>
|
|
||||||
|
|
||||||
#include <basic_widgets/w/base/widget.hpp>
|
|
||||||
#include <basic_widgets/w/feat/keyboard_handler.hpp>
|
|
||||||
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
|
||||||
|
|
||||||
using namespace bwidgets;
|
|
||||||
|
|
||||||
auto Widget::handle_event(const SDL_Event& ev) -> Widget*
|
|
||||||
{
|
|
||||||
if (auto* handler = dynamic_cast<KeyboardHandler*>(this); handler) {
|
|
||||||
switch (ev.type) {
|
|
||||||
case SDL_KEYDOWN:
|
|
||||||
case SDL_KEYUP:
|
|
||||||
handler->handle_keyboard(ev.key);
|
|
||||||
break;
|
|
||||||
case SDL_TEXTINPUT:
|
|
||||||
handler->handle_keyboard(ev.text);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto* handler = dynamic_cast<MouseHandler*>(this); handler) {
|
|
||||||
switch (ev.type) {
|
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
|
||||||
case SDL_MOUSEBUTTONUP:
|
|
||||||
handler->handle_mouse(ev.button, _viewport);
|
|
||||||
break;
|
|
||||||
case SDL_MOUSEMOTION:
|
|
||||||
handler->handle_mouse(ev.motion, _viewport);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Widget::render() -> Widget*
|
|
||||||
{
|
|
||||||
if (_renderer == nullptr) return this;
|
|
||||||
|
|
||||||
#ifndef _NDEBUG
|
|
||||||
_renderer
|
|
||||||
->draw_color({
|
|
||||||
// NOLINTNEXTLINE(readability-magic-numbers)
|
|
||||||
{0, 255, 0, SDL_ALPHA_TRANSPARENT}
|
|
||||||
})
|
|
||||||
->draw_rect(nullptr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_renderer->viewport(_viewport);
|
|
||||||
_handle_rendering();
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Widget::renderer(Renderer* r) -> Widget*
|
|
||||||
{
|
|
||||||
if (r != _renderer) {
|
|
||||||
_handle_renderer_change(r);
|
|
||||||
_renderer = r;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Widget::viewport(const SDL_Rect& vp) -> Widget*
|
|
||||||
{
|
|
||||||
_handle_geometry_change(vp);
|
|
||||||
_viewport = vp;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Widget::viewport() const -> const SDL_Rect&
|
|
||||||
{
|
|
||||||
return _viewport;
|
|
||||||
}
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
#include <basic_widgets/w/base/widget_impl.hpp>
|
||||||
|
|
||||||
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
void WidgetImpl::render()
|
||||||
|
{
|
||||||
|
if (!_renderer) return;
|
||||||
|
|
||||||
|
#ifdef BWIDGETS_DEBUG
|
||||||
|
// Render a debug outline of widget viewport.
|
||||||
|
_renderer
|
||||||
|
->draw_color({
|
||||||
|
// NOLINTNEXTLINE(readability-magic-numbers)
|
||||||
|
{0, 255, 0, SDL_ALPHA_OPAQUE}
|
||||||
|
})
|
||||||
|
->draw_rect(nullptr);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
_renderer->viewport(&_viewport);
|
||||||
|
_handle_rendering();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetImpl::renderer(std::shared_ptr<Renderer> r)
|
||||||
|
{
|
||||||
|
if (r != _renderer) {
|
||||||
|
_handle_renderer_change(r);
|
||||||
|
_renderer = std::move(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WidgetImpl::viewport(const SDL_Rect& vp)
|
||||||
|
{
|
||||||
|
_handle_geometry_change(vp);
|
||||||
|
_viewport = vp;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto WidgetImpl::viewport() const -> const SDL_Rect&
|
||||||
|
{
|
||||||
|
return _viewport;
|
||||||
|
}
|
108
src/w/button.cpp
108
src/w/button.cpp
|
@ -1,108 +0,0 @@
|
||||||
#include <cmath>
|
|
||||||
|
|
||||||
#include <SDL2/SDL_render.h>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/math.hpp>
|
|
||||||
#include <basic_widgets/w/button.hpp>
|
|
||||||
|
|
||||||
using namespace bwidgets;
|
|
||||||
|
|
||||||
Button::Button(Widget* parent) noexcept : Widget(parent), _caption(this)
|
|
||||||
{
|
|
||||||
_focus_area = _click_area = &_widget_area;
|
|
||||||
_caption.alignment = Caption::Alignment::CENTER;
|
|
||||||
|
|
||||||
_caption.render_mode(Font::RenderMode::BLENDED);
|
|
||||||
|
|
||||||
border_gradient = [this](int len, int pos) -> Color {
|
|
||||||
const auto& end_color = _is_hovered ? color_bg_hover : color_bg;
|
|
||||||
const auto start_color = end_color / 2;
|
|
||||||
const auto factor = linear(pos, 0, len);
|
|
||||||
return lerp(start_color, end_color, factor);
|
|
||||||
};
|
|
||||||
|
|
||||||
border_gradient_pushed = [this](int len, int pos) -> Color {
|
|
||||||
const auto& end_color = _is_hovered ? color_bg_hover : color_bg;
|
|
||||||
const Color start_color = end_color / 1.5;
|
|
||||||
const auto factor = linear(pos, 0, len);
|
|
||||||
return lerp(start_color, end_color, factor);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Button::size() const noexcept -> Size
|
|
||||||
{
|
|
||||||
return _caption.size() + border_size * 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Button::text() const noexcept -> const std::string&
|
|
||||||
{
|
|
||||||
return _caption.text();
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Button::text(const std::string& txt) -> Button*
|
|
||||||
{
|
|
||||||
_caption.text(txt);
|
|
||||||
_handle_geometry_change(_viewport);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::_handle_font_change(Font* f)
|
|
||||||
{
|
|
||||||
_caption.font(f);
|
|
||||||
_handle_geometry_change(_viewport);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::_handle_font_color_change(const Color& fg, const Color&)
|
|
||||||
{
|
|
||||||
_caption.font_color_fg(fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::_handle_geometry_change(const SDL_Rect& vp)
|
|
||||||
{
|
|
||||||
const auto h = _caption.size().h + 2 * border_size.h;
|
|
||||||
|
|
||||||
_widget_area = {0, center_line(vp.h, _caption.size().h) - border_size.h, vp.w, h};
|
|
||||||
|
|
||||||
const auto txt_size = _caption.size();
|
|
||||||
|
|
||||||
_caption_area = {center_line(vp.w, txt_size.w), center_line(vp.h, txt_size.h),
|
|
||||||
txt_size.w, txt_size.h};
|
|
||||||
_caption.viewport(rect_offset(_caption_area, vp));
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::_handle_renderer_change(Renderer* r)
|
|
||||||
{
|
|
||||||
_caption.renderer(r);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::_handle_rendering()
|
|
||||||
{
|
|
||||||
const Color& c = _is_hovered ? color_bg_hover : color_bg;
|
|
||||||
const auto& gradient = _is_pushed ? border_gradient_pushed : border_gradient;
|
|
||||||
auto x = 0;
|
|
||||||
auto y = 0;
|
|
||||||
const auto biggest = border_size.w > border_size.h ? border_size.w : border_size.h;
|
|
||||||
while (x < border_size.w || y < border_size.h) {
|
|
||||||
const auto max = x > y ? x : y;
|
|
||||||
const auto margin = Size({x, y});
|
|
||||||
_renderer->draw_color(gradient(biggest, max)())
|
|
||||||
->draw_rect(rect_margin(_widget_area, margin));
|
|
||||||
if (x < border_size.w) x++;
|
|
||||||
if (y < border_size.h) y++;
|
|
||||||
}
|
|
||||||
|
|
||||||
_renderer->draw_color(c())->fill_rect(rect_margin(_widget_area, border_size));
|
|
||||||
_caption.font_color_bg(c());
|
|
||||||
_caption.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Button::_on_push(bool state)
|
|
||||||
{
|
|
||||||
SDL_Point offset {_viewport.x, _viewport.y};
|
|
||||||
if (state) {
|
|
||||||
offset.x += 1;
|
|
||||||
offset.y += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
_caption.viewport(rect_offset(_caption_area, offset));
|
|
||||||
}
|
|
|
@ -0,0 +1,102 @@
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include <basic_widgets/core/math.hpp>
|
||||||
|
#include <basic_widgets/w/button_impl.hpp>
|
||||||
|
#include <basic_widgets/w/widget_factory.hpp>
|
||||||
|
|
||||||
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
ButtonImpl::ButtonImpl(Widget* parent) noexcept
|
||||||
|
: WidgetImpl {parent}, _caption {create_caption(this)}
|
||||||
|
{
|
||||||
|
enable_mouse_handler(_widget_area, _viewport);
|
||||||
|
|
||||||
|
_caption->alignment = Caption::Alignment::CENTER;
|
||||||
|
_caption->render_mode(Font::RenderMode::BLENDED);
|
||||||
|
|
||||||
|
border_gradient = [this](const int len, const int pos,
|
||||||
|
const float divider) -> Color {
|
||||||
|
const auto& end_color = hovered() ? color_bg_hover : color_bg;
|
||||||
|
return lerp(end_color / divider, end_color, linear(pos, 0, len));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ButtonImpl::size() const noexcept -> Size
|
||||||
|
{
|
||||||
|
return _caption->size() + border_size * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto ButtonImpl::text() const noexcept -> std::string_view
|
||||||
|
{
|
||||||
|
return _caption->text();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonImpl::text(std::string txt)
|
||||||
|
{
|
||||||
|
_caption->text(std::move(txt));
|
||||||
|
_handle_geometry_change(_viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonImpl::_handle_font_change(const std::shared_ptr<Font>& f)
|
||||||
|
{
|
||||||
|
_caption->font(f);
|
||||||
|
_handle_geometry_change(_viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonImpl::_handle_font_color_change(const Color fg, Color)
|
||||||
|
{
|
||||||
|
_caption->font_color_fg(fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonImpl::_handle_geometry_change(const SDL_Rect& vp)
|
||||||
|
{
|
||||||
|
const auto h = _caption->size().h + 2 * border_size.h;
|
||||||
|
_widget_area = {0, center_line(vp.h, _caption->size().h) - border_size.h, vp.w, h};
|
||||||
|
|
||||||
|
const auto txt_size = _caption->size();
|
||||||
|
_caption_area = {center_line(vp.w, txt_size.w), center_line(vp.h, txt_size.h),
|
||||||
|
txt_size.w, txt_size.h};
|
||||||
|
|
||||||
|
_caption->viewport(rect_offset(_caption_area, vp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonImpl::_handle_renderer_change(const std::shared_ptr<Renderer>& r)
|
||||||
|
{
|
||||||
|
_caption->renderer(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonImpl::_handle_rendering()
|
||||||
|
{
|
||||||
|
const Color& c = hovered() ? color_bg_hover : color_bg;
|
||||||
|
const auto divider = pushed() ? 1.5F : 2.F;
|
||||||
|
auto x = 0;
|
||||||
|
auto y = 0;
|
||||||
|
const auto biggest_dimension =
|
||||||
|
border_size.w > border_size.h ? border_size.w : border_size.h;
|
||||||
|
const auto& max_xy = border_size.w > border_size.h ? x : y;
|
||||||
|
|
||||||
|
// Render button borders.
|
||||||
|
while (x < border_size.w || y < border_size.h) {
|
||||||
|
_renderer->draw_color(border_gradient(biggest_dimension, max_xy, divider))
|
||||||
|
->draw_rect(rect_margin(_widget_area, {x, y}));
|
||||||
|
if (x < border_size.w) x++;
|
||||||
|
if (y < border_size.h) y++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderer->draw_color(c)->fill_rect(rect_margin(_widget_area, border_size));
|
||||||
|
_caption->font_color_bg(c);
|
||||||
|
_caption->render();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ButtonImpl::_on_push(const bool state)
|
||||||
|
{
|
||||||
|
// Move slightly the caption position to give a push effect.
|
||||||
|
const auto offset = [state, this]() -> SDL_Point {
|
||||||
|
if (state) {
|
||||||
|
return {_viewport.x + 1, _viewport.y + 1};
|
||||||
|
}
|
||||||
|
return {_viewport.x, _viewport.y};
|
||||||
|
}();
|
||||||
|
|
||||||
|
_caption->viewport(rect_offset(_caption_area, offset));
|
||||||
|
}
|
|
@ -1,121 +0,0 @@
|
||||||
#include <SDL2/SDL_assert.h>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/math.hpp>
|
|
||||||
#include <basic_widgets/core/texture.hpp>
|
|
||||||
#include <basic_widgets/w/caption.hpp>
|
|
||||||
#include <basic_widgets/w/numeric_input.hpp>
|
|
||||||
|
|
||||||
using namespace bwidgets;
|
|
||||||
|
|
||||||
Caption::Caption(Widget* parent) noexcept : Widget(parent)
|
|
||||||
{
|
|
||||||
_font_color_bg = default_font_color_bg;
|
|
||||||
_font_color_fg = default_font_color_fg;
|
|
||||||
}
|
|
||||||
|
|
||||||
Caption::~Caption() noexcept
|
|
||||||
{
|
|
||||||
discard(_text_texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Caption::render_mode(Font::RenderMode m) -> Caption*
|
|
||||||
{
|
|
||||||
if (m != _render_mode) {
|
|
||||||
discard(_text_texture);
|
|
||||||
_render_mode = m;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Caption::size() const noexcept -> Size
|
|
||||||
{
|
|
||||||
if (_font == nullptr) return {0, 0};
|
|
||||||
|
|
||||||
const Size size = _font->text_size(_text);
|
|
||||||
|
|
||||||
return {size.w + 2 * margins.w, size.h + 2 * margins.h};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Caption::text() const noexcept -> const std::string&
|
|
||||||
{
|
|
||||||
return _text;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto Caption::text(const std::string& t) -> Caption*
|
|
||||||
{
|
|
||||||
if (t != _text) {
|
|
||||||
discard(_text_texture);
|
|
||||||
_text = t;
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Caption::_handle_font_change(Font*)
|
|
||||||
{
|
|
||||||
discard(_text_texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Caption::_handle_font_color_change(const Color& fg, const Color& bg)
|
|
||||||
{
|
|
||||||
if (fg != _font_color_fg
|
|
||||||
|| (bg != _font_color_bg && _font_render_mode == Font::RenderMode::SHADED))
|
|
||||||
{
|
|
||||||
discard(_text_texture);
|
|
||||||
_font_color_bg = bg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Caption::_handle_geometry_change(const SDL_Rect& vp)
|
|
||||||
{
|
|
||||||
if (vp.w != _viewport.w || vp.h != _viewport.h) {
|
|
||||||
_widget_area = rect_margin({0, 0, vp.w, vp.h}, margins);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Caption::_handle_renderer_change(Renderer*)
|
|
||||||
{
|
|
||||||
discard(_text_texture);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Caption::_handle_rendering()
|
|
||||||
{
|
|
||||||
if (_text_texture == nullptr) _handle_texture_update();
|
|
||||||
|
|
||||||
if (_render_mode == Font::RenderMode::SHADED) {
|
|
||||||
_renderer->draw_color(_font_color_bg)
|
|
||||||
->fill_rect({0, 0, _viewport.w, _viewport.h});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Size size_dst {
|
|
||||||
(int)((float)_text_texture->attributes().w / (float)_text_texture->attributes().h
|
|
||||||
* (float)_widget_area.h),
|
|
||||||
_widget_area.h};
|
|
||||||
SDL_Rect texture_dst {margins.w, margins.h, size_dst.w, size_dst.h};
|
|
||||||
switch (alignment) {
|
|
||||||
case Alignment::CENTER:
|
|
||||||
texture_dst.x = center_line(_widget_area.w, texture_dst.w) + _widget_area.x;
|
|
||||||
break;
|
|
||||||
case Alignment::LEFT:
|
|
||||||
break;
|
|
||||||
case Alignment::RIGHT:
|
|
||||||
texture_dst.x = _widget_area.w - texture_dst.w - margins.w + _widget_area.x;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_renderer->copy(_text_texture, nullptr, texture_dst);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Caption::_handle_texture_update()
|
|
||||||
{
|
|
||||||
SDL_assert_release(_font != nullptr && _text_texture == nullptr); // NOLINT
|
|
||||||
SDL_Surface* s;
|
|
||||||
switch (_render_mode) {
|
|
||||||
case Font::RenderMode::SHADED:
|
|
||||||
s = _font->render(_render_mode, _text, _font_color_fg, _font_color_bg);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
s = _font->render(_render_mode, _text, _font_color_fg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_text_texture = new Texture(_renderer, s);
|
|
||||||
SDL_FreeSurface(s);
|
|
||||||
}
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
#include <basic_widgets/core/math.hpp>
|
||||||
|
#include <basic_widgets/core/texture.hpp>
|
||||||
|
#include <basic_widgets/w/caption_impl.hpp>
|
||||||
|
#include <basic_widgets/w/numeric_input.hpp>
|
||||||
|
|
||||||
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
CaptionImpl::CaptionImpl(Widget* parent) noexcept : WidgetImpl {parent}
|
||||||
|
{
|
||||||
|
_font_color_bg = default_font_color_bg;
|
||||||
|
_font_color_fg = default_font_color_fg;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::render_mode(const Font::RenderMode m)
|
||||||
|
{
|
||||||
|
if (m != _render_mode) {
|
||||||
|
_text_texture.reset();
|
||||||
|
_render_mode = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CaptionImpl::size() const noexcept -> Size
|
||||||
|
{
|
||||||
|
if (!_font) return {0, 0};
|
||||||
|
|
||||||
|
const auto [w, h] = _font->text_size(_text);
|
||||||
|
return {w + 2 * margins.w, h + 2 * margins.h};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto CaptionImpl::text() const noexcept -> std::string_view
|
||||||
|
{
|
||||||
|
return _text;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::text(std::string t)
|
||||||
|
{
|
||||||
|
if (t != _text) {
|
||||||
|
_text_texture.reset();
|
||||||
|
_text = std::move(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::_handle_font_change(const std::shared_ptr<Font>&)
|
||||||
|
{
|
||||||
|
_text_texture.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::_handle_font_color_change(const Color fg, const Color bg)
|
||||||
|
{
|
||||||
|
if (fg != _font_color_fg) {
|
||||||
|
_text_texture.reset();
|
||||||
|
_font_color_fg = fg;
|
||||||
|
}
|
||||||
|
if (bg != _font_color_bg && _font_render_mode == Font::RenderMode::SHADED) {
|
||||||
|
_text_texture.reset();
|
||||||
|
_font_color_bg = fg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::_handle_geometry_change(const SDL_Rect& vp)
|
||||||
|
{
|
||||||
|
if (vp.w != _viewport.w) {
|
||||||
|
_widget_area = rect_margin({0, 0, vp.w, vp.h}, margins);
|
||||||
|
}
|
||||||
|
else if (vp.h != _viewport.h) {
|
||||||
|
_widget_area = rect_margin({0, 0, vp.w, vp.h}, margins);
|
||||||
|
_text_texture.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::_handle_renderer_change(const std::shared_ptr<Renderer>&)
|
||||||
|
{
|
||||||
|
_text_texture.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::_handle_rendering()
|
||||||
|
{
|
||||||
|
if (!_text_texture) _handle_texture_update();
|
||||||
|
|
||||||
|
// fill caption viewport with background color when using
|
||||||
|
// shaded text rendering mode.
|
||||||
|
if (_render_mode == Font::RenderMode::SHADED) {
|
||||||
|
_renderer->draw_color(_font_color_bg)
|
||||||
|
->fill_rect({0, 0, _viewport.w, _viewport.h});
|
||||||
|
}
|
||||||
|
|
||||||
|
const Size size_dst {
|
||||||
|
(int)((float)_text_texture->attributes().w / (float)_text_texture->attributes().h
|
||||||
|
* (float)_widget_area.h),
|
||||||
|
_widget_area.h};
|
||||||
|
|
||||||
|
const auto texture_dst = [size_dst, this]() -> SDL_Rect {
|
||||||
|
switch (alignment) {
|
||||||
|
case Alignment::CENTER:
|
||||||
|
return {center_line(_widget_area.w, size_dst.w) + _widget_area.x,
|
||||||
|
margins.h, size_dst.w, size_dst.h};
|
||||||
|
case Alignment::LEFT:
|
||||||
|
return {margins.w, margins.h, size_dst.w, size_dst.h};
|
||||||
|
case Alignment::RIGHT:
|
||||||
|
return {_widget_area.w - size_dst.w - margins.w + _widget_area.x,
|
||||||
|
margins.h, size_dst.w, size_dst.h};
|
||||||
|
default:
|
||||||
|
throw std::logic_error("missing switch case.");
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
_renderer->copy(*_text_texture, nullptr, texture_dst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CaptionImpl::_handle_texture_update()
|
||||||
|
{
|
||||||
|
if (!_font) return;
|
||||||
|
_text_texture.reset();
|
||||||
|
|
||||||
|
auto s = [this]() {
|
||||||
|
switch (_render_mode) {
|
||||||
|
case Font::RenderMode::SHADED:
|
||||||
|
return _font->render(_render_mode, _text, _font_color_fg,
|
||||||
|
_font_color_bg);
|
||||||
|
default:
|
||||||
|
return _font->render(_render_mode, _text, _font_color_fg);
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
_text_texture = std::make_shared<Texture>(*_renderer, s.get());
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
#include <basic_widgets/w/feat/event_handler_impl.hpp>
|
||||||
|
|
||||||
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
void EventHandlerImpl::handle_event(const SDL_Event& ev)
|
||||||
|
{
|
||||||
|
auto type = static_cast<SDL_EventType>(ev.type);
|
||||||
|
if (!_event_handlers.contains(type)) return;
|
||||||
|
|
||||||
|
_event_handlers[type](ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto EventHandlerImpl::_add_event_handler(handler_t event_handler) -> bool
|
||||||
|
{
|
||||||
|
if (_event_handlers.contains(event_handler.first)) return false;
|
||||||
|
|
||||||
|
_event_handlers.emplace(std::move(event_handler));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto EventHandlerImpl::_remove_event_handler(const SDL_EventType ev_type)
|
||||||
|
-> std::pair<handler_t, bool>
|
||||||
|
{
|
||||||
|
handler_t handler;
|
||||||
|
auto pos = _event_handlers.find(ev_type);
|
||||||
|
if (pos == _event_handlers.end()) return {handler, false};
|
||||||
|
|
||||||
|
handler = {ev_type, _event_handlers[ev_type]};
|
||||||
|
_event_handlers.erase(pos);
|
||||||
|
|
||||||
|
return {handler, true};
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
#include <basic_widgets/w/feat/keyboard_handler_impl.hpp>
|
||||||
|
|
||||||
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
void KeyboardHandlerImpl::disable_keyboard_handler()
|
||||||
|
{
|
||||||
|
_remove_event_handler(SDL_KEYDOWN);
|
||||||
|
_remove_event_handler(SDL_KEYUP);
|
||||||
|
_remove_event_handler(SDL_TEXTINPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KeyboardHandlerImpl::enable_keyboard_handler()
|
||||||
|
{
|
||||||
|
const auto keyboard_handler = [this](const SDL_Event& ev) {
|
||||||
|
if (focus()) _key_handler(ev.key);
|
||||||
|
};
|
||||||
|
|
||||||
|
_add_event_handler({SDL_KEYDOWN, keyboard_handler});
|
||||||
|
_add_event_handler({SDL_KEYUP, keyboard_handler});
|
||||||
|
_add_event_handler({SDL_TEXTINPUT, [this](const SDL_Event& ev) {
|
||||||
|
if (focus()) _input_handler(ev.text);
|
||||||
|
}});
|
||||||
|
}
|
|
@ -1,60 +0,0 @@
|
||||||
#include <SDL2/SDL_events.h>
|
|
||||||
#include <SDL2/SDL_rect.h>
|
|
||||||
|
|
||||||
#include <basic_widgets/core/math.hpp>
|
|
||||||
#include <basic_widgets/w/feat/mouse_handler.hpp>
|
|
||||||
|
|
||||||
using namespace bwidgets;
|
|
||||||
|
|
||||||
auto MouseHandler::handle_mouse(const SDL_MouseButtonEvent& ev, const SDL_Rect& orig)
|
|
||||||
-> MouseHandler*
|
|
||||||
{
|
|
||||||
if (_click_area == nullptr) return this;
|
|
||||||
|
|
||||||
const SDL_Point p {ev.x, ev.y};
|
|
||||||
const SDL_Rect vp {rect_offset(*_click_area, orig)};
|
|
||||||
|
|
||||||
if (ev.type == SDL_MOUSEBUTTONDOWN) {
|
|
||||||
if (SDL_PointInRect(&p, &vp) == SDL_TRUE) {
|
|
||||||
push(true);
|
|
||||||
_handle_mouse_button(ev, vp);
|
|
||||||
}
|
|
||||||
else focus(false);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (_is_pushed) {
|
|
||||||
if (SDL_PointInRect(&p, &vp) == SDL_TRUE) {
|
|
||||||
focus(true);
|
|
||||||
|
|
||||||
if (click_handler != nullptr) click_handler(ev);
|
|
||||||
}
|
|
||||||
push(false);
|
|
||||||
_handle_mouse_button(ev, vp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto MouseHandler::handle_mouse(const SDL_MouseMotionEvent& ev, const SDL_Rect& orig)
|
|
||||||
-> MouseHandler*
|
|
||||||
{
|
|
||||||
if (_click_area == nullptr) return this;
|
|
||||||
|
|
||||||
const SDL_Point p {ev.x, ev.y};
|
|
||||||
const SDL_Rect vp {rect_offset(*_click_area, orig)};
|
|
||||||
|
|
||||||
_is_hovered = SDL_PointInRect(&p, &vp) != 0;
|
|
||||||
|
|
||||||
if (_is_hovered) _handle_mouse_motion(ev, orig);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto MouseHandler::push(bool state) -> MouseHandler*
|
|
||||||
{
|
|
||||||
_on_push(state);
|
|
||||||
_is_pushed = state;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
#include <basic_widgets/core/math.hpp>
|
||||||
|
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
|
||||||
|
|
||||||
|
using namespace bwidgets;
|
||||||
|
|
||||||
|
void MouseHandlerImpl::disable_mouse_handler()
|
||||||
|
{
|
||||||
|
_remove_event_handler(SDL_MOUSEBUTTONDOWN);
|
||||||
|
_remove_event_handler(SDL_MOUSEBUTTONUP);
|
||||||
|
_remove_event_handler(SDL_MOUSEMOTION);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||||
|
void MouseHandlerImpl::enable_mouse_handler(const SDL_Rect& rel_area,
|
||||||
|
const SDL_Rect& origin)
|
||||||
|
{
|
||||||
|
const auto mouse_in_rect = [&rel_area, &origin](const SDL_Point p) {
|
||||||
|
const auto area = rect_offset(rel_area, origin);
|
||||||
|
return SDL_PointInRect(&p, &area) == SDL_TRUE;
|
||||||
|
};
|
||||||
|
|
||||||
|
_add_event_handler({SDL_MOUSEBUTTONDOWN, [this, mouse_in_rect](const SDL_Event& ev) {
|
||||||
|
if (!mouse_in_rect({ev.button.x, ev.button.y})) {
|
||||||
|
focus(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_on_push(_pushed = true);
|
||||||
|
}});
|
||||||
|
_add_event_handler({SDL_MOUSEBUTTONUP, [this, mouse_in_rect](const SDL_Event& ev) {
|
||||||
|
if (_pushed) {
|
||||||
|
_on_push(_pushed = false);
|
||||||
|
// Only execute handler and give focus if mouse button
|
||||||
|
// has been pushed and released over the widget.
|
||||||
|
if (mouse_in_rect({ev.button.x, ev.button.y})) {
|
||||||
|
focus(true);
|
||||||
|
_click_handler(ev.button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}});
|
||||||
|
_add_event_handler({SDL_MOUSEMOTION, [this, mouse_in_rect](const SDL_Event& ev) {
|
||||||
|
if (!mouse_in_rect({ev.motion.x, ev.motion.y})) {
|
||||||
|
if (_hovered) _hover_handler(_hovered = false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_hovered) _hover_handler(_hovered = true);
|
||||||
|
_motion_handler(ev.motion);
|
||||||
|
}});
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
#include <basic_widgets/w/aligned_layout_impl.hpp>
|
||||||
|
#include <basic_widgets/w/button_impl.hpp>
|
||||||
|
#include <basic_widgets/w/caption_impl.hpp>
|
||||||
|
#include <basic_widgets/w/numeric_input_impl.hpp>
|
||||||
|
#include <basic_widgets/w/widget_factory.hpp>
|
||||||
|
|
||||||
|
auto bwidgets::create_horizontal_layout(Widget* parent) -> std::unique_ptr<AlignedLayout>
|
||||||
|
{
|
||||||
|
return std::unique_ptr<AlignedLayout>(
|
||||||
|
new AlignedLayoutImpl<LayoutAlignment::HORIZONTAL>(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bwidgets::create_vertical_layout(Widget* parent) -> std::unique_ptr<AlignedLayout>
|
||||||
|
{
|
||||||
|
return std::unique_ptr<AlignedLayout>(
|
||||||
|
new AlignedLayoutImpl<LayoutAlignment::VERTICAL>(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bwidgets::create_button(Widget* parent) -> std::unique_ptr<Button>
|
||||||
|
{
|
||||||
|
return std::unique_ptr<Button>(new ButtonImpl(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bwidgets::create_caption(Widget* parent) -> std::unique_ptr<Caption>
|
||||||
|
{
|
||||||
|
return std::unique_ptr<Caption>(new CaptionImpl(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bwidgets::create_input_float(Widget* parent) -> std::unique_ptr<NumericInput<float>>
|
||||||
|
{
|
||||||
|
return std::unique_ptr<NumericInput<float>>(new NumericInputImpl<float>(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto bwidgets::create_input_int(Widget* parent) -> std::unique_ptr<NumericInput<int>>
|
||||||
|
{
|
||||||
|
return std::unique_ptr<NumericInput<int>>(new NumericInputImpl<int>(parent));
|
||||||
|
}
|
Loading…
Reference in New Issue