diff --git a/src/messages/layouts/MessageLayoutElement.cpp b/src/messages/layouts/MessageLayoutElement.cpp index fba47287603..e1df2170670 100644 --- a/src/messages/layouts/MessageLayoutElement.cpp +++ b/src/messages/layouts/MessageLayoutElement.cpp @@ -1,4 +1,5 @@ #include "messages/layouts/MessageLayoutElement.hpp" +#include #include "Application.hpp" #include "messages/Emote.hpp" @@ -11,6 +12,11 @@ #include #include +#include +#include +#include +#include + namespace chatterino { const QRect &MessageLayoutElement::getRect() const @@ -244,6 +250,219 @@ int TextLayoutElement::getSelectionIndexCount() const return this->getText().length() + (this->trailingSpace ? 1 : 0); } +template +constexpr inline static T lerp(T a, T b, M t) +{ + return a + (b - a) * t; +} + +union ColorUnion { + std::int32_t c; + struct { + std::uint8_t a; + std::uint8_t b; + std::uint8_t g; + std::uint8_t r; + }; + + [[nodiscard]] operator QColor() const + { + return QColor{r, g, b, a}; + } + + [[nodiscard]] constexpr inline static ColorUnion fromRGBA( + decltype(r) r, decltype(g) g, decltype(b) b, decltype(a) a = 255) + { + return { + .a = a, + .b = b, + .g = g, + .r = r, + }; + } + + [[nodiscard]] inline static ColorUnion fromQColor(QColor const &qcolor) + { + return { + .a = static_cast(qcolor.alpha()), + .b = static_cast(qcolor.blue()), + .g = static_cast(qcolor.green()), + .r = static_cast(qcolor.red()), + }; + } + + template + [[nodiscard]] constexpr inline ColorUnion mix(ColorUnion const &other, + T t) const + { + return { + // .a = lerp(a, other.a, t), + .a = 255, + .b = lerp(b, other.b, t), + .g = lerp(g, other.g, t), + .r = lerp(r, other.r, t), + }; + } + + [[nodiscard]] constexpr inline ColorUnion mix(ColorUnion const &other) const + { + const float t{static_cast(other.a) / 255.0f}; + return mix(other, t); + } +}; + +struct SevenTVGradient { + std::string function{"linear-gradient"}; + std::optional color{{0}}; + std::vector> stops{}; + bool repeat{false}; + float angle{0.0f}; + + [[nodiscard]] inline QPen asPen(ColorUnion const &userColor, + bool overlay = false) const + { + if (function != "linear-gradient") + throw std::runtime_error( + "Only linear-gradient is supported for now."); + + constexpr float pi = 3.141592653589793238462643383279502884f; + float cosRotation{std::cos(angle * pi / 180.0f)}; + float sinRotation{std::sin(angle * pi / 180.0f)}; + + auto scale{repeat ? 0.25f : 1.0f}; + QLinearGradient gradient{ + QPointF{std::clamp(sinRotation, -1.0f, 0.0f) * -1.0f, + std::clamp(cosRotation, -1.0f, 0.0f) * -1.0f} * + scale, + QPointF{std::clamp(sinRotation, 0.0f, 1.0f), + std::clamp(cosRotation, 0.0f, 1.0f)} * + scale, + }; + + gradient.setCoordinateMode(QGradient::ObjectMode); + gradient.setSpread(repeat ? QGradient::Spread::RepeatSpread + : QGradient::Spread::PadSpread); + + auto baseColor{color.value_or(userColor)}; + for (auto const &[t, c] : stops) + gradient.setColorAt(t, overlay ? c : baseColor.mix(c)); + + QBrush brush{gradient}; + + QPen pen{}; + pen.setBrush(brush); + return pen; + } +}; + +std::unordered_map demoGradients{ + { + "Sunrise", + { + .function = "linear-gradient", + .color = {{-1441816321}}, + .stops = + { + {0.0f, {-5610241}}, + {0.5f, {-1608944641}}, + {1.0f, {1175096575}}, + }, + .repeat = false, + .angle = 90.0f, + }, + }, + + { + "Metallic", + { + .function = "linear-gradient", + .color = std::nullopt, + .stops = + { + {0.01f, {2105376127}}, + {0.15f, {-421075301}}, + {0.3f, {-1330597761}}, + }, + .repeat = true, + .angle = 45.0f, + }, + }, + + { + "Warm Winds", + { + .function = "linear-gradient", + .color = {{-16734977}}, + .stops = + { + {0.0f, {690960127}}, + {0.3f, {1322108159}}, + {0.5f, {-134219777}}, + {0.7f, {-9737217}}, + {0.99f, {-1675777}}, + }, + .repeat = false, + .angle = 90.0f, + }, + }, + + { + "Egg Hunt", + { + .function = "linear-gradient", + .color = std::nullopt, + .stops = + { + {0.0f, {-172425217}}, + {0.36f, {-1442841601}}, + {0.71f, {-2130935809}}, + {0.98f, {-83902721}}, + }, + .repeat = false, + .angle = 90.0f, + }, + }, + + { + "Bubblegum", + { + .function = "linear-gradient", + .color = std::nullopt, + .stops = + { + {0.01f, {64}}, + {0.25f, {1347440704}}, + {0.25f, {-2105376192}}, + {0.35f, {-926365632}}, + {0.35f, {-13323265}}, + {0.75f, {-13323265}}, + {0.75f, {1450243583}}, + {0.99f, {1450243583}}, + }, + .repeat = false, + .angle = 65.0f, + }, + }, + + { + "Anniversary", + { + .function = "linear-gradient", + .color = {{276859903}}, + .stops = + { + {0.0f, {16774655}}, + {0.25f, {-1426326529}}, + {0.5f, {1289014527}}, + {0.75f, {77314815}}, + {0.99f, {276859903}}, + }, + .repeat = false, + .angle = 90.0f, + }, + }, +}; + void TextLayoutElement::paint(QPainter &painter) { auto app = getApp(); @@ -252,6 +471,18 @@ void TextLayoutElement::paint(QPainter &painter) painter.setFont(app->fonts->getFont(this->style_, this->scale_)); + // Grab a 'unique' gradient for demonstration + auto gradientIterator{demoGradients.begin()}; + std::advance(gradientIterator, + (this->getText().length() > 0) + ? (this->getText().toStdString()[0] % demoGradients.size()) + : 0); + auto const &gradient{(*gradientIterator).second}; + + auto userColor{ColorUnion::fromQColor(color_)}; + if (this->getLink().type == chatterino::Link::UserInfo) + painter.setPen(gradient.asPen(userColor)); + painter.drawText( QRectF(this->getRect().x(), this->getRect().y(), 10000, 10000), this->getText(), QTextOption(Qt::AlignLeft | Qt::AlignTop));