summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
authorLeo Tenenbaum <pommicket@gmail.com>2018-08-20 20:34:57 -0400
committerLeo Tenenbaum <pommicket@gmail.com>2018-08-20 20:34:57 -0400
commita4460f6d9453bbd7e584937686449cef3e19f052 (patch)
tree037c208f1e20302ed048c0952ef8e3418add9c86 /src/gui
Initial commit0.0.0
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/button.cpp191
-rw-r--r--src/gui/button.hpp83
-rw-r--r--src/gui/colors.cpp101
-rw-r--r--src/gui/colors.hpp55
-rw-r--r--src/gui/gui.hpp28
-rw-r--r--src/gui/menu.cpp74
-rw-r--r--src/gui/menu.hpp53
-rw-r--r--src/gui/position.cpp107
-rw-r--r--src/gui/position.hpp73
-rw-r--r--src/gui/window.hpp181
-rw-r--r--src/gui/window_events.cpp112
-rw-r--r--src/gui/window_events_keyboard.cpp107
-rw-r--r--src/gui/window_events_keyboard.hpp32
-rw-r--r--src/gui/window_events_mouse.cpp166
-rw-r--r--src/gui/window_events_mouse.hpp37
-rw-r--r--src/gui/window_main.cpp102
-rw-r--r--src/gui/window_rendering.cpp282
17 files changed, 1784 insertions, 0 deletions
diff --git a/src/gui/button.cpp b/src/gui/button.cpp
new file mode 100644
index 0000000..daa224f
--- /dev/null
+++ b/src/gui/button.cpp
@@ -0,0 +1,191 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "button.hpp"
+
+#include <iostream>
+
+#include "utils/geometry.hpp"
+
+namespace gui {
+
+const std::vector<guint> Button::MOUSE_BUTTONS = {
+ GDK_BUTTON_PRIMARY, GDK_BUTTON_MIDDLE, GDK_BUTTON_SECONDARY};
+
+Button::Button(Window* window_, const std::string& text_,
+ Position position_, Size size_, Color color_,
+ Position::Alignment horizontal_align_, Position::Alignment vertical_align_,
+ Shape shape_)
+ : window(window_),
+ text(text_),
+ position(position_),
+ size(size_),
+ color(color_),
+ horizontal_align(horizontal_align_),
+ vertical_align(vertical_align_),
+ shape(shape_),
+ hovering(false)
+{
+ // Given a mouse button, this lambda gives the appropriate
+ // mouse_callback_t (which will call CheckClick with the button).
+ std::function<Window::mouse_callback_t(guint)> callback =
+ [this](guint button)->Window::mouse_callback_t {
+ return [this,button](Window*,int,int) {
+ CheckClick(button);
+ };
+ };
+
+
+
+ for (guint button : MOUSE_BUTTONS)
+ {
+ // Initialize callbacks.
+ callbacks[button] = std::vector<callback_t>();
+ int callback_id = window->SetMouseupCallback(callback(button), button);
+ mouseup_callback_ids.push_back(callback_id);
+ }
+}
+
+Button::~Button()
+{
+ int i;
+ for (i = 0; i < (int)MOUSE_BUTTONS.size(); i++)
+ window->RemoveMouseupCallback(mouseup_callback_ids[i],MOUSE_BUTTONS[i]);
+}
+
+
+
+void Button::CheckHovering()
+{
+ int mx = window->GetMouseX(), my = window->GetMouseY();
+ hovering = shape == Shape::RECTANGLE
+ ? utils::geometry::InRectangle(mx, my, GetX(), GetY(),
+ GetWidth(), GetHeight())
+ : utils::geometry::InCircle(mx, my,
+ GetX()+GetRadius(), GetY()+GetRadius(), GetRadius());
+ if (hovering)
+ for (callback_t callback : hover_callbacks)
+ callback();
+}
+
+void Button::CheckClick(guint button)
+{
+ CheckHovering();
+ if (hovering)
+ {
+ for (callback_t callback : callbacks[button])
+ {
+ callback();
+ }
+ }
+}
+
+void Button::Render()
+{
+ CheckHovering();
+ window->SetLineWidth(BORDER_WIDTH);
+ window->SetDrawColor(colors::Shade(color, 0.3));
+ if (shape == Shape::CIRCLE)
+ window->SetTextSize(GetRadius()*CIRCLE_TEXT_SIZE_FACTOR);
+ else
+ window->SetTextSize(GetHeight()*TEXT_SIZE_FACTOR);
+
+ if (hovering)
+ {
+ switch (shape)
+ {
+ case Shape::RECTANGLE:
+ window->DrawRectangle(GetX()+BORDER_WIDTH/2, GetY()+BORDER_WIDTH/2,
+ GetWidth()-BORDER_WIDTH, GetHeight()-BORDER_WIDTH, true);
+ break;
+ case Shape::CIRCLE:
+ window->DrawCircle(GetX()+GetRadius(), GetY()+GetRadius(),
+ GetRadius()-BORDER_WIDTH);
+ break;
+ }
+ }
+
+ window->SetDrawColor(color);
+ switch (shape)
+ {
+ case Shape::RECTANGLE:
+ window->DrawRectangle(GetX(), GetY(), GetWidth(), GetHeight(), false);
+ break;
+ case Shape::CIRCLE:
+ window->DrawCircle(GetX()+GetRadius(), GetY()+GetRadius(), GetRadius(),
+ false);
+ break;
+ }
+
+ int text_x = 0, text_y = 0;
+ switch (shape)
+ {
+ case Shape::RECTANGLE:
+ {
+ Position text_pos(GetX(), GetY(), 0.5, 0.5, &size);
+ text_x = text_pos.X();
+ text_y = text_pos.Y();
+ }
+ break;
+ case Shape::CIRCLE:
+ text_x = GetX() + GetRadius();
+ text_y = GetY() + GetRadius();
+ break;
+ }
+
+ Position text_pos(text_x, text_y);
+ window->DrawText(text, text_pos, Alignment::CENTER, Alignment::CENTER);
+}
+
+void Button::SetCommand(callback_t callback, guint button)
+{
+ callbacks[button].push_back(callback);
+}
+
+void Button::SetHoverCallback(callback_t callback)
+{
+ hover_callbacks.push_back(callback);
+}
+
+int Button::GetWidth() const { return size.X(); }
+int Button::GetHeight() const { return size.Y(); }
+int Button::GetRadius() const { return size.X(); }
+
+int Button::GetX() const
+{
+ return position.AlignedX(horizontal_align, GetWidth());
+}
+
+int Button::GetY() const
+{
+ return position.AlignedY(vertical_align, GetHeight());
+}
+
+void Button::SetPosition(Position position_)
+{
+ position = position_;
+}
+
+void Button::SetAlignment(Alignment horizontal_align_,Alignment vertical_align_)
+{
+ horizontal_align = horizontal_align_;
+ vertical_align = vertical_align_;
+}
+
+
+} // namespace gui
diff --git a/src/gui/button.hpp b/src/gui/button.hpp
new file mode 100644
index 0000000..29a071f
--- /dev/null
+++ b/src/gui/button.hpp
@@ -0,0 +1,83 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GUI_BUTTON_H_
+#define GRAPHCOLORING_GUI_BUTTON_H_
+
+
+#include <string>
+#include <vector>
+#include <functional>
+#include <iostream>
+
+#include "colors.hpp"
+#include "position.hpp"
+#include "window.hpp"
+
+namespace gui {
+
+class Button
+{
+public:
+ typedef std::function<void()> callback_t;
+ typedef std::map<guint, std::vector<callback_t>> callback_map_t;
+ enum class Shape
+ {
+ RECTANGLE,
+ CIRCLE // NOTE: If you select this, size.X() will be used for the radius of the circle.
+ };
+ Button(Window* window, const std::string& text, Position pos, Size size,
+ Color color,
+ Position::Alignment horizontal_align = Position::Alignment::LEFT,
+ Position::Alignment vertical_align = Position::Alignment::TOP,
+ Shape shape = Shape::RECTANGLE);
+ virtual ~Button();
+ void SetCommand(callback_t callback, guint button = GDK_BUTTON_PRIMARY);
+ void SetHoverCallback(callback_t callback);
+ void Render();
+ int GetX() const;
+ int GetY() const;
+ int GetWidth() const;
+ int GetHeight() const;
+ int GetRadius() const;
+ void SetPosition(Position position);
+ void SetAlignment(Alignment horizontal_align, Alignment vertical_align);
+ static constexpr double CIRCLE_TEXT_SIZE_FACTOR = 0.9; // Allows for ~2 characters
+private:
+ void CheckHovering();
+ void CheckClick(guint button);
+ static constexpr int BORDER_WIDTH = 3;
+ static constexpr double TEXT_SIZE_FACTOR = 0.7;
+ static const std::vector<guint> MOUSE_BUTTONS;
+ Window* const window;
+ std::string text;
+ Position position;
+ Size size;
+ Color color;
+ Alignment horizontal_align, vertical_align;
+ Shape shape;
+ bool hovering;
+ std::vector<int> mouseup_callback_ids;
+ callback_map_t callbacks;
+ std::vector<callback_t> hover_callbacks;
+};
+
+} // namespace gui
+
+
+#endif // GRAPHCOLORING_GUI_BUTTON_H_
diff --git a/src/gui/colors.cpp b/src/gui/colors.cpp
new file mode 100644
index 0000000..9ab75a8
--- /dev/null
+++ b/src/gui/colors.cpp
@@ -0,0 +1,101 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "colors.hpp"
+#include "utils/errors.hpp"
+
+#include <iostream>
+
+
+#define SHADE_FACTOR 1.5
+
+namespace gui {
+namespace colors {
+
+void Unpack(Color color, uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a)
+{
+ *r = (color >> 24) & 0xFF;
+ *g = (color >> 16) & 0xFF;
+ *b = (color >> 8) & 0xFF;
+ *a = (color >> 0) & 0xFF;
+}
+
+Color Pack(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ return (r << 24) + (g << 16) + (b << 8) + (a << 0);
+}
+
+static uint8_t shade_channel(uint8_t channel, double factor)
+{
+ if (channel * factor > 0xFF)
+ channel = 0xFF;
+ else
+ channel *= factor;
+ return channel;
+}
+
+Color Shade(Color color, double factor)
+{
+ uint8_t r, g, b, a;
+ Unpack(color, &r, &g, &b, &a);
+ r = shade_channel(r, factor);
+ g = shade_channel(g, factor);
+ b = shade_channel(b, factor);
+ a = shade_channel(a, factor);
+ return Pack(r, g, b, a);
+}
+
+Color Light(Color color)
+{
+ return Shade(color, SHADE_FACTOR);
+}
+
+Color Dark(Color color)
+{
+ return Shade(color, 1/SHADE_FACTOR);
+}
+
+Color FromString(const std::string& str)
+{
+ uint8_t r, g, b, a;
+ try
+ {
+ r = std::stoi(str.substr(1,2),nullptr,16);
+ g = std::stoi(str.substr(3,2),nullptr,16);
+ b = std::stoi(str.substr(5,2),nullptr,16);
+ if (str.length() >= 9)
+ a = std::stoi(str.substr(7,2),nullptr,16);
+ else
+ a = 255;
+
+ return Pack(r, g, b, a);
+ }
+ catch (std::invalid_argument&)
+ {
+ utils::errors::Die("Invalid color string: " + str);
+ }
+ return 0;
+}
+
+Color FromAttribute(pugi::xml_attribute attr)
+{
+ return FromString(std::string(attr.value()));
+}
+
+} // namespace colors
+} // namespace gui
diff --git a/src/gui/colors.hpp b/src/gui/colors.hpp
new file mode 100644
index 0000000..0ecefb0
--- /dev/null
+++ b/src/gui/colors.hpp
@@ -0,0 +1,55 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GUI_COLORS_H_
+#define GRAPHCOLORING_GUI_COLORS_H_
+
+#include <cstdint>
+#include <string>
+
+#include "pugi/pugixml.hpp"
+
+namespace gui {
+
+typedef uint32_t Color;
+
+namespace colors {
+
+// Color constants
+static constexpr Color WHITE = 0xFFFFFFFF;
+static constexpr Color BLACK = 0x000000FF;
+static constexpr Color RED = 0xFF0000FF;
+static constexpr Color GREEN = 0x00FF00FF;
+static constexpr Color BLUE = 0x0000FFFF;
+static constexpr Color YELLOW = 0xFFFF00FF;
+static constexpr Color CYAN = 0x00FFFFFF;
+static constexpr Color MAGENTA = 0xFF00FFFF;
+
+extern void Unpack(Color color,
+ uint8_t* r, uint8_t* g, uint8_t* b, uint8_t* a); // 32-bit color => 8-bit channels
+extern Color Pack(uint8_t r, uint8_t g, uint8_t b, uint8_t a); // 8-bit channels => 32-bit color
+extern Color Shade(Color color, double factor);
+extern Color Light(Color color);
+extern Color Dark(Color color);
+extern Color FromString(const std::string& str); // Reads #FF00AA or #AA33FFCC
+extern Color FromAttribute(pugi::xml_attribute attr);
+
+} // namespace colors
+} // namespace gui
+
+#endif // GRAPHCOLORING_GUI_COLORS_H_
diff --git a/src/gui/gui.hpp b/src/gui/gui.hpp
new file mode 100644
index 0000000..a9aea3e
--- /dev/null
+++ b/src/gui/gui.hpp
@@ -0,0 +1,28 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GUI_GUI_H_
+#define GRAPHCOLORING_GUI_GUI_H_
+
+#include "button.hpp"
+#include "colors.hpp"
+#include "menu.hpp"
+#include "position.hpp"
+#include "window.hpp"
+
+#endif // GRAPHCOLORING_GUI_GUI_H_
diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp
new file mode 100644
index 0000000..18bacb2
--- /dev/null
+++ b/src/gui/menu.cpp
@@ -0,0 +1,74 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "menu.hpp"
+
+#include <iostream>
+
+namespace gui {
+
+Menu::Menu(Window* window,
+ const std::vector<std::shared_ptr<Button>>& buttons_,
+ Position position_, Size size_)
+ : window(window), buttons(buttons_), position(position_), size(size_)
+{
+ int total_button_height = 0;
+ for (std::shared_ptr<Button>& button : buttons)
+ {
+ total_button_height += button->GetHeight();
+ }
+
+ int number_of_buttons = buttons.size();
+ double spacing_rel = 1.0 / (number_of_buttons + 1);
+ double spacing_abs = (-total_button_height) / (number_of_buttons + 1);
+ if (spacing_rel * size.Y() + spacing_abs < 0) // Window is very short; just give up.
+ {
+ spacing_rel = 0;
+ spacing_abs = 0;
+ }
+ double y_abs = spacing_abs;
+ double y_rel = spacing_rel;
+
+ for (std::shared_ptr<Button>& button : buttons)
+ {
+ button->SetPosition(Position(0, y_abs, 0.5, y_rel, &size, &position));
+ button->SetAlignment(Alignment::CENTER, Alignment::TOP);
+ y_abs += spacing_abs + button->GetHeight();
+ y_rel += spacing_rel;
+ }
+}
+
+void Menu::SetCommand(int button, Button::callback_t command)
+{
+ buttons[button]->SetCommand(command);
+}
+
+void Menu::SetCommands(std::vector<Button::callback_t> commands)
+{
+ for (unsigned i = 0; i < buttons.size(); i++)
+ SetCommand((int)i, commands[i]);
+}
+
+
+void Menu::Render()
+{
+ for (std::shared_ptr<Button>& button : buttons)
+ button->Render();
+}
+
+} // namespace gui
diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp
new file mode 100644
index 0000000..74b9696
--- /dev/null
+++ b/src/gui/menu.hpp
@@ -0,0 +1,53 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GUI_MENU_H_
+#define GRAPHCOLORING_GUI_MENU_H_
+
+// Menus are vertical lists of buttons.
+
+#include <vector>
+#include <string>
+#include <memory>
+
+#include "button.hpp"
+
+namespace gui {
+
+class Menu
+{
+public:
+ Menu(Window* window,
+ const std::vector<std::shared_ptr<Button>>& buttons,
+ Position pos, Size size);
+ virtual ~Menu() {}
+ void SetCommand(int button, Button::callback_t command);
+ void SetCommands(std::vector<Button::callback_t> commands);
+ void Render();
+private:
+ Window* const window;
+ std::vector<std::shared_ptr<Button>> buttons;
+ Position position;
+ Size size;
+};
+
+} // namespace gui
+
+
+
+#endif /* GRAPHCOLORING_GUI_MENU_H_ */
diff --git a/src/gui/position.cpp b/src/gui/position.cpp
new file mode 100644
index 0000000..03514a4
--- /dev/null
+++ b/src/gui/position.cpp
@@ -0,0 +1,107 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "position.hpp"
+
+#include <iostream>
+
+#include "utils/errors.hpp"
+
+namespace gui {
+
+Position::Position(int x_, int y_, double relx_, double rely_,
+ const Size* parent_size_, const Position* parent_pos_)
+{
+ SetPos(x_, y_);
+ SetRel(relx_, rely_);
+ SetParent(parent_size_, parent_pos_);
+}
+
+int Position::X() const
+{
+ int xpos = x;
+ if (parent_size != nullptr)
+ xpos += relx * parent_size->X();
+ if (parent_pos != nullptr)
+ xpos += parent_pos->X();
+ return xpos;
+}
+
+int Position::Y() const
+{
+ int ypos = y;
+ if (parent_size != nullptr)
+ ypos += rely * parent_size->Y();
+ if (parent_pos != nullptr)
+ ypos += parent_pos->Y();
+ return ypos;
+}
+
+void Position::SetX(int x_) { x = x_; }
+void Position::SetY(int y_) { y = y_; }
+
+
+void Position::SetPos(int x_, int y_)
+{
+ SetX(x_);
+ SetY(y_);
+}
+
+void Position::SetRel(double relx_, double rely_)
+{
+ relx = relx_;
+ rely = rely_;
+}
+
+void Position::SetParent(const Size* parent_size_, const Position* parent_pos_)
+{
+ parent_size = parent_size_;
+ parent_pos = parent_pos_;
+}
+
+int Position::AlignAxis(Alignment alignment, int val, int size)
+{
+ switch (alignment)
+ {
+ case Alignment::LEFT:
+ return val;
+ case Alignment::RIGHT:
+ return val - size;
+ case Alignment::CENTER:
+ return val - size/2;
+ }
+ return -1;
+}
+
+int Position::AlignedX(Alignment horizontal_align, int width) const
+{
+ return AlignAxis(horizontal_align, X(), width);
+}
+
+int Position::AlignedY(Alignment vertical_align, int height) const
+{
+ return AlignAxis(vertical_align, Y(), height);
+}
+
+std::ostream& operator<<(std::ostream& os, Position pos)
+{
+ os << "(" << pos.X() << ", " << pos.Y() << ")";
+ return os;
+}
+
+} // namespace gui
diff --git a/src/gui/position.hpp b/src/gui/position.hpp
new file mode 100644
index 0000000..a905348
--- /dev/null
+++ b/src/gui/position.hpp
@@ -0,0 +1,73 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+
+// This position class can be used for relative positions, like
+// (30% of parent width + 5 pixels, 20% of parent height - 20 pixels)
+
+#ifndef GRAPHCOLORING_GUI_POSITION_H_
+#define GRAPHCOLORING_GUI_POSITION_H_
+
+#include <string>
+
+namespace gui {
+
+class Position {
+public:
+ typedef Position Size; // "Size" alias for Positions that really refer to sizes.
+ enum class Alignment
+ {
+ LEFT,
+ CENTER,
+ RIGHT,
+ TOP = LEFT,
+ BOTTOM = RIGHT
+ };
+ Position(int x = 0, int y = 0, double relx = 0, double rely = 0,
+ const Size* parent_size = nullptr, const Position* parent_pos
+ = nullptr);
+ virtual ~Position() {}
+ int X() const;
+ int Y() const;
+ void SetX(int x);
+ void SetY(int y);
+ void SetPos(int x, int y);
+ void SetRel(double relx, double rely);
+ void SetParent(const Size* parent_size = nullptr,
+ const Position* parent_pos = nullptr);
+ int AlignedX(Alignment horizontal_align, int width) const;
+ int AlignedY(Alignment vertical_align, int height) const;
+ int x;
+ int y;
+ double relx;
+ double rely;
+ const Size* parent_size;
+ const Position* parent_pos;
+private:
+ static int AlignAxis(Alignment alignment, int val, int size);
+ friend std::ostream& operator<<(std::ostream& os, Position pos);
+};
+
+extern std::ostream& operator<<(std::ostream& os, Position pos);
+
+typedef Position::Alignment Alignment;
+typedef Position::Size Size;
+
+} // namespace gui
+
+#endif // GRAPHCOLORING_GUI_POSITION_H_
diff --git a/src/gui/window.hpp b/src/gui/window.hpp
new file mode 100644
index 0000000..eb6216e
--- /dev/null
+++ b/src/gui/window.hpp
@@ -0,0 +1,181 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GUI_WINDOW_H_
+#define GRAPHCOLORING_GUI_WINDOW_H_
+
+#include <cstdint>
+#include <functional>
+#include <vector>
+#include <map>
+
+#include <gtk/gtk.h>
+
+#include "colors.hpp"
+#include "position.hpp"
+
+namespace gui {
+
+
+class Window
+{
+public:
+ typedef std::function<void(Window*)> callback_t; // General 0-parameter callbacks.
+ typedef std::map<guint, std::vector<callback_t>> callback_map_t;
+ typedef std::function<void(Window*,int,int)> mouse_callback_t; // Mouse callbacks take x and y coordinates.
+ typedef std::map<guint, std::vector<mouse_callback_t>> mouse_callback_map_t;
+ typedef std::function<void(Window*,GdkScrollDirection)> scroll_callback_t;
+
+ Window(const char* title, int width, int height, int fps = 30);
+ virtual ~Window();
+ // window_main.cpp methods
+ int GetWidth() const;
+ int GetHeight() const;
+ Position GetPosition(int x = 0,int y = 0, double xrel = 0, double yrel = 0);
+ const Size* GetSizePtr() const;
+ void RemoveAllCallbacks();
+ void OnActivate(std::function<void()> callback);
+ void Quit();
+ void Mainloop(); // Start application.
+ // window_rendering.cpp methods
+ int SetRenderCallback(callback_t callback);
+ void RemoveRenderCallback(int id);
+ void SetDrawColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
+ void SetDrawColor(Color color); // Sets color to value. Format: 0xRRGGBBAA.
+ void SetLineWidth(int line_width);
+ void Clear();
+ void DrawRectangle(int x, int y, int w, int h, bool filled = true);
+ void DrawPoint(int x, int y);
+ void DrawLine(int x1, int y1, int x2, int y2);
+ void DrawArc(int x, int y, int r, double startAngle, double endAngle,
+ bool filled = true);
+ void DrawCircle(int x, int y, int r, bool filled = true);
+ void DrawPolygon(std::vector<std::pair<int,int>> points, bool filled=true);
+ void SetTextSize(int size);
+ void DrawText(const std::string& text, int x, int y);
+ void DrawText(const std::string& text, Position pos,
+ Alignment horizontal_align = Alignment::LEFT,
+ Alignment vertical_align = Alignment::TOP);
+ void DrawImage(const std::string& filename, int x, int y);
+ void DrawImage(const std::string& filename, Position pos,
+ Alignment horizontal_align = Alignment::LEFT,
+ Alignment vertical_align = Alignment::TOP);
+ void GetImageSize(const std::string& filename, int* w, int* h);
+ void GetTextSize(const std::string& text, int* w, int* h);
+
+ // window_events_keyboard.cpp methods
+ int SetKeydownCallback(callback_t callback, guint key);
+ void RemoveKeydownCallback(int id, guint key);
+ int SetKeyupCallback(callback_t callback, guint key);
+ void RemoveKeyupCallback(int id, guint key);
+ bool IsKeyDown(guint key) const;
+ bool IsControlDown() const;
+
+ // window_events_mouse.cpp
+ int SetMousedownCallback(mouse_callback_t callback,
+ guint button = GDK_BUTTON_PRIMARY);
+ void RemoveMousedownCallback(int id, guint button = GDK_BUTTON_PRIMARY);
+ int SetMouseupCallback(mouse_callback_t callback,
+ guint button = GDK_BUTTON_PRIMARY);
+ void RemoveMouseupCallback(int id, guint button = GDK_BUTTON_PRIMARY);
+ int SetMousemotionCallback(mouse_callback_t callback);
+ void RemoveMousemotionCallback(int id);
+ int SetScrollCallback(scroll_callback_t callback);
+ void RemoveScrollCallback(int id);
+ bool IsMouseDown(guint button = GDK_BUTTON_PRIMARY) const;
+ int GetMouseX() const;
+ int GetMouseY() const;
+
+private:
+ // window_main.cpp methods
+ void InitializeWindow();
+ void InitializeApplication(const char* title);
+ friend void Activate(GtkApplication*, gpointer);
+
+ // window_events.cpp methods
+ void InitializeEvents();
+
+ template<typename F>
+ static int AddToCallbackMap(std::map<guint,std::vector<F>>& callback_map,
+ guint id, F callback);
+ template<typename F>
+ static std::vector<F> CheckCallbackMap(
+ std::map<guint,std::vector<F>>& callback_map, guint id);
+ template<typename F>
+ static void RemoveFromCallbackList(
+ std::vector<F>& callback_list, int id);
+
+ friend void GtkKeydownCallback(GtkWidget*, GdkEventKey*, gpointer);
+ void ProcessKeydown(GdkEventKey* event);
+ friend void GtkKeyupCallback(GtkWidget*, GdkEventKey*, gpointer);
+ void ProcessKeyup(GdkEventKey* event);
+
+ friend void GtkMousedownCallback(GtkWidget*, GdkEventButton*, gpointer);
+ void ProcessMousedown(GdkEventButton* event);
+
+ friend void GtkMouseupCallback(GtkWidget*, GdkEventButton*, gpointer);
+ void ProcessMouseup(GdkEventButton* event);
+
+ friend void GtkMousemotionCallback(GtkWidget*, GdkEventMotion*, gpointer);
+ void ProcessMousemotion(GdkEventMotion* event);
+
+ friend void GtkScrollCallback(GtkWidget*, GdkEventScroll*, gpointer);
+ void ProcessScroll(GdkEventScroll* event);
+
+ // window_rendering.cpp methods
+ friend void GtkDrawCallback(GtkWidget*, cairo_t*, gpointer);
+ void InitializeDrawingArea();
+ void Render();
+ cairo_surface_t* GetSurface(const std::string& filename);
+
+ // window_main.cpp members
+ Size size;
+ const char* title;
+ const int FPS;
+ GtkApplication* application;
+ GtkWidget* window;
+ std::function<void()> on_activate;
+
+ // window_rendering.cpp members
+ cairo_t* cr;
+ cairo_font_face_t* font;
+ std::map<std::string, cairo_surface_t*> images; // Only load each image once.
+ std::vector<callback_t> render_callbacks;
+ bool is_render_callbacks_modified;
+
+ // window_events.cpp members
+ mouse_callback_map_t mousedown_callbacks;
+ bool is_mousedown_callbacks_modified;
+ mouse_callback_map_t mouseup_callbacks;
+ bool is_mouseup_callbacks_modified;
+ std::vector<mouse_callback_t> mousemotion_callbacks;
+ bool is_mousemotion_callbacks_modified;
+ std::vector<scroll_callback_t> scroll_callbacks;
+ bool is_scroll_callbacks_modified;
+ callback_map_t keyup_callbacks;
+ bool is_keyup_callbacks_modified;
+ callback_map_t keydown_callbacks;
+ bool is_keydown_callbacks_modified;
+ std::map<guint, bool> keymap; // Which keys are down?
+ std::map<guint, bool> mousemap; // Which mouse buttons are down?
+ int mouse_x, mouse_y;
+};
+
+} // namespace gui
+
+#endif // GRAPHCOLORING_GUI_WINDOW_H_
diff --git a/src/gui/window_events.cpp b/src/gui/window_events.cpp
new file mode 100644
index 0000000..635d86e
--- /dev/null
+++ b/src/gui/window_events.cpp
@@ -0,0 +1,112 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+#include "utils/errors.hpp"
+#include "window.hpp"
+#include "window_events_keyboard.hpp"
+#include "window_events_mouse.hpp"
+
+namespace gui
+{
+
+template<typename F>
+int Window::AddToCallbackMap(std::map<guint,std::vector<F>>& callback_map,
+ guint id, F callback)
+{
+ if (callback_map.count(id))
+ {
+ // There are already callbacks for this key.
+ callback_map[id].push_back(callback);
+ }
+ else
+ {
+ std::vector<F> callbacks = {callback};
+ callback_map[id] = callbacks;
+ }
+ return callback_map[id].size()-1;
+}
+
+template<typename F>
+std::vector<F> Window::CheckCallbackMap(
+ std::map<guint,std::vector<F>>& callback_map, guint id)
+{
+ // mouse_x and mouse_y will be passed to the function if it is a
+ // mouse_callback_t. Otherwise, they will be set to -1, and will
+ // not be passed.
+ if (callback_map.count(id))
+ {
+ // There are callbacks for this key
+ return callback_map[id];
+ }
+ else
+ {
+ return std::vector<F>();
+ }
+}
+
+template<typename F>
+void Window::RemoveFromCallbackList(
+ std::vector<F>& callback_list, int id)
+{
+ if (id < 0 || id >= (int)callback_list.size())
+ utils::errors::Die("Invalid callback ID.");
+ callback_list[id] = nullptr;
+}
+
+template int Window::AddToCallbackMap<Window::mouse_callback_t>(
+ mouse_callback_map_t&, guint, mouse_callback_t);
+template int Window::AddToCallbackMap<Window::callback_t>(
+ callback_map_t&, guint, callback_t);
+
+template std::vector<Window::mouse_callback_t>
+ Window::CheckCallbackMap<Window::mouse_callback_t>(
+ mouse_callback_map_t&, guint);
+template std::vector<Window::callback_t>
+ Window::CheckCallbackMap<Window::callback_t>(callback_map_t&, guint);
+
+
+template void Window::RemoveFromCallbackList<Window::mouse_callback_t>(
+ std::vector<mouse_callback_t>&, int);
+template void Window::RemoveFromCallbackList<Window::callback_t>(
+ std::vector<callback_t>&, int);
+template void Window::RemoveFromCallbackList<Window::scroll_callback_t>(
+ std::vector<scroll_callback_t>&, int);
+
+void Window::InitializeEvents()
+{
+ gtk_widget_set_events(window, GDK_POINTER_MOTION_MASK | GDK_SCROLL_MASK
+ | GDK_BUTTON1_MASK | GDK_BUTTON2_MASK | GDK_BUTTON3_MASK
+ | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_BUTTON_PRESS_MASK
+ | GDK_BUTTON_RELEASE_MASK);
+
+ g_signal_connect(G_OBJECT(window), "key-press-event",
+ G_CALLBACK(GtkKeydownCallback), this);
+ g_signal_connect(G_OBJECT(window), "key-release-event",
+ G_CALLBACK(GtkKeyupCallback), this);
+ g_signal_connect(G_OBJECT(window), "button-press-event",
+ G_CALLBACK(GtkMousedownCallback), this);
+ g_signal_connect(G_OBJECT(window), "button-release-event",
+ G_CALLBACK(GtkMouseupCallback), this);
+ g_signal_connect(G_OBJECT(window), "motion-notify-event",
+ G_CALLBACK(GtkMousemotionCallback), this);
+ g_signal_connect(G_OBJECT(window), "scroll-event",
+ G_CALLBACK(GtkScrollCallback), this);
+}
+
+} // namespace gui
diff --git a/src/gui/window_events_keyboard.cpp b/src/gui/window_events_keyboard.cpp
new file mode 100644
index 0000000..d3a5841
--- /dev/null
+++ b/src/gui/window_events_keyboard.cpp
@@ -0,0 +1,107 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "window.hpp"
+
+#include "utils/errors.hpp"
+
+namespace gui {
+
+int Window::SetKeydownCallback(callback_t callback, guint key)
+{
+ is_keydown_callbacks_modified = true;
+ return AddToCallbackMap(keydown_callbacks, key, callback);
+}
+
+void Window::RemoveKeydownCallback(int id, guint key)
+{
+ if (keydown_callbacks.count(key) == 0)
+ utils::errors::Die("Invalid key passed to remove key callback.");
+ RemoveFromCallbackList(keydown_callbacks[key], id);
+ is_keydown_callbacks_modified = true;
+}
+
+void Window::ProcessKeydown(GdkEventKey* event)
+{
+ keymap[event->keyval] = true;
+ is_keydown_callbacks_modified = false;
+ for (callback_t callback
+ : CheckCallbackMap<callback_t>(keydown_callbacks, event->keyval))
+ {
+ if (!callback) continue;
+ callback(this);
+ if (is_keydown_callbacks_modified)
+ break;
+ }
+}
+
+void GtkKeydownCallback(GtkWidget*, GdkEventKey* event, gpointer data)
+{
+ Window* win = (Window*)data;
+ win->ProcessKeydown(event);
+}
+
+
+int Window::SetKeyupCallback(callback_t callback, guint key)
+{
+ is_keyup_callbacks_modified = true;
+ return AddToCallbackMap(keyup_callbacks, key, callback);
+}
+
+void Window::RemoveKeyupCallback(int id, guint key)
+{
+ if (keyup_callbacks.count(key) == 0)
+ utils::errors::Die("Invalid key passed to remove key callback.");
+ RemoveFromCallbackList(keyup_callbacks[key], id);
+ is_keyup_callbacks_modified = true;
+}
+
+void Window::ProcessKeyup(GdkEventKey* event)
+{
+ keymap[event->keyval] = false;
+ is_keyup_callbacks_modified = false;
+ for (callback_t callback : CheckCallbackMap(keyup_callbacks, event->keyval))
+ {
+ if (!callback) continue;
+ callback(this);
+ if (is_keyup_callbacks_modified)
+ break;
+ }
+}
+
+void GtkKeyupCallback(GtkWidget*, GdkEventKey* event, gpointer data)
+{
+ Window* win = (Window*)data;
+ win->ProcessKeyup(event);
+}
+
+bool Window::IsKeyDown(guint key) const
+{
+ if (keymap.count(key))
+ return keymap.at(key);
+ else
+ return false;
+}
+
+bool Window::IsControlDown() const
+{
+ return IsKeyDown(GDK_KEY_Control_L) || IsKeyDown(GDK_KEY_Control_R);
+}
+
+} // namespace gui
+
diff --git a/src/gui/window_events_keyboard.hpp b/src/gui/window_events_keyboard.hpp
new file mode 100644
index 0000000..e903d11
--- /dev/null
+++ b/src/gui/window_events_keyboard.hpp
@@ -0,0 +1,32 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GUI_WINDOW_EVENTS_KEYBOARD_H_
+#define GRAPHCOLORING_GUI_WINDOW_EVENTS_KEYBOARD_H_
+
+#include <gtk/gtk.h>
+
+namespace gui {
+
+extern void GtkKeydownCallback(GtkWidget*, GdkEventKey* event, gpointer data);
+extern void GtkKeyupCallback(GtkWidget*, GdkEventKey* event, gpointer data);
+
+}
+
+
+#endif /* GRAPHCOLORING_GUI_WINDOW_EVENTS_KEYBOARD_H_ */
diff --git a/src/gui/window_events_mouse.cpp b/src/gui/window_events_mouse.cpp
new file mode 100644
index 0000000..16238d5
--- /dev/null
+++ b/src/gui/window_events_mouse.cpp
@@ -0,0 +1,166 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include "window.hpp"
+
+#include <iostream>
+
+#include "utils/errors.hpp"
+
+namespace gui {
+
+
+void GtkMousedownCallback(GtkWidget*, GdkEventButton* event, gpointer data)
+{
+ Window* win = (Window*)data;
+ win->ProcessMousedown(event);
+}
+
+void Window::ProcessMousedown(GdkEventButton* event)
+{
+ mousemap[event->button] = true;
+ is_mousedown_callbacks_modified = false;
+ for (mouse_callback_t callback :
+ CheckCallbackMap(mousedown_callbacks, event->button))
+ {
+ if (!callback) continue;
+ callback(this, event->x, event->y);
+ if (is_mousedown_callbacks_modified)
+ break;
+ }
+}
+
+int Window::SetMousedownCallback(mouse_callback_t callback, guint button)
+{
+ is_mousedown_callbacks_modified = true;
+ return AddToCallbackMap(mousedown_callbacks, button, callback);
+}
+
+void Window::RemoveMousedownCallback(int id, guint button)
+{
+ is_mousedown_callbacks_modified = true;
+ if (mousedown_callbacks.count(button) == 0)
+ utils::errors::Die("Invalid button passed to remove mouse callback.");
+ RemoveFromCallbackList(mousedown_callbacks[button], id);
+}
+
+void GtkMouseupCallback(GtkWidget*, GdkEventButton* event, gpointer data)
+{
+ Window* win = (Window*)data;
+ win->ProcessMouseup(event);
+}
+
+void Window::ProcessMouseup(GdkEventButton* event)
+{
+ mousemap[event->button] = false;
+ is_mouseup_callbacks_modified = false;
+ for (mouse_callback_t callback : mouseup_callbacks[event->button])
+ {
+ if (!callback) continue;
+ callback(this, event->x, event->y);
+ if (is_mouseup_callbacks_modified)
+ break;
+ }
+
+}
+
+int Window::SetMouseupCallback(mouse_callback_t callback, guint button)
+{
+ is_mouseup_callbacks_modified = true;
+ return AddToCallbackMap(mouseup_callbacks, button, callback);
+}
+
+void Window::RemoveMouseupCallback(int id, guint button)
+{
+ is_mouseup_callbacks_modified = true;
+ if (mouseup_callbacks.count(button) == 0)
+ utils::errors::Die("Invalid button passed to remove mouse callback.");
+ RemoveFromCallbackList(mouseup_callbacks[button], id);
+}
+
+void GtkMousemotionCallback(GtkWidget*, GdkEventMotion* event, gpointer data)
+{
+ Window* win = (Window*)data;
+ win->ProcessMousemotion(event);
+}
+
+void Window::ProcessMousemotion(GdkEventMotion* event)
+{
+ mouse_x = event->x;
+ mouse_y = event->y;
+ is_mousemotion_callbacks_modified = false;
+ for (mouse_callback_t callback : mousemotion_callbacks)
+ {
+ if (!callback) continue;
+ callback(this, event->x, event->y);
+ if (is_mousemotion_callbacks_modified)
+ break;
+ }
+}
+
+int Window::SetMousemotionCallback(mouse_callback_t callback)
+{
+ is_mousemotion_callbacks_modified = true;
+ mousemotion_callbacks.push_back(callback);
+ return mousemotion_callbacks.size()-1;
+}
+
+void Window::RemoveMousemotionCallback(int id)
+{
+ is_mousemotion_callbacks_modified = true;
+ RemoveFromCallbackList(mousemotion_callbacks, id);
+}
+
+void GtkScrollCallback(GtkWidget*, GdkEventScroll* event, gpointer data)
+{
+ Window* window = (Window*) data;
+ window->ProcessScroll(event);
+}
+
+void Window::ProcessScroll(GdkEventScroll* event)
+{
+ is_scroll_callbacks_modified = false;
+ for (scroll_callback_t callback : scroll_callbacks)
+ {
+ if (!callback) continue;
+ callback(this, event->direction);
+ if (is_scroll_callbacks_modified)
+ break;
+ }
+}
+
+int Window::SetScrollCallback(scroll_callback_t callback)
+{
+ is_scroll_callbacks_modified = true;
+ scroll_callbacks.push_back(callback);
+ return scroll_callbacks.size()-1;
+}
+
+void Window::RemoveScrollCallback(int id)
+{
+ is_scroll_callbacks_modified = true;
+ RemoveFromCallbackList(scroll_callbacks, id);
+}
+
+bool Window::IsMouseDown(guint button) const { return mousemap.at(button); }
+
+int Window::GetMouseX() const { return mouse_x; }
+int Window::GetMouseY() const { return mouse_y; }
+
+} // namespace gui
+
diff --git a/src/gui/window_events_mouse.hpp b/src/gui/window_events_mouse.hpp
new file mode 100644
index 0000000..b9eb0ae
--- /dev/null
+++ b/src/gui/window_events_mouse.hpp
@@ -0,0 +1,37 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#ifndef GRAPHCOLORING_GUI_WINDOW_EVENTS_MOUSE_H_
+#define GRAPHCOLORING_GUI_WINDOW_EVENTS_MOUSE_H_
+
+#include <gtk/gtk.h>
+
+namespace gui {
+
+extern void GtkMousedownCallback(GtkWidget*, GdkEventButton* event,
+ gpointer data);
+extern void GtkMouseupCallback(GtkWidget*, GdkEventButton* event,
+ gpointer data);
+extern void GtkMousemotionCallback(GtkWidget*, GdkEventMotion* event,
+ gpointer data);
+extern void GtkScrollCallback(GtkWidget*, GdkEventScroll* event, gpointer data);
+
+}
+
+
+#endif /* GRAPHCOLORING_GUI_WINDOW_EVENTS_MOUSE_H_ */
diff --git a/src/gui/window_main.cpp b/src/gui/window_main.cpp
new file mode 100644
index 0000000..3d79989
--- /dev/null
+++ b/src/gui/window_main.cpp
@@ -0,0 +1,102 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#include <iostream>
+
+#include "utils/errors.hpp"
+#include "window.hpp"
+
+namespace gui {
+
+void Window::InitializeWindow()
+{
+ // Initialize the Gtk Window
+ window = gtk_application_window_new(application);
+ gtk_window_set_title(GTK_WINDOW(window), title);
+ gtk_window_set_default_size(GTK_WINDOW(window), GetWidth(), GetHeight());
+
+ InitializeDrawingArea();
+ InitializeEvents();
+
+ gtk_widget_show_all(window);
+ on_activate();
+}
+
+void Window::OnActivate(std::function<void()> callback)
+{
+ on_activate = callback;
+}
+
+void Window::RemoveAllCallbacks()
+{
+ keyup_callbacks.clear();
+ keydown_callbacks.clear();
+ mouseup_callbacks.clear();
+ mousedown_callbacks.clear();
+ mousemotion_callbacks.erase(mousemotion_callbacks.begin(),
+ mousemotion_callbacks.end());
+ scroll_callbacks.erase(scroll_callbacks.begin(), scroll_callbacks.end());
+ render_callbacks.erase(render_callbacks.begin(), render_callbacks.end());
+}
+
+void Activate(GtkApplication*, gpointer data)
+{
+ // This will be called when the application activates.
+ // It simply calls Window::InitializeWindow.
+ Window* win = (Window*)data;
+ win->InitializeWindow();
+}
+
+void Window::InitializeApplication(const char* t)
+{
+ title = t;
+ application = gtk_application_new("com.pommicket.graphcoloring",
+ G_APPLICATION_FLAGS_NONE);
+ g_signal_connect(application, "activate", G_CALLBACK(&Activate), this);
+}
+
+Window::Window(const char* title, int w, int h, int fps)
+ : size(w,h), FPS(fps)
+{
+ InitializeApplication(title);
+}
+
+Window::~Window() {}
+
+int Window::GetWidth() const { return size.X(); }
+int Window::GetHeight() const { return size.Y(); }
+
+const Size* Window::GetSizePtr() const { return &size; }
+
+Position Window::GetPosition(int x, int y, double xrel, double yrel)
+{
+ return Position(x, y, xrel, yrel, GetSizePtr());
+}
+
+void Window::Quit()
+{
+ g_application_quit(G_APPLICATION(application));
+}
+
+void Window::Mainloop()
+{
+ g_application_run(G_APPLICATION(application), 0, NULL);
+ g_object_unref(application);
+}
+
+} // namespace gui
diff --git a/src/gui/window_rendering.cpp b/src/gui/window_rendering.cpp
new file mode 100644
index 0000000..1069595
--- /dev/null
+++ b/src/gui/window_rendering.cpp
@@ -0,0 +1,282 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright (C) 2018 Leo Tenenbaum
+// This file is part of GraphColoring.
+//
+// GraphColoring is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// GraphColoring is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with GraphColoring. If not, see <https://www.gnu.org/licenses/>.
+////////////////////////////////////////////////////////////////////////////////
+
+#define _USE_MATH_DEFINES
+#include <cmath>
+#include <iostream>
+#include <cairo/cairo-ft.h>
+#include <freetype2/ft2build.h>
+#include FT_FREETYPE_H
+
+#include "utils/errors.hpp"
+#include "window.hpp"
+
+namespace gui {
+
+
+void GtkDrawCallback(GtkWidget* widget, cairo_t* c, gpointer data)
+{
+ Window* win = (Window*) data;
+ win->cr = c;
+ win->Render();
+}
+
+gboolean QueueDraw(gpointer w)
+{
+ GtkWidget* window = (GtkWidget*) w;
+ gtk_widget_queue_draw(window);
+ return TRUE;
+}
+
+void Window::InitializeDrawingArea()
+{
+ GtkWidget* drawing_area = gtk_drawing_area_new ();
+ gtk_widget_set_size_request (drawing_area, 100, 100);
+ g_signal_connect(G_OBJECT(drawing_area), "draw",
+ G_CALLBACK(&GtkDrawCallback), this);
+ g_timeout_add(1000/FPS, QueueDraw, drawing_area);
+
+ gtk_container_add(GTK_CONTAINER(window), drawing_area);
+
+ // Also initialize font
+ FT_Library library;
+ FT_Face font_face;
+ if (FT_Init_FreeType(&library))
+ utils::errors::Die("Failed to initialize freetype.");
+ if (FT_New_Face(library, "assets/fonts/DejaVuSans-ExtraLight.ttf",
+ 0, &font_face))
+ utils::errors::Die("Failed to open font.");
+
+
+ font = cairo_ft_font_face_create_for_ft_face(font_face, 0);
+ const cairo_user_data_key_t key = {0};
+ auto status = cairo_font_face_set_user_data (font, &key,
+ font_face, (cairo_destroy_func_t) FT_Done_Face);
+ if (status)
+ utils::errors::Die("Failed to load font.");
+
+}
+
+int Window::SetRenderCallback(callback_t callback)
+{
+ render_callbacks.push_back(callback);
+ is_render_callbacks_modified = true;
+ return render_callbacks.size()-1;
+}
+
+void Window::RemoveRenderCallback(int id)
+{
+ RemoveFromCallbackList(render_callbacks, id);
+ is_render_callbacks_modified = true;
+}
+
+void Window::Render()
+{
+ int width, height;
+ gtk_window_get_size(GTK_WINDOW(window), &width, &height);
+ size.SetPos(width, height);
+ is_render_callbacks_modified = false;
+ for (callback_t callback : render_callbacks)
+ {
+ if (!callback) continue;
+ callback(this);
+ // If a callback modifies the list of render callbacks, give up on
+ // trying to read the whole list.
+ if (is_render_callbacks_modified)
+ break;
+ }
+}
+
+
+
+
+void Window::SetDrawColor(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
+{
+ cairo_set_source_rgba(cr,
+ (double)r/255, (double)g/255, (double)b/255, (double)a/255);
+}
+
+void Window::SetDrawColor(Color color)
+{
+ uint8_t r, g, b, a;
+ colors::Unpack(color, &r, &g, &b, &a);
+ SetDrawColor(r, g, b, a);
+}
+
+void Window::SetLineWidth(int line_width)
+{
+ cairo_set_line_width(cr, line_width);
+}
+
+void Window::Clear()
+{
+ DrawRectangle(0, 0, GetWidth(), GetHeight(), true);
+}
+
+void Window::DrawRectangle(int x, int y, int w, int h, bool filled)
+{
+ cairo_rectangle(cr, x, y, w, h);
+ if (filled)
+ cairo_fill(cr);
+ else
+ cairo_stroke(cr);
+}
+
+void Window::DrawPoint(int x, int y)
+{
+ DrawRectangle(x, y, 1, 1);
+}
+
+void Window::DrawLine(int x1, int y1, int x2, int y2)
+{
+ cairo_move_to(cr, x1, y1);
+ cairo_line_to(cr, x2, y2);
+ cairo_stroke(cr);
+}
+
+void Window::DrawArc(int x, int y, int r, double startAngle, double endAngle,
+ bool filled)
+{
+ cairo_arc(cr, x, y, r, startAngle, endAngle);
+ if (filled)
+ cairo_fill(cr);
+ else
+ cairo_stroke(cr);
+}
+
+void Window::DrawCircle(int x, int y, int r, bool filled)
+{
+ DrawArc(x, y, r, 0, 2*M_PI, filled);
+}
+
+void Window::DrawPolygon(std::vector<std::pair<int,int>> points, bool filled)
+{
+ if (points.size() < 2)
+ utils::errors::Die("Trying to create polygon with less than 2 points.");
+ cairo_move_to(cr, points[0].first, points[0].second);
+ for (unsigned i = 1; i < points.size(); i++)
+ cairo_line_to(cr, points[i].first, points[i].second);
+ if (filled)
+ cairo_fill(cr);
+ else
+ cairo_stroke(cr);
+}
+
+void Window::SetTextSize(int size)
+{
+ cairo_set_font_size(cr, size);
+}
+
+void Window::DrawText(const std::string& text, int x, int y)
+{
+ cairo_set_font_face(cr, font);
+ cairo_move_to(cr, x, y);
+ cairo_show_text(cr, text.c_str());
+ cairo_stroke(cr);
+}
+
+void Window::DrawText(const std::string& text, Position pos,
+ Alignment horizontal_align, Alignment vertical_align)
+{
+ int w, h, x, y;
+ GetTextSize(text, &w, &h);
+ x = pos.X();
+ y = pos.Y();
+ switch (horizontal_align)
+ {
+ case Alignment::LEFT:
+ break;
+ case Alignment::CENTER:
+ x -= w/2;
+ break;
+ case Alignment::RIGHT:
+ x -= w;
+ break;
+ }
+ switch (vertical_align)
+ {
+ case Alignment::TOP:
+ y += h;
+ break;
+ case Alignment::CENTER:
+ y += h/2;
+ break;
+ case Alignment::BOTTOM:
+ break;
+ }
+ DrawText(text, x, y);
+}
+
+cairo_surface_t* Window::GetSurface(const std::string& filename)
+{
+ cairo_surface_t* image;
+ std::string path = "assets/images/" + filename + ".png";
+ if (images.count(path))
+ {
+ image = images[path];
+ }
+ else
+ {
+ image = cairo_image_surface_create_from_png(path.c_str());
+ if (cairo_surface_status(image) != CAIRO_STATUS_SUCCESS)
+ utils::errors::Die("Failed to load image: " + path);
+ images[path] = image;
+ }
+ return image;
+}
+
+
+void Window::DrawImage(const std::string& filename, int x, int y)
+{
+ cairo_surface_t* image = GetSurface(filename);
+ cairo_set_source_surface(cr, image, x, y);
+ cairo_paint(cr);
+}
+
+void Window::DrawImage(const std::string& filename, Position pos,
+ Alignment horizontal_align, Alignment vertical_align)
+{
+ int w, h;
+ GetImageSize(filename, &w, &h);
+ DrawImage(filename, pos.AlignedX(horizontal_align, w),
+ pos.AlignedY(vertical_align, h));
+}
+
+void Window::GetImageSize(const std::string& filename, int* w, int* h)
+{
+ cairo_surface_t* image = GetSurface(filename);
+ if (w != nullptr)
+ *w = cairo_image_surface_get_width(image);
+ if (h != nullptr)
+ *h = cairo_image_surface_get_height(image);
+}
+
+void Window::GetTextSize(const std::string& text, int* w, int* h)
+{
+ cairo_set_font_face(cr, font);
+ cairo_text_extents_t extents;
+ cairo_text_extents(cr, text.c_str(), &extents);
+ if (w != nullptr)
+ *w = extents.width;
+ if (h != nullptr)
+ *h = extents.height;
+}
+
+
+
+} // namespace gui