aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLoic Guegan <manzerbredes@mailbox.org>2022-02-12 19:13:34 +0100
committerLoic Guegan <manzerbredes@mailbox.org>2022-02-12 19:13:34 +0100
commita359219e33fdf3afb5ddfbb084563054a947b106 (patch)
tree91dab9c21321f73152993183cd6e8cf4a04017f8 /src
Create project
Diffstat (limited to 'src')
-rw-r--r--src/CGEHalfMove.cpp59
-rw-r--r--src/CGEHalfMove.hpp47
-rw-r--r--src/CGEditor.cpp99
-rw-r--r--src/CGEditor.hpp37
-rw-r--r--src/Types.cpp29
-rw-r--r--src/Types.hpp88
-rw-r--r--src/components/Component.hpp19
-rw-r--r--src/components/Margin.cpp30
-rw-r--r--src/components/Margin.hpp12
-rw-r--r--src/components/Menu.cpp64
-rw-r--r--src/components/Menu.hpp12
-rw-r--r--src/components/MoveTable.cpp280
-rw-r--r--src/components/MoveTable.hpp65
-rw-r--r--src/components/Scrollbar.cpp112
-rw-r--r--src/components/Scrollbar.hpp16
15 files changed, 969 insertions, 0 deletions
diff --git a/src/CGEHalfMove.cpp b/src/CGEHalfMove.cpp
new file mode 100644
index 0000000..7a5d095
--- /dev/null
+++ b/src/CGEHalfMove.cpp
@@ -0,0 +1,59 @@
+#include "CGEHalfMove.hpp"
+
+namespace cgeditor {
+
+CGEHalfMove::CGEHalfMove()
+ : MainLine(NULL), IsBlack(false), Number(1), Parent(NULL) {}
+
+CGEHalfMove::CGEHalfMove(CGEHalfMove *parent) {
+ CGEHalfMove();
+ Parent = parent;
+ Parent->MainLine = this;
+ if (parent->IsBlack) {
+ Number = parent->Number + 1;
+ IsBlack = false;
+ } else {
+ Number = parent->Number;
+ IsBlack = true;
+ }
+}
+
+CGEHalfMove::CGEHalfMove(std::string move)
+ : MainLine(NULL), IsBlack(false), Number(0), Parent(NULL) {
+ this->move = move;
+}
+
+void CGEHalfMove::SetComment(const std::string &c) {
+ if (c.size() > 0) {
+ NbLineComment = 1;
+ for (const char &c : c) {
+ if (c == '\n') {
+ NbLineComment++;
+ }
+ }
+ this->comment = c;
+ }
+}
+
+std::string CGEHalfMove::GetComment() { return (comment); }
+
+std::uint16_t CGEHalfMove::GetNbLineComment() { return (this->NbLineComment); }
+
+void CGEHalfMove::RemoveChild(CGEHalfMove *m) {
+ std::uint32_t i = 0;
+ bool found = false;
+ for (i; i < variations.size(); i++) {
+ if (variations[i] == m) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ variations.erase(variations.begin() + i);
+ }
+ if (MainLine == m) {
+ MainLine = NULL;
+ }
+}
+
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/CGEHalfMove.hpp b/src/CGEHalfMove.hpp
new file mode 100644
index 0000000..2ed7516
--- /dev/null
+++ b/src/CGEHalfMove.hpp
@@ -0,0 +1,47 @@
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace cgeditor {
+
+/**
+ * @brief Move (mainlines and variations) displayed in the editor
+ *
+ */
+class CGEHalfMove {
+ /// @brief Comment linked to the move
+ std::string comment;
+ /// @brief Number of line in @a comment
+ std::uint16_t NbLineComment = 0;
+
+public:
+ CGEHalfMove();
+ CGEHalfMove(CGEHalfMove *parent);
+ CGEHalfMove(std::string move);
+
+ /// @brief CUrrent move number
+ std::uint16_t Number;
+ /// @brief Current move value
+ std::string move;
+
+ CGEHalfMove *MainLine;
+ CGEHalfMove *Parent;
+ bool IsBlack;
+ /// @brief Says if variations of that move must be drawn
+ bool Folded = false;
+ /// @brief Says if this move must be drawn
+ bool Hide = false;
+ /// @brief Variations of the move
+ std::vector<CGEHalfMove *> variations;
+
+ /// @brief Set comment and update number of lines
+ void SetComment(const std::string &c);
+ /// @brief Get current value of comment
+ std::string GetComment();
+ /// @brief Get number of lines in comment
+ std::uint16_t GetNbLineComment();
+ /// @brief Remove a move from the MainLine and/or variations
+ void RemoveChild(CGEHalfMove *m);
+};
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/CGEditor.cpp b/src/CGEditor.cpp
new file mode 100644
index 0000000..fc7c567
--- /dev/null
+++ b/src/CGEditor.cpp
@@ -0,0 +1,99 @@
+#include "CGEditor.hpp"
+
+namespace cgeditor {
+
+CGEditor::CGEditor() {
+ SBV = new Scrollbar(&status, false);
+ SBH = new Scrollbar(&status, true);
+ MT = new MoveTable(&status);
+ MA = new Margin(&status);
+ ME = new Menu(&status);
+}
+
+CGEditor::~CGEditor() {
+ delete SBV;
+ delete SBH;
+ delete MT;
+ delete MA;
+ delete ME;
+}
+
+void CGEditor::Draw() {
+ bool ShoudUpdateMouse = false;
+ if (status.LeftClick || status.RightClick) {
+ ShoudUpdateMouse = true;
+ }
+
+ // Should be refreshed before Scrollbar!
+ // To update status.MoveTableMaxX and status.MoveTableMaxY
+ MA->Refresh();
+ MT->Refresh();
+ MA->DrawMargin(MT->GetVariationsMarging());
+ SBV->Refresh();
+ SBH->Refresh();
+ ME->Refresh();
+
+ // Order matter
+ DrawComponent(MA);
+ DrawComponent(MT);
+ DrawComponent(SBV);
+ DrawComponent(SBH);
+ DrawComponent(ME);
+
+ // Handle events
+ for (Event &e : status.Events) {
+ HandleEvent(e);
+ }
+ status.Events.clear();
+
+ // Update mouse events
+ status.LeftClick = false;
+ status.RightClick = false;
+ status.IsDrag = false;
+ if (ShoudUpdateMouse) {
+ status.LastMouseClicX = status.MouseX;
+ status.LastMouseClicY = status.MouseY;
+ }
+}
+
+void CGEditor::CallDrawElement(Element e) {
+ // For element that want to expands up to the edge
+ if (e.width < 0) {
+ if (e.ShouldApplyScroll) {
+ e.width =
+ status.CanvasWidth - status.ScrollbarWidth - (e.x + status.ScrollX);
+ } else {
+ e.width = status.CanvasWidth - status.ScrollbarWidth - e.x;
+ }
+ if (e.width < 0) {
+ e.width *= -1;
+ }
+ }
+
+ // Apply scroll
+ if (e.ShouldApplyScroll) {
+ e.x += status.ScrollX;
+ if (!e.IgnoreScrollY) {
+ e.y += status.ScrollY;
+ }
+ }
+
+ // Check if element is visible
+ if (((e.x) >= 0 && ((e.x) <= status.CanvasWidth) && (e.y) >= 0 &&
+ ((e.y) <= status.CanvasHeight)) ||
+ ((e.x + e.width) >= 0 && ((e.x + e.width) <= status.CanvasWidth) &&
+ (e.y + e.height) >= 0 && ((e.y + e.height) <= status.CanvasHeight))) {
+ if (e.IsOver(status.MouseX, status.MouseY)) {
+ e.prop |= Property::Mouseover;
+ }
+ DrawElement(e);
+ }
+}
+
+void CGEditor::DrawComponent(Component *c) {
+ for (Element &e : c->GetElements()) {
+ CallDrawElement(e);
+ }
+}
+
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/CGEditor.hpp b/src/CGEditor.hpp
new file mode 100644
index 0000000..d30d05e
--- /dev/null
+++ b/src/CGEditor.hpp
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "Types.hpp"
+#include "components/Margin.hpp"
+#include "components/Menu.hpp"
+#include "components/MoveTable.hpp"
+#include "components/Scrollbar.hpp"
+
+#include <string>
+
+namespace cgeditor {
+
+class CGEditor {
+ /// @brief Prepare element for drawing and draw
+ void CallDrawElement(Element);
+ /// @brief Draw all elements of a component
+ void DrawComponent(Component *);
+
+ Scrollbar *SBV, *SBH;
+ MoveTable *MT;
+ Margin *MA;
+ Menu *ME;
+
+protected:
+ Status status;
+ ///@brief Draw the Chess Game Editor on the canvas using current status
+ void Draw();
+ /// @brief Draw an element on the canvas
+ virtual void DrawElement(const Element &) = 0;
+ /// @brief Handle event that occured during editor drawing
+ virtual void HandleEvent(const Event &) = 0;
+
+public:
+ CGEditor();
+ ~CGEditor();
+};
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/Types.cpp b/src/Types.cpp
new file mode 100644
index 0000000..76bf398
--- /dev/null
+++ b/src/Types.cpp
@@ -0,0 +1,29 @@
+#include "Types.hpp"
+
+namespace cgeditor {
+
+bool Element::IsOver(const double &X, const double &Y) const {
+ if (width < 0) {
+ return (x >= x && Y >= y && Y <= (y + height));
+ }
+ return ((X >= x && X <= (x + width) && Y >= y && Y <= (y + height)));
+}
+
+Property operator|(Property lhs, Property rhs) {
+ return static_cast<Property>(
+ static_cast<std::underlying_type_t<Property>>(lhs) |
+ static_cast<std::underlying_type_t<Property>>(rhs));
+}
+
+bool operator&(Property lhs, Property rhs) {
+ return (static_cast<std::underlying_type_t<Property>>(lhs) &
+ static_cast<std::underlying_type_t<Property>>(rhs));
+}
+
+Property &operator|=(Property &lhs, Property rhs) {
+ return lhs = static_cast<Property>(
+ static_cast<std::underlying_type_t<Property>>(lhs) |
+ static_cast<std::underlying_type_t<Property>>(rhs));
+}
+
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/Types.hpp b/src/Types.hpp
new file mode 100644
index 0000000..db8b6f3
--- /dev/null
+++ b/src/Types.hpp
@@ -0,0 +1,88 @@
+#pragma once
+
+#include "CGEHalfMove.hpp"
+#include <string>
+
+namespace cgeditor {
+
+enum class Property : std::uint32_t {
+ None = 0,
+ Image = 1,
+ Rectangle = 1 << 1,
+ Text = 1 << 2,
+ On = 1 << 3,
+ Move = 1 << 4,
+ Margin = 1 << 5,
+ Menuitem = 1 << 6, // Is it a menu item
+ Comment = 1 << 7,
+ Black = 1 << 8, // Is it a move for black
+ Scrollbar = 1 << 9,
+ Horizontal = 1 << 10, // Is it an horizontal scrollbar
+ Scrollbarbg = 1 << 11, // Is it the background of the scrollbar
+ Button = 1 << 12, // Is it a button
+ Dots = 1 << 13, // Move dots
+ Movenumber = 1 << 14,
+ Current = 1 << 15,
+ Mouseover = 1 << 16 // Set on every element where mouse is over
+};
+Property operator|(Property lhs, Property rhs);
+Property &operator|=(Property &lhs, Property rhs);
+bool operator&(Property lhs, Property rhs);
+
+class Element {
+public:
+ Property prop = Property::None;
+ std::string text;
+ double x, y;
+ double width, height;
+ /// @brief Should element be scrolled
+ bool ShouldApplyScroll = false;
+ /// @brief For margin bar to avoid scrolling it vertically
+ bool IgnoreScrollY = false;
+ /// @brief Check if a given point is over the element
+ bool IsOver(const double &X, const double &Y) const;
+};
+
+typedef struct Event {
+ enum Type { CommentSelected, Promote, Delete, SetAsMainline, Goto, None };
+ Type type = None;
+ /// @brief Move related to the event
+ CGEHalfMove *move = NULL;
+} Event;
+
+/**
+ * @brief Chess Game Editor status
+ * Various parameters that can be tuned by the user.
+ * The user should manually set mouse event boolean
+ * for the editor to work properly
+ */
+typedef struct Status {
+ double MouseX = 0, MouseY = 0;
+ double LastMouseClicX = 0, LastMouseClicY = 0;
+ double CanvasWidth, CanvasHeight;
+ double MenuItemWidth = 150, MenuItemHeight = 50;
+ double MoveWidth = 100, MoveHeight = 50;
+ double MarginBarWidth = 50;
+ double ScrollbarWidth = 30;
+ double MenuX, MenuY;
+ double MoveX, MoveY;
+ std::uint16_t CommentLinePerRow = 2;
+ /// @brief Ask the editor to scroll for a specific amout of pixels
+ double EventVScroll = 0, EventHScroll = 0;
+ /// @brief Amount of pixel to scroll elements
+ double ScrollX = 0, ScrollY = 0;
+ /// @brief Set according to mouse events
+ bool LeftClick, RightClick;
+ /// @brief Can be use to close the menu
+ bool IsMenuOpen = false;
+ bool UseMoveImages = false;
+ double MoveTableMaxX = 0, MoveTableMaxY = 0;
+ /// @brief User should set it to true when mouse is dragging
+ bool IsDrag = false;
+ CGEHalfMove *Moves = NULL;
+ CGEHalfMove *CurrentMove = NULL;
+ CGEHalfMove *SelectedMove = NULL;
+ std::vector<Event> Events;
+} Status;
+
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/Component.hpp b/src/components/Component.hpp
new file mode 100644
index 0000000..204ee47
--- /dev/null
+++ b/src/components/Component.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include "Types.hpp"
+#include <vector>
+
+namespace cgeditor {
+
+class Component {
+protected:
+ Status *status;
+ std::vector<Element> elements;
+
+public:
+ Component(Status *s) : status(s){};
+ std::vector<Element> GetElements() { return (this->elements); }
+ virtual void Refresh() = 0;
+};
+
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/Margin.cpp b/src/components/Margin.cpp
new file mode 100644
index 0000000..077cce5
--- /dev/null
+++ b/src/components/Margin.cpp
@@ -0,0 +1,30 @@
+#include "Margin.hpp"
+
+namespace cgeditor {
+
+Margin::Margin(Status *s) : Component(s) {}
+
+void Margin::Refresh() {
+ elements.clear();
+ Element e;
+ e.x = 0;
+ e.y = 0;
+ e.height = status->CanvasHeight - status->ScrollbarWidth;
+ e.ShouldApplyScroll = true;
+ e.IgnoreScrollY = true;
+ DrawMargin(e);
+}
+
+void Margin::DrawMargin(Element e) {
+ e.prop=Property::Margin | Property::Rectangle;
+ e.width = status->MarginBarWidth;
+ elements.push_back(e);
+}
+
+void Margin::DrawMargin(std::vector<Element> elts) {
+ for(Element &e:elts){
+ DrawMargin(e);
+ }
+}
+
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/Margin.hpp b/src/components/Margin.hpp
new file mode 100644
index 0000000..8c2f243
--- /dev/null
+++ b/src/components/Margin.hpp
@@ -0,0 +1,12 @@
+#include "Component.hpp"
+
+namespace cgeditor {
+class Margin : public Component {
+
+public:
+ Margin(Status *s);
+ void Refresh();
+ void DrawMargin(Element e);
+ void DrawMargin(std::vector<Element> elts);
+};
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/Menu.cpp b/src/components/Menu.cpp
new file mode 100644
index 0000000..36d1d1a
--- /dev/null
+++ b/src/components/Menu.cpp
@@ -0,0 +1,64 @@
+#include "Menu.hpp"
+
+namespace cgeditor {
+
+Menu::Menu(Status *s) : Component(s), WasOpen(false) {
+ entries.push_back("Delete from here");
+ entries.push_back("Promote");
+ entries.push_back("Set as main line");
+}
+void Menu::Refresh() {
+ if (WasOpen && (status->LeftClick || status->RightClick)) {
+ char i = 0;
+ for (Element &e : elements) {
+ if (e.IsOver(status->MouseX, status->MouseY)) {
+ if (i == 0) {
+ status->Events.push_back({Event::Type::Delete,status->SelectedMove});
+ } else if (i == 1) {
+ status->Events.push_back({Event::Type::Promote,status->SelectedMove});
+ } else if (i == 2) {
+ status->Events.push_back({Event::Type::SetAsMainline,status->SelectedMove});
+ }
+ }
+ i++;
+ }
+ status->IsMenuOpen = false;
+ WasOpen = false;
+ elements.clear();
+ return;
+ }
+
+ elements.clear();
+ // Draw menu backward to avoid getting out of the canvas
+ bool backwardY =
+ (status->MouseY + status->MenuItemHeight * entries.size()) >=
+ status->CanvasHeight;
+ bool backwardX =
+ (status->MouseX + status->MenuItemWidth) >= status->CanvasWidth;
+
+ if (status->IsMenuOpen) {
+ char i = 0;
+ for (std::string &en : entries) {
+ Element e;
+ e.prop=Property::Text|Property::Menuitem;
+ e.text = en;
+ if (backwardX) {
+ e.x = (status->MouseX - status->MenuItemWidth);
+ } else {
+ e.x = status->MouseX;
+ }
+ if (backwardY) {
+ e.y = (status->MouseY - status->MenuItemHeight) -
+ i * status->MenuItemHeight;
+ } else {
+ e.y = status->MouseY + i * status->MenuItemHeight;
+ }
+ e.width = status->MenuItemWidth;
+ e.height = status->MenuItemHeight;
+ elements.push_back(e);
+ i++;
+ }
+ WasOpen = true;
+ }
+}
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/Menu.hpp b/src/components/Menu.hpp
new file mode 100644
index 0000000..ca33ac3
--- /dev/null
+++ b/src/components/Menu.hpp
@@ -0,0 +1,12 @@
+#include "Component.hpp"
+
+namespace cgeditor {
+class Menu : public Component {
+ std::vector<std::string> entries;
+ /// @brief Set to true if the menu was open during the last editor draw
+ bool WasOpen;
+public:
+ Menu(Status *s);
+ void Refresh();
+};
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/MoveTable.cpp b/src/components/MoveTable.cpp
new file mode 100644
index 0000000..e7f7af3
--- /dev/null
+++ b/src/components/MoveTable.cpp
@@ -0,0 +1,280 @@
+#include "MoveTable.hpp"
+
+namespace cgeditor {
+
+MoveTable::MoveTable(Status *s) : Component(s) {
+ ImageWidth = status->MoveWidth * 0.25; // Image is 25% of the cell
+}
+void MoveTable::Refresh() {
+ status->MoveTableMaxX = 0;
+ status->MoveTableMaxY = 0;
+ elements.clear();
+ VariationMargins.clear();
+ CurrentMove = -1; // No current move by default
+ if (status->Moves != NULL) {
+ UpdateMoves(status->Moves, 0, 0, status->Moves->IsBlack);
+ // We only set the type after the call to UpdateMoves()
+ // This way only a single move will be the current move
+ if (CurrentMove >= 0) {
+ if (status->UseMoveImages) {
+ elements[CurrentMove].prop |= Property::Current;
+ elements[CurrentMove + 1].prop |= Property::Current;
+ } else {
+ elements[CurrentMove].prop |= Property::Current;
+ }
+ }
+ } else {
+ Element e;
+ e.prop = Property::Text;
+ e.text = "No move to show";
+ e.x = status->MarginBarWidth;
+ e.y = 0;
+ e.height = status->MoveHeight;
+ e.width = -1;
+ elements.push_back(e);
+ }
+}
+
+bool MoveTable::IsMouseOver(const Element &e) const {
+ // Check if we clicked on scroll bars
+ if (status->IsMenuOpen ||
+ status->MouseX >= (status->CanvasWidth - status->ScrollbarWidth) ||
+ status->MouseY >= (status->CanvasHeight - status->ScrollbarWidth)) {
+ return (false);
+ }
+ return (e.IsOver(status->MouseX - status->ScrollX,
+ status->MouseY - status->ScrollY));
+}
+
+std::uint32_t MoveTable::UpdateMoves(CGEHalfMove *m, std::uint32_t line,
+ std::uint32_t indent, bool only_black) {
+
+ //---------- Check black or white ----------
+ char indent_black = 0;
+ if (m->IsBlack) {
+ indent_black++;
+ }
+
+ //---------- Create temporary move surrounding area ----------
+ Element move_bound;
+ move_bound.prop = Property::Move;
+ if (m->IsBlack) {
+ move_bound.prop |= Property::Black;
+ }
+ move_bound.x = status->MarginBarWidth + status->MoveX +
+ status->MoveWidth * (indent + indent_black) +
+ ((indent + 1) / 2 * status->MarginBarWidth);
+ move_bound.y = status->MoveHeight * line;
+ move_bound.width = status->MoveWidth;
+ move_bound.height = status->MoveHeight;
+ move_bound.text = m->move;
+ move_bound.ShouldApplyScroll = true;
+ bool isMouseOver = IsMouseOver(move_bound);
+
+ //---------- Update current focus move ----------
+ if (isMouseOver) {
+ if (status->LeftClick) {
+ if (!status->IsMenuOpen) {
+ status->Events.push_back({Event::Type::Goto, m});
+ status->CurrentMove = m;
+ }
+ } else if (status->RightClick) {
+ status->IsMenuOpen = true;
+ status->SelectedMove = m;
+ }
+ }
+
+ //---------- Check if current move is focused ----------
+ if (status->CurrentMove == m) {
+ CurrentMove = elements.size();
+ }
+
+ //---------- Draw move ----------
+ if (status->UseMoveImages) {
+ // Image
+ Element img;
+ img.prop = Property::Image | Property::Move;
+ img.x = move_bound.x;
+ img.y = status->MoveHeight * line;
+ img.width = ImageWidth;
+ img.height = status->MoveHeight;
+ img.ShouldApplyScroll = true;
+ elements.push_back(img);
+ // Move
+ Element e;
+ e.prop = move_bound.prop | Property::Text;
+ e.text = m->move;
+ e.x = ImageWidth + move_bound.x;
+ e.y = status->MoveHeight * line;
+ e.width = status->MoveWidth - ImageWidth;
+ e.height = status->MoveHeight;
+ e.ShouldApplyScroll = true;
+ elements.push_back(e);
+ } else {
+ move_bound.prop |= Property::Text;
+ elements.push_back(move_bound);
+ }
+
+ //---------- Move number in marge or for variation ----------
+ if (indent == 0 && (!m->IsBlack || only_black)) {
+ DRAW_NB(0, status->MoveHeight * line, m->Number);
+ } else if (indent > 0 && (!m->IsBlack || only_black)) {
+ if (only_black) {
+ DRAW_NB_VAR((move_bound.x - status->MoveWidth) - status->MarginBarWidth,
+ status->MoveHeight * line, m->Number);
+ } else {
+ DRAW_NB_VAR(move_bound.x - ((indent + 1) / 2 * status->MarginBarWidth),
+ status->MoveHeight * line, m->Number);
+ }
+ }
+
+ //---------- Draw dots ----------
+ if (only_black) {
+ DRAW_DOTS(move_bound.x - status->MoveWidth, move_bound.y);
+ }
+
+ //---------- Scrolling infos ----------
+ if ((move_bound.x + move_bound.width) > status->MoveTableMaxX) {
+ status->MoveTableMaxX = move_bound.x + move_bound.width;
+ }
+ if ((move_bound.y + move_bound.height) > status->MoveTableMaxY) {
+ status->MoveTableMaxY = move_bound.y + move_bound.height;
+ }
+
+ //---------- Comments ----------
+ if (m->GetNbLineComment() > 0) {
+ line = DrawComment(m, line, indent, move_bound, indent_black);
+ }
+
+ //---------- Variations ----------
+ if (m->variations.size() > 0) {
+ line = DrawVariations(m, line, indent, move_bound, indent_black);
+ }
+
+ //---------- Mainline ----------
+ if (m->MainLine != NULL) {
+ only_black = (m->MainLine->IsBlack &&
+ (m->GetNbLineComment() > 0 || m->variations.size()));
+ if (m->IsBlack) {
+ line = UpdateMoves(m->MainLine, line + 1, indent, only_black);
+ } else {
+ line = UpdateMoves(m->MainLine, line, indent, only_black);
+ }
+ }
+
+ return (line);
+}
+
+std::uint32_t MoveTable::DrawComment(CGEHalfMove *m, std::uint32_t line,
+ std::uint32_t indent,
+ const Element &move_bound,
+ const char &indent_black) {
+ // Show three dots
+ if (!m->IsBlack) {
+ DRAW_DOTS(status->MarginBarWidth + status->MoveX +
+ status->MoveWidth * (indent + 1) +
+ ((indent + 1) / 2 * status->MarginBarWidth),
+ status->MoveHeight * line);
+ }
+ // Print comment
+ line++;
+ // Computer number of rows for the comment
+ std::uint16_t CommentRows = m->GetNbLineComment() / status->CommentLinePerRow;
+ if (m->GetNbLineComment() % status->CommentLinePerRow > 0) {
+ CommentRows++;
+ }
+ // Draw comment
+ Element e;
+ e.prop = Property::Text | Property::Comment;
+ e.x = move_bound.x -
+ (indent_black *
+ status->MoveWidth); // status->MarginBarWidth + status->MoveX;
+ e.y = status->MoveHeight * line;
+ e.width = -1; // Negative width means expands to the edge of the canvas
+ e.height = CommentRows * status->MoveHeight;
+ e.text = m->GetComment();
+ e.ShouldApplyScroll = true;
+ elements.push_back(e);
+ // Do not forget to add marging before comment
+ if (indent > 0) {
+ e.x -= status->MarginBarWidth;
+ VariationMargins.push_back(e);
+ }
+ if (status->LeftClick && IsMouseOver(e)) {
+ status->Events.push_back({Event::Type::CommentSelected, m});
+ }
+ line += CommentRows; // Skip right amount of lines
+ // Since we already increment line for black later on:
+ if (m->IsBlack || m->variations.size() > 0) {
+ line--;
+ }
+
+ return (line);
+}
+
+std::uint32_t MoveTable::DrawVariations(CGEHalfMove *m, std::uint32_t line,
+ std::uint32_t indent,
+ const Element &move_bound,
+ const char &indent_black) {
+ // Show three dots next to move if white turn
+ if ((m->variations.size() == 0) && !m->IsBlack) {
+ DRAW_DOTS(status->MarginBarWidth + status->MoveX +
+ status->MoveWidth * (indent + 1),
+ status->MoveHeight * line);
+ }
+ // Show button on the right side of the move
+ {
+ Element e;
+ e.prop = Property::Rectangle | Property::Button;
+ e.x = move_bound.x + status->MoveWidth;
+ if (!m->IsBlack)
+ e.x += status->MoveWidth;
+ e.y = move_bound.y + std::ceil(status->MoveHeight / 4);
+ e.width = std::ceil(status->MoveHeight / 2);
+ e.height = e.width;
+ e.ShouldApplyScroll = true;
+ if (status->LeftClick && IsMouseOver(e)) {
+ m->Folded = !m->Folded;
+ }
+ if (!m->Folded) {
+ e.prop |= Property::On;
+ }
+ elements.push_back(e);
+ }
+ if (!m->Folded) {
+ for (CGEHalfMove *v : m->variations) {
+ // For each variation show show/hide button
+ {
+ Element e;
+ e.prop = Property::Rectangle | Property::Button;
+ e.x = (status->MarginBarWidth + status->MoveX +
+ status->MoveWidth * indent) +
+ status->MoveWidth - std::ceil(status->MoveHeight / 2) -
+ std::ceil(status->MoveHeight / 4);
+ e.y = (status->MoveHeight * (line + 1)) +
+ std::ceil(status->MoveHeight / 4);
+ e.width = std::ceil(status->MoveHeight / 2);
+ e.height = e.width;
+ e.ShouldApplyScroll = true;
+ if (status->LeftClick && IsMouseOver(e)) {
+ v->Hide = !v->Hide;
+ }
+ if (!v->Hide) {
+ e.prop |= Property::On;
+ }
+ elements.push_back(e);
+ }
+ if (!v->Hide) {
+ line = UpdateMoves(v, line + 1, indent + 1, v->IsBlack);
+ } else {
+ line++;
+ }
+ }
+ }
+ // New line after variation
+ if (m->MainLine != NULL && m->MainLine->IsBlack) {
+ line++;
+ }
+ return (line);
+}
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/MoveTable.hpp b/src/components/MoveTable.hpp
new file mode 100644
index 0000000..d220f5e
--- /dev/null
+++ b/src/components/MoveTable.hpp
@@ -0,0 +1,65 @@
+#include "Component.hpp"
+#include <cmath>
+
+#define IS_VISIBLE(e) \
+ (((e.x + status->ScrollX) >= 0 && \
+ ((e.x + status->ScrollX) <= status->CanvasWidth) && \
+ (e.y + status->ScrollY) >= 0 && \
+ ((e.y + status->ScrollY) <= status->CanvasHeight)) || \
+ ((e.x + e.width + status->ScrollX) >= 0 && \
+ ((e.x + e.width + status->ScrollX) <= status->CanvasWidth) && \
+ (e.y + e.height + status->ScrollY) >= 0 && \
+ ((e.y + e.height + status->ScrollY) <= status->CanvasHeight)))
+
+#define DRAW_DOTS(XX, YY) \
+ { \
+ Element e; \
+ e.prop = Property::Text | Property::Dots; \
+ e.x = (XX); \
+ e.y = (YY); \
+ e.width = status->MoveWidth; \
+ e.height = status->MoveHeight; \
+ e.text = "..."; \
+ e.ShouldApplyScroll = true; \
+ elements.push_back(e); \
+ }
+
+#define DRAW_NB_(XX, YY, NB) \
+ Element ln; \
+ ln.prop = Property::Text | Property::Movenumber; \
+ ln.text = std::to_string(NB); \
+ ln.x = (XX); \
+ ln.y = (YY); \
+ ln.width = status->MarginBarWidth; \
+ ln.height = status->MoveHeight; \
+ ln.ShouldApplyScroll = true; \
+ elements.push_back(ln);
+
+#define DRAW_NB(XX, YY, NB) \
+ { DRAW_NB_(XX, YY, NB); }
+
+#define DRAW_NB_VAR(XX, YY, NB) \
+ { \
+ DRAW_NB_(XX, YY, NB); \
+ VariationMargins.push_back(ln); \
+ }
+
+namespace cgeditor {
+class MoveTable : public Component {
+ std::uint32_t UpdateMoves(CGEHalfMove *, std::uint32_t, std::uint32_t,bool only_black);
+ std::int32_t CurrentMove;
+ double ImageWidth;
+ std::vector<Element> VariationMargins;
+ bool IsMouseOver(const Element &e) const;
+ std::uint32_t DrawComment(CGEHalfMove *m, std::uint32_t line, std::uint32_t indent,
+ const Element &move_bound, const char &indent_black);
+ std::uint32_t DrawVariations(CGEHalfMove *m, std::uint32_t line, std::uint32_t indent,
+ const Element &move_bound,
+ const char &indent_black);
+
+public:
+ MoveTable(Status *s);
+ void Refresh();
+ std::vector<Element> GetVariationsMarging() { return (VariationMargins); }
+};
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/Scrollbar.cpp b/src/components/Scrollbar.cpp
new file mode 100644
index 0000000..76c5c1a
--- /dev/null
+++ b/src/components/Scrollbar.cpp
@@ -0,0 +1,112 @@
+#include "Scrollbar.hpp"
+
+namespace cgeditor {
+Scrollbar::Scrollbar(Status *s, bool IsHorizontal) : Component(s) {
+ this->IsHorizontal = IsHorizontal;
+
+ bg.prop = Property::Rectangle | Property::Scrollbarbg;
+ bar.prop = Property::Rectangle | Property::Scrollbar;
+
+ if (IsHorizontal) {
+ bg.prop |= Property::Horizontal;
+ bar.x = 0;
+ } else {
+ bar.y = 0;
+ }
+
+ DragX = 0;
+ DragY = 0;
+ Trigger = false;
+}
+
+void Scrollbar::Refresh() {
+ if (IsHorizontal) {
+ bg.y = status->CanvasHeight - status->ScrollbarWidth;
+ bg.width = status->CanvasWidth - status->ScrollbarWidth;
+ bg.height = status->ScrollbarWidth;
+ bar.y = bg.y;
+ } else {
+ bg.x = status->CanvasWidth - status->ScrollbarWidth;
+ bg.width = status->CanvasWidth;
+ bg.height = status->CanvasHeight - status->ScrollbarWidth;
+ bar.x = bg.x;
+ }
+
+ bar.width = bg.width;
+ bar.height = bg.height;
+
+ // Compute move table canvas
+ double MTCanvasHeight = status->CanvasHeight - status->ScrollbarWidth;
+ double MTCanvasWidth = status->CanvasWidth - status->ScrollbarWidth;
+
+ bool shouldScroll = false;
+ if (!IsHorizontal && status->MoveTableMaxY > MTCanvasHeight) {
+ bar.height =
+ std::ceil(bg.height * (MTCanvasHeight / status->MoveTableMaxY));
+ shouldScroll = true;
+ }
+
+ if (IsHorizontal && status->MoveTableMaxX > MTCanvasWidth) {
+ bar.width = std::ceil(bg.width * (MTCanvasWidth / status->MoveTableMaxX));
+ shouldScroll = true;
+ }
+
+ if (shouldScroll) {
+ if (IsHorizontal && status->EventHScroll != 0) {
+ double percent = status->EventHScroll / status->MoveTableMaxX;
+ double maxScroll = bg.width - bar.width;
+ bar.x += maxScroll * percent;
+ bar.x = std::max(bg.x, bar.x);
+ bar.x = std::min(bg.x + maxScroll, bar.x);
+ double curScroll = bar.x - bg.x;
+ double scrollPercent = curScroll / maxScroll;
+ status->ScrollX =
+ -(status->MoveTableMaxX - MTCanvasWidth) * scrollPercent;
+ status->EventHScroll = 0;
+ } else if (status->EventVScroll != 0) {
+ double percent = status->EventVScroll / status->MoveTableMaxY;
+ double maxScroll = bg.height - bar.height;
+ bar.y += maxScroll * percent;
+ bar.y = std::max(bg.y, bar.y);
+ bar.y = std::min(bg.y + maxScroll, bar.y);
+ double curScroll = bar.y - bg.y;
+ double scrollPercent = curScroll / maxScroll;
+ status->ScrollY =
+ -(status->MoveTableMaxY - MTCanvasHeight) * scrollPercent;
+ status->EventVScroll = 0;
+ } else if (status->LeftClick &&
+ bar.IsOver(status->MouseX, status->MouseY)) {
+ DragX = bar.x;
+ DragY = bar.y;
+ Trigger = true;
+ } else if (Trigger && status->IsDrag) {
+ if (IsHorizontal) {
+ bar.x = DragX + (status->MouseX - status->LastMouseClicX);
+ bar.x = std::max(bg.x, bar.x);
+ double maxScroll = bg.width - bar.width;
+ bar.x = std::min(bg.x + maxScroll, bar.x);
+ double curScroll = bar.x - bg.x;
+ double scrollPercent = curScroll / maxScroll;
+ status->ScrollX =
+ -(status->MoveTableMaxX - MTCanvasWidth) * scrollPercent;
+ } else {
+ bar.y = DragY + (status->MouseY - status->LastMouseClicY);
+ bar.y = std::max(bg.y, bar.y);
+ double maxScroll = bg.height - bar.height;
+ bar.y = std::min(bg.y + maxScroll, bar.y);
+ double curScroll = bar.y - bg.y;
+ double scrollPercent = curScroll / maxScroll;
+ status->ScrollY =
+ -(status->MoveTableMaxY - MTCanvasHeight) * scrollPercent;
+ }
+ } else {
+ Trigger = false;
+ }
+ }
+
+ elements.clear();
+ elements.push_back(bg);
+ elements.push_back(bar);
+}
+
+} // namespace cgeditor \ No newline at end of file
diff --git a/src/components/Scrollbar.hpp b/src/components/Scrollbar.hpp
new file mode 100644
index 0000000..3ed97ae
--- /dev/null
+++ b/src/components/Scrollbar.hpp
@@ -0,0 +1,16 @@
+#include "Component.hpp"
+#include <algorithm> // std::max
+#include <cmath>
+
+namespace cgeditor {
+class Scrollbar : public Component {
+ /// @brief Set to true if it is the horizontal scrollbar
+ bool IsHorizontal;
+ Element bg,bar;
+ double DragY,DragX;
+ bool Trigger;
+public:
+ Scrollbar(Status* s,bool IsHorizontal);
+ void Refresh();
+};
+} // namespace cgeditor \ No newline at end of file