ochess/src/game_tab/left_panel/board/BoardCanvas.cpp

436 lines
14 KiB
C++
Raw Normal View History

2022-02-23 18:11:55 +01:00
#include "BoardCanvas.hpp"
wxDEFINE_EVENT(PLAY_MOVE_EVENT, wxCommandEvent);
BoardCanvas::BoardCanvas(wxFrame *parent)
2023-01-02 11:36:13 +01:00
: wxPanel(parent), black_side(false), frozen(false),
2022-12-29 13:00:23 +01:00
lock_square_size(false), t(new Theme()), t_captures(new Theme()) {
2022-02-23 18:11:55 +01:00
is_dragging = false;
valid_drag = false;
2023-01-02 11:36:13 +01:00
// Init animation data
adata.duration=200;
adata.duration_fast=80;
adata.fps=30;
adata.buffer=new wxBitmap(500,500,32);
2022-12-29 13:00:23 +01:00
adata.reuseBuffer=false;
adata.buffer=nullptr;
2023-01-02 11:36:13 +01:00
// Init game state
gs.is_black_turn=false;
gs.board = "rnbqkbnrpppppppp PPPPPPPPRNBQKBNR";
gs.mat_black=false;
gs.mat_white=false;
// Init clocks
2022-02-23 18:11:55 +01:00
SetClockTime(-1, -1, -1, false);
SetClockTime(-1, -1, -1, true);
2023-01-02 11:36:13 +01:00
// Init capture pieces
t_captures->ResizePieces(t->GetPiecesSizes() * CAPTURE_FACTOR);
// Load preferences
2022-02-23 18:11:55 +01:00
ApplyPreferences();
2022-12-28 08:38:21 +01:00
// The following should be called when using an EVT_PAINT handler
SetBackgroundStyle(wxBG_STYLE_PAINT);
2023-01-01 12:15:08 +01:00
// Let GameTableLeftPanel process keyboard events:
2022-12-29 12:28:51 +01:00
Bind(wxEVT_KEY_DOWN, [p=this](wxKeyEvent &e){e.ResumePropagation(1);e.Skip();});
Bind(wxEVT_KEY_UP, [p=this](wxKeyEvent &e){e.ResumePropagation(1);e.Skip();});
2023-01-01 16:09:23 +01:00
Bind(wxEVT_PAINT, &BoardCanvas::OnPaint, this);
2023-01-01 20:21:23 +01:00
Bind(wxEVT_SIZE, &BoardCanvas::OnResize, this);
2023-01-02 08:28:59 +01:00
// Mouse events:
Bind(wxEVT_MOTION, &BoardCanvas::MouseEvent, this);
Bind(wxEVT_LEFT_DOWN, &BoardCanvas::MouseEvent, this);
Bind(wxEVT_LEFT_UP, &BoardCanvas::MouseEvent, this);
2023-01-01 20:21:23 +01:00
}
2023-01-01 16:09:23 +01:00
2023-01-01 20:21:23 +01:00
void BoardCanvas::OnResize(wxSizeEvent &e){
wxSize size=e.GetSize();
if(size.x>100 && size.y>100){
// Setup buffer (use for animations)
if(adata.buffer!=nullptr)
free(adata.buffer);
adata.buffer=new wxBitmap(size.x,size.y,32);
}
2022-12-28 12:10:08 +01:00
}
BoardCanvas::~BoardCanvas() {
delete t;
delete t_captures;
2022-12-31 23:24:14 +01:00
if(adata.buffer!= nullptr)
free(adata.buffer);
}
2022-02-23 18:11:55 +01:00
BoardCanvas::BoardCanvas(wxFrame *parent, std::uint32_t square_width,
bool frozen)
: BoardCanvas(parent) {
t->ResizeSquaresAndPieces(square_width);
t_captures->ResizePieces(t->GetPiecesSizes() * CAPTURE_FACTOR);
this->frozen = true;
lock_square_size = true;
}
void BoardCanvas::OnPaint(wxPaintEvent &event) {
2022-12-28 08:38:21 +01:00
wxBufferedPaintDC dc(this);
dc.SetBackground(*wxWHITE_BRUSH);
2023-01-04 17:07:20 +01:00
dc.Clear();
2022-12-29 13:00:23 +01:00
if(!adata.reuseBuffer){
2022-12-28 12:10:08 +01:00
// Setting up required attributes
REFRESH_MOUSE_LOCATION();
square_width = t->GetSquaresSizes();
2022-12-29 16:34:14 +01:00
canvas_size = this->GetSize();
2022-12-28 12:10:08 +01:00
boardX = (canvas_size.x - (8 * square_width)) / 2;
boardY = (canvas_size.y - (8 * square_width)) / 2;
if (boardX > canvas_size.x)
boardX = 0;
if (boardY > canvas_size.y)
boardY = 0;
2022-12-29 13:00:23 +01:00
wxMemoryDC memDC(*adata.buffer);
memDC.SetBackground(*wxWHITE_BRUSH);
2023-01-01 20:21:23 +01:00
memDC.Clear();
2022-12-28 12:10:08 +01:00
DrawBoard(memDC);
dc.Blit(0,0,canvas_size.x,canvas_size.y,(wxDC*)&memDC,0,0);
}
else {
2022-12-29 13:24:23 +01:00
// Otherwise reuse buffer and animate
2022-12-29 13:00:23 +01:00
dc.DrawBitmap(*adata.buffer, 0, 0, true);
2022-12-29 13:08:53 +01:00
double percent=adata.frame/adata.frames;
2022-12-29 10:08:22 +01:00
// Draw moving piece
2022-12-29 13:00:23 +01:00
dc.DrawBitmap(*t->Get(adata.piece_moved),
2022-12-29 13:08:53 +01:00
adata.src.x + adata.frame*(adata.transVect.x/adata.frames),
adata.src.y + adata.frame*(adata.transVect.y/adata.frames), false);
2023-01-01 20:21:23 +01:00
// End drawing
2022-12-29 13:00:23 +01:00
adata.frame++;
2022-12-28 12:10:08 +01:00
}
2022-02-23 18:11:55 +01:00
}
void BoardCanvas::ApplyPreferences() {
2022-12-31 20:45:03 +01:00
if (t != nullptr)
2022-02-23 18:11:55 +01:00
delete t;
2022-12-31 20:45:03 +01:00
if (t_captures != nullptr)
2022-02-23 18:11:55 +01:00
delete t_captures;
t = new Theme();
t_captures = new Theme();
CONFIG_OPEN(config);
black_side = config->Read("board/black_by_default", false);
if (lock_square_size) {
t->ResizeSquaresAndPieces(square_width);
} else {
t->ResizeSquaresAndPieces(config->Read("board/square_size", 80));
}
t->SetSquareRadius(config->Read("board/corner_radius", 10));
t_captures->ResizePieces(t->GetPiecesSizes() * CAPTURE_FACTOR);
CONFIG_CLOSE(config);
Refresh();
}
2023-01-02 11:36:13 +01:00
void BoardCanvas::SetupBoard(const GameState &new_gs) {
gs.board = new_gs.board;
gs.is_black_turn = new_gs.is_black_turn;
gs.captures = new_gs.captures;
gs.white=new_gs.white;
gs.black=new_gs.black;
gs.mat_white=new_gs.mat_white;
gs.mat_black=new_gs.mat_black;
gs.black_time=new_gs.black_time;
gs.white_time=new_gs.white_time;
2022-02-23 18:11:55 +01:00
Refresh();
}
2023-01-02 12:43:46 +01:00
void BoardCanvas::Animate(const GameState &new_gs, const std::string &src, const std::string &dst, bool faster){
2022-12-28 12:10:08 +01:00
std::uint8_t pfile = src[0]-'a';
std::uint8_t prank = src[1]-'1';
2023-01-02 11:36:13 +01:00
adata.piece_moved = gs.board[pfile + 8 * (7-prank)]; // Piece to move
2022-12-29 10:08:22 +01:00
// Now remove the piece that will be moved
2023-01-02 11:36:13 +01:00
gs.board[pfile + 8 * (7-prank)]=' ';
2022-12-29 13:00:23 +01:00
wxMemoryDC memDC(*adata.buffer);
2022-12-29 11:52:24 +01:00
DrawBoard(memDC);
2022-12-29 12:28:51 +01:00
2022-12-29 13:08:53 +01:00
// Now compute piece start position and translation vector
2022-12-29 10:08:22 +01:00
std::uint32_t piece_width = t->GetPiecesSizes();
std::uint32_t centrer_offset = (square_width - piece_width) / 2;
if (!black_side) {
prank = 7 - prank;
pfile = 7 - pfile;
}
std::uint32_t x = boardX + (7 - pfile) * square_width;
std::uint32_t y = boardY + prank * square_width;
2022-12-29 13:00:23 +01:00
adata.src.x = x + centrer_offset;
adata.src.y = y + centrer_offset;
2022-12-29 10:08:22 +01:00
// Now dst:
pfile = dst[0]-'a';
prank = dst[1]-'1';
if (!black_side) {
prank = 7 - prank;
pfile = 7 - pfile;
}
x = boardX + (7 - pfile) * square_width;
y = boardY + prank * square_width;
2022-12-29 13:00:23 +01:00
adata.transVect.x=x-adata.src.x+centrer_offset;
adata.transVect.y=y-adata.src.y+centrer_offset;
2022-12-29 10:08:22 +01:00
// Start animation:
2022-12-29 13:00:23 +01:00
adata.reuseBuffer=true;
int duration_backup=adata.duration;
adata.duration=faster ? adata.duration_fast : adata.duration;
2022-12-29 15:02:17 +01:00
int frame_duration=(1000/adata.fps);
2022-12-29 13:00:23 +01:00
adata.frame=0;
2022-12-29 15:02:17 +01:00
adata.frames=adata.duration/frame_duration; // total number of frames
2022-12-29 13:08:53 +01:00
int time_per_frame=adata.duration/adata.frames;
2022-12-29 11:52:24 +01:00
wxStopWatch sw;
2022-12-29 13:08:53 +01:00
for(int i=adata.frames;i>0;i--){
2022-12-29 10:08:22 +01:00
Refresh();
Update();
2022-12-29 15:02:17 +01:00
int delay=sw.Time()-frame_duration;
if(delay>5){ // 5ms tolerance
2022-12-29 11:52:24 +01:00
wxMilliSleep(delay);
}
sw.Start(0);
2022-12-29 10:08:22 +01:00
}
2022-12-29 13:00:23 +01:00
adata.duration=faster ? duration_backup : duration_backup;
adata.reuseBuffer=false;
2023-01-02 11:36:13 +01:00
SetupBoard(new_gs);
2022-12-28 12:10:08 +01:00
}
2022-12-28 10:37:57 +01:00
void BoardCanvas::DrawBoard(wxDC &dc) {
2022-02-23 18:11:55 +01:00
std::uint32_t piece_width = t->GetPiecesSizes();
std::uint32_t centrer_offset = (square_width - piece_width) / 2;
2022-12-29 18:32:35 +01:00
wxSize numbers_size=dc.GetTextExtent("0");
2022-02-23 18:11:55 +01:00
bool DrawDraggingPiece = false;
char dp = 'p';
std::uint32_t dpx = 0, dpy = 0;
for (std::int8_t file = 7; file >= 0; file--) {
for (std::uint8_t rank = 0; rank <= 7; rank++) {
std::uint32_t x = boardX + (7 - file) * square_width;
std::uint32_t y = boardY + rank * square_width;
if ((file + rank) % 2 == 0) {
if (file == 0 && rank == 0) {
dc.DrawBitmap(*t->Get('1'), x, y, true);
} else if (file == 7 && rank == 7) {
dc.DrawBitmap(*t->Get('2'), x, y, true);
} else {
dc.DrawBitmap(*t->Get('s'), x, y, true);
}
} else {
if (file == 7 && rank == 0) {
dc.DrawBitmap(*t->Get('0'), x, y, true);
} else if (file == 0 && rank == 7) {
dc.DrawBitmap(*t->Get('3'), x, y, true);
} else {
dc.DrawBitmap(*t->Get('S'), x, y, true);
}
}
2022-12-29 18:32:35 +01:00
// Draw numbers
dc.SetFont(wxFont(*wxNORMAL_FONT).MakeBold());
2022-12-30 16:16:41 +01:00
if(file==7){ // Right numbers
2022-12-29 18:32:35 +01:00
dc.DrawText(wxString((char)('1'+7-rank)),
2022-12-30 16:16:41 +01:00
x-numbers_size.x*1.5,y+square_width/2-numbers_size.y/2);
2022-12-29 18:32:35 +01:00
}
if(rank==7){ // Bottom numbers
dc.DrawText(wxString((char)('a'+7-file)),
x+square_width/2-numbers_size.x/2,y+square_width);
2022-12-30 13:17:06 +01:00
}
2022-12-29 18:32:35 +01:00
2022-12-30 13:17:06 +01:00
// Draw pieces
2022-02-23 18:11:55 +01:00
std::uint8_t prank = rank;
std::uint8_t pfile = file;
if (black_side) {
prank = 7 - rank;
pfile = 7 - file;
}
std::uint32_t px = x + centrer_offset;
std::uint32_t py = y + centrer_offset;
2023-01-02 11:36:13 +01:00
char piece = gs.board[(7 - pfile) + 8 * prank];
2022-02-23 18:11:55 +01:00
if (is_dragging && (7 - pfile) == active_square.x &&
(7 - prank) == active_square.y) {
dp = piece;
dpx = px - (lastClickX - mouseX);
dpy = py - (lastClickY - mouseY);
DrawDraggingPiece = true;
continue;
}
if (piece != ' ') {
dc.DrawBitmap(*t->Get(piece), px, py, false);
2023-01-02 11:36:13 +01:00
if((piece == 'k' && gs.mat_black) || (piece == 'K' && gs.mat_white))
2023-01-02 10:56:27 +01:00
dc.DrawBitmap(*t->Get('#'), x+square_width/2+centrer_offset, y+centrer_offset, false);
2022-02-23 18:11:55 +01:00
}
}
}
// Draw badge
dc.SetPen(wxPen(*wxBLACK, 3));
std::uint32_t badgeY = boardY;
std::uint32_t badgeWidth = square_width / 2;
2023-01-02 11:36:13 +01:00
if (gs.is_black_turn) {
2022-02-23 18:11:55 +01:00
dc.SetBrush(*wxBLACK_BRUSH);
if (black_side) {
badgeY = boardY + (8 * square_width) - badgeWidth;
}
} else {
dc.SetBrush(*wxWHITE_BRUSH);
if (!black_side) {
badgeY = boardY + (8 * square_width) - badgeWidth;
}
}
2022-12-30 16:16:41 +01:00
wxRect badge(boardX + (8 * square_width) + badgeWidth/2, badgeY, badgeWidth,
2022-02-23 18:11:55 +01:00
badgeWidth);
dc.DrawRectangle(badge);
2022-12-30 13:17:06 +01:00
// Draw captures (+player names) first for white then for black
2022-02-23 18:11:55 +01:00
std::uint32_t captures_size = t_captures->GetPiecesSizes();
2022-12-30 14:55:59 +01:00
std::uint8_t padding = 10;
2022-02-23 18:11:55 +01:00
std::uint32_t offsetX = 0;
std::uint32_t offsetY = -(captures_size + padding);
2022-12-30 14:55:59 +01:00
std::uint32_t offsetYPlayerName=offsetY-captures_size-padding; // For top player name we
// add -padding at the end (ortherwise bottom of the letters
// are to close to captured pieces)
// White (black's captures):
2022-02-23 18:11:55 +01:00
if (black_side) {
2022-12-30 14:55:59 +01:00
offsetY = 8 * square_width + padding + numbers_size.y;
2022-12-30 13:17:06 +01:00
offsetYPlayerName = offsetY+captures_size;
2022-02-23 18:11:55 +01:00
}
for (char p : {'P', 'N', 'B', 'R', 'Q'}) {
2023-01-02 11:36:13 +01:00
if (gs.captures.find(p) != gs.captures.end()) {
for (std::uint8_t i = 0; i < gs.captures[p]; i++) {
2022-02-23 18:11:55 +01:00
dc.DrawBitmap(*t_captures->Get(p), boardX + offsetX, boardY + offsetY);
offsetX += captures_size / 2;
}
offsetX += captures_size / 2;
}
}
2023-01-02 11:36:13 +01:00
dc.DrawText(wxString(gs.black),boardX,boardY + offsetYPlayerName);
2022-12-30 14:55:59 +01:00
// Black (white's captures):
2022-02-23 18:11:55 +01:00
offsetX = 0;
if (black_side) {
offsetY = -(captures_size + padding);
2022-12-30 14:55:59 +01:00
offsetYPlayerName = offsetY-captures_size-padding; // Same for -padding (cf. for black's captures)
2022-02-23 18:11:55 +01:00
} else {
2022-12-30 14:55:59 +01:00
offsetY = 8 * square_width + padding + numbers_size.y;
2022-12-30 13:17:06 +01:00
offsetYPlayerName = offsetY+captures_size;
2022-02-23 18:11:55 +01:00
}
for (char p : {'p', 'n', 'b', 'r', 'q'}) {
2023-01-02 11:36:13 +01:00
if (gs.captures.find(p) != gs.captures.end()) {
for (std::uint8_t i = 0; i < gs.captures[p]; i++) {
2022-02-23 18:11:55 +01:00
dc.DrawBitmap(*t_captures->Get(p), boardX + offsetX, boardY + offsetY);
offsetX += captures_size / 2;
}
offsetX += captures_size / 2;
}
}
2023-01-02 11:36:13 +01:00
dc.DrawText(wxString(gs.white),boardX,boardY + offsetYPlayerName);
2022-02-23 18:11:55 +01:00
// Draw dragging piece
if (DrawDraggingPiece) {
dc.DrawBitmap(*t->Get(dp), dpx, dpy, false);
}
// Draw numbers
for (char l = 'a'; l < 'a' + 8; l++) {
dc.DrawText(wxString(l), wxPoint(boardX + l - 'a' * square_width,
boardY + 8 * square_width + 10));
}
// Draw Clocks
2023-01-02 11:36:13 +01:00
if (std::get<0>(gs.black_time) >= 0) {
2022-02-23 18:11:55 +01:00
wxFont font = dc.GetFont();
2023-01-02 11:36:13 +01:00
ClockTime clock = black_side ? gs.white_time : gs.black_time;
2022-02-23 18:11:55 +01:00
wxString time =
wxString::Format("%ds", std::get<1>(clock), std::get<2>(clock));
if (std::get<0>(clock) > 0) {
time = wxString::Format("%d:%d", std::get<0>(clock), std::get<1>(clock));
} else if (std::get<1>(clock) > 0) {
time = wxString::Format("%d:%ds", std::get<1>(clock), std::get<2>(clock));
}
dc.SetFont(font.Scale(1.5).MakeBold());
wxCoord width, height;
dc.GetTextExtent(time, &width, &height);
dc.DrawText(time,
2022-12-29 18:32:35 +01:00
wxPoint(boardX + square_width * 8 - width, boardY - height - numbers_size.y*2));
2023-01-02 11:36:13 +01:00
clock = black_side ? gs.black_time : gs.white_time;
2022-02-23 18:11:55 +01:00
time = wxString::Format("%ds", std::get<1>(clock), std::get<2>(clock));
if (std::get<0>(clock) > 0) {
time = wxString::Format("%d:%d", std::get<0>(clock), std::get<1>(clock));
} else if (std::get<1>(clock) > 0) {
time = wxString::Format("%d:%ds", std::get<1>(clock), std::get<2>(clock));
}
dc.GetTextExtent(time, &width, &height);
dc.DrawText(time, wxPoint(boardX + square_width * 8 - width,
2022-12-29 18:32:35 +01:00
boardY + square_width * 8 + numbers_size.y*2));
2022-02-23 18:11:55 +01:00
}
}
void BoardCanvas::MouseEvent(wxMouseEvent &event) {
if (!frozen) {
if (event.Dragging()) {
if (valid_drag) {
is_dragging = true;
Refresh();
}
} else {
if (is_dragging) {
is_dragging = false;
valid_drag = false;
// Handle drop
REFRESH_MOUSE_LOCATION();
INIT_CURRENT_SQUARE();
if (IsCurrentSquareValid) {
wxLogDebug("Drag end on square (%d,%d)", file, rank);
/// Play the move
wxCommandEvent event(PLAY_MOVE_EVENT, GetId());
event.SetEventObject(this);
std::string move = ((char)('a' + active_square.x)) +
std::to_string(+active_square.y + 1) +
((char)('a' + file)) + std::to_string(rank + 1);
event.SetString(move);
ProcessEvent(event);
2023-01-02 08:28:59 +01:00
} else {
// If square not valid just redraw (place piece back to its square)
Refresh();
2022-02-23 18:11:55 +01:00
}
}
if (event.LeftDown()) {
SetFocus();
REFRESH_MOUSE_LOCATION();
lastClickX = mouseX;
lastClickY = mouseY;
INIT_CURRENT_SQUARE();
if (IsCurrentSquareValid) {
active_square.x = file;
active_square.y = rank;
2023-01-02 11:36:13 +01:00
if (gs.board[(7 - rank) * 8 + file] != ' ') {
2022-02-23 18:11:55 +01:00
wxLogDebug("Drag start on square (%d,%d)", file, rank);
valid_drag = true;
}
}
}
}
}
2023-01-01 12:15:08 +01:00
// Let GameTableLeftPanel process mouse wheel events:
2022-12-31 13:59:55 +01:00
if (event.GetWheelRotation() != 0) {
event.ResumePropagation(1);event.Skip();
}
2022-02-23 18:11:55 +01:00
}
void BoardCanvas::Zoom(std::int32_t zoom) {
2023-01-01 20:21:23 +01:00
if(!t->Zoom(zoom))
return;
2022-02-23 18:11:55 +01:00
t_captures->ResizePieces(t->GetPiecesSizes() * CAPTURE_FACTOR);
Refresh();
}
void BoardCanvas::Swap() {
black_side = !black_side;
Refresh();
}
void BoardCanvas::SetClockTime(short hours, short min, short sec,
bool IsBlack) {
if (IsBlack) {
2023-01-02 11:36:13 +01:00
gs.black_time = std::make_tuple(hours, min, sec);
2022-02-23 18:11:55 +01:00
} else {
2023-01-02 11:36:13 +01:00
gs.white_time = std::make_tuple(hours, min, sec);
2022-02-23 18:11:55 +01:00
}
}