SDL GUI rewrite

This commit is contained in:
Andrea Blankenstijn 2022-02-09 16:27:57 +01:00
parent 1014a90660
commit f714e43338
15 changed files with 730 additions and 21 deletions

View File

@ -25,6 +25,7 @@ BraceWrapping:
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeConceptDeclarations: true
BreakInheritanceList: AfterComma
ColumnLimit: 89
ConstructorInitializerAllOnOneLineOrOnePerLine: true
@ -38,7 +39,7 @@ IncludeCategories:
Priority: -10
- Regex: '^<fontconfig/'
Priority: -5
- Regex: '^<SDL2/'
- Regex: '^<SDL_.*'
Priority: -5
- Regex: '^<basic_widgets/'
Priority: 0

View File

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

View File

@ -19,7 +19,19 @@ elif compiler == 'g++'
add_project_arguments('-fcoroutines', language: 'cpp')
endif
executable('chainreact',
executable('chainreact-sdl',
'src/core/board2d.cpp',
'src/sdlui/board_widget_impl.cpp',
'src/sdlui/game_screen_impl.cpp',
'src/sdlui/sdlui.cpp',
dependencies : [
dependency('basic_widgets',
static : true,
),
],
install : true)
executable('chainreact-cli',
'src/core/board2d.cpp',
'src/tui/tui.cpp',
'src/tui/main.cpp',

View File

@ -97,8 +97,8 @@ auto Board2D::move(const Coord c, const Player p)
queue<Coord> overloaded;
if (is_overloaded(c)) {
overloaded.push(c);
do {
const auto src = overloaded.back();
while (!overloaded.empty()) {
const auto src = overloaded.front();
const auto targets = neighbours(src);
overloaded.pop();
@ -114,7 +114,7 @@ auto Board2D::move(const Coord c, const Player p)
overloaded.push(t);
};
}
} while (!overloaded.empty());
}
}
}
@ -137,19 +137,18 @@ auto Board2D::operator()(const Coord c) const noexcept -> const Board2D::Space&
void Board2D::remove_unit(const Coord c)
{
auto& s = at(c);
const auto owner = s.owner;
auto& s = at(c);
cout << "- remove " << c.x << "," << c.y << " "
<< "total: " << units[s.owner] << endl;
if (s.value > 1) {
s.value--;
units[owner]--;
units[s.owner]--;
}
else if (s.value == 1) {
s.reset();
units[owner]--;
units[s.owner]--;
}
else throw logic_error("Space is empty");
cout << "- remove " << c.x << "," << c.y << " "
<< "total: " << units[owner] << endl;
}
auto Board2D::size() const noexcept -> pair<size_type, size_type>
@ -157,15 +156,14 @@ auto Board2D::size() const noexcept -> pair<size_type, size_type>
return {w, h};
}
auto Board2D::spread(const Coord src, const vector<Coord>& targets, const Player p)
auto Board2D::spread(const Coord src, const vector<Coord>& targets, const Player player)
-> Generator<Space>
{
auto& s = at(src);
const auto& s = at(src);
cout << "= spread " << src.x << "," << src.y << " " << s.value << "/" << s.capacity
<< endl;
{
const auto count = targets.size();
if (s.value != static_cast<int>(count)) {
if (s.value != static_cast<int>(targets.size())) {
throw logic_error("cannot spread: unit count and target "
"spaces count differ");
}
@ -175,7 +173,7 @@ auto Board2D::spread(const Coord src, const vector<Coord>& targets, const Player
remove_unit(src);
co_yield at(src);
capture(t, p);
capture(t, player);
add_unit(t);
co_yield at(t);
}

View File

@ -60,7 +60,8 @@ namespace core
void init(size_type, size_type);
[[nodiscard]] auto is_overloaded(Coord) const noexcept -> bool;
// Make a move step by step.
auto move(Coord, Player) -> Generator<std::pair<Space, std::optional<Player>>>;
[[nodiscard]] auto move(Coord, Player)
-> Generator<std::pair<Space, std::optional<Player>>>;
// Get coordinate of neighbour spaces.
[[nodiscard]] auto neighbours(Coord) const noexcept -> std::vector<Coord>;
// Get space by its coordinate.

View File

@ -0,0 +1,39 @@
#ifndef GUI_BOARD_WIDGET_HPP
#define GUI_BOARD_WIDGET_HPP
#include <cstdint>
#include <basic_widgets/w/base/widget.hpp>
#include <basic_widgets/w/feat/mouse_handler.hpp>
#include "../core/board2d.hpp"
namespace sdlui
{
class BoardWidget : public virtual bwidgets::Widget,
public virtual bwidgets::MouseHandler
{
public:
using Widget::Widget;
enum class EventCode : std::int32_t
{
BOARD_CHANGED,
MOVE
};
[[nodiscard]] virtual auto board() -> const std::weak_ptr<core::Board2D>& = 0;
virtual void board(std::weak_ptr<core::Board2D>) = 0;
[[nodiscard]] virtual auto color_p1() const -> const bwidgets::Color& = 0;
virtual void color_p1(bwidgets::Color) = 0;
[[nodiscard]] virtual auto color_p2() const -> const bwidgets::Color& = 0;
virtual void color_p2(bwidgets::Color) = 0;
[[nodiscard]] virtual auto default_color_p1() const
-> const bwidgets::Color& = 0;
[[nodiscard]] virtual auto default_color_p2() const
-> const bwidgets::Color& = 0;
[[nodiscard]] virtual auto event_type() const -> std::uint32_t = 0;
};
}
#endif

View File

@ -0,0 +1,198 @@
#include <cstdint>
#include <iostream>
#include <basic_widgets/core/draw.hpp>
#include <basic_widgets/core/math.hpp>
#include "board_widget_impl.hpp"
using namespace bwidgets;
using namespace core;
using namespace sdlui;
using namespace std;
BoardWidgetImpl::BoardWidgetImpl(Widget* parent) : WidgetImpl(parent)
{
click_handler([this](const SDL_MouseButtonEvent& ev) {
auto* c = new Board2D::Coord(coord({ev.x, ev.y}));
SDL_Event event;
SDL_zero(event);
event.type = event_type();
event.user.code = static_cast<uint32_t>(EventCode::MOVE);
event.user.data1 = c;
SDL_PushEvent(&event);
});
enable_mouse_handler(_widget_area, viewport());
}
auto BoardWidgetImpl::board() -> const weak_ptr<Board2D>&
{
return _board_wptr;
}
void BoardWidgetImpl::board(weak_ptr<Board2D> b)
{
_board_wptr = b;
discard_textures();
SDL_Event event;
SDL_zero(event);
event.type = event_type();
event.user.code = static_cast<uint32_t>(EventCode::BOARD_CHANGED);
SDL_PushEvent(&event);
}
auto BoardWidgetImpl::color_p1() const -> const Color&
{
return _texture_colors.at(TextureKey::P1_ATOM);
}
void BoardWidgetImpl::color_p1(Color c)
{
_texture_colors[TextureKey::P1_ATOM] = c;
}
auto BoardWidgetImpl::color_p2() const -> const Color&
{
return _texture_colors.at(TextureKey::P2_ATOM);
}
void BoardWidgetImpl::color_p2(Color c)
{
_texture_colors[TextureKey::P2_ATOM] = c;
}
auto BoardWidgetImpl::default_color_p1() const -> const Color&
{
return _default_color_p1;
}
auto BoardWidgetImpl::default_color_p2() const -> const Color&
{
return _default_color_p2;
}
auto BoardWidgetImpl::event_type() const -> std::uint32_t
{
return _event_type;
}
auto BoardWidgetImpl::size() const noexcept -> Size
{
if (const auto& board = _board_wptr.lock(); !_board_wptr.expired()) {
return {static_cast<int>(64 * board->size().first),
static_cast<int>(64 * board->size().second)};
}
return {0, 0};
}
auto BoardWidgetImpl::atom_size() -> int
{
const auto [w, h] = space_size() * (1 / 4.);
const auto diameter = w > h ? h : w;
return diameter;
}
auto BoardWidgetImpl::coord(SDL_Point p) -> Board2D::Coord
{
const auto [w, h] = space_size();
if (w < 1 || h < 1) return {0, 0};
return {static_cast<Board2D::size_type>((p.x - viewport().x - _widget_area.x) / w),
static_cast<Board2D::size_type>((p.y - viewport().y - _widget_area.y) / h)};
}
void BoardWidgetImpl::discard_textures()
{
for (const auto k : {TextureKey::P1_ATOM, TextureKey::P2_ATOM}) {
if (_textures.contains(k)) _textures[k].reset();
}
}
void BoardWidgetImpl::draw_atoms(const SDL_Rect& draw_area, Player p, int atoms)
{
const auto texture = p == core::Player::P1 ? render_texture(TextureKey::P1_ATOM)
: render_texture(TextureKey::P2_ATOM);
if (!texture) return;
const auto size = atom_size();
const auto draw = [this, draw_area, size, &texture](int a, int b) {
const auto x = draw_area.x + (1 + a) * draw_area.w / 4 - size / 2;
const auto y = draw_area.y + (1 + b) * draw_area.h / 4 - size / 2;
renderer()->copy(*texture, nullptr, {x, y, size, size});
};
switch (atoms) {
case 0:
break;
case 1:
draw(1, 1);
break;
case 2:
draw(2, 0);
draw(0, 2);
break;
case 3:
draw(0, 0);
draw(1, 1);
draw(2, 2);
break;
case 4:
draw(0, 0);
draw(0, 2);
draw(2, 0);
draw(2, 2);
break;
}
}
auto BoardWidgetImpl::render_texture(TextureKey key) -> const shared_ptr<Texture>&
{
if (!_textures.contains(key)) {
_textures.emplace(key,
filled_circle(_texture_colors[key], atom_size(), *renderer()));
}
else if (!_textures[key]) {
_textures[key] = filled_circle(_texture_colors[key], atom_size(), *renderer());
}
return _textures[key];
}
auto BoardWidgetImpl::space(Board2D::Coord c) -> SDL_Rect
{
const auto [w, h] = space_size();
return {_widget_area.x + static_cast<int>(c.x) * w,
_widget_area.y + static_cast<int>(c.y) * h, w, h};
}
auto BoardWidgetImpl::space_size() const -> Size
{
if (const auto& board = _board_wptr.lock(); !_board_wptr.expired()) {
const auto [w, h] = board->size();
return {static_cast<int>(_widget_area.w / w),
static_cast<int>(_widget_area.h / h)};
}
return {0, 0};
}
void BoardWidgetImpl::_handle_geometry_change(const SDL_Rect& vp)
{
_widget_area = {0, 0, vp.w, vp.h};
if (vp.w != viewport().w || vp.h != viewport().h) discard_textures();
}
void BoardWidgetImpl::_handle_rendering()
{
if (const auto board = _board_wptr.lock(); !_board_wptr.expired()) {
for (const auto& s : *board) {
const auto space_area = space(s.coord);
fill_rect_gradient(*renderer(), space_area, theme()->color_widget_border(),
theme()->color_widget_bg());
draw_atoms(space_area, s.owner, s.value);
}
}
}
const Color BoardWidgetImpl::_default_color_p1 {0, 0, 255, SDL_ALPHA_OPAQUE};
const Color BoardWidgetImpl::_default_color_p2 {255, 0, 0, SDL_ALPHA_OPAQUE};

View File

@ -0,0 +1,65 @@
#ifndef GUI_BOARD_WIDGET_IMPL_HPP
#define GUI_BOARD_WIDGET_IMPL_HPP
#include <map>
#include <memory>
#include <basic_widgets/core/texture.hpp>
#include <basic_widgets/core/type/color.hpp>
#include <basic_widgets/w/base/widget_impl.hpp>
#include <basic_widgets/w/feat/mouse_handler_impl.hpp>
#include "board_widget.hpp"
namespace sdlui
{
class BoardWidgetImpl : public virtual BoardWidget,
public virtual bwidgets::WidgetImpl,
public virtual bwidgets::MouseHandlerImpl
{
public:
BoardWidgetImpl(bwidgets::Widget* parent = nullptr);
[[nodiscard]] auto board() -> const std::weak_ptr<core::Board2D>& override;
void board(std::weak_ptr<core::Board2D>) override;
[[nodiscard]] auto color_p1() const -> const bwidgets::Color& override;
void color_p1(bwidgets::Color) override;
[[nodiscard]] auto color_p2() const -> const bwidgets::Color& override;
void color_p2(bwidgets::Color) override;
[[nodiscard]] auto default_color_p1() const -> const bwidgets::Color& override;
[[nodiscard]] auto default_color_p2() const -> const bwidgets::Color& override;
[[nodiscard]] auto event_type() const -> std::uint32_t override;
[[nodiscard]] auto size() const noexcept -> bwidgets::Size override;
private:
enum class TextureKey
{
P1_ATOM,
P2_ATOM
};
[[nodiscard]] auto atom_size() -> int;
[[nodiscard]] auto coord(SDL_Point) -> core::Board2D::Coord;
void discard_textures();
void draw_atoms(const SDL_Rect&, core::Player, int);
auto render_texture(TextureKey) -> const std::shared_ptr<bwidgets::Texture>&;
[[nodiscard]] auto space(core::Board2D::Coord) -> SDL_Rect;
[[nodiscard]] auto space_size() const -> bwidgets::Size;
void _handle_geometry_change(const SDL_Rect&) override;
void _handle_rendering() override;
static const bwidgets::Color _default_color_p1;
static const bwidgets::Color _default_color_p2;
std::weak_ptr<core::Board2D> _board_wptr;
const std::uint32_t _event_type {SDL_RegisterEvents(1)};
std::map<TextureKey, std::shared_ptr<bwidgets::Texture>> _textures;
std::map<TextureKey, bwidgets::Color> _texture_colors {
{TextureKey::P1_ATOM, default_color_p1()},
{TextureKey::P2_ATOM, default_color_p2()}
};
SDL_Rect _widget_area;
};
}
#endif

28
src/sdlui/game_screen.hpp Normal file
View File

@ -0,0 +1,28 @@
#ifndef GUI_GAME_SCREEN_HPP
#define GUI_GAME_SCREEN_HPP
#include <cstdint>
#include <basic_widgets/w/base/widget.hpp>
namespace core
{
class Board2D;
}
namespace sdlui
{
class GameScreen : public virtual bwidgets::Widget
{
public:
[[nodiscard]] virtual auto board() const
-> const std::shared_ptr<core::Board2D>& = 0;
virtual void board(std::shared_ptr<core::Board2D>) = 0;
[[nodiscard]] virtual auto board_event_type() const -> std::uint32_t = 0;
virtual void flash(const std::string&) = 0;
[[nodiscard]] virtual auto header_text() const -> std::string_view = 0;
virtual void header_text(const std::string&) = 0;
};
}
#endif

View File

@ -0,0 +1,192 @@
#include <chrono>
#include <cstdint>
#include <iostream>
#include <limits>
#include "game_screen_impl.hpp"
#include <basic_widgets/core/draw.hpp>
#include <basic_widgets/core/math.hpp>
#include <basic_widgets/w/widget_factory.hpp>
#include "board_widget_impl.hpp"
using namespace bwidgets;
using namespace core;
using namespace sdlui;
using namespace std;
GameScreenImpl::GameScreenImpl(Widget* parent)
: WidgetImpl {parent},
_board_widget {make_shared<BoardWidgetImpl>(this)},
_caption_flash {create_caption(this)},
_caption_height {create_caption(this)},
_caption_width {create_caption(this)},
_header {create_caption(this)},
_menu {create_vertical_layout(this)},
_menu_open {true},
_time_flash {0LL}
{
_caption_flash->alignment(Caption::Alignment::CENTER);
_caption_flash->render_mode(Font::RenderMode::BLENDED);
// menu widgets
auto button_cancel = create_button();
auto button_new = create_button();
auto input_height = create_input_int();
auto input_width = create_input_int();
auto layout_buttons = create_horizontal_layout(this);
auto layout_height = create_horizontal_layout(this);
auto layout_width = create_horizontal_layout(this);
button_cancel->click_handler([this](auto) { _menu_open = false; });
button_cancel->text("Cancel");
button_new->click_handler([this, input_width, input_height](auto) {
board(make_shared<Board2D>(input_width->value, input_height->value));
_menu_open = false;
});
button_new->text("New");
_caption_height->text("Height:");
_caption_width->text("Width:");
input_height->value_range(2, 8);
input_width->value_range(2, 8);
layout_buttons->add_widget(button_cancel);
layout_buttons->add_widget(button_new);
layout_height->add_widget(_caption_height);
layout_height->add_widget(input_height);
layout_width->add_widget(_caption_width);
layout_width->add_widget(input_width);
_menu->add_widget(layout_width);
_menu->add_widget(layout_height);
_menu->add_widget(layout_buttons);
// Toggle menu with Esc.
_add_event_handler({SDL_KEYUP, [this](const SDL_Event& ev) {
if (ev.key.keysym.sym == SDLK_ESCAPE)
_menu_open = !_menu_open;
}});
}
auto GameScreenImpl::board() const -> const shared_ptr<Board2D>&
{
return _board;
}
void GameScreenImpl::board(std::shared_ptr<Board2D> board)
{
_board_widget->board(board);
_board = board;
}
auto GameScreenImpl::board_event_type() const -> uint32_t
{
return _board_widget->event_type();
}
void GameScreenImpl::flash(const string& s)
{
_caption_flash->text(s);
_time_flash = chrono::duration_cast<chrono::milliseconds>(
chrono::steady_clock::now().time_since_epoch())
.count();
}
void GameScreenImpl::handle_event(const SDL_Event& ev)
{
WidgetImpl::handle_event(ev);
if (!_menu_open) _board_widget->handle_event(ev);
else _menu->handle_event(ev);
}
auto GameScreenImpl::header_text() const -> string_view
{
return _header->text();
}
void GameScreenImpl::header_text(const string& s)
{
_header->text(s);
}
auto GameScreenImpl::size() const noexcept -> Size
{
const auto hdr_size = _header->size();
const auto board_size = _board_widget->size();
return {board_size.w, board_size.h + hdr_size.h};
}
void GameScreenImpl::_handle_geometry_change(const SDL_Rect& vp)
{
const auto hdr_height {theme()->font_default_biggest()->height};
_header->viewport({vp.x, vp.y, vp.w, hdr_height});
_board_widget->viewport({vp.x, vp.y + hdr_height, vp.w, vp.h - hdr_height});
const auto menu_size {_menu->size()};
_menu->viewport({vp.x + center_line(vp.w, menu_size.w),
vp.y + center_line(vp.h, menu_size.h), menu_size.w, menu_size.h});
const auto flash_size {_caption_flash->size()};
_caption_flash->viewport(
{vp.x, vp.y + center_line(vp.h, flash_size.h), vp.w, flash_size.h});
}
void GameScreenImpl::_handle_renderer_change(const shared_ptr<Renderer>& r)
{
_board_widget->renderer(r);
_caption_flash->renderer(r);
_header->renderer(r);
_menu->renderer(r);
}
void GameScreenImpl::_handle_rendering()
{
_board_widget->render();
_header->render();
if (_menu_open) {
renderer()->viewport(&viewport());
renderer()->draw_color({0, 0, 0, 175});
renderer()->fill_rect({0, 0, viewport().w, viewport().h});
renderer()->viewport(&_menu->viewport());
fill_rect_gradient(*renderer(),
{0, 0, renderer()->viewport().w, renderer()->viewport().h},
theme()->color_widget_border(), theme()->color_widget_bg());
_menu->render();
}
if (const auto now = chrono::duration_cast<chrono::milliseconds>(
chrono::steady_clock::now().time_since_epoch())
.count();
now - _time_flash <= 3000)
{
const auto factor {smoothstep(now - _time_flash, 0LL, 3000LL)};
const auto [x, y, w, h] = _caption_flash->viewport();
renderer()
->viewport(&viewport())
->draw_color(lerp({255, 255, 255, 255, true}, {255, 255, 255, 0, true}, factor,
true, false))
->fill_rect({0, y - viewport().y, w, h});
_caption_flash->font_color_fg(
lerp({0, 0, 0, 255, true}, {0, 0, 0, 0, true}, factor, true, false));
_caption_flash->render();
}
}
void GameScreenImpl::_handle_theme_change(const shared_ptr<Theme>& t)
{
_board_widget->theme(t);
_header->theme(t);
_header->alignment(bwidgets::Caption::Alignment::CENTER);
_header->theme(t);
_header->font(t->font_default_biggest());
_header->font_color_bg(t->color_widget_bg());
_header->font_color_fg(t->color_font_fg());
_header->render_mode(bwidgets::Font::RenderMode::SHADED);
_menu->theme(t);
_caption_flash->font(std::make_shared<Font>(Font::find("Serif"), 64));
_caption_height->font(t->font_default());
_caption_height->font_color_bg(t->color_widget_bg());
_caption_height->font_color_fg(t->color_font_fg());
_caption_height->render_mode(Font::RenderMode::SHADED);
_caption_width->font(t->font_default());
_caption_width->font_color_bg(t->color_widget_bg());
_caption_width->font_color_fg(t->color_font_fg());
}

View File

@ -0,0 +1,48 @@
#ifndef GUI_GAME_SCREEN_IMPL_HPP
#define GUI_GAME_SCREEN_IMPL_HPP
#include <basic_widgets/w/base/widget_impl.hpp>
#include <basic_widgets/w/caption.hpp>
#include "basic_widgets/w/base/layout.hpp"
#include "board_widget.hpp"
#include "game_screen.hpp"
namespace sdlui
{
class GameScreenImpl final : public virtual GameScreen,
public virtual bwidgets::WidgetImpl
{
public:
GameScreenImpl(bwidgets::Widget* p = nullptr);
[[nodiscard]] auto board() const
-> const std::shared_ptr<core::Board2D>& override;
void board(std::shared_ptr<core::Board2D>) override;
[[nodiscard]] auto board_event_type() const -> std::uint32_t override;
void flash(const std::string&) override;
void handle_event(const SDL_Event&) override;
[[nodiscard]] auto header_text() const -> std::string_view override;
void header_text(const std::string&) override;
[[nodiscard]] auto size() const noexcept -> bwidgets::Size override;
private:
void _handle_geometry_change(const SDL_Rect&) override;
void
_handle_renderer_change(const std::shared_ptr<bwidgets::Renderer>&) override;
void _handle_rendering() override;
void _handle_theme_change(const std::shared_ptr<bwidgets::Theme>&) override;
std::shared_ptr<core::Board2D> _board;
std::shared_ptr<sdlui::BoardWidget> _board_widget;
std::shared_ptr<bwidgets::Caption> _caption_flash;
std::shared_ptr<bwidgets::Caption> _caption_height;
std::shared_ptr<bwidgets::Caption> _caption_width;
std::shared_ptr<bwidgets::Caption> _header;
std::shared_ptr<bwidgets::Layout> _menu;
bool _menu_open;
long long _time_flash;
};
}
#endif

123
src/sdlui/sdlui.cpp Normal file
View File

@ -0,0 +1,123 @@
#include <chrono>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <thread>
#include <utility>
#include <basic_widgets/core/type/deleter.hpp>
#include <basic_widgets/w/default_theme.hpp>
#include <basic_widgets/w/widget_factory.hpp>
#include "game_screen_impl.hpp"
#include "sdlui.hpp"
using namespace std;
int main()
{
SDL_Init(SDL_INIT_VIDEO);
TTF_Init();
std::atexit([]() {
SDL_Quit();
TTF_Quit();
});
auto win = unique_ptr<SDL_Window, bwidgets::Deleter>(SDL_CreateWindow(
"chainreact-cpp", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_UTILITY));
auto renderer =
make_shared<bwidgets::Renderer>(win.get(), -1, SDL_RENDERER_ACCELERATED);
renderer->blend_mode(SDL_BLENDMODE_BLEND);
auto theme = make_shared<bwidgets::DefaultTheme>();
auto game_screen = make_shared<sdlui::GameScreenImpl>();
game_screen->renderer(renderer);
game_screen->theme(theme);
game_screen->header_text("Player 1");
const uint32_t header_caption_text_event = SDL_RegisterEvents(1);
auto player {core::Player::P1};
bool quit {false};
bool accept_input {true};
thread move_thread {};
while (!quit) {
SDL_Event ev;
while (SDL_PollEvent(&ev) != 0) {
switch (ev.type) {
case SDL_QUIT:
quit = true;
break;
case SDL_WINDOWEVENT:
if (ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
const auto [w, h] = renderer->output_size();
game_screen->viewport({0, 0, w, h});
}
break;
}
if (ev.type == game_screen->board_event_type()) {
switch (static_cast<sdlui::BoardWidget::EventCode>(ev.user.code)) {
case sdlui::BoardWidget::EventCode::MOVE:
if (accept_input) {
if (move_thread.joinable()) move_thread.join();
const auto* next_move =
static_cast<core::Board2D::Coord*>(ev.user.data1);
const auto move_task =
[&accept_input, &player, &game_screen,
&header_caption_text_event](core::Board2D::Coord c) {
accept_input = false;
try {
for (auto mover =
game_screen->board()->move(c, player);
mover;) {
const auto [_, winner] = mover();
if (winner) {
if (winner.value() == core::Player::P1)
game_screen->flash("Player 1 wins!");
else game_screen->flash("Player 2 wins!");
return;
}
this_thread::sleep_for(750ms);
}
SDL_Event header_update;
SDL_zero(header_update);
header_update.type = header_caption_text_event;
if (player == core::Player::P1) {
header_update.user.data1 = (void*)"Player 2";
player = core::Player::P2;
game_screen->flash("Player 2 turn…");
}
else {
header_update.user.data1 = (void*)"Player 1";
player = core::Player::P1;
game_screen->flash("Player 1 turn…");
}
accept_input = true;
SDL_PushEvent(&header_update);
} catch (const core::Board2D::InvalidMove&) {
}
};
move_thread = thread {move_task, *next_move};
delete next_move;
}
break;
case sdlui::BoardWidget::EventCode::BOARD_CHANGED:
player = core::Player::P1;
if (!game_screen->board()->winner()) accept_input = true;
}
}
else if (ev.type == header_caption_text_event) {
game_screen->header_text(static_cast<char*>(ev.user.data1));
}
game_screen->handle_event(ev);
}
renderer->draw_color({50, 60, 70, SDL_ALPHA_OPAQUE});
renderer->clear();
game_screen->render();
renderer->present();
}
return EXIT_SUCCESS;
}

4
src/sdlui/sdlui.hpp Normal file
View File

@ -0,0 +1,4 @@
#ifndef GUI_SDLUI
#define GUI_SDLUI
#endif

View File

@ -24,11 +24,11 @@ int main()
winner != Player::NONE)
{
cout << "Player " << static_cast<int>(p) << " won" << endl;
break;
//break;
}
cout << endl;
p = p == Player::P1 ? Player::P2 : Player::P1;
//p = p == Player::P1 ? Player::P2 : Player::P1;
}
break;
} catch (const Board2D::InvalidMove& e) {

View File

@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/soratobukuroneko/sdl2_basic_widgets.git
revision = 0.9.0
revision = 3c5f5c8ea9bc49cad348827191ca66a1dc254ec1
[provide]
basic_widgets = libbasic_widgets_dep