SDL GUI rewrite
This commit is contained in:
parent
1014a90660
commit
f714e43338
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
14
meson.build
14
meson.build
|
@ -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',
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
|
@ -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};
|
|
@ -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
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#ifndef GUI_SDLUI
|
||||
#define GUI_SDLUI
|
||||
|
||||
#endif
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue