From 1e49aa6b835b03f801b89c8e5bd07dcc7db9db2c Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 10:11:30 +0200 Subject: [PATCH 01/14] Added color codes --- loader/include/Geode/utils/ColorCode.hpp | 65 +++++++++++ loader/include/Geode/utils/ColorProvider.hpp | 6 +- loader/src/utils/ColorCode.cpp | 115 +++++++++++++++++++ 3 files changed, 183 insertions(+), 3 deletions(-) create mode 100644 loader/include/Geode/utils/ColorCode.hpp create mode 100644 loader/src/utils/ColorCode.cpp diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp new file mode 100644 index 000000000..4a001019c --- /dev/null +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +namespace geode { + template + concept has_4b = requires(T object) { object.setColor(cocos2d::ccColor4B()); }; + template + concept has_3b = requires(T object) { + object.setColor(cocos2d::ccColor3B()); + object.setOpacity(GLubyte()); + }; + + class GEODE_DLL ColorCode { + public: + /// @returns The current alpha value + operator float() const; + operator cocos2d::ccColor3B() const; + operator cocos2d::ccColor4B() const; + + GLubyte m_r; + GLubyte m_g; + GLubyte m_b; + GLubyte m_a; + + /// @returns The equivelant of #000000{a} + ColorCode(GLubyte a = 0); + /// Parses the given hex string using one of the following formats: + /// #R/G/B, #RR/GG/BB, #RGB, #RGBA, #RRGGBB or #RRGGBBAA + /// Where the # prefix is optional and / means it's 1 byte/nibble representing all channels + /// @returns The parsed hex string + ColorCode(std::string_view hex); + /// @returns The equivelant of #{r}{g}{b}{a} + ColorCode(GLubyte r, const GLubyte g, const GLubyte b, const GLubyte a = 255); + + GLubyte getR() const; + GLubyte getRed() const; + GLubyte getG() const; + GLubyte getGreen() const; + GLubyte getB() const; + GLubyte getBlue() const; + GLubyte getA() const; + GLubyte getAlpha() const; + cocos2d::ccColor3B to3B() const; + cocos2d::ccColor4B to4B() const; + /// @returns True if alpha is 0 + bool isInvisible() const; + /// @returns True if alpha is bigger than 0 and smaller than 255 + bool isTransparent() const; + /// @returns True if alpha is 255 + bool isOpaque() const; + template requires (has_4b || has_3b) + inline void applyTo(T* node) const { + if constexpr (has_4b) { + node->setColor(this->to4B()); + } else { + node->setColor(this->to3B()); + node->setOpacity(this->getA()); + } + } + private: + void extractChannels(std::string_view hex); + GLubyte extractChannel(std::string_view hexString, size_t index, bool isShort) const; + }; +} \ No newline at end of file diff --git a/loader/include/Geode/utils/ColorProvider.hpp b/loader/include/Geode/utils/ColorProvider.hpp index 5b26dd913..97ce7b7b8 100644 --- a/loader/include/Geode/utils/ColorProvider.hpp +++ b/loader/include/Geode/utils/ColorProvider.hpp @@ -10,7 +10,7 @@ namespace geode { * An event that gets posted whenever `ColorProvider` provides a color * for a specific id. */ - class ColorProvidedEvent final : public Event { + class GEODE_DLL ColorProvidedEvent final : public Event { public: // listener params color // filter params id @@ -105,7 +105,7 @@ namespace geode { cocos2d::ccColor3B color3b(std::string_view id) const; }; - class ThemeIDProvidingEvent final : public Event { + class GEODE_DLL ThemeIDProvidingEvent final : public Event { public: // listener params idOut using Event::Event; @@ -115,7 +115,7 @@ namespace geode { * An event that gets posted for someone to request a node for a specific ID. This is * used in Geode themes to provide custom nodes. */ - class NodeProvidingEvent final : public GlobalEvent { + class GEODE_DLL NodeProvidingEvent final : public GlobalEvent { public: // listener params node // filter params id diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp new file mode 100644 index 000000000..4ee24a50d --- /dev/null +++ b/loader/src/utils/ColorCode.cpp @@ -0,0 +1,115 @@ +#include + +using namespace geode::prelude; + +ColorCode::operator float() const { + return m_a; +} + +ColorCode::operator ccColor3B() const { + return { m_r, m_g, m_b }; +} + +ColorCode::operator ccColor4B() const { + return { m_r, m_g, m_b, m_a }; +} + +ColorCode::ColorCode(GLubyte a) : m_r(0x00), m_g(0x00), m_b(0x00), m_a(a) { } + +ColorCode::ColorCode(std::string_view hex) : m_r(0x00), m_g(0x00), m_b(0x00), m_a(0xFF) { + this->extractChannels(hex); +} + +ColorCode::ColorCode(GLubyte r, GLubyte g, GLubyte b, GLubyte a) : m_r(r), m_g(g), m_b(b), m_a(a) { } + +GLubyte ColorCode::getR() const { + return m_r; +} + +GLubyte ColorCode::getRed() const { + return m_r; +} + +GLubyte ColorCode::getG() const { + return m_g; +} + +GLubyte ColorCode::getGreen() const { + return m_g; +} + +GLubyte ColorCode::getB() const { + return m_b; +} + +GLubyte ColorCode::getBlue() const { + return m_b; +} + +GLubyte ColorCode::getA() const { + return m_a; +} + +GLubyte ColorCode::getAlpha() const { + return m_a; +} + +cocos2d::ccColor3B ColorCode::to3B() const { + return *this; +} + +cocos2d::ccColor4B ColorCode::to4B() const { + return *this; +} + + +bool ColorCode::isInvisible() const { + return m_a == 0x00; +} + +bool ColorCode::isTransparent() const { + return m_a > 0x00 && m_a < 0xFF; +} + +bool ColorCode::isOpaque() const { + return m_a == 0xFF; +} + +void ColorCode::extractChannels(std::string_view hex) { + const bool isPrefixed = hex.starts_with("#"); + + if (hex.size() < 1 + isPrefixed) return; + + const std::string_view hexStr = hex.substr(isPrefixed); + const size_t size = hexStr.size(); + + // If the string is not hex and the size is not an exact size of 1, 2, 3, 4, 6 or 8 + if (hexStr.find_first_not_of("0123456789ABCDEFabcdef") != std::string_view::npos || (size > 4 && size != 6 && size != 8)) return; + + // If the size is 2 or 1 then all channels are equal and alpha is max + if (size <= 2) { + m_r = m_g = m_b = extractChannel(hexStr, 0, size == 1); + m_a = 0xFF; + } else { + const bool isShort = size < 6; + + m_r = extractChannel(hexStr, 0, isShort); + m_g = extractChannel(hexStr, 1, isShort); + m_b = extractChannel(hexStr, 2, isShort); + + // If a 4th byte or nibble exists, extract the alpha channel, otherwise default to full opacity + if (size % 4 == 0) { + m_a = extractChannel(hexStr, 3, isShort); + } else { + m_a = 0xFF; + } + } +} + +GLubyte ColorCode::extractChannel(std::string_view hexString, size_t index, bool isShort) const { + const size_t channelSize = 1 + !isShort; + const GLubyte byte = utils::numFromString(hexString.substr(channelSize * index, channelSize), 16).unwrapOr(0); + + // If it's short, mirror the nibble + return isShort ? (byte << 4) | byte : byte; +} From 313b235bd2d23f24e0f3274afa09dfd7472ac47c Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 10:13:34 +0200 Subject: [PATCH 02/14] Some small corrections --- loader/src/utils/ColorCode.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index 4ee24a50d..402757a15 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -78,7 +78,7 @@ bool ColorCode::isOpaque() const { void ColorCode::extractChannels(std::string_view hex) { const bool isPrefixed = hex.starts_with("#"); - if (hex.size() < 1 + isPrefixed) return; + if (hex.size() == isPrefixed) return; const std::string_view hexStr = hex.substr(isPrefixed); const size_t size = hexStr.size(); @@ -97,7 +97,7 @@ void ColorCode::extractChannels(std::string_view hex) { m_g = extractChannel(hexStr, 1, isShort); m_b = extractChannel(hexStr, 2, isShort); - // If a 4th byte or nibble exists, extract the alpha channel, otherwise default to full opacity + // If a 4th byte or nibble exists, extract the alpha channel, otherwise default to full alpha if (size % 4 == 0) { m_a = extractChannel(hexStr, 3, isShort); } else { From 26794a60285c2326ec64634a5847510a6e11b280 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 10:14:28 +0200 Subject: [PATCH 03/14] Consistent defaults incase the extraction fails --- loader/src/utils/ColorCode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index 402757a15..aa94f215e 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -16,7 +16,7 @@ ColorCode::operator ccColor4B() const { ColorCode::ColorCode(GLubyte a) : m_r(0x00), m_g(0x00), m_b(0x00), m_a(a) { } -ColorCode::ColorCode(std::string_view hex) : m_r(0x00), m_g(0x00), m_b(0x00), m_a(0xFF) { +ColorCode::ColorCode(std::string_view hex) : m_r(0x00), m_g(0x00), m_b(0x00), m_a(0x00) { this->extractChannels(hex); } From f98e1bcf8412c778b0882aba8960543e4268eee9 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 10:34:53 +0200 Subject: [PATCH 04/14] Typo fix --- loader/include/Geode/utils/ColorCode.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index 4a001019c..0a7908d1d 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -23,14 +23,14 @@ namespace geode { GLubyte m_b; GLubyte m_a; - /// @returns The equivelant of #000000{a} + /// @returns The equivalent of #000000{a} ColorCode(GLubyte a = 0); /// Parses the given hex string using one of the following formats: /// #R/G/B, #RR/GG/BB, #RGB, #RGBA, #RRGGBB or #RRGGBBAA /// Where the # prefix is optional and / means it's 1 byte/nibble representing all channels /// @returns The parsed hex string ColorCode(std::string_view hex); - /// @returns The equivelant of #{r}{g}{b}{a} + /// @returns The equivalent of #{r}{g}{b}{a} ColorCode(GLubyte r, const GLubyte g, const GLubyte b, const GLubyte a = 255); GLubyte getR() const; From 271566643c9fa260536109a5a66aa9e8571f6559 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 13:11:25 +0200 Subject: [PATCH 05/14] Made the parsing return a result and added HSV support --- loader/include/Geode/utils/ColorCode.hpp | 56 +++-- loader/src/utils/ColorCode.cpp | 275 +++++++++++++++++------ 2 files changed, 249 insertions(+), 82 deletions(-) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index 0a7908d1d..58e0cc5f5 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -11,38 +11,65 @@ namespace geode { object.setOpacity(GLubyte()); }; - class GEODE_DLL ColorCode { + struct GEODE_DLL Color final { + /// Parses the given hex string using one of the following formats: + /// #R/G/B, #RR/GG/BB, #RGB, #RGBA, #RRGGBB or #RRGGBBAA + /// Where the # prefix is optional and / means it's 1 byte/nibble representing all channels + /// @returns The parsed result + static Result parse(std::string_view hex); + private: + inline static GLubyte getByte(uint32_t colorNum, size_t index, bool isShort); public: /// @returns The current alpha value operator float() const; operator cocos2d::ccColor3B() const; operator cocos2d::ccColor4B() const; + operator cocos2d::extension::HSV() const; + Color& operator=(Color&& other) = default; + Color& operator=(const Color& other) = default; - GLubyte m_r; - GLubyte m_g; - GLubyte m_b; - GLubyte m_a; + GLubyte r; + GLubyte g; + GLubyte b; + GLubyte a; + Color(Color&& other) = default; + Color(const Color& other) = default; /// @returns The equivalent of #000000{a} - ColorCode(GLubyte a = 0); - /// Parses the given hex string using one of the following formats: - /// #R/G/B, #RR/GG/BB, #RGB, #RGBA, #RRGGBB or #RRGGBBAA - /// Where the # prefix is optional and / means it's 1 byte/nibble representing all channels - /// @returns The parsed hex string - ColorCode(std::string_view hex); - /// @returns The equivalent of #{r}{g}{b}{a} - ColorCode(GLubyte r, const GLubyte g, const GLubyte b, const GLubyte a = 255); + Color(GLubyte alpha = 0); + Color(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha = 0xFF); + Color(const cocos2d::ccColor3B& color, GLubyte alpha = 0xFF); + Color(const cocos2d::ccColor4B& color); + Color(const cocos2d::extension::HSV& hsv, GLubyte alpha = 0xFF); + void applyH(double hue); + void applyHue(double hue); + void applyS(double saturation); + void applySaturation(double saturation); + void applyV(double value); + void applyValue(double value); + void applyB(double brightness); + void applyBrightness(double brightness); + void applyHSV(const cocos2d::extension::HSV& hsv); + void setR(GLubyte red); GLubyte getR() const; + void setRed(GLubyte red); GLubyte getRed() const; + void setG(GLubyte green); GLubyte getG() const; + void setGreen(GLubyte green); GLubyte getGreen() const; + void setB(GLubyte blue); GLubyte getB() const; + void setBlue(GLubyte blue); GLubyte getBlue() const; + void setA(GLubyte alpha); GLubyte getA() const; + void setAlpha(GLubyte alpha); GLubyte getAlpha() const; cocos2d::ccColor3B to3B() const; cocos2d::ccColor4B to4B() const; + cocos2d::extension::HSV toHSV() const; /// @returns True if alpha is 0 bool isInvisible() const; /// @returns True if alpha is bigger than 0 and smaller than 255 @@ -58,8 +85,5 @@ namespace geode { node->setOpacity(this->getA()); } } - private: - void extractChannels(std::string_view hex); - GLubyte extractChannel(std::string_view hexString, size_t index, bool isShort) const; }; } \ No newline at end of file diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index aa94f215e..9ac6996b3 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -2,114 +2,257 @@ using namespace geode::prelude; -ColorCode::operator float() const { - return m_a; +Result Color::parse(std::string_view hex) { + const bool isPrefixed = hex.starts_with("#"); + + if (hex.size() == isPrefixed) { + return Err("Empty color string"); + } + + const std::string_view hexStr = hex.substr(isPrefixed); + const size_t size = hexStr.size(); + + // If the string is not hex and the size is not an exact size of 1, 2, 3, 4, 6 or 8 + if (hexStr.find_first_not_of("0123456789ABCDEFabcdef") != std::string_view::npos) { + return Err("Non base16 characters"); + } else if (size > 4 && size != 6 && size != 8) { + return Err("Unsupported size"); + } + + GEODE_UNWRAP_INTO(const uint32_t colorNum, utils::numFromString(hexStr, 16)); + ccColor4B color; + + if (size <= 2) { + color.r = color.g = color.b = Color::getByte(colorNum, 0, size == 1); + color.a = 0xFF; + } else if (size % 3 == 0) { + const bool isShort = size == 3; + + color.r = Color::getByte(colorNum, 2, isShort); + color.g = Color::getByte(colorNum, 1, isShort); + color.b = Color::getByte(colorNum, 0, isShort); + color.a = 0xFF; + } else { + const bool isShort = size == 4; + + color.r = Color::getByte(colorNum, 3, isShort); + color.g = Color::getByte(colorNum, 2, isShort); + color.b = Color::getByte(colorNum, 1, isShort); + color.a = Color::getByte(colorNum, 0, isShort); + } + + return Ok(Color(color)); } -ColorCode::operator ccColor3B() const { - return { m_r, m_g, m_b }; +GLubyte Color::getByte(uint32_t colorNum, size_t index, bool isShort) { + const GLubyte byte = colorNum >> (isShort ? 4 : 8) * index & 0xFF; + + // If it's short, mirror the nibble + return isShort ? (byte << 4) | byte : byte; } -ColorCode::operator ccColor4B() const { - return { m_r, m_g, m_b, m_a }; + +Color::operator float() const { + return a; } -ColorCode::ColorCode(GLubyte a) : m_r(0x00), m_g(0x00), m_b(0x00), m_a(a) { } +Color::operator ccColor3B() const { + return { r, g, b }; +} -ColorCode::ColorCode(std::string_view hex) : m_r(0x00), m_g(0x00), m_b(0x00), m_a(0x00) { - this->extractChannels(hex); +Color::operator ccColor4B() const { + return { r, g, b, a }; } -ColorCode::ColorCode(GLubyte r, GLubyte g, GLubyte b, GLubyte a) : m_r(r), m_g(g), m_b(b), m_a(a) { } +Color::operator HSV() const { + HSV hsv; + double red = r / 255.0; + double green = g / 255.0; + double blue = b / 255.0; + double max = std::max({ red, green, blue }); + double delta = max - std::min({ red, green, blue }); + + if (delta == 0) { + hsv.h = 0; + } else if (max == red) { + hsv.h = 60 * std::fmod((green - blue) / delta, 6); + } else if (max == green) { + hsv.h = 60 * ((blue - red) / delta + 2); + } else { + hsv.h = 60 * ((red - green) / delta + 4); + } + + if (hsv.h < 0) { + hsv.h += 360; + } -GLubyte ColorCode::getR() const { - return m_r; + hsv.s = max == 0 ? 0 : delta / max; + hsv.v = max; + + return hsv; } -GLubyte ColorCode::getRed() const { - return m_r; +Color::Color(GLubyte alpha): r(0x00), g(0x00), b(0x00), a(alpha) { } + +Color::Color(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha): r(red), g(green), b(blue), a(alpha) { } + +Color::Color(const ccColor3B& color, GLubyte alpha): r(color.r), g(color.g), b(color.b), a(alpha) { } + +Color::Color(const ccColor4B& color): r(color.r), g(color.g), b(color.b), a(color.a) { } + +Color::Color(const HSV& hsv, GLubyte alpha): a(alpha) { + double c = hsv.v * hsv.s; + double x = c * (1 - std::abs(std::fmod(hsv.h / 60, 2) - 1)); + double m = hsv.v - c; + double rp, gp, bp; + + if (hsv.h < 60) { + rp = c; gp = x; bp = 0; + } else if (hsv.h < 120) { + rp = x; gp = c; bp = 0; + } else if (hsv.h < 180) { + rp = 0; gp = c; bp = x; + } else if (hsv.h < 240) { + rp = 0; gp = x; bp = c; + } else if (hsv.h < 300) { + rp = x; gp = 0; bp = c; + } else { + rp = c; gp = 0; bp = x; + } + + r = std::round((rp + m) * 255.0); + g = std::round((gp + m) * 255.0); + b = std::round((bp + m) * 255.0); } -GLubyte ColorCode::getG() const { - return m_g; +void Color::applyH(double hue) { + this->applyHSV({ hue, 1, 1 }); } -GLubyte ColorCode::getGreen() const { - return m_g; +void Color::applyHue(double hue) { + this->applyHSV({ hue, 1, 1 }); } -GLubyte ColorCode::getB() const { - return m_b; +void Color::applyS(double saturation) { + this->applyHSV({ 0, saturation, 1 }); } -GLubyte ColorCode::getBlue() const { - return m_b; +void Color::applySaturation(double saturation) { + this->applyHSV({ 0, saturation, 1 }); } -GLubyte ColorCode::getA() const { - return m_a; +void Color::applyV(double value) { + this->applyHSV({ 0, 1, value }); } -GLubyte ColorCode::getAlpha() const { - return m_a; +void Color::applyValue(double value) { + this->applyHSV({ 0, 1, value }); } -cocos2d::ccColor3B ColorCode::to3B() const { - return *this; +void Color::applyB(double brightness) { + this->applyHSV({ 0, 1, brightness }); } -cocos2d::ccColor4B ColorCode::to4B() const { - return *this; +void Color::applyBrightness(double brightness) { + this->applyHSV({ 0, 1, brightness }); } +void Color::applyHSV(const HSV& hsv) { + HSV currentHSV = this->toHSV(); + + currentHSV.h = std::fmod(currentHSV.h + std::fmod(hsv.h, 360) + 360, 360); + currentHSV.s = std::clamp(currentHSV.s * hsv.s, 0, 1); + currentHSV.v = std::clamp(currentHSV.v * hsv.v, 0, 1); -bool ColorCode::isInvisible() const { - return m_a == 0x00; + *this = currentHSV; } -bool ColorCode::isTransparent() const { - return m_a > 0x00 && m_a < 0xFF; + + +void Color::setR(GLubyte red) { + r = red; } -bool ColorCode::isOpaque() const { - return m_a == 0xFF; +GLubyte Color::getR() const { + return r; } -void ColorCode::extractChannels(std::string_view hex) { - const bool isPrefixed = hex.starts_with("#"); +void Color::setRed(GLubyte red) { + r = red; +} - if (hex.size() == isPrefixed) return; +GLubyte Color::getRed() const { + return r; +} - const std::string_view hexStr = hex.substr(isPrefixed); - const size_t size = hexStr.size(); +void Color::setG(GLubyte green) { + g = green; +} - // If the string is not hex and the size is not an exact size of 1, 2, 3, 4, 6 or 8 - if (hexStr.find_first_not_of("0123456789ABCDEFabcdef") != std::string_view::npos || (size > 4 && size != 6 && size != 8)) return; +GLubyte Color::getG() const { + return g; +} - // If the size is 2 or 1 then all channels are equal and alpha is max - if (size <= 2) { - m_r = m_g = m_b = extractChannel(hexStr, 0, size == 1); - m_a = 0xFF; - } else { - const bool isShort = size < 6; - - m_r = extractChannel(hexStr, 0, isShort); - m_g = extractChannel(hexStr, 1, isShort); - m_b = extractChannel(hexStr, 2, isShort); - - // If a 4th byte or nibble exists, extract the alpha channel, otherwise default to full alpha - if (size % 4 == 0) { - m_a = extractChannel(hexStr, 3, isShort); - } else { - m_a = 0xFF; - } - } +void Color::setGreen(GLubyte green) { + g = green; } -GLubyte ColorCode::extractChannel(std::string_view hexString, size_t index, bool isShort) const { - const size_t channelSize = 1 + !isShort; - const GLubyte byte = utils::numFromString(hexString.substr(channelSize * index, channelSize), 16).unwrapOr(0); +GLubyte Color::getGreen() const { + return g; +} - // If it's short, mirror the nibble - return isShort ? (byte << 4) | byte : byte; +void Color::setB(GLubyte blue) { + b = blue; +} + +GLubyte Color::getB() const { + return b; +} + +void Color::setBlue(GLubyte blue) { + b = blue; +} + +GLubyte Color::getBlue() const { + return b; +} + +void Color::setA(GLubyte alpha) { + a = alpha; } + +GLubyte Color::getA() const { + return a; +} + +void Color::setAlpha(GLubyte alpha) { + a = alpha; +} + +GLubyte Color::getAlpha() const { + return a; +} + +ccColor3B Color::to3B() const { + return *this; +} + +ccColor4B Color::to4B() const { + return *this; +} + +HSV Color::toHSV() const { + return *this; +} + +bool Color::isInvisible() const { + return a == 0x00; +} + +bool Color::isTransparent() const { + return a > 0x00 && a < 0xFF; +} + +bool Color::isOpaque() const { + return a == 0xFF; +} \ No newline at end of file From 726921acde7a4755e5a1490602dd92e020f6dd3f Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 13:21:07 +0200 Subject: [PATCH 06/14] Made some corrections --- loader/src/utils/ColorCode.cpp | 37 +++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index 9ac6996b3..daa045f75 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -101,20 +101,26 @@ Color::Color(const ccColor3B& color, GLubyte alpha): r(color.r), g(color.g), b(c Color::Color(const ccColor4B& color): r(color.r), g(color.g), b(color.b), a(color.a) { } Color::Color(const HSV& hsv, GLubyte alpha): a(alpha) { - double c = hsv.v * hsv.s; - double x = c * (1 - std::abs(std::fmod(hsv.h / 60, 2) - 1)); - double m = hsv.v - c; + double correctedHue = std::fmod(hsv.h, 360); + + if (correctedHue < 0) { + correctedHue += 360; + } + + const double c = hsv.v * hsv.s; + const double x = c * (1 - std::abs(std::fmod(correctedHue / 60, 2) - 1)); + const double m = hsv.v - c; double rp, gp, bp; - if (hsv.h < 60) { + if (correctedHue < 60) { rp = c; gp = x; bp = 0; - } else if (hsv.h < 120) { + } else if (correctedHue < 120) { rp = x; gp = c; bp = 0; - } else if (hsv.h < 180) { + } else if (correctedHue < 180) { rp = 0; gp = c; bp = x; - } else if (hsv.h < 240) { + } else if (correctedHue < 240) { rp = 0; gp = x; bp = c; - } else if (hsv.h < 300) { + } else if (correctedHue < 300) { rp = x; gp = 0; bp = c; } else { rp = c; gp = 0; bp = x; @@ -158,16 +164,19 @@ void Color::applyBrightness(double brightness) { } void Color::applyHSV(const HSV& hsv) { - HSV currentHSV = this->toHSV(); + HSV currentHSV = *this; - currentHSV.h = std::fmod(currentHSV.h + std::fmod(hsv.h, 360) + 360, 360); - currentHSV.s = std::clamp(currentHSV.s * hsv.s, 0, 1); - currentHSV.v = std::clamp(currentHSV.v * hsv.v, 0, 1); + currentHSV.h = std::fmod(currentHSV.h + hsv.h, 360); - *this = currentHSV; -} + if (currentHSV.h < 0) { + currentHSV.h += 360; + } + currentHSV.s = std::clamp(currentHSV.s * hsv.s, 0, 1); + currentHSV.v = std::clamp(currentHSV.v * hsv.v, 0, 1); + *this = currentHSV; +} void Color::setR(GLubyte red) { r = red; From 03b3e11da2b5c92bb483460374edd22f0a0b7a3d Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 13:22:35 +0200 Subject: [PATCH 07/14] Added some missing consts --- loader/src/utils/ColorCode.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index daa045f75..f1efb3525 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -65,12 +65,12 @@ Color::operator ccColor4B() const { } Color::operator HSV() const { + const double red = r / 255.0; + const double green = g / 255.0; + const double blue = b / 255.0; + const double max = std::max({ red, green, blue }); + const double delta = max - std::min({ red, green, blue }); HSV hsv; - double red = r / 255.0; - double green = g / 255.0; - double blue = b / 255.0; - double max = std::max({ red, green, blue }); - double delta = max - std::min({ red, green, blue }); if (delta == 0) { hsv.h = 0; From dbda137f0584ce9b025150a3f57eaf35fa7a4724 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 13:33:01 +0200 Subject: [PATCH 08/14] Undo incorrect exports --- loader/include/Geode/utils/ColorProvider.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/loader/include/Geode/utils/ColorProvider.hpp b/loader/include/Geode/utils/ColorProvider.hpp index 97ce7b7b8..5b26dd913 100644 --- a/loader/include/Geode/utils/ColorProvider.hpp +++ b/loader/include/Geode/utils/ColorProvider.hpp @@ -10,7 +10,7 @@ namespace geode { * An event that gets posted whenever `ColorProvider` provides a color * for a specific id. */ - class GEODE_DLL ColorProvidedEvent final : public Event { + class ColorProvidedEvent final : public Event { public: // listener params color // filter params id @@ -105,7 +105,7 @@ namespace geode { cocos2d::ccColor3B color3b(std::string_view id) const; }; - class GEODE_DLL ThemeIDProvidingEvent final : public Event { + class ThemeIDProvidingEvent final : public Event { public: // listener params idOut using Event::Event; @@ -115,7 +115,7 @@ namespace geode { * An event that gets posted for someone to request a node for a specific ID. This is * used in Geode themes to provide custom nodes. */ - class GEODE_DLL NodeProvidingEvent final : public GlobalEvent { + class NodeProvidingEvent final : public GlobalEvent { public: // listener params node // filter params id From eba7415555ddf358370eb13ef1a75ffb27f5060e Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 13:36:28 +0200 Subject: [PATCH 09/14] Added a 4F conversion --- loader/include/Geode/utils/ColorCode.hpp | 1 + loader/src/utils/ColorCode.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index 58e0cc5f5..a710286ec 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -24,6 +24,7 @@ namespace geode { operator float() const; operator cocos2d::ccColor3B() const; operator cocos2d::ccColor4B() const; + operator cocos2d::ccColor4F() const; operator cocos2d::extension::HSV() const; Color& operator=(Color&& other) = default; Color& operator=(const Color& other) = default; diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index f1efb3525..f1291a34f 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -64,6 +64,10 @@ Color::operator ccColor4B() const { return { r, g, b, a }; } +Color::operator ccColor4F() const { + return { static_cast(r / 255.0), static_cast(g / 255.0), static_cast(b / 255.0), static_cast(a / 255.0) }; +} + Color::operator HSV() const { const double red = r / 255.0; const double green = g / 255.0; From caacd57dd52deb17aa1355931ccdcffb1d9e240c Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 13:39:36 +0200 Subject: [PATCH 10/14] Added a 4F ctor --- loader/include/Geode/utils/ColorCode.hpp | 1 + loader/src/utils/ColorCode.cpp | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index a710286ec..686e43b12 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -41,6 +41,7 @@ namespace geode { Color(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha = 0xFF); Color(const cocos2d::ccColor3B& color, GLubyte alpha = 0xFF); Color(const cocos2d::ccColor4B& color); + Color(const cocos2d::ccColor4F& color); Color(const cocos2d::extension::HSV& hsv, GLubyte alpha = 0xFF); void applyH(double hue); diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index f1291a34f..de1e4ca56 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -104,6 +104,8 @@ Color::Color(const ccColor3B& color, GLubyte alpha): r(color.r), g(color.g), b(c Color::Color(const ccColor4B& color): r(color.r), g(color.g), b(color.b), a(color.a) { } +Color::Color(const ccColor4F& color): r(std::round(color.r * 255)), g(std::round(color.g * 255)), b(std::round(color.b * 255)), a(std::round(color.a * 255)) { } + Color::Color(const HSV& hsv, GLubyte alpha): a(alpha) { double correctedHue = std::fmod(hsv.h, 360); @@ -130,9 +132,9 @@ Color::Color(const HSV& hsv, GLubyte alpha): a(alpha) { rp = c; gp = 0; bp = x; } - r = std::round((rp + m) * 255.0); - g = std::round((gp + m) * 255.0); - b = std::round((bp + m) * 255.0); + r = std::round((rp + m) * 255); + g = std::round((gp + m) * 255); + b = std::round((bp + m) * 255); } void Color::applyH(double hue) { From 4d580400c2d613c309718001fc0644773dde5dc4 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 13:40:12 +0200 Subject: [PATCH 11/14] Added a missing method for the 4F conversion --- loader/include/Geode/utils/ColorCode.hpp | 1 + loader/src/utils/ColorCode.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index 686e43b12..6df57a6f2 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -71,6 +71,7 @@ namespace geode { GLubyte getAlpha() const; cocos2d::ccColor3B to3B() const; cocos2d::ccColor4B to4B() const; + cocos2d::ccColor4F to4F() const; cocos2d::extension::HSV toHSV() const; /// @returns True if alpha is 0 bool isInvisible() const; diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index de1e4ca56..d3d76efd9 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -256,6 +256,10 @@ ccColor4B Color::to4B() const { return *this; } +ccColor4F Color::to4F() const { + return *this; +} + HSV Color::toHSV() const { return *this; } From 51468c3dce41c4a14655a8b2753bb74bd18b50a4 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 15:19:56 +0200 Subject: [PATCH 12/14] Fix PR comments --- loader/include/Geode/utils/ColorCode.hpp | 32 +++++++++++++----------- loader/src/utils/ColorCode.cpp | 26 +++++++------------ 2 files changed, 27 insertions(+), 31 deletions(-) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index 6df57a6f2..d10a774fd 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -1,15 +1,19 @@ #pragma once +#include #include +#include namespace geode { template - concept has_4b = requires(T object) { object.setColor(cocos2d::ccColor4B()); }; - template - concept has_3b = requires(T object) { + concept CanSetColor3B = requires(T object) { object.setColor(cocos2d::ccColor3B()); object.setOpacity(GLubyte()); }; + template + concept CanSetColor4B = requires(T object) { object.setColor(cocos2d::ccColor4B()); }; + template + concept CanSetColor4F = requires(T object) { object.setColor(cocos2d::ccColor4F()); }; struct GEODE_DLL Color final { /// Parses the given hex string using one of the following formats: @@ -18,24 +22,22 @@ namespace geode { /// @returns The parsed result static Result parse(std::string_view hex); private: - inline static GLubyte getByte(uint32_t colorNum, size_t index, bool isShort); + static GLubyte getByte(uint32_t colorNum, size_t index, bool isShort); public: - /// @returns The current alpha value - operator float() const; operator cocos2d::ccColor3B() const; operator cocos2d::ccColor4B() const; operator cocos2d::ccColor4F() const; operator cocos2d::extension::HSV() const; - Color& operator=(Color&& other) = default; - Color& operator=(const Color& other) = default; + Color& operator=(Color&& other) noexcept = default; + Color& operator=(const Color& other) noexcept = default; GLubyte r; GLubyte g; GLubyte b; GLubyte a; - Color(Color&& other) = default; - Color(const Color& other) = default; + Color(Color&& other) noexcept = default; + Color(const Color& other) noexcept = default; /// @returns The equivalent of #000000{a} Color(GLubyte alpha = 0); Color(GLubyte red, GLubyte green, GLubyte blue, GLubyte alpha = 0xFF); @@ -79,13 +81,15 @@ namespace geode { bool isTransparent() const; /// @returns True if alpha is 255 bool isOpaque() const; - template requires (has_4b || has_3b) + template requires (CanSetColor3B || CanSetColor4B || CanSetColor4F) inline void applyTo(T* node) const { - if constexpr (has_4b) { - node->setColor(this->to4B()); - } else { + if constexpr (CanSetColor3B) { node->setColor(this->to3B()); node->setOpacity(this->getA()); + } else if constexpr (CanSetColor4B) { + node->setColor(this->to4B()); + } else { + node->setColor(this->to4F()); } } }; diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index d3d76efd9..196c67aab 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -3,23 +3,18 @@ using namespace geode::prelude; Result Color::parse(std::string_view hex) { - const bool isPrefixed = hex.starts_with("#"); - - if (hex.size() == isPrefixed) { - return Err("Empty color string"); + if (hex.starts_with("#")) { + hex.remove_prefix(1); } - const std::string_view hexStr = hex.substr(isPrefixed); - const size_t size = hexStr.size(); + const size_t size = hex.size(); - // If the string is not hex and the size is not an exact size of 1, 2, 3, 4, 6 or 8 - if (hexStr.find_first_not_of("0123456789ABCDEFabcdef") != std::string_view::npos) { - return Err("Non base16 characters"); - } else if (size > 4 && size != 6 && size != 8) { + // If the string is an exact size of 1, 2, 3, 4, 6 or 8 + if (!size || (size > 4 && size != 6 && size != 8)) { return Err("Unsupported size"); } - GEODE_UNWRAP_INTO(const uint32_t colorNum, utils::numFromString(hexStr, 16)); + GEODE_UNWRAP_INTO(const uint32_t colorNum, utils::numFromString(hex, 16)); ccColor4B color; if (size <= 2) { @@ -45,17 +40,14 @@ Result Color::parse(std::string_view hex) { } GLubyte Color::getByte(uint32_t colorNum, size_t index, bool isShort) { - const GLubyte byte = colorNum >> (isShort ? 4 : 8) * index & 0xFF; + const uint32_t shiftSize = (isShort ? 4 : 8) * index; + const GLubyte byte = (colorNum >> shiftSize) & 0xFF; // If it's short, mirror the nibble return isShort ? (byte << 4) | byte : byte; } -Color::operator float() const { - return a; -} - Color::operator ccColor3B() const { return { r, g, b }; } @@ -65,7 +57,7 @@ Color::operator ccColor4B() const { } Color::operator ccColor4F() const { - return { static_cast(r / 255.0), static_cast(g / 255.0), static_cast(b / 255.0), static_cast(a / 255.0) }; + return { r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f }; } Color::operator HSV() const { From 7b81717b5d4f2b3a55091232c463a83d4fa4a459 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 15:24:05 +0200 Subject: [PATCH 13/14] Removed the getters and setters --- loader/include/Geode/utils/ColorCode.hpp | 18 +------ loader/src/utils/ColorCode.cpp | 64 ------------------------ 2 files changed, 1 insertion(+), 81 deletions(-) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index d10a774fd..63498a281 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -55,22 +55,6 @@ namespace geode { void applyB(double brightness); void applyBrightness(double brightness); void applyHSV(const cocos2d::extension::HSV& hsv); - void setR(GLubyte red); - GLubyte getR() const; - void setRed(GLubyte red); - GLubyte getRed() const; - void setG(GLubyte green); - GLubyte getG() const; - void setGreen(GLubyte green); - GLubyte getGreen() const; - void setB(GLubyte blue); - GLubyte getB() const; - void setBlue(GLubyte blue); - GLubyte getBlue() const; - void setA(GLubyte alpha); - GLubyte getA() const; - void setAlpha(GLubyte alpha); - GLubyte getAlpha() const; cocos2d::ccColor3B to3B() const; cocos2d::ccColor4B to4B() const; cocos2d::ccColor4F to4F() const; @@ -85,7 +69,7 @@ namespace geode { inline void applyTo(T* node) const { if constexpr (CanSetColor3B) { node->setColor(this->to3B()); - node->setOpacity(this->getA()); + node->setOpacity(a); } else if constexpr (CanSetColor4B) { node->setColor(this->to4B()); } else { diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index 196c67aab..561e66517 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -176,70 +176,6 @@ void Color::applyHSV(const HSV& hsv) { *this = currentHSV; } -void Color::setR(GLubyte red) { - r = red; -} - -GLubyte Color::getR() const { - return r; -} - -void Color::setRed(GLubyte red) { - r = red; -} - -GLubyte Color::getRed() const { - return r; -} - -void Color::setG(GLubyte green) { - g = green; -} - -GLubyte Color::getG() const { - return g; -} - -void Color::setGreen(GLubyte green) { - g = green; -} - -GLubyte Color::getGreen() const { - return g; -} - -void Color::setB(GLubyte blue) { - b = blue; -} - -GLubyte Color::getB() const { - return b; -} - -void Color::setBlue(GLubyte blue) { - b = blue; -} - -GLubyte Color::getBlue() const { - return b; -} - -void Color::setA(GLubyte alpha) { - a = alpha; -} - -GLubyte Color::getA() const { - return a; -} - -void Color::setAlpha(GLubyte alpha) { - a = alpha; -} - -GLubyte Color::getAlpha() const { - return a; -} - ccColor3B Color::to3B() const { return *this; } From 79d78cace5390d2560077f36177d090431f7c1f1 Mon Sep 17 00:00:00 2001 From: SMJS Date: Wed, 8 Jul 2026 15:29:17 +0200 Subject: [PATCH 14/14] Refactored isTransparent to isTranslucent --- loader/include/Geode/utils/ColorCode.hpp | 2 +- loader/src/utils/ColorCode.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/loader/include/Geode/utils/ColorCode.hpp b/loader/include/Geode/utils/ColorCode.hpp index 63498a281..7c57d4f0e 100644 --- a/loader/include/Geode/utils/ColorCode.hpp +++ b/loader/include/Geode/utils/ColorCode.hpp @@ -62,7 +62,7 @@ namespace geode { /// @returns True if alpha is 0 bool isInvisible() const; /// @returns True if alpha is bigger than 0 and smaller than 255 - bool isTransparent() const; + bool isTranslucent() const; /// @returns True if alpha is 255 bool isOpaque() const; template requires (CanSetColor3B || CanSetColor4B || CanSetColor4F) diff --git a/loader/src/utils/ColorCode.cpp b/loader/src/utils/ColorCode.cpp index 561e66517..c0d7a3f62 100644 --- a/loader/src/utils/ColorCode.cpp +++ b/loader/src/utils/ColorCode.cpp @@ -196,7 +196,7 @@ bool Color::isInvisible() const { return a == 0x00; } -bool Color::isTransparent() const { +bool Color::isTranslucent() const { return a > 0x00 && a < 0xFF; }