Skip to content

Commit

Permalink
Color Clipboard (#3100)
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
bruhmoent and MatusGuy authored Dec 4, 2024
1 parent 6cb2c4b commit ea022d0
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 28 deletions.
11 changes: 5 additions & 6 deletions src/gui/item_action.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,18 @@

#include "gui/item_action.hpp"

ItemAction::ItemAction(const std::string& text, int id, std::function<void()> callback) :
MenuItem(text, id),
ItemAction::ItemAction(const std::string& text, int id, std::function<void()> callback, const Color& text_color) :
MenuItem(text, id, text_color),
m_callback(std::move(callback))
{
}

void
ItemAction::process_action(const MenuAction& action)
{
if (action == MenuAction::HIT) {
if (m_callback) {
m_callback();
}
if (action == MenuAction::HIT && m_callback)
{
m_callback();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/gui/item_action.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
class ItemAction final : public MenuItem
{
public:
ItemAction(const std::string& text, int id = -1, std::function<void()> callback = {});
ItemAction(const std::string& text, int id = -1, std::function<void()> callback = {}, const Color& text_color = Color(1.f, 1.f, 1.f));

virtual void process_action(const MenuAction& action) override;

Expand Down
12 changes: 12 additions & 0 deletions src/gui/menu.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ Menu::add_entry(const std::string& text, const std::function<void()>& callback)
return add_item<ItemAction>(text, -1, callback);
}

ItemAction&
Menu::add_entry(int id, const std::string& text, const Color& text_color)
{
return add_item<ItemAction>(text, id, [](){}, text_color);
}

ItemAction&
Menu::add_entry(const std::string& text, const std::function<void()>& callback, const Color& text_color)
{
return add_item<ItemAction>(text, -1, callback, text_color);
}

ItemInactive&
Menu::add_inactive(const std::string& text, bool default_color)
{
Expand Down
2 changes: 2 additions & 0 deletions src/gui/menu.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<void()>& callback);
ItemAction& add_entry(const std::string& text, const std::function<void()>& 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<bool()>& get_func,
Expand Down
95 changes: 87 additions & 8 deletions src/gui/menu_color.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SuperTux
// Copyright (C) 2015 Hume2 <teratux.mail@gmail.com>
// 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
Expand All @@ -14,30 +15,108 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include <sstream>

#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<Color> 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<Color> 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 */
15 changes: 13 additions & 2 deletions src/gui/menu_color.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
25 changes: 16 additions & 9 deletions src/gui/menu_item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Color>& 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
Expand All @@ -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<int>(m_font->get_text_width(m_text)) + 16;
}

Expand Down
5 changes: 4 additions & 1 deletion src/gui/menu_item.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
#define HEADER_SUPERTUX_GUI_MENU_ITEM_HPP

#include "gui/menu.hpp"
#include <optional>

class MenuItem
{
public:
MenuItem(const std::string& text, int id = -1);
explicit MenuItem(const std::string& text, int id = -1, const std::optional<Color>& text_color = std::nullopt);
virtual ~MenuItem();

inline int get_id() const { return m_id; }
Expand Down Expand Up @@ -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 {
Expand All @@ -86,6 +88,7 @@ class MenuItem
std::string m_text;
std::string m_help;
FontPtr m_font;
std::optional<Color> m_text_color;

private:
MenuItem(const MenuItem&) = delete;
Expand Down
73 changes: 73 additions & 0 deletions src/video/color.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "math/util.hpp"
#include "video/color.hpp"

#include <assert.h>
#include <iomanip>
#include <regex>
#include <sstream>

const Color Color::BLACK(0.0, 0.0, 0.0);
const Color Color::RED(1.0, 0.0, 0.0);
Expand Down Expand Up @@ -116,4 +120,73 @@ Color::toVector()
return result;
}

std::optional<Color>
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<float>(r) / 255.0f,
static_cast<float>(g) / 255.0f,
static_cast<float>(b) / 255.0f,
1.0f);
}
}
return std::nullopt;
}

std::optional<Color>
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<int>(color.red * 255.f)
<< std::setw(2) << static_cast<int>(color.green * 255.f)
<< std::setw(2) << static_cast<int>(color.blue * 255.f);
return ss.str();
}

std::string
Color::serialize_to_rgb(const Color& color)
{
std::stringstream ss;
ss << "rgb("
<< static_cast<int>(color.red * 255.f) << ","
<< static_cast<int>(color.green * 255.f) << ","
<< static_cast<int>(color.blue * 255.f) << ")";
return ss.str();
}

/* EOF */
Loading

0 comments on commit ea022d0

Please sign in to comment.