chainreaction/board_widget.cpp

224 lines
7.7 KiB
C++

#include <array>
#include <cmath>
#include <SDL2/SDL_assert.h>
#include <SDL2/SDL_events.h>
#include <basic_widgets/core/draw.hpp>
#include <basic_widgets/core/renderer.hpp>
#include <basic_widgets/core/texture.hpp>
#include "board_widget.hpp"
#include "game.hpp"
using namespace bwidgets;
using namespace game;
using namespace game::data;
ui::BoardWidget::BoardWidget(Widget* p) : Widget(p)
{
_click_area = &_widget_area;
click_handler = [this](const SDL_MouseButtonEvent& p) -> void {
if (board != nullptr) {
auto* coord = new SDL_Point(this->board_coord_from_input({p.x, p.y}));
SDL_Event play_ev;
SDL_zero(play_ev);
play_ev.type = this->event_type;
play_ev.user.code = (Sint32)BoardWidget::event_code::PLAY;
play_ev.user.data1 = this;
play_ev.user.data2 = coord;
SDL_PushEvent(&play_ev);
}
};
}
ui::BoardWidget::~BoardWidget()
{
discard(_texture_p1, _texture_p2);
}
auto ui::BoardWidget::board_coord_from_input(const SDL_Point& p) const -> SDL_Point
{
if (board == nullptr) return {-1, -1};
return {(p.x - _viewport.x - _widget_area.x) / (_widget_area.w / board->width),
(p.y - _viewport.y - _widget_area.y) / (_widget_area.h / board->height)};
}
auto ui::BoardWidget::size() const noexcept -> Size
{
Size min_board_size;
// NOLINTNEXTLINE(readability-magic-numbers)
if (board != nullptr) min_board_size = {board->width * 64, board->height * 64};
// NOLINTNEXTLINE(readability-magic-numbers)
else min_board_size = {64, 64};
auto shortest {min_board_size.w < min_board_size.h ? min_board_size.w
: min_board_size.h};
int border {shortest / _border_ratio};
return {min_board_size.w + 2 * border, min_board_size.h + 2 * border};
}
void ui::BoardWidget::_draw_atoms(const SDL_Rect& area, int atoms, Texture* texture)
{
const auto radius = area.w < area.h ? area.w / 8 : area.h / 8;
const auto diameter = radius * 2;
const SDL_Point center {area.w / 2 - radius, area.h / 2 - radius};
const SDL_Point diagonal_radius {
(int)std::sqrt(radius * radius / 2),
(int)std::sqrt(radius * radius / 2),
};
const int disabled = -1;
// clang-format off
std::array<SDL_Point, 4> render_origins;
render_origins[0] = {
atoms == 1 || atoms == 3
? center.x
: atoms == 2 || atoms == 4
? center.x + diagonal_radius.x
: disabled,
atoms == 1 || atoms == 3
? center.y
: atoms == 2 || atoms == 4
? center.y + diagonal_radius.y
: disabled
};
render_origins[1] = {
atoms >= 2 && atoms <= 4
? center.x - diagonal_radius.x
: disabled,
atoms >= 2 && atoms <= 4
? center.y - diagonal_radius.y
: disabled,
};
render_origins[2] = {
atoms == 3
? center.x + diagonal_radius.x
: atoms == 4
? center.x + diagonal_radius.x
: disabled,
atoms == 3
? center.y + diagonal_radius.y
: atoms == 4
? center.y - diagonal_radius.y
: disabled
};
render_origins[3] = {
atoms == 4
? center.x - diagonal_radius.x
: disabled,
atoms == 4
? center.y + diagonal_radius.y
: disabled
};
// clang-format on
for (auto o : render_origins) {
const int size {o.x == disabled ? 0 : diameter};
_renderer->copy(texture, nullptr, {area.x + o.x, area.y + o.y, size, size});
}
}
void ui::BoardWidget::_handle_geometry_change(const SDL_Rect& vp) noexcept
{
_widget_area = {0, 0, vp.w, vp.h};
auto smallest_size {_widget_area.w < _widget_area.h ? _widget_area.w
: _widget_area.h};
_border_area = {_widget_area.x, _widget_area.y, smallest_size / _border_ratio,
smallest_size / _border_ratio};
_widget_area = {_border_area.x + _border_area.w, _border_area.y + _border_area.w,
_widget_area.w - 2 * (_border_area.w),
_widget_area.h - 2 * (_border_area.w)};
discard(_texture_p1, _texture_p2);
}
void ui::BoardWidget::_handle_renderer_change(Renderer* /*unused*/)
{
discard(_texture_p1, _texture_p2);
}
void ui::BoardWidget::_handle_rendering()
{
if ((_texture_p1 == nullptr) || (_texture_p2 == nullptr)) _handle_texture_update();
_renderer->draw_color(color_bg)->fill_rect(_widget_area);
_render_empty_board();
_render_square_content();
}
void ui::BoardWidget::_handle_texture_update()
{
SDL_assert_release((_texture_p1 == nullptr) && (_texture_p2 == nullptr)); // NOLINT
if (board == nullptr) return;
auto circle_size = _widget_area.w > _widget_area.h
? _widget_area.h / board->height / 4
: _widget_area.w / board->width / 4;
// NOLINTNEXTLINE(readability-magic-numbers)
auto aa = circle_size > 20 ? 3 : circle_size > 10 ? 2 : circle_size > 4 ? 1 : 0;
// NOLINTNEXTLINE(readability-magic-numbers)
_texture_p1 = filled_circle({75, 125, 255, 255}, circle_size, _renderer, aa);
// NOLINTNEXTLINE(readability-magic-numbers)
_texture_p2 = filled_circle({255, 100, 100, 255}, circle_size, _renderer, aa);
}
void ui::BoardWidget::_render_empty_board()
{
_renderer->draw_color(color_outline);
if (board != nullptr) {
for (int col = 0; col <= board->width; col++) {
int x = col * (_widget_area.w - 1) / board->width;
_renderer->draw_line({_widget_area.x + x, _widget_area.y},
{_widget_area.x + x, _widget_area.y + _widget_area.h});
}
for (int row = 0; row <= board->height; row++) {
int y = row * (_widget_area.h - 1) / board->height;
_renderer->draw_line({_widget_area.x, _widget_area.y + y},
{_widget_area.x + _widget_area.w, _widget_area.y + y});
}
}
for (int xy = 0; xy < _border_area.w; xy++) {
const float color_factor = 1 - (float)xy / (float)_border_area.w / 3;
_renderer
->draw_color({(uint8_t)((float)color_outline.r * color_factor),
(uint8_t)((float)color_outline.g * color_factor),
(uint8_t)((float)color_outline.b * color_factor),
color_outline.a})
->draw_rect({_border_area.x + xy, _border_area.y + xy,
_widget_area.w + 2 * (_widget_area.x - _border_area.x - xy) + 1,
_widget_area.h + 2 * (_widget_area.y - _border_area.y - xy) + 1});
}
}
void ui::BoardWidget::_render_square_content()
{
if (board == nullptr) return;
for (int y = 0; y < board->height; y++) {
for (int x = 0; x < board->width; x++) {
const SDL_Rect sqr_area {
_widget_area.x + x * _widget_area.w / board->width + 1,
_widget_area.y + y * _widget_area.h / board->height + 1,
_widget_area.w / board->width - 2, _widget_area.h / board->height - 2};
const auto& sqr = Board::square(*board, {x, y});
const auto capacity = logic::square_capacity(*board, {x, y});
if (sqr.value > capacity) {
_renderer->draw_color(color_hl1)->fill_rect(sqr_area);
}
else if (sqr.value == capacity) {
_renderer->draw_color(color_hl2)->fill_rect(sqr_area);
}
_draw_atoms(sqr_area, sqr.value,
sqr.owner == game::data::Player::P1 ? _texture_p1 : _texture_p2);
}
}
}