From ea022d03b56e6ac8648045481581a73183fe2f29 Mon Sep 17 00:00:00 2001 From: bruhmoent <69918580+bruhmoent@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:22:54 +0100 Subject: [PATCH] Color Clipboard (#3100) This PR adds three new options to the Color Menu: - Copy to clipboard (rgb), - Copy to clipboard (hex), - Paste from clipboard These options enable users to copy and paste colors between objects with the color menu via the SDL's clipboard feature. Co-authored-by: Marty <85036874+MatusGuy@users.noreply.github.com> --- src/gui/item_action.cpp | 11 +++-- src/gui/item_action.hpp | 2 +- src/gui/menu.cpp | 12 ++++++ src/gui/menu.hpp | 2 + src/gui/menu_color.cpp | 95 +++++++++++++++++++++++++++++++++++++---- src/gui/menu_color.hpp | 15 ++++++- src/gui/menu_item.cpp | 25 +++++++---- src/gui/menu_item.hpp | 5 ++- src/video/color.cpp | 73 +++++++++++++++++++++++++++++++ src/video/color.hpp | 9 +++- 10 files changed, 221 insertions(+), 28 deletions(-) diff --git a/src/gui/item_action.cpp b/src/gui/item_action.cpp index 7c250635885..812657d83ae 100644 --- a/src/gui/item_action.cpp +++ b/src/gui/item_action.cpp @@ -16,8 +16,8 @@ #include "gui/item_action.hpp" -ItemAction::ItemAction(const std::string& text, int id, std::function callback) : - MenuItem(text, id), +ItemAction::ItemAction(const std::string& text, int id, std::function callback, const Color& text_color) : + MenuItem(text, id, text_color), m_callback(std::move(callback)) { } @@ -25,10 +25,9 @@ ItemAction::ItemAction(const std::string& text, int id, std::function ca void ItemAction::process_action(const MenuAction& action) { - if (action == MenuAction::HIT) { - if (m_callback) { - m_callback(); - } + if (action == MenuAction::HIT && m_callback) + { + m_callback(); } } diff --git a/src/gui/item_action.hpp b/src/gui/item_action.hpp index c166fde5137..e9a7170fc6d 100644 --- a/src/gui/item_action.hpp +++ b/src/gui/item_action.hpp @@ -22,7 +22,7 @@ class ItemAction final : public MenuItem { public: - ItemAction(const std::string& text, int id = -1, std::function callback = {}); + ItemAction(const std::string& text, int id = -1, std::function callback = {}, const Color& text_color = Color(1.f, 1.f, 1.f)); virtual void process_action(const MenuAction& action) override; diff --git a/src/gui/menu.cpp b/src/gui/menu.cpp index 697223f50e7..3f6c82cc057 100644 --- a/src/gui/menu.cpp +++ b/src/gui/menu.cpp @@ -191,6 +191,18 @@ Menu::add_entry(const std::string& text, const std::function& callback) return add_item(text, -1, callback); } +ItemAction& +Menu::add_entry(int id, const std::string& text, const Color& text_color) +{ + return add_item(text, id, [](){}, text_color); +} + +ItemAction& +Menu::add_entry(const std::string& text, const std::function& callback, const Color& text_color) +{ + return add_item(text, -1, callback, text_color); +} + ItemInactive& Menu::add_inactive(const std::string& text, bool default_color) { diff --git a/src/gui/menu.hpp b/src/gui/menu.hpp index 3dbafa34fd2..4d923d5797f 100644 --- a/src/gui/menu.hpp +++ b/src/gui/menu.hpp @@ -76,7 +76,9 @@ class Menu ItemHorizontalLine& add_hl(); ItemLabel& add_label(const std::string& text); ItemAction& add_entry(int id, const std::string& text); + ItemAction& add_entry(int id, const std::string& text, const Color& text_color); ItemAction& add_entry(const std::string& text, const std::function& callback); + ItemAction& add_entry(const std::string& text, const std::function& callback, const Color& text_color); ItemToggle& add_toggle(int id, const std::string& text, bool* toggled, bool center_text = false); ItemToggle& add_toggle(int id, const std::string& text, const std::function& get_func, diff --git a/src/gui/menu_color.cpp b/src/gui/menu_color.cpp index af068748b14..b6ec5adb4fd 100644 --- a/src/gui/menu_color.cpp +++ b/src/gui/menu_color.cpp @@ -1,5 +1,6 @@ // SuperTux // Copyright (C) 2015 Hume2 +// 2024 bruhmoent // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -14,30 +15,108 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include + #include "gui/menu_color.hpp" +#include "gui/menu_item.hpp" +#include "util/log.hpp" #include "util/gettext.hpp" -ColorMenu::ColorMenu(Color* color_) : - color(color_) +ColorMenu::ColorMenu(Color* color) : + m_color(color) { add_label(_("Mix the colour")); add_hl(); - add_color_picker_2d(*color); - add_color_channel_rgba(&(color->red), Color::RED); - add_color_channel_rgba(&(color->green), Color::GREEN); - add_color_channel_rgba(&(color->blue), Color::BLUE); - add_color_channel_rgba(&(color->alpha), Color::BLACK, -1, true); - add_color_display(color); + add_color_picker_2d(*m_color); + add_color_channel_rgba(&(m_color->red), Color::RED); + add_color_channel_rgba(&(m_color->green), Color::GREEN); + add_color_channel_rgba(&(m_color->blue), Color::BLUE); + add_color_channel_rgba(&(m_color->alpha), Color::BLACK, -1, true); + add_color_display(m_color); + + add_hl(); + add_entry(MNID_COPY_CLIPBOARD_RGB, _("Copy to clipboard (rgb)")); + add_entry(MNID_COPY_CLIPBOARD_HEX, _("Copy to clipboard (hex)")); + if (SDL_HasClipboardText()) + { + char* clipboard_text = SDL_GetClipboardText(); + std::optional clipboard_color; + + if (clipboard_text) + { + const std::string text(clipboard_text); + SDL_free(clipboard_text); + + clipboard_color = Color::deserialize_from_rgb(text); + if (!clipboard_color) + clipboard_color = Color::deserialize_from_hex(text); + } + + add_entry(MNID_PASTE_CLIPBOARD, _("Paste from clipboard"), clipboard_color.value_or(Color(1.f, 1.f, 1.f))); + } + else + add_entry(MNID_PASTE_CLIPBOARD, _("Paste from clipboard"), Color(1.f, 1.f, 1.f)); add_hl(); add_back(_("OK")); } +void +ColorMenu::copy_to_clipboard(const std::string& color_str) +{ + if (SDL_SetClipboardText(color_str.c_str()) != 0) + log_warning << "Failed to set SDL clipboard text: " << SDL_GetError() << std::endl; + + MenuItem& menu_paste_item = get_item_by_id(MNID_PASTE_CLIPBOARD); + menu_paste_item.set_text_color(*m_color); +} + void ColorMenu::menu_action(MenuItem& item) { + if (item.get_id() == MNID_COPY_CLIPBOARD_RGB) + { + if (m_color) + { + const std::string clipboard_text(Color::serialize_to_rgb(*m_color)); + copy_to_clipboard(clipboard_text); + } + } + else if (item.get_id() == MNID_COPY_CLIPBOARD_HEX) + { + if (m_color) + { + const std::string clipboard_text(Color::serialize_to_hex(*m_color)); + copy_to_clipboard(clipboard_text); + } + } + else if (item.get_id() == MNID_PASTE_CLIPBOARD) + { + if (SDL_HasClipboardText()) + { + char* clipboard_text = SDL_GetClipboardText(); + if (!clipboard_text) + return; + + const std::string text(clipboard_text); + SDL_free(clipboard_text); + + std::optional clipboard_color = Color::deserialize_from_rgb(text); + if (!clipboard_color) + clipboard_color = Color::deserialize_from_hex(text); + + if (clipboard_color) + { + *m_color = *clipboard_color; + MenuItem& menu_paste_item = get_item_by_id(MNID_PASTE_CLIPBOARD); + menu_paste_item.set_text_color(*clipboard_color); + } + else + log_warning << "Invalid color format: " << text << ". Supported formats: rgb(r,g,b) and #rrggbb" << std::endl; + } + } } /* EOF */ diff --git a/src/gui/menu_color.hpp b/src/gui/menu_color.hpp index bec16fd9f84..de1dbad9cf0 100644 --- a/src/gui/menu_color.hpp +++ b/src/gui/menu_color.hpp @@ -22,12 +22,23 @@ class ColorMenu final : public Menu { public: - ColorMenu(Color* color_); + ColorMenu(Color* color); void menu_action(MenuItem& item) override; private: - Color* color; + void copy_to_clipboard(const std::string& color_str); + +private: + enum MenuIDs + { + MNID_COPY_CLIPBOARD_RGB = 1, + MNID_COPY_CLIPBOARD_HEX, + MNID_PASTE_CLIPBOARD + }; + +private: + Color* m_color; private: ColorMenu(const ColorMenu&) = delete; diff --git a/src/gui/menu_item.cpp b/src/gui/menu_item.cpp index 6b6e24777bd..64ca37abc0b 100644 --- a/src/gui/menu_item.cpp +++ b/src/gui/menu_item.cpp @@ -23,18 +23,17 @@ #include "supertux/resources.hpp" #include "video/drawing_context.hpp" -//static const float FLICK_CURSOR_TIME = 0.5f; - -MenuItem::MenuItem(const std::string& text, int id) : +MenuItem::MenuItem(const std::string& text, int id, const std::optional& text_color) : m_id(id), m_text(text), m_help(), - m_font(Resources::normal_font) + m_font(Resources::normal_font), + m_text_color(text_color) { } -MenuItem::~MenuItem() { - +MenuItem::~MenuItem() +{ } void @@ -59,12 +58,20 @@ MenuItem::draw(DrawingContext& context, const Vector& pos, int menu_width, bool } Color -MenuItem::get_color() const { - return ColorScheme::Menu::default_color; +MenuItem::get_color() const +{ + return m_text_color.value_or(ColorScheme::Menu::default_color); +} + +void +MenuItem::set_text_color(const Color& color) +{ + m_text_color = color; } int -MenuItem::get_width() const { +MenuItem::get_width() const +{ return static_cast(m_font->get_text_width(m_text)) + 16; } diff --git a/src/gui/menu_item.hpp b/src/gui/menu_item.hpp index 95aaccbb4e1..8e6ba0dde76 100644 --- a/src/gui/menu_item.hpp +++ b/src/gui/menu_item.hpp @@ -19,11 +19,12 @@ #define HEADER_SUPERTUX_GUI_MENU_ITEM_HPP #include "gui/menu.hpp" +#include class MenuItem { public: - MenuItem(const std::string& text, int id = -1); + explicit MenuItem(const std::string& text, int id = -1, const std::optional& text_color = std::nullopt); virtual ~MenuItem(); inline int get_id() const { return m_id; } @@ -65,6 +66,7 @@ class MenuItem virtual void event(const SDL_Event& ev) { } virtual Color get_color() const; + virtual void set_text_color(const Color& color); /** Returns true when the MenuManager shouldn't do anything else. */ virtual bool no_other_action() const { @@ -86,6 +88,7 @@ class MenuItem std::string m_text; std::string m_help; FontPtr m_font; + std::optional m_text_color; private: MenuItem(const MenuItem&) = delete; diff --git a/src/video/color.cpp b/src/video/color.cpp index 244e59ece3b..d0b06de2b60 100644 --- a/src/video/color.cpp +++ b/src/video/color.cpp @@ -14,9 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#include "math/util.hpp" #include "video/color.hpp" #include +#include +#include +#include const Color Color::BLACK(0.0, 0.0, 0.0); const Color Color::RED(1.0, 0.0, 0.0); @@ -116,4 +120,73 @@ Color::toVector() return result; } +std::optional +Color::deserialize_from_rgb(const std::string & rgb_string) +{ + const std::regex rgb_format(R"(^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$)"); + std::smatch matches; + + if (std::regex_match(rgb_string, matches, rgb_format)) + { + const int r = std::stoi(matches[1].str()); + const int g = std::stoi(matches[2].str()); + const int b = std::stoi(matches[3].str()); + + if (math::in_bounds(r, 0, 255) && math::in_bounds(g, 0, 255) && math::in_bounds(b, 0, 255)) + { + return Color(static_cast(r) / 255.0f, + static_cast(g) / 255.0f, + static_cast(b) / 255.0f, + 1.0f); + } + } + return std::nullopt; +} + +std::optional +Color::deserialize_from_hex(const std::string& hex_string) +{ + const std::regex hex_format(R"(^\s*#([A-Fa-f0-9]{6})\s*$)"); + std::smatch matches; + + if (std::regex_match(hex_string, matches, hex_format)) + { + const std::string hex_value = matches[1].str(); + unsigned int hex_color; + std::stringstream ss; + ss << std::hex << hex_value; + ss >> hex_color; + + const float r = ((hex_color >> 16) & 0xFF) / 255.0f; + const float g = ((hex_color >> 8) & 0xFF) / 255.0f; + const float b = (hex_color & 0xFF) / 255.0f; + + return Color(r, g, b, 1.0f); + } + return std::nullopt; +} + +std::string +Color::serialize_to_hex(const Color& color) +{ + std::stringstream ss; + ss << "#" + << std::hex << std::setfill('0') << std::uppercase + << std::setw(2) << static_cast(color.red * 255.f) + << std::setw(2) << static_cast(color.green * 255.f) + << std::setw(2) << static_cast(color.blue * 255.f); + return ss.str(); +} + +std::string +Color::serialize_to_rgb(const Color& color) +{ + std::stringstream ss; + ss << "rgb(" + << static_cast(color.red * 255.f) << "," + << static_cast(color.green * 255.f) << "," + << static_cast(color.blue * 255.f) << ")"; + return ss.str(); +} + /* EOF */ diff --git a/src/video/color.hpp b/src/video/color.hpp index 84e5f57e0b4..5523a1674fe 100644 --- a/src/video/color.hpp +++ b/src/video/color.hpp @@ -17,9 +17,10 @@ #ifndef HEADER_SUPERTUX_VIDEO_COLOR_HPP #define HEADER_SUPERTUX_VIDEO_COLOR_HPP +#include #include +#include #include -#include #include @@ -81,6 +82,12 @@ class Color final static_cast(a) / 255.0f); } + static std::optional deserialize_from_rgb(const std::string& rgb_string); + static std::optional deserialize_from_hex(const std::string& hex_string); + + static std::string serialize_to_rgb(const Color& color); + static std::string serialize_to_hex(const Color& color); + static Color from_linear(float r, float g, float b, float a = 1.0f) { return Color(add_gamma(r), add_gamma(g), add_gamma(b), a);