diff options
author | Leo Tenenbaum <pommicket@gmail.com> | 2018-08-20 20:34:57 -0400 |
---|---|---|
committer | Leo Tenenbaum <pommicket@gmail.com> | 2018-08-20 20:34:57 -0400 |
commit | a4460f6d9453bbd7e584937686449cef3e19f052 (patch) | |
tree | 037c208f1e20302ed048c0952ef8e3418add9c86 /src/gui |
Initial commit0.0.0
Diffstat (limited to 'src/gui')
-rw-r--r-- | src/gui/button.cpp | 191 | ||||
-rw-r--r-- | src/gui/button.hpp | 83 | ||||
-rw-r--r-- | src/gui/colors.cpp | 101 | ||||
-rw-r--r-- | src/gui/colors.hpp | 55 | ||||
-rw-r--r-- | src/gui/gui.hpp | 28 | ||||
-rw-r--r-- | src/gui/menu.cpp | 74 | ||||
-rw-r--r-- | src/gui/menu.hpp | 53 | ||||
-rw-r--r-- | src/gui/position.cpp | 107 | ||||
-rw-r--r-- | src/gui/position.hpp | 73 | ||||
-rw-r--r-- | src/gui/window.hpp | 181 | ||||
-rw-r--r-- | src/gui/window_events.cpp | 112 | ||||
-rw-r--r-- | src/gui/window_events_keyboard.cpp | 107 | ||||
-rw-r--r-- | src/gui/window_events_keyboard.hpp | 32 | ||||
-rw-r--r-- | src/gui/window_events_mouse.cpp | 166 | ||||
-rw-r--r-- | src/gui/window_events_mouse.hpp | 37 | ||||
-rw-r--r-- | src/gui/window_main.cpp | 102 | ||||
-rw-r--r-- | src/gui/window_rendering.cpp | 282 |
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 |