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