diff options
Diffstat (limited to 'src/game_tab')
| -rw-r--r-- | src/game_tab/Game.cpp | 143 | ||||
| -rw-r--r-- | src/game_tab/Game.hpp | 38 | ||||
| -rw-r--r-- | src/game_tab/GameTab.cpp | 52 | ||||
| -rw-r--r-- | src/game_tab/GameTab.hpp | 28 | ||||
| -rw-r--r-- | src/game_tab/HalfMove.cpp | 213 | ||||
| -rw-r--r-- | src/game_tab/HalfMove.hpp | 57 | ||||
| -rw-r--r-- | src/game_tab/board/BoardCanvas.cpp | 294 | ||||
| -rw-r--r-- | src/game_tab/board/BoardCanvas.hpp | 68 | ||||
| -rw-r--r-- | src/game_tab/board/BoardPanel.cpp | 96 | ||||
| -rw-r--r-- | src/game_tab/board/BoardPanel.hpp | 30 | ||||
| -rw-r--r-- | src/game_tab/board/Theme.cpp | 184 | ||||
| -rw-r--r-- | src/game_tab/board/Theme.hpp | 37 | ||||
| -rw-r--r-- | src/game_tab/editor/EditorCanvas.cpp | 186 | ||||
| -rw-r--r-- | src/game_tab/editor/EditorCanvas.hpp | 33 | ||||
| -rw-r--r-- | src/game_tab/editor/EditorPanel.cpp | 207 | ||||
| -rw-r--r-- | src/game_tab/editor/EditorPanel.hpp | 44 |
16 files changed, 1710 insertions, 0 deletions
diff --git a/src/game_tab/Game.cpp b/src/game_tab/Game.cpp new file mode 100644 index 0000000..ad712bb --- /dev/null +++ b/src/game_tab/Game.cpp @@ -0,0 +1,143 @@ +#include "Game.hpp" + +Game::Game() : current(NULL), moves(NULL) { + tags["White"] = ""; + tags["Black"] = ""; + initial_fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; + board = "rnbqkbnrpppppppp PPPPPPPPRNBQKBNR"; +} + +Game::Game(std::string fen) : current(NULL), moves(NULL) { + tags["White"] = ""; + tags["Black"] = ""; + tags["FEN"] = fen; + initial_fen = fen; + board = chessarbiter::FENParser::Parse(fen).board; +} + +Game::Game(HalfMove *m) { + moves = m; + current = m; + initial_fen = m->GetFen(); + board = chessarbiter::FENParser::Parse(initial_fen).board; +} + +std::string Game::GetBoard() { return (board); } + +std::string Game::GetTag(std::string tagname) { return (tags[tagname]); } + +void Game::SetTag(std::string tagname, std::string value) { + tags[tagname] = value; +} +bool Game::IsBlackToPlay() { + if (current == NULL) { + return (false); + } + return (!current->IsBlack); +} + +void Game::DeleteTag(std::string tagname) { tags.erase(tagname); } + +void Game::DeleteMove(HalfMove *m) { + if (moves == m) { + current = NULL; + moves = NULL; + delete m; + } else { + if (m != NULL) { + current = m->GetParent(); + if (current != NULL) { + current->RemoveChild(m); + } + delete m; + } + } +} + +HalfMove *Game::GetCurrentMove() { return (current); } +HalfMove *Game::GetMoves() { return (moves); } + +void Game::PromoteMove(HalfMove *m) { + if (m != NULL) { + current = m; + m->Promote(); + } +} + +void Game::SetMoveAsMainline(HalfMove *m) { + if (m != NULL) { + current = m; + m->SetAsMainline(); + } +} + +bool Game::Play(std::string move) { + wxLogDebug("Playing move %s", move); + std::string fen = GetFen(); + arbiter.Setup(fen); + if (arbiter.Play(move)) { + HalfMove *m = new HalfMove(arbiter.GetSAN(), arbiter.GetFEN()); + char capture = arbiter.GetCapture(); + if (capture != ' ') { + wxLogDebug("%c", capture); + m->SetCapture(capture); + } + if (current != NULL) { + current->AddMove(m); + } else if (moves != NULL) { + moves->AddVariation(m); + } + current = m; + if (moves == NULL) { + moves = m; + } + wxLogDebug("%s",GetPGN()); + return (true); + } + return (false); +} + +void Game::Previous() { + if (current != NULL) { + current = current->GetParent(); + } +} + +std::vector<std::string> Game::ListTags() { + std::vector<std::string> keys; + for (auto const &element : tags) { + keys.push_back(element.first); + } + return (keys); +} + +void Game::Next() { + if (current != NULL) { + HalfMove *m = current->GetMainline(); + if (m != NULL) { + current = m; + } + } else { + current = moves; + } +} + +void Game::SetCurrent(HalfMove *m) { current = m; } + +std::string Game::GetFen() { + if (current == NULL) { + return (initial_fen); + } + return (current->GetFen()); +} + +std::string Game::GetPGN() { + std::string pgn; + if (moves != NULL) { + for (auto const &element : tags) { + pgn += '[' + element.first + " \"" + element.second + "\"]\n"; + } + pgn += moves->GetPGN(); + } + return (pgn); +}
\ No newline at end of file diff --git a/src/game_tab/Game.hpp b/src/game_tab/Game.hpp new file mode 100644 index 0000000..790d18c --- /dev/null +++ b/src/game_tab/Game.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "ChessArbiter.hpp" +#include "HalfMove.hpp" +#include "ochess.hpp" +#include <unordered_map> + +class Game { + std::string board; + std::string initial_fen; + std::unordered_map<std::string, std::string> tags; + HalfMove *moves; + HalfMove *current; + chessarbiter::ChessArbiter arbiter; + +public: + Game(); + Game(std::string fen); + Game(HalfMove *m); + + std::string GetBoard(); + std::string GetTag(std::string tagname); + void SetTag(std::string tagname, std::string value); + void DeleteTag(std::string tagname); + HalfMove *GetCurrentMove(); + HalfMove *GetMoves(); + std::string GetFen(); + bool Play(std::string move); + bool IsBlackToPlay(); + void Previous(); + void Next(); + void DeleteMove(HalfMove *m); + void PromoteMove(HalfMove *m); + void SetMoveAsMainline(HalfMove *m); + void SetCurrent(HalfMove *m); + std::vector<std::string> ListTags(); + std::string GetPGN(); +};
\ No newline at end of file diff --git a/src/game_tab/GameTab.cpp b/src/game_tab/GameTab.cpp new file mode 100644 index 0000000..3e32b4e --- /dev/null +++ b/src/game_tab/GameTab.cpp @@ -0,0 +1,52 @@ +#include "GameTab.hpp" +#include <wx/clipbrd.h> + +wxDEFINE_EVENT(GAME_CHANGE, wxCommandEvent); + +GameTab::GameTab(wxFrame *parent, Game *game) + : wxPanel(parent), game(game), TabInfos(TabInfos::GAME) { + // Splitter + wxSplitterWindow *splitter = new wxSplitterWindow(this, wxID_ANY); + splitter->SetMinimumPaneSize(100); + + // Panels + board_panel = new BoardPanel((wxFrame *)splitter, game); + editor_panel = new EditorPanel((wxFrame *)splitter, game); + splitter->SplitVertically(board_panel, editor_panel); + + // Setup splitter + wxBoxSizer *topSizer = new wxBoxSizer(wxHORIZONTAL); + topSizer->Add(splitter, 1, wxEXPAND); + SetSizerAndFit(topSizer); + + // Refresh panels + wxCommandEvent event(REFRESH_TAB_TITLE, GetId()); + event.SetEventObject(this); + OnRefreshTabTitle(event); + board_panel->Notify(); + editor_panel->Notify(); + + Bind(REFRESH_TAB_TITLE, &GameTab::OnRefreshTabTitle, this, wxID_ANY); + Bind(GAME_CHANGE, &GameTab::OnGameChange, this, wxID_ANY); +} + +void GameTab::OnGameChange(wxCommandEvent &event) { + board_panel->Notify(); + editor_panel->Notify(); +} + +void GameTab::OnRefreshTabTitle(wxCommandEvent &event) { + std::string white = game->GetTag("White"); + std::string black = game->GetTag("Black"); + if (white.size() == 0 && black.size() == 0) { + SetLabel("New Game"); + } else { + SetLabel(white + "-" + black); + } + event.SetEventObject(this); + event.Skip(); +} + +void GameTab::ApplyPreferences() { + board_panel->ApplyPreferences(); +} diff --git a/src/game_tab/GameTab.hpp b/src/game_tab/GameTab.hpp new file mode 100644 index 0000000..7686f12 --- /dev/null +++ b/src/game_tab/GameTab.hpp @@ -0,0 +1,28 @@ +#pragma once +#include "ChessArbiter.hpp" +#include "Game.hpp" +#include "HalfMove.hpp" +#include "board/BoardPanel.hpp" +#include "editor/EditorPanel.hpp" +#include "ochess.hpp" +#include <utility> +#include <wx/collpane.h> +#include <wx/splitter.h> +#include <wx/textctrl.h> + +wxDECLARE_EVENT(REFRESH_TAB_TITLE, wxCommandEvent); +wxDECLARE_EVENT(GAME_CHANGE, wxCommandEvent); + + +class GameTab : public wxPanel, public TabInfos { + EditorPanel *editor_panel; + BoardPanel *board_panel; + Game *game; + void RefreshLabel(); + void OnRefreshTabTitle(wxCommandEvent &event); + void OnGameChange(wxCommandEvent &event); +public: + GameTab(wxFrame *parent, Game *game); + void ApplyPreferences(); + +}; diff --git a/src/game_tab/HalfMove.cpp b/src/game_tab/HalfMove.cpp new file mode 100644 index 0000000..72ee24c --- /dev/null +++ b/src/game_tab/HalfMove.cpp @@ -0,0 +1,213 @@ +#include "HalfMove.hpp" + +HalfMove::HalfMove(std::string move) : capture(' ') { + this->move = move; + fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; +} + +HalfMove::HalfMove(std::string move, std::string fen) : fen(fen), capture(' ') { + this->move = move; +} + +HalfMove::~HalfMove() { + for (HalfMove *m : variations) { + delete m; + } +} + +void HalfMove::AddVariation(HalfMove *m) { + m->IsBlack = this->IsBlack; + m->Number = this->Number; + HalfMove::variations.push_back(m); + cgeditor::CGEHalfMove::variations.push_back(m); + m->SetParent(this); +} + +std::map<char, std::uint8_t> HalfMove::GetLineCaptures() { + std::map<char, std::uint8_t> captures; + HalfMove *m = this; + do { + char c = m->capture; + if (captures.find(c) != captures.end()) { + captures[c]++; + } else { + captures[c] = 1; + } + m = m->parent; + } while (m != NULL); + return (captures); +} + +void HalfMove::SetCapture(char c) { capture = c; } + +void HalfMove::AddMove(HalfMove *m) { + if (this->mainline == NULL) { + SetMainline(m); + } else { + if (mainline != NULL) { + mainline->AddVariation(m); + } + } +} + +void HalfMove::SetMainline(HalfMove *m) { + if (!this->IsBlack) { + m->IsBlack = true; + m->Number = this->Number; + } else { + m->IsBlack = false; + m->Number = this->Number + 1; + } + HalfMove::mainline = m; + cgeditor::CGEHalfMove::MainLine = m; + if (m != NULL) { + m->SetParent(this); + } +} + +void HalfMove::SetParent(HalfMove *m) { + HalfMove::parent = m; + CGEHalfMove::Parent = m; +} + +void HalfMove::RemoveChild(HalfMove *m) { + std::uint32_t i = 0; + bool found = false; + for (i; i < HalfMove::variations.size(); i++) { + if (HalfMove::variations[i] == m) { + found = true; + break; + } + } + if (found) { + HalfMove::variations.erase(HalfMove::variations.begin() + i); + } + if (HalfMove::mainline == m) { + HalfMove::mainline = NULL; + } + cgeditor::CGEHalfMove::RemoveChild((CGEHalfMove *)m); +} + +HalfMove *HalfMove::GetParent() { return (parent); } + +HalfMove *HalfMove::GetRoot() { + HalfMove *m = this; + HalfMove *p = HalfMove::parent; + while (p != NULL) { + if (p->mainline != m) { + return (m); + } + m = p; + p = m->HalfMove::parent; + } + return (m); +} + +void HalfMove::SetAsMainline() { + HalfMove *root = GetRoot(); + HalfMove *lastRoot; + do { + lastRoot = root; + root->HalfMove::Promote(); + root = GetRoot(); + } while (root != lastRoot); +} + +HalfMove *HalfMove::GetMainline() { return (mainline); } + +HalfMove::HalfMove(pgnp::HalfMove *m, std::string initial_fen) { + chessarbiter::ChessArbiter arbiter; + arbiter.Setup(initial_fen); + arbiter.Play(arbiter.ParseSAN(m->move)); + this->fen = arbiter.GetFEN(); + this->move = m->move; + this->IsBlack = m->isBlack; + this->SetComment(m->comment); + this->Number = m->count; + if (m->MainLine != NULL) { + this->SetMainline(new HalfMove(m->MainLine, arbiter.GetFEN())); + } + for (pgnp::HalfMove *v : m->variations) { + arbiter.Setup(initial_fen); + arbiter.Play(arbiter.ParseSAN(v->move)); + this->AddVariation(new HalfMove(v, arbiter.GetFEN())); + } +} + +void HalfMove::SetFen(std::string fen) { this->fen = fen; } + +void HalfMove::Promote() { + HalfMove *root = GetRoot(); + if (root->parent != NULL) { + HalfMove *p = root->parent; + if (p->HalfMove::mainline != root) { + if (root->parent->HalfMove::parent != NULL) { + HalfMove *pp = root->parent->HalfMove::parent; + if (pp->HalfMove::mainline == p) { + pp->HalfMove::SetMainline(root); + } else { + pp->AddVariation(root); + pp->HalfMove::RemoveChild(p); + } + } + if (p->HalfMove::mainline == root) { + p->HalfMove::SetMainline(NULL); + } else { + p->HalfMove::RemoveChild(root); + } + root->AddVariation(p); + } + } +} + +bool HalfMove::IsVariation() { + HalfMove *m = this; + HalfMove *p = HalfMove::parent; + while (p != NULL) { + if (p->mainline != m) { + return (true); + } + m = p; + p = m->HalfMove::parent; + } + return (false); +} + +std::string HalfMove::GetFen() { return (fen); } + +std::string HalfMove::GetPGN() { return (GetPGN(IsBlack)); } + +std::string HalfMove::GetPGN(bool needDots) { + std::string part; + bool newNeedDots = false; + + if (!IsBlack || needDots) { + part += std::to_string(Number) + "."; + if (needDots) { + part += ".."; + } + } + part += move; + + if (GetNbLineComment() > 0) { + part += " {"; + part += GetComment(); + part += "}"; + newNeedDots = true; + } + + if (variations.size() > 0) { + newNeedDots = true; + for (HalfMove *v : variations) { + part += " ("; + part += v->GetPGN(IsBlack); + part += ")"; + } + } + + if (mainline != NULL) { + part += " " + mainline->GetPGN(newNeedDots); + } + + return (part); +} diff --git a/src/game_tab/HalfMove.hpp b/src/game_tab/HalfMove.hpp new file mode 100644 index 0000000..c8ef8c0 --- /dev/null +++ b/src/game_tab/HalfMove.hpp @@ -0,0 +1,57 @@ +#pragma once +#include "CGEditor.hpp" +#include "ChessArbiter.hpp" +#include "ochess.hpp" +#include "pgnp.hpp" +#include <map> +#include <vector> + +/** + * @brief Create your custom half move class + * + * The implementation of the class should give you + * an overview of how to keep your move sync with the one of CGEditor + * + */ +class HalfMove : public cgeditor::CGEHalfMove { + HalfMove *parent = NULL; + HalfMove *mainline = NULL; + std::vector<HalfMove *> variations; + std::string fen; + char capture; + std::string GetPGN(bool needDots); + +public: + HalfMove(std::string move); + HalfMove(std::string move, std::string fen); + HalfMove(pgnp::HalfMove *m, std::string initial_fen); + + ~HalfMove(); + /// @brief Add variation to current move + void AddVariation(HalfMove *m); + /// @brief Remove the specified child from mainline and/or variations + void RemoveChild(HalfMove *m); + void AddMove(HalfMove *m); + /// @brief Set value of the mailine + void SetMainline(HalfMove *m); + /// @brief Set this move as mainline + void SetAsMainline(); + /// @brief Promote the current move and submove + void Promote(); + /// @brief Check if current half move is within a variation + bool IsVariation(); + /// @brief Get the root of a variation + HalfMove *GetRoot(); + /// @brief Get parent of the current move + HalfMove *GetParent(); + HalfMove *GetMainline(); + std::map<char, std::uint8_t> GetLineCaptures(); + + /// @brief Set parent of the current move + void SetParent(HalfMove *m); + std::string GetFen(); + void SetFen(std::string fen); + void SetCapture(char c); + std::string GetPGN(); +}; + diff --git a/src/game_tab/board/BoardCanvas.cpp b/src/game_tab/board/BoardCanvas.cpp new file mode 100644 index 0000000..237a68a --- /dev/null +++ b/src/game_tab/board/BoardCanvas.cpp @@ -0,0 +1,294 @@ +#include "BoardCanvas.hpp" + +wxDEFINE_EVENT(PLAY_MOVE_EVENT, wxCommandEvent); + +BoardCanvas::BoardCanvas(wxFrame *parent) + : wxPanel(parent), black_side(false), is_black_turn(true), frozen(false), + lock_square_size(false), t(new Theme()), t_captures(new Theme()) { + board = "rnbqkbnrpppppppp PPPPPPPPRNBQKBNR"; + is_dragging = false; + valid_drag = false; + t_captures->ResizePieces(t->GetPiecesSizes() * CAPTURE_FACTOR); + SetClockTime(-1, -1, -1, false); + SetClockTime(-1, -1, -1, true); + ApplyPreferences(); +} + +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) { + wxPaintDC dc(this); + REFRESH_MOUSE_LOCATION(); + square_width = t->GetSquaresSizes(); + canvas_size = dc.GetSize(); + 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; + DrawBoard(dc); +} + +void BoardCanvas::ApplyPreferences() { + if (t != NULL) + delete t; + if (t_captures != NULL) + 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(); +} + +void BoardCanvas::SetupBoard(std::string board, bool is_black_turn, + std::map<char, std::uint8_t> captures) { + this->board = board; + this->is_black_turn = is_black_turn; + this->captures = captures; + Refresh(); +} + +void BoardCanvas::DrawBoard(wxPaintDC &dc) { + std::uint32_t piece_width = t->GetPiecesSizes(); + std::uint32_t centrer_offset = (square_width - piece_width) / 2; + + 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); + } + } + + 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; + char piece = board[(7 - pfile) + 8 * prank]; + 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); + } + } + } + + // Draw badge + dc.SetPen(wxPen(*wxBLACK, 3)); + std::uint32_t badgeY = boardY; + std::uint32_t badgeWidth = square_width / 2; + if (is_black_turn) { + 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; + } + } + wxRect badge(boardX + (8 * square_width) + badgeWidth / 2, badgeY, badgeWidth, + badgeWidth); + dc.DrawRectangle(badge); + + // Draw captures first for white then for black + std::uint32_t captures_size = t_captures->GetPiecesSizes(); + std::uint8_t padding = 10; + std::uint32_t offsetX = 0; + std::uint32_t offsetY = -(captures_size + padding); + if (black_side) { + offsetY = 8 * square_width + padding; + } + for (char p : {'P', 'N', 'B', 'R', 'Q'}) { + if (captures.find(p) != captures.end()) { + for (std::uint8_t i = 0; i < captures[p]; i++) { + dc.DrawBitmap(*t_captures->Get(p), boardX + offsetX, boardY + offsetY); + offsetX += captures_size / 2; + } + offsetX += captures_size / 2; + } + } + offsetX = 0; + if (black_side) { + offsetY = -(captures_size + padding); + } else { + offsetY = 8 * square_width + padding; + } + for (char p : {'p', 'n', 'b', 'r', 'q'}) { + if (captures.find(p) != captures.end()) { + for (std::uint8_t i = 0; i < captures[p]; i++) { + dc.DrawBitmap(*t_captures->Get(p), boardX + offsetX, boardY + offsetY); + offsetX += captures_size / 2; + } + offsetX += captures_size / 2; + } + } + + // 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 + if (std::get<0>(black_time) >= 0) { + wxFont font = dc.GetFont(); + ClockTime clock = black_side ? white_time : black_time; + 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, + wxPoint(boardX + square_width * 8 - width, boardY - height)); + clock = black_side ? black_time : white_time; + 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, + boardY + square_width * 8)); + } +} + +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); + } + } + if (event.LeftDown()) { + SetFocus(); + REFRESH_MOUSE_LOCATION(); + lastClickX = mouseX; + lastClickY = mouseY; + INIT_CURRENT_SQUARE(); + if (IsCurrentSquareValid) { + active_square.x = file; + active_square.y = rank; + if (board[(7 - rank) * 8 + file] != ' ') { + wxLogDebug("Drag start on square (%d,%d)", file, rank); + valid_drag = true; + } + } + } + } + } +} + +void BoardCanvas::Zoom(std::int32_t zoom) { + t->Zoom(zoom); + t_captures->ResizePieces(t->GetPiecesSizes() * CAPTURE_FACTOR); + Refresh(); +} + +void BoardCanvas::Swap() { + black_side = !black_side; + Refresh(); +} + +void BoardCanvas::OnKeyEvent(wxKeyEvent &event) { + if (event.GetKeyCode() == WXK_LEFT) { + wxCommandEvent previousEvent(PREVIOUS_MOVE_EVENT, GetId()); + previousEvent.SetEventObject(this); + ProcessEvent(previousEvent); + } else if (event.GetKeyCode() == WXK_RIGHT) { + wxCommandEvent nextEvent(NEXT_MOVE_EVENT, GetId()); + nextEvent.SetEventObject(this); + ProcessEvent(nextEvent); + } +} + +void BoardCanvas::SetClockTime(short hours, short min, short sec, + bool IsBlack) { + if (IsBlack) { + black_time = std::make_tuple(hours, min, sec); + } else { + white_time = std::make_tuple(hours, min, sec); + } +} + +wxBEGIN_EVENT_TABLE(BoardCanvas, wxPanel) EVT_PAINT(BoardCanvas::OnPaint) + EVT_MOUSE_EVENTS(BoardCanvas::MouseEvent) + EVT_CHAR_HOOK(BoardCanvas::OnKeyEvent) wxEND_EVENT_TABLE() diff --git a/src/game_tab/board/BoardCanvas.hpp b/src/game_tab/board/BoardCanvas.hpp new file mode 100644 index 0000000..34c4911 --- /dev/null +++ b/src/game_tab/board/BoardCanvas.hpp @@ -0,0 +1,68 @@ +#pragma once + +#include "Theme.hpp" +#include "ochess.hpp" +#include <map> +#include <tuple> +#include <utility> +#include <vector> +#include <wx/artprov.h> + +// Local events +wxDECLARE_EVENT(PLAY_MOVE_EVENT, wxCommandEvent); + +// Foreign events +wxDECLARE_EVENT(PREVIOUS_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(NEXT_MOVE_EVENT, wxCommandEvent); + +#define REFRESH_MOUSE_LOCATION() \ + { \ + const wxPoint pt = wxGetMousePosition(); \ + mouseX = pt.x - this->GetScreenPosition().x; \ + mouseY = pt.y - this->GetScreenPosition().y; \ + } + +#define INIT_CURRENT_SQUARE() \ + std::uint32_t file = 7 - (mouseX - boardX) / square_width; \ + std::uint32_t rank = (mouseY - boardY) / square_width; \ + if (!black_side) { \ + file = 7 - file; \ + rank = 7 - rank; \ + } \ + bool IsCurrentSquareValid = file >= 0 && file <= 7 && rank >= 0 && rank <= 7; + +#define MOUSE_ON(x, y, width, height) \ + (mouseX >= (x) && mouseX <= ((x) + (width)) && mouseY >= (y) && \ + mouseY <= ((y) + (height))) + +#define CAPTURE_FACTOR 0.5 + +typedef std::tuple<short, short, short> ClockTime; + +class BoardCanvas : public wxPanel { + Theme *t, *t_captures; + std::string board; + bool black_side, is_dragging, valid_drag, is_black_turn; + std::uint32_t boardX, boardY, square_width, mouseX, mouseY, lastClickX, + lastClickY; + wxSize canvas_size; + wxPoint active_square; + std::map<char, std::uint8_t> captures; + ClockTime black_time, white_time; + bool frozen,lock_square_size; + +public: + BoardCanvas(wxFrame *parent); + BoardCanvas(wxFrame *parent,std::uint32_t square_width, bool frozen); + void ApplyPreferences(); + void DrawBoard(wxPaintDC &dc); + void OnPaint(wxPaintEvent &event); + void OnKeyEvent(wxKeyEvent &event); + void MouseEvent(wxMouseEvent &event); + void Zoom(std::int32_t zoom); + void Swap(); + void SetupBoard(std::string board, bool is_black_turn, + std::map<char, std::uint8_t> captures); + void SetClockTime(short hours, short min, short sec, bool IsBlack); + DECLARE_EVENT_TABLE() +}; diff --git a/src/game_tab/board/BoardPanel.cpp b/src/game_tab/board/BoardPanel.cpp new file mode 100644 index 0000000..ff4fb62 --- /dev/null +++ b/src/game_tab/board/BoardPanel.cpp @@ -0,0 +1,96 @@ +#include "BoardPanel.hpp" +#include <wx/clipbrd.h> + +BoardPanel::BoardPanel(wxFrame *parent, Game *game) + : wxPanel(parent), game(game) { + + wxBoxSizer *board_panel_sizer = new wxBoxSizer(wxVERTICAL); + board_canvas = new BoardCanvas((wxFrame *)this); + board_panel_sizer->Add(board_canvas, 1, wxEXPAND); + + // Left Panel buttons + wxBoxSizer *board_panel_button_sizer = new wxBoxSizer(wxHORIZONTAL); + board_panel_button_sizer->Add( + new wxBitmapButton(this, SWAP_BTN, LoadPNG("swap")), 0); + board_panel_button_sizer->Add( + new wxBitmapButton(this, ZOOM_IN_BTN, LoadPNG("zoomin")), 0); + board_panel_button_sizer->Add( + new wxBitmapButton(this, ZOOM_OUT_BTN, LoadPNG("zoomout")), 0); + board_panel_button_sizer->Add(new wxButton(this, COPY_FEN_BTN, L"Copy FEN"), + 0, wxEXPAND); + board_panel_sizer->Add(board_panel_button_sizer, 0); + this->SetSizer(board_panel_sizer); + + Bind(PLAY_MOVE_EVENT, &BoardPanel::OnPlay, this, wxID_ANY); + Bind(PREVIOUS_MOVE_EVENT, &BoardPanel::OnPreviousMove, this, wxID_ANY); + Bind(NEXT_MOVE_EVENT, &BoardPanel::OnNextMove, this, wxID_ANY); + Bind(wxEVT_BUTTON, &BoardPanel::OnSwap, this, SWAP_BTN); + Bind(wxEVT_BUTTON, &BoardPanel::OnZoomIn, this, ZOOM_IN_BTN); + Bind(wxEVT_BUTTON, &BoardPanel::OnZoomOut, this, ZOOM_OUT_BTN); + Bind(wxEVT_BUTTON, &BoardPanel::OnCopyFEN, this, COPY_FEN_BTN); +} + +void BoardPanel::OnPreviousMove(wxCommandEvent &event) { + game->Previous(); + Notify(); + NotifyEditor(); +} + +void BoardPanel::OnZoomIn(wxCommandEvent &event) { + wxLogDebug("Clicked on zoom in"); + board_canvas->Zoom(10); +} + +void BoardPanel::OnZoomOut(wxCommandEvent &event) { + wxLogDebug("Clicked on zoom out"); + board_canvas->Zoom(-10); +} + +void BoardPanel::OnSwap(wxCommandEvent &event) { + wxLogDebug("Clicked on swap"); + board_canvas->Swap(); +} + +void BoardPanel::OnNextMove(wxCommandEvent &event) { + wxLogDebug("Game tab received NEXT_MOVE_EVENT"); + game->Next(); + Notify(); + NotifyEditor(); +} + +void BoardPanel::OnPlay(wxCommandEvent &event) { + wxLogDebug("Game tab received PLAY_MOVE_EVENT"); + if (game->Play(event.GetString().ToStdString())) { + NotifyEditor(); + } + Notify(); +} + +void BoardPanel::OnCopyFEN(wxCommandEvent &event) { + wxLogDebug("Clicked on copy fen"); + if (wxTheClipboard->Open()) { + wxTheClipboard->SetData(new wxTextDataObject(game->GetFen())); + wxTheClipboard->Close(); + } +} + +void BoardPanel::Notify() { + std::string fen = game->GetFen(); + std::map<char, std::uint8_t> captures; + HalfMove *m = game->GetCurrentMove(); + if (m != NULL) { + captures = m->GetLineCaptures(); + } + board_canvas->SetupBoard(chessarbiter::FENParser::Parse(fen).board, + game->IsBlackToPlay(), captures); +} + +void BoardPanel::NotifyEditor() { + wxCommandEvent previousEvent(GAME_CHANGE, GetId()); + previousEvent.SetEventObject(this); + ProcessEvent(previousEvent); +} + +void BoardPanel::ApplyPreferences() { + board_canvas->ApplyPreferences(); +} diff --git a/src/game_tab/board/BoardPanel.hpp b/src/game_tab/board/BoardPanel.hpp new file mode 100644 index 0000000..3c4bf77 --- /dev/null +++ b/src/game_tab/board/BoardPanel.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "../Game.hpp" +#include "BoardCanvas.hpp" +#include "ochess.hpp" + +// Foreign events +wxDECLARE_EVENT(GAME_CHANGE, wxCommandEvent); + +enum { COPY_FEN_BTN = wxID_HIGHEST + 1, ZOOM_IN_BTN, ZOOM_OUT_BTN, SWAP_BTN }; + +class BoardPanel : public wxPanel { + Game *game; + BoardCanvas *board_canvas; + void NotifyEditor(); + +public: + BoardPanel(wxFrame *parent, Game *game); + void Notify(); + void OnPlay(wxCommandEvent &event); + void OnGotoMove(wxCommandEvent &event); + void OnPreviousMove(wxCommandEvent &event); + void OnNextMove(wxCommandEvent &event); + void OnCopyFEN(wxCommandEvent &event); + void OnZoomIn(wxCommandEvent &event); + void OnZoomOut(wxCommandEvent &event); + void OnSwap(wxCommandEvent &event); + void OnRefreshBoard(wxCommandEvent &event); + void ApplyPreferences(); +};
\ No newline at end of file diff --git a/src/game_tab/board/Theme.cpp b/src/game_tab/board/Theme.cpp new file mode 100644 index 0000000..6ecbafc --- /dev/null +++ b/src/game_tab/board/Theme.cpp @@ -0,0 +1,184 @@ +#include "Theme.hpp" +#include <wx/filename.h> + +Theme::Theme() : square_radius(10) { + // Load config + CONFIG_OPEN(config); + std::string piece = + config->Read("board/theme/pieces/path", "default").ToStdString(); + wxFileName piece_file(piece); + std::string square = + config->Read("board/theme/squares/path", "default").ToStdString(); + wxFileName square_file(square); + CONFIG_CLOSE(config); + // Piece + if (piece == "default" || !piece_file.FileExists()) { + wxLogDebug("Loading piece skin from binres"); + LoadPiecesSkin(LoadPNG("cburnett").ConvertToImage()); + } else { + wxLogDebug("Loading piece skin: %s", piece); + LoadPiecesSkin(wxImage(piece, wxBITMAP_TYPE_PNG)); + } + // Square + if (square == "default" || !square_file.FileExists()) { + wxLogDebug("Loading square skin from binres"); + LoadSquaresSkin(LoadPNG("chesscom_8bits").ConvertToImage()); + } else { + wxLogDebug("Loading square skin: %s", square); + LoadSquaresSkin(wxImage(square, wxBITMAP_TYPE_PNG)); + } +} + +Theme::Theme(std::string piece, std::string square) : square_radius(10) { + wxLogDebug("Loading piece skin: %s", piece); + LoadPiecesSkin(wxImage(piece, wxBITMAP_TYPE_PNG)); + wxLogDebug("Loading square skin: %s", square); + LoadSquaresSkin(wxImage(square, wxBITMAP_TYPE_PNG)); +} + +Theme::~Theme() { + for (std::pair<char, wxBitmap *> c : skin_scaled) { + delete c.second; + } +} + +std::uint8_t Theme::GetSquareRadius() { return (square_radius); } + +void Theme::LoadPiecesSkin(wxImage iskin) { + if (iskin.GetWidth() != 2 * ELT_DIM || iskin.GetHeight() != 6 * ELT_DIM) { + throw "Invalid piece theme"; + } + + int offset = 0; + // Kings + skin['k'] = iskin.GetSubImage(wxRect(0, offset, ELT_DIM, ELT_DIM)); + skin['K'] = iskin.GetSubImage(wxRect(ELT_DIM, offset, ELT_DIM, ELT_DIM)); + // Queen + offset = ELT_DIM; + skin['q'] = iskin.GetSubImage(wxRect(0, offset, ELT_DIM, ELT_DIM)); + skin['Q'] = iskin.GetSubImage(wxRect(ELT_DIM, offset, ELT_DIM, ELT_DIM)); + // Rook + offset = ELT_DIM * 2; + skin['r'] = iskin.GetSubImage(wxRect(0, offset, ELT_DIM, ELT_DIM)); + skin['R'] = iskin.GetSubImage(wxRect(ELT_DIM, offset, ELT_DIM, ELT_DIM)); + // Bishop + offset = ELT_DIM * 3; + skin['b'] = iskin.GetSubImage(wxRect(0, offset, ELT_DIM, ELT_DIM)); + skin['B'] = iskin.GetSubImage(wxRect(ELT_DIM, offset, ELT_DIM, ELT_DIM)); + // Knight + offset = ELT_DIM * 4; + skin['n'] = iskin.GetSubImage(wxRect(0, offset, ELT_DIM, ELT_DIM)); + skin['N'] = iskin.GetSubImage(wxRect(ELT_DIM, offset, ELT_DIM, ELT_DIM)); + // Pawn + offset = ELT_DIM * 5; + skin['p'] = iskin.GetSubImage(wxRect(0, offset, ELT_DIM, ELT_DIM)); + skin['P'] = iskin.GetSubImage(wxRect(ELT_DIM, offset, ELT_DIM, ELT_DIM)); + + // Update scaled version + ResizePieces(DEFAULT_SIZE * PIECE_SIZE_FACTOR); +} + +void Theme::LoadSquaresSkin(wxImage iskin) { + if (iskin.GetWidth() != 2 * ELT_DIM || iskin.GetHeight() != ELT_DIM) { + throw "Invalid piece theme"; + } + + // Square + skin['s'] = iskin.GetSubImage(wxRect(0, 0, ELT_DIM, ELT_DIM)); + skin['S'] = iskin.GetSubImage(wxRect(ELT_DIM, 0, ELT_DIM, ELT_DIM)); + + // Update scaled version + ResizeSquares(DEFAULT_SIZE); +} + +wxBitmap *Theme::Get(char c) { return (skin_scaled[c]); } + +void Theme::ResizePieces(std::uint32_t width) { + for (std::pair<char, wxImage> c : skin) { + if (c.first != 's' && c.first != 'S') { + if (skin_scaled.count(c.first)) + delete skin_scaled[c.first]; + skin_scaled[c.first] = + new wxBitmap(c.second.Scale(width, width, wxIMAGE_QUALITY_HIGH)); + } + } +} + +void Theme::ResizeSquaresAndPieces(std::uint32_t width) { + ResizeSquares(width); + ResizePieces(width * PIECE_SIZE_FACTOR); +} + +void Theme::ResizeSquares(std::uint32_t width) { + if (skin_scaled.count('s')) + delete skin_scaled['s']; + skin_scaled['s'] = + new wxBitmap(skin['s'].Scale(width, width, wxIMAGE_QUALITY_HIGH)); + if (skin_scaled.count('S')) + delete skin_scaled['S']; + skin_scaled['S'] = + new wxBitmap(skin['S'].Scale(width, width, wxIMAGE_QUALITY_HIGH)); + + skin_scaled['0'] = new wxBitmap(*skin_scaled['S']); + skin_scaled['1'] = new wxBitmap(*skin_scaled['s']); + skin_scaled['2'] = new wxBitmap(*skin_scaled['s']); + skin_scaled['3'] = new wxBitmap(*skin_scaled['S']); + + skin_scaled['0']->SetMask(RoundedMask(width, 0)); + skin_scaled['1']->SetMask(RoundedMask(width, 1)); + skin_scaled['2']->SetMask(RoundedMask(width, 2)); + skin_scaled['3']->SetMask(RoundedMask(width, 3)); +} + +void Theme::Zoom(int amount) { + double width = skin_scaled['s']->GetWidth() + amount; + ResizeSquares(std::max(width, 1.0)); + ResizePieces(std::max(width * PIECE_SIZE_FACTOR, 1.0)); +} + +void Theme::SetSquareRadius(std::uint8_t radius) { + square_radius = radius; + Zoom(0); // Refresh scale +} + +/** + * This will never fail since k entry always exists (cf constructor and + * ResizePieces) + */ +double Theme::GetPiecesSizes() { return (skin_scaled['k']->GetWidth()); } + +/** + * This will never fail since s entry always exists (cf constructor and + * ResizeSquares) + */ +double Theme::GetSquaresSizes() { return (skin_scaled['s']->GetWidth()); } + +wxMask *Theme::RoundedMask(std::uint32_t width, std::uint8_t corner) { + + wxBitmap b(width, width, 1); + wxMemoryDC dc; + dc.SelectObject(b); + dc.SetPen(*wxBLACK_PEN); + dc.SetBrush(*wxBLACK_BRUSH); + dc.DrawRectangle(0, 0, width, width); + dc.SetBrush(*wxWHITE_BRUSH); + dc.SetPen(*wxWHITE_PEN); + dc.DrawRoundedRectangle(0, 0, width, width, square_radius); + + dc.SetBrush(*wxWHITE_BRUSH); + dc.SetPen(*wxWHITE_PEN); + if (corner == 0) { + dc.DrawRectangle(0, width / 2, width, width); + dc.DrawRectangle(width / 2, 0, width, width); + } else if (corner == 1) { + dc.DrawRectangle(0, 0, width / 2, width); + dc.DrawRectangle(0, width / 2, width, width); + } else if (corner == 2) { + dc.DrawRectangle(0, 0, width, width / 2); + dc.DrawRectangle(width / 2, 0, width, width); + } else if (corner == 3) { + dc.DrawRectangle(0, 0, width / 2, width); + dc.DrawRectangle(0, 0, width, width / 2); + } + return (new wxMask(b)); +} diff --git a/src/game_tab/board/Theme.hpp b/src/game_tab/board/Theme.hpp new file mode 100644 index 0000000..88036d8 --- /dev/null +++ b/src/game_tab/board/Theme.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include "ochess.hpp" + +#include <string> +#include <unordered_map> + +#define ELT_DIM 200 +#define DEFAULT_SIZE 80 +#define PIECE_SIZE_FACTOR 0.8 // Should be between 0 and 1 +#define DEFAULT_PIECE_THEME "assets/pieces/cburnett.png" +#define DEFAULT_SQUARE_THEME "assets/boards/chesscom_8bits.png" + +class Theme { +private: + std::unordered_map<char, wxImage> skin; + std::unordered_map<char, wxBitmap *> skin_scaled; + std::uint8_t square_radius; + wxMask *RoundedMask(std::uint32_t width, std::uint8_t corner); + +public: + Theme(); + Theme(std::string piece, std::string square); + ~Theme(); + void LoadPiecesSkin(wxImage skin); + void LoadSquaresSkin(wxImage iskin); + void ResizePieces(std::uint32_t width); + void ResizeSquares(std::uint32_t width); + void ResizeSquaresAndPieces(std::uint32_t width); + void SetSquareRadius(std::uint8_t radius); + std::uint8_t GetSquareRadius(); + void Zoom(int amount); + double GetPiecesSizes(); + double GetSquaresSizes(); + + wxBitmap *Get(char c); +}; diff --git a/src/game_tab/editor/EditorCanvas.cpp b/src/game_tab/editor/EditorCanvas.cpp new file mode 100644 index 0000000..441118d --- /dev/null +++ b/src/game_tab/editor/EditorCanvas.cpp @@ -0,0 +1,186 @@ +#include "EditorCanvas.hpp" + +EditorCanvas::EditorCanvas(wxFrame *parent) + : wxPanel(parent), NeedRedraw(false) { + hide_icon = LoadPNG("hide", wxSize(CGEditor::status.MoveIconWidth, + CGEditor::status.MoveIconWidth)); + t.ResizePieces(CGEditor::status.MoveIconWidth); +} + +void EditorCanvas::OnPaint(wxPaintEvent &event) { + wxPaintDC current_dc(this); + dc = ¤t_dc; + + // Refresh canvas size + wxSize sz = GetClientSize(); + CGEditor::status.CanvasWidth = sz.GetWidth(); + CGEditor::status.CanvasHeight = sz.GetHeight(); + CGEditor::status.UseMoveIcons = + true; // Piece image should be drawn before the move ? + + const wxPoint pt = wxGetMousePosition(); + CGEditor::status.MouseX = pt.x - this->GetScreenPosition().x; + CGEditor::status.MouseY = pt.y - this->GetScreenPosition().y; + CGEditor::Draw(); +} + +/** + * @brief Convenient fonction to center text + * + * @param e Element to center + * @return wxPoint The centered version of e according to wdWidget API + */ +wxPoint EditorCanvas::Middle(cgeditor::Element e) { + wxSize sz = dc->GetTextExtent(e.text); + return (wxPoint(e.x + (e.width - sz.GetWidth()) / 2, + e.y + (e.height - sz.GetHeight()) / 2)); +} + +void EditorCanvas::DrawElement(const cgeditor::Element &e) { + dc->SetPen(wxNullPen); + dc->SetBrush(*wxRED_BRUSH); + if (e.prop & cgeditor::Property::Rectangle) { + if (e.prop & cgeditor::Property::Scrollbarbg) { + dc->SetBrush(*wxCYAN_BRUSH); + } else if (e.prop & cgeditor::Property::Scrollbar) { + dc->SetBrush(*wxGREY_BRUSH); + } else if (e.prop & cgeditor::Property::Margin) { + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + } else if (e.prop & cgeditor::Property::Button) { + if (e.prop & cgeditor::Property::On) { + dc->DrawBitmap(hide_icon, e.x, e.y); + return; + } + dc->SetBrush(*wxBLACK_BRUSH); + } + wxRect recToDraw(e.x, e.y, e.width, e.height); + dc->DrawRectangle(recToDraw); + } else if (e.prop & cgeditor::Property::Text || + e.prop & cgeditor::Property::Image) { + if (e.prop & cgeditor::Property::Image) { + // Draw your pieces images instead + std::uint32_t y = Middle(e).y - CGEditor::status.MoveIconWidth / 2; + char p = 'P'; + if (e.prop & cgeditor::Property::Knight) { + p = 'N'; + } else if (e.prop & cgeditor::Property::Bishop) { + p = 'B'; + } else if (e.prop & cgeditor::Property::Queen) { + p = 'Q'; + } else if (e.prop & cgeditor::Property::King) { + p = 'K'; + } + if (e.prop & cgeditor::Property::Black) { + p = std::tolower(p); + } + if (e.prop & cgeditor::Property::Current) { + wxRect recToDraw(e.x, e.y, e.width, e.height); + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + dc->DrawRectangle(recToDraw); + } + dc->DrawBitmap(*t.Get(p), e.x, y); + } else if (e.prop & cgeditor::Property::Comment) { + wxRect recToDraw(e.x, e.y, e.width, e.height); + dc->SetBrush(*wxYELLOW_BRUSH); + dc->DrawRectangle(recToDraw); + dc->DrawText(wxString(e.text), wxPoint(e.x, e.y)); + } else if (e.prop & cgeditor::Property::Menuitem) { + wxRect recToDraw(e.x, e.y, e.width, e.height); + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + dc->DrawRectangle(recToDraw); + dc->DrawText(wxString(e.text), wxPoint(e.x, Middle(e).y)); + } else { + if (e.prop & cgeditor::Property::Move) { + if (e.prop & cgeditor::Property::Current) { + wxRect recToDraw(e.x, e.y, e.width, e.height); + dc->SetBrush(*wxLIGHT_GREY_BRUSH); + dc->DrawRectangle(recToDraw); + } + if (CGEditor::status.UseMoveIcons) { + dc->DrawText(wxString(e.text), wxPoint(e.x, Middle(e).y)); + } else { + dc->DrawText(wxString(e.text), Middle(e)); + } + } else { + dc->DrawText(wxString(e.text), Middle(e)); + } + } + } +} +void EditorCanvas::HandleEvent(const cgeditor::Event &e) { + wxLogDebug("Editor event!"); + if (e.type == cgeditor::Event::Goto) { + wxCommandEvent event(GOTO_MOVE_EVENT, GetId()); + event.SetEventObject(this); + event.SetClientData(e.move); + ProcessEvent(event); + } else if (e.type == cgeditor::Event::Delete) { + wxCommandEvent event(DELETE_MOVE_EVENT, GetId()); + event.SetEventObject(this); + event.SetClientData(e.move); + ProcessEvent(event); + } else if (e.type == cgeditor::Event::Promote) { + wxCommandEvent event(PROMOTE_MOVE_EVENT, GetId()); + event.SetEventObject(this); + event.SetClientData(e.move); + ProcessEvent(event); + } else if (e.type == cgeditor::Event::SetAsMainline) { + wxCommandEvent event(SET_AS_MAINLINE_EVENT, GetId()); + event.SetEventObject(this); + event.SetClientData(e.move); + ProcessEvent(event); + } +} + +void EditorCanvas::MouseEvent(wxMouseEvent &event) { + if (event.Dragging()) { + CGEditor::status.LeftClick = false; + CGEditor::status.IsDrag = true; + Refresh(); + } else if (event.LeftDown()) { + SetFocus(); + CGEditor::status.LeftClick = true; + Refresh(); + } else if (event.RightDown()) { + SetFocus(); + CGEditor::status.RightClick = true; + Refresh(); + } else if (event.GetWheelRotation() != 0) { + SetFocus(); + if (event.GetWheelRotation() < 0) { + CGEditor::status.EventVScroll = 50; + } else { + CGEditor::status.EventVScroll = -50; + } + Refresh(); + } + + // Should another draw of CGEditor be made? + if (NeedRedraw) { + Refresh(); + NeedRedraw = false; + } +} + +void EditorCanvas::SetMoves(HalfMove *moves, HalfMove *current) { + CGEditor::status.Moves = moves; + CGEditor::status.CurrentMove = current; + + Refresh(); +} + +void EditorCanvas::OnKeyEvent(wxKeyEvent &event) { + if (event.GetKeyCode() == WXK_LEFT) { + wxCommandEvent previousEvent(PREVIOUS_MOVE_EVENT, GetId()); + previousEvent.SetEventObject(this); + ProcessEvent(previousEvent); + } else if (event.GetKeyCode() == WXK_RIGHT) { + wxCommandEvent nextEvent(NEXT_MOVE_EVENT, GetId()); + nextEvent.SetEventObject(this); + ProcessEvent(nextEvent); + } +} + +wxBEGIN_EVENT_TABLE(EditorCanvas, wxPanel) EVT_PAINT(EditorCanvas::OnPaint) + EVT_MOUSE_EVENTS(EditorCanvas::MouseEvent) + EVT_CHAR_HOOK(EditorCanvas::OnKeyEvent) wxEND_EVENT_TABLE() diff --git a/src/game_tab/editor/EditorCanvas.hpp b/src/game_tab/editor/EditorCanvas.hpp new file mode 100644 index 0000000..b3bb29f --- /dev/null +++ b/src/game_tab/editor/EditorCanvas.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "../HalfMove.hpp" +#include "CGEditor.hpp" +#include "ochess.hpp" +#include "../board/Theme.hpp" + +// Foreign events +wxDECLARE_EVENT(GOTO_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(PREVIOUS_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(NEXT_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(DELETE_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(PROMOTE_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(SET_AS_MAINLINE_EVENT, wxCommandEvent); + +class EditorCanvas : public wxPanel, public cgeditor::CGEditor { + wxPaintDC *dc; + bool NeedRedraw; + wxPoint Middle(cgeditor::Element e); + wxBitmap hide_icon; + Theme t; + +public: + EditorCanvas(wxFrame *parent); + void OnPaint(wxPaintEvent &event); + void MouseEvent(wxMouseEvent &event); + void DrawElement(const cgeditor::Element &e); + void HandleEvent(const cgeditor::Event &e); + void SetMoves(HalfMove *moves, HalfMove *current); + void OnKeyEvent(wxKeyEvent &event); + + DECLARE_EVENT_TABLE() +}; diff --git a/src/game_tab/editor/EditorPanel.cpp b/src/game_tab/editor/EditorPanel.cpp new file mode 100644 index 0000000..7abe09a --- /dev/null +++ b/src/game_tab/editor/EditorPanel.cpp @@ -0,0 +1,207 @@ +#include "EditorPanel.hpp" + +wxDEFINE_EVENT(GOTO_MOVE_EVENT, wxCommandEvent); +wxDEFINE_EVENT(DELETE_MOVE_EVENT, wxCommandEvent); +wxDEFINE_EVENT(PROMOTE_MOVE_EVENT, wxCommandEvent); +wxDEFINE_EVENT(SET_AS_MAINLINE_EVENT, wxCommandEvent); +wxDEFINE_EVENT(PREVIOUS_MOVE_EVENT, wxCommandEvent); +wxDEFINE_EVENT(NEXT_MOVE_EVENT, wxCommandEvent); + +EditorPanel::EditorPanel(wxFrame *parent, Game *game) + : wxPanel(parent), game(game), selected_item(-1) { + + // ----- Init ----- + wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL); + wxNotebook *notebook = new wxNotebook(this, wxID_ANY); + + //----- CGEditor Page ----- + wxPanel *cgeditor_panel = new wxPanel(notebook, wxID_ANY); + wxBoxSizer *cgeditor_panel_sizer = new wxBoxSizer(wxVERTICAL); + editor_canvas = new EditorCanvas((wxFrame *)cgeditor_panel); + cgeditor_panel_sizer->Add(editor_canvas, 1, wxEXPAND); + // Comment box + wxStaticBox *commentBox = + new wxStaticBox(cgeditor_panel, wxID_ANY, "Move Comment"); + wxBoxSizer *commentBoxSizer = new wxBoxSizer(wxVERTICAL); + comment_input = new wxTextCtrl( + commentBox, COMMENT_INPUT_BOX, wxEmptyString, // Use right ID + wxDefaultPosition, wxSize(0, 150), wxTE_MULTILINE); + commentBoxSizer->Add(comment_input, 0, wxEXPAND); + commentBox->SetSizer(commentBoxSizer); + cgeditor_panel_sizer->Add(commentBox, 0, wxEXPAND); + cgeditor_panel->SetSizer(cgeditor_panel_sizer); + + //----- Tags Page ----- + wxPanel *tag_panel = new wxPanel(notebook, wxID_ANY); + wxBoxSizer *tag_panel_sizer = new wxBoxSizer(wxVERTICAL); + wxPanel *tag_edit_panel = new wxPanel(tag_panel, wxID_ANY); + wxBoxSizer *tag_edit_panel_sizer = new wxBoxSizer(wxVERTICAL); + tagTextCtrl = new wxTextCtrl(tag_edit_panel, wxID_ANY); + tagTextCtrl->SetHint(wxString("Name")); + tag_edit_panel_sizer->Add(tagTextCtrl, 1, wxEXPAND); + valueTextCtrl = new wxTextCtrl(tag_edit_panel, wxID_ANY); + valueTextCtrl->SetHint(wxString("Value")); + tag_edit_panel_sizer->Add(valueTextCtrl, 1, wxEXPAND); + tag_edit_panel_sizer->Add(new wxButton(tag_edit_panel, UPDATE_BTN, L"Update"), + 1, wxEXPAND); + tag_edit_panel->SetSizer(tag_edit_panel_sizer); + tag_panel_sizer->Add(tag_edit_panel, 0, wxEXPAND); + tags_list = new wxListCtrl(tag_panel, wxID_ANY, wxDefaultPosition, + wxDefaultSize, wxLC_REPORT); + tags_list->InsertColumn(0, L"Name"); + tags_list->InsertColumn(1, L"Value", wxLIST_FORMAT_LEFT, 500); + RefreshTagsList(); + tag_panel_sizer->Add(tags_list, 1, wxEXPAND); + delete_button = new wxButton(tag_panel, DELETE_BTN, L"Delete"); + delete_button->Enable(false); + tag_panel_sizer->Add(delete_button, 0, wxEXPAND); + tag_panel->SetSizer(tag_panel_sizer); + + //----- Notebook ----- + notebook->AddPage(cgeditor_panel, L"Editor"); + notebook->AddPage(tag_panel, L"Tags"); + sizer->Add(notebook, 1, wxEXPAND); + + //----- Sizer ----- + this->SetSizer(sizer); + + // Bind events + this->Bind(wxEVT_TEXT, &EditorPanel::OnCommentChange, this, + COMMENT_INPUT_BOX); + this->Bind(GOTO_MOVE_EVENT, &EditorPanel::OnGotoMove, this, wxID_ANY); + this->Bind(DELETE_MOVE_EVENT, &EditorPanel::OnMoveDelete, this, wxID_ANY); + this->Bind(PROMOTE_MOVE_EVENT, &EditorPanel::OnMovePromote, this, wxID_ANY); + this->Bind(SET_AS_MAINLINE_EVENT, &EditorPanel::OnMoveSetAsMainline, this, + wxID_ANY); + this->Bind(NEXT_MOVE_EVENT, &EditorPanel::OnNextMove, this, wxID_ANY); + this->Bind(PREVIOUS_MOVE_EVENT, &EditorPanel::OnPreviousMove, this, wxID_ANY); + this->Bind(wxEVT_LIST_ITEM_SELECTED, &EditorPanel::OnTagSelected, this, + wxID_ANY); + this->Bind(wxEVT_LIST_ITEM_DESELECTED, &EditorPanel::OnTagDeselected, this, + wxID_ANY); + this->Bind(wxEVT_BUTTON, &EditorPanel::OnApply, this, UPDATE_BTN); + this->Bind(wxEVT_BUTTON, &EditorPanel::OnDelete, this, DELETE_BTN); +} + +void EditorPanel::OnTagSelected(wxListEvent &event) { + wxListItem item = event.GetItem(); + std::string key = item.GetText().ToStdString(); + tagTextCtrl->ChangeValue(key); + item.SetColumn(1); + tags_list->GetItem(item); + valueTextCtrl->ChangeValue(item.GetText().ToStdString()); + selected_item = item.GetId(); + delete_button->Enable(true); +} + +void EditorPanel::OnTagDeselected(wxListEvent &event) { + selected_item = -1; + delete_button->Enable(false); +} + +void EditorPanel::NotifyBoard() { + wxCommandEvent previousEvent(GAME_CHANGE, GetId()); + previousEvent.SetEventObject(this); + ProcessEvent(previousEvent); +} + +void EditorPanel::OnCommentChange(wxCommandEvent &event) { + wxLogDebug("EditorPanel: comment input change"); + HalfMove *m = game->GetCurrentMove(); + if (m != NULL) { + m->SetComment(event.GetString().ToStdString()); + } + editor_canvas->Refresh(); +} + +void EditorPanel::OnApply(wxCommandEvent &event) { + std::string key = tagTextCtrl->GetValue().ToStdString(); + if (key == "FEN") { + SHOW_DIALOG_ERROR("Editing the FEN tag is forbidden"); + return; + } + if (key.size() > 0) { + std::string value = valueTextCtrl->GetValue().ToStdString(); + game->SetTag(key, value); + RefreshTagsList(); + wxCommandEvent event(REFRESH_TAB_TITLE, GetId()); + event.SetEventObject(this); + ProcessEvent(event); + } +} + +void EditorPanel::OnDelete(wxCommandEvent &event) { + if (selected_item >= 0) { + wxListItem item; + item.SetColumn(0); + item.SetId(selected_item); + tags_list->GetItem(item); + std::string key = item.GetText().ToStdString(); + if (key != "FEN") { + game->DeleteTag(key); + selected_item = -1; + RefreshTagsList(); + } else { + SHOW_DIALOG_ERROR("Deleting the FEN tag is forbidden."); + } + } +} + +void EditorPanel::OnGotoMove(wxCommandEvent &event) { + wxLogDebug("EditorPanel: received GOTO_MOVE_EVENT"); + game->SetCurrent((HalfMove *)event.GetClientData()); + NotifyBoard(); + editor_canvas->Refresh(); +} + +void EditorPanel::OnMoveDelete(wxCommandEvent &event) { + game->DeleteMove((HalfMove *)event.GetClientData()); + NotifyBoard(); + editor_canvas->Refresh(); +} + +void EditorPanel::OnMovePromote(wxCommandEvent &event) { + wxLogDebug("EditorPanel: promote move called"); + game->PromoteMove((HalfMove *)event.GetClientData()); + NotifyBoard(); + editor_canvas->Refresh(); +} + +void EditorPanel::OnMoveSetAsMainline(wxCommandEvent &event) { + wxLogDebug("EditorPanel: set move as mainline called"); + game->SetMoveAsMainline((HalfMove *)event.GetClientData()); + NotifyBoard(); + editor_canvas->Refresh(); +} + +void EditorPanel::Notify() { + HalfMove *m = game->GetCurrentMove(); + if (m != NULL) { + comment_input->ChangeValue( + m->GetComment()); // ChangeValue do not raise events + } + editor_canvas->SetMoves(game->GetMoves(), m); +} + +void EditorPanel::RefreshTagsList() { + tags_list->DeleteAllItems(); + for (std::string s : game->ListTags()) { + long index = tags_list->InsertItem(0, s); + tags_list->SetItem(index, 1, game->GetTag(s)); + if (s == "FEN") { + tags_list->SetItemBackgroundColour(index, wxColour(200, 200, 200)); + } + } +} + +void EditorPanel::OnPreviousMove(wxCommandEvent &event) { + game->Previous(); + Notify(); + NotifyBoard(); +} + +void EditorPanel::OnNextMove(wxCommandEvent &event) { + game->Next(); + Notify(); + NotifyBoard(); +}
\ No newline at end of file diff --git a/src/game_tab/editor/EditorPanel.hpp b/src/game_tab/editor/EditorPanel.hpp new file mode 100644 index 0000000..0a7c0d0 --- /dev/null +++ b/src/game_tab/editor/EditorPanel.hpp @@ -0,0 +1,44 @@ +#include "../Game.hpp" +#include "EditorCanvas.hpp" +#include "ochess.hpp" +#include <wx/listctrl.h> +#include <wx/notebook.h> + +// Local events +wxDECLARE_EVENT(GOTO_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(DELETE_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(PROMOTE_MOVE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(SET_AS_MAINLINE_EVENT, wxCommandEvent); +wxDECLARE_EVENT(REFRESH_TAB_TITLE, wxCommandEvent); + +// Foreign events +wxDECLARE_EVENT(GAME_CHANGE, wxCommandEvent); + +enum { COMMENT_INPUT_BOX = wxID_HIGHEST + 100, UPDATE_BTN, DELETE_BTN }; + +class EditorPanel : public wxPanel { + Game *game; + EditorCanvas *editor_canvas; + wxTextCtrl *comment_input; + wxListCtrl *tags_list; + wxTextCtrl *tagTextCtrl, *valueTextCtrl; + wxButton *delete_button; + long selected_item; + +public: + EditorPanel(wxFrame *parent, Game *game); + void NotifyBoard(); + void Notify(); + void OnCommentChange(wxCommandEvent &event); + void OnGotoMove(wxCommandEvent &event); + void OnMoveDelete(wxCommandEvent &event); + void OnMovePromote(wxCommandEvent &event); + void OnMoveSetAsMainline(wxCommandEvent &event); + void RefreshTagsList(); + void OnTagSelected(wxListEvent &event); + void OnTagDeselected(wxListEvent &event); + void OnApply(wxCommandEvent &event); + void OnDelete(wxCommandEvent &event); + void OnPreviousMove(wxCommandEvent &event); + void OnNextMove(wxCommandEvent &event); +};
\ No newline at end of file |
