From 6c3c444136d6f87d3cd9610b12e45e4c2130ef3a Mon Sep 17 00:00:00 2001 From: Maximilian Seidler <78690852+PaideiaDilemma@users.noreply.github.com> Date: Sat, 9 Nov 2024 17:54:44 +0100 Subject: [PATCH] core: add border shader and border gradients (#548) * renderer: add renderBorder function * config: add CGradientValueData from Hyprland * input-field: change outer to take gradients Added gradient support to the following color options: - `outer_color` - `fail_color` - `check_color` - `capslock_color` - `numlock_color` - `bothlock_color`` * image: add gradient border * shape: add gradient border * shaders: adapt the new rounded smoothing factor from Hyprland --- src/config/ConfigDataValues.hpp | 65 +++++++++- src/config/ConfigManager.cpp | 96 ++++++++++++-- src/helpers/MiscFunctions.cpp | 44 +++++++ src/helpers/MiscFunctions.hpp | 1 + src/renderer/Renderer.cpp | 54 ++++++++ src/renderer/Renderer.hpp | 4 +- src/renderer/Shaders.hpp | 135 +++++++++++++++++++- src/renderer/widgets/Image.cpp | 5 +- src/renderer/widgets/Image.hpp | 3 +- src/renderer/widgets/PasswordInputField.cpp | 108 ++++++++++------ src/renderer/widgets/PasswordInputField.hpp | 37 +++--- src/renderer/widgets/Shape.cpp | 28 ++-- src/renderer/widgets/Shape.hpp | 31 ++--- 13 files changed, 499 insertions(+), 112 deletions(-) diff --git a/src/config/ConfigDataValues.hpp b/src/config/ConfigDataValues.hpp index 67e3dce..e09eed5 100644 --- a/src/config/ConfigDataValues.hpp +++ b/src/config/ConfigDataValues.hpp @@ -1,12 +1,19 @@ #pragma once #include "../helpers/Log.hpp" +#include "../helpers/Color.hpp" #include +#include #include #include +#include +#include + +using namespace Hyprutils::String; enum eConfigValueDataTypes { - CVD_TYPE_INVALID = -1, - CVD_TYPE_LAYOUT = 0, + CVD_TYPE_INVALID = -1, + CVD_TYPE_LAYOUT = 0, + CVD_TYPE_GRADIENT = 1, }; class ICustomConfigValueData { @@ -51,3 +58,57 @@ class CLayoutValueData : public ICustomConfigValueData { bool y = false; } m_sIsRelative; }; + +class CGradientValueData : public ICustomConfigValueData { + public: + CGradientValueData() {}; + CGradientValueData(CColor col) { + m_vColors.push_back(col); + }; + virtual ~CGradientValueData() {}; + + virtual eConfigValueDataTypes getDataType() { + return CVD_TYPE_GRADIENT; + } + + void reset(CColor col) { + m_vColors.clear(); + m_vColors.emplace_back(col); + m_fAngle = 0; + } + + /* Vector containing the colors */ + std::vector m_vColors; + + /* Float corresponding to the angle (rad) */ + float m_fAngle = 0; + + // + bool operator==(const CGradientValueData& other) const { + if (other.m_vColors.size() != m_vColors.size() || m_fAngle != other.m_fAngle) + return false; + + for (size_t i = 0; i < m_vColors.size(); ++i) + if (m_vColors[i] != other.m_vColors[i]) + return false; + + return true; + } + + virtual std::string toString() { + std::string result; + for (auto& c : m_vColors) { + result += std::format("{:x} ", c.getAsHex()); + } + + result += std::format("{}deg", (int)(m_fAngle * 180.0 / M_PI)); + return result; + } + + static CGradientValueData* fromAnyPv(const std::any& v) { + RASSERT(v.type() == typeid(void*), "Invalid config value type"); + const auto P = (CGradientValueData*)std::any_cast(v); + RASSERT(P, "Empty config value"); + return P; + } +}; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index 013f5f3..79cf164 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -2,6 +2,7 @@ #include "../helpers/MiscFunctions.hpp" #include "../helpers/Log.hpp" #include "../config/ConfigDataValues.hpp" +#include #include #include #include @@ -69,6 +70,65 @@ static void configHandleLayoutOptionDestroy(void** data) { delete reinterpret_cast(*data); } +static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) { + std::string V = VALUE; + + if (!*data) + *data = new CGradientValueData(); + + const auto DATA = reinterpret_cast(*data); + + CVarList varlist(V, 0, ' '); + DATA->m_vColors.clear(); + + std::string parseError = ""; + + for (auto const& var : varlist) { + if (var.find("deg") != std::string::npos) { + // last arg + try { + DATA->m_fAngle = std::stoi(var.substr(0, var.find("deg"))) * (M_PI / 180.0); // radians + } catch (...) { + Debug::log(WARN, "Error parsing gradient {}", V); + parseError = "Error parsing gradient " + V; + } + + break; + } + + if (DATA->m_vColors.size() >= 10) { + Debug::log(WARN, "Error parsing gradient {}: max colors is 10.", V); + parseError = "Error parsing gradient " + V + ": max colors is 10."; + break; + } + + try { + DATA->m_vColors.push_back(CColor(configStringToInt(var))); + } catch (std::exception& e) { + Debug::log(WARN, "Error parsing gradient {}", V); + parseError = "Error parsing gradient " + V + ": " + e.what(); + } + } + + if (DATA->m_vColors.size() == 0) { + Debug::log(WARN, "Error parsing gradient {}", V); + parseError = "Error parsing gradient " + V + ": No colors?"; + + DATA->m_vColors.push_back(0); // transparent + } + + Hyprlang::CParseResult result; + if (!parseError.empty()) + result.setError(parseError.c_str()); + + return result; +} + +static void configHandleGradientDestroy(void** data) { + if (*data) + delete reinterpret_cast(*data); +} + static std::string getMainConfigPath() { static const auto paths = Hyprutils::Path::findConfig("hyprlock"); if (paths.first.has_value()) @@ -82,6 +142,14 @@ CConfigManager::CConfigManager(std::string configPath) : configCurrentPath = configPath.empty() ? getMainConfigPath() : configPath; } +inline static constexpr auto GRADIENTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE { + return Hyprlang::CUSTOMTYPE{&configHandleGradientSet, configHandleGradientDestroy, default_value}; +}; + +inline static constexpr auto LAYOUTCONFIG = [](const char* default_value) -> Hyprlang::CUSTOMTYPE { + return Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, default_value}; +}; + void CConfigManager::init() { #define SHADOWABLE(name) \ @@ -118,12 +186,12 @@ void CConfigManager::init() { m_config.addSpecialCategory("shape", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("shape", "monitor", Hyprlang::STRING{""}); - m_config.addSpecialConfigValue("shape", "size", Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, "100,100"}); + m_config.addSpecialConfigValue("shape", "size", LAYOUTCONFIG("100,100")); m_config.addSpecialConfigValue("shape", "rounding", Hyprlang::INT{0}); m_config.addSpecialConfigValue("shape", "border_size", Hyprlang::INT{0}); - m_config.addSpecialConfigValue("shape", "border_color", Hyprlang::INT{0xFF00CFE6}); + m_config.addSpecialConfigValue("shape", "border_color", GRADIENTCONFIG("0xFF00CFE6")); m_config.addSpecialConfigValue("shape", "color", Hyprlang::INT{0xFF111111}); - m_config.addSpecialConfigValue("shape", "position", Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, "0,0"}); + m_config.addSpecialConfigValue("shape", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("shape", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("shape", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("shape", "rotate", Hyprlang::FLOAT{0}); @@ -137,8 +205,8 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("image", "size", Hyprlang::INT{150}); m_config.addSpecialConfigValue("image", "rounding", Hyprlang::INT{-1}); m_config.addSpecialConfigValue("image", "border_size", Hyprlang::INT{4}); - m_config.addSpecialConfigValue("image", "border_color", Hyprlang::INT{0xFFDDDDDD}); - m_config.addSpecialConfigValue("image", "position", Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, "0,0"}); + m_config.addSpecialConfigValue("image", "border_color", GRADIENTCONFIG("0xFFDDDDDD")); + m_config.addSpecialConfigValue("image", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("image", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "valign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("image", "rotate", Hyprlang::FLOAT{0}); @@ -149,9 +217,9 @@ void CConfigManager::init() { m_config.addSpecialCategory("input-field", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("input-field", "monitor", Hyprlang::STRING{""}); - m_config.addSpecialConfigValue("input-field", "size", Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, "400,90"}); + m_config.addSpecialConfigValue("input-field", "size", LAYOUTCONFIG("400,90")); m_config.addSpecialConfigValue("input-field", "inner_color", Hyprlang::INT{0xFFDDDDDD}); - m_config.addSpecialConfigValue("input-field", "outer_color", Hyprlang::INT{0xFF111111}); + m_config.addSpecialConfigValue("input-field", "outer_color", GRADIENTCONFIG("0xFF111111")); m_config.addSpecialConfigValue("input-field", "outline_thickness", Hyprlang::INT{4}); m_config.addSpecialConfigValue("input-field", "dots_size", Hyprlang::FLOAT{0.25}); m_config.addSpecialConfigValue("input-field", "dots_center", Hyprlang::INT{1}); @@ -165,18 +233,18 @@ void CConfigManager::init() { m_config.addSpecialConfigValue("input-field", "font_family", Hyprlang::STRING{"Sans"}); m_config.addSpecialConfigValue("input-field", "halign", Hyprlang::STRING{"center"}); m_config.addSpecialConfigValue("input-field", "valign", Hyprlang::STRING{"center"}); - m_config.addSpecialConfigValue("input-field", "position", Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, "0,0"}); + m_config.addSpecialConfigValue("input-field", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("input-field", "placeholder_text", Hyprlang::STRING{"Input Password"}); m_config.addSpecialConfigValue("input-field", "hide_input", Hyprlang::INT{0}); m_config.addSpecialConfigValue("input-field", "rounding", Hyprlang::INT{-1}); - m_config.addSpecialConfigValue("input-field", "check_color", Hyprlang::INT{0xFFCC8822}); - m_config.addSpecialConfigValue("input-field", "fail_color", Hyprlang::INT{0xFFCC2222}); + m_config.addSpecialConfigValue("input-field", "check_color", GRADIENTCONFIG("0xFF22CC88")); + m_config.addSpecialConfigValue("input-field", "fail_color", GRADIENTCONFIG("0xFFCC2222")); m_config.addSpecialConfigValue("input-field", "fail_text", Hyprlang::STRING{"$FAIL"}); m_config.addSpecialConfigValue("input-field", "fail_timeout", Hyprlang::INT{2000}); m_config.addSpecialConfigValue("input-field", "fail_transition", Hyprlang::INT{300}); - m_config.addSpecialConfigValue("input-field", "capslock_color", Hyprlang::INT{-1}); - m_config.addSpecialConfigValue("input-field", "numlock_color", Hyprlang::INT{-1}); - m_config.addSpecialConfigValue("input-field", "bothlock_color", Hyprlang::INT{-1}); + m_config.addSpecialConfigValue("input-field", "capslock_color", GRADIENTCONFIG("")); + m_config.addSpecialConfigValue("input-field", "numlock_color", GRADIENTCONFIG("")); + m_config.addSpecialConfigValue("input-field", "bothlock_color", GRADIENTCONFIG("")); m_config.addSpecialConfigValue("input-field", "invert_numlock", Hyprlang::INT{0}); m_config.addSpecialConfigValue("input-field", "swap_font_color", Hyprlang::INT{0}); m_config.addSpecialConfigValue("input-field", "zindex", Hyprlang::INT{0}); @@ -184,7 +252,7 @@ void CConfigManager::init() { m_config.addSpecialCategory("label", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true}); m_config.addSpecialConfigValue("label", "monitor", Hyprlang::STRING{""}); - m_config.addSpecialConfigValue("label", "position", Hyprlang::CUSTOMTYPE{&configHandleLayoutOption, configHandleLayoutOptionDestroy, "0,0"}); + m_config.addSpecialConfigValue("label", "position", LAYOUTCONFIG("0,0")); m_config.addSpecialConfigValue("label", "color", Hyprlang::INT{0xFFFFFFFF}); m_config.addSpecialConfigValue("label", "font_size", Hyprlang::INT{16}); m_config.addSpecialConfigValue("label", "text", Hyprlang::STRING{"Sample Text"}); diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index e0d7b07..967cdd3 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -1,5 +1,9 @@ #include #include "MiscFunctions.hpp" +#include "../helpers/Log.hpp" +#include + +using namespace Hyprutils::String; std::string absolutePath(const std::string& rawpath, const std::string& currentDir) { std::filesystem::path path(rawpath); @@ -15,4 +19,44 @@ std::string absolutePath(const std::string& rawpath, const std::string& currentD } else { return std::filesystem::weakly_canonical(path); } +} + +int64_t configStringToInt(const std::string& VALUE) { + if (VALUE.starts_with("0x")) { + // Values with 0x are hex + const auto VALUEWITHOUTHEX = VALUE.substr(2); + return stol(VALUEWITHOUTHEX, nullptr, 16); + } else if (VALUE.starts_with("rgba(") && VALUE.ends_with(')')) { + const auto VALUEWITHOUTFUNC = VALUE.substr(5, VALUE.length() - 6); + + if (trim(VALUEWITHOUTFUNC).length() != 8) { + Debug::log(WARN, "invalid length {} for rgba", VALUEWITHOUTFUNC.length()); + throw std::invalid_argument("rgba() expects length of 8 characters (4 bytes)"); + } + + const auto RGBA = std::stol(VALUEWITHOUTFUNC, nullptr, 16); + + // now we need to RGBA -> ARGB. The config holds ARGB only. + return (RGBA >> 8) + 0x1000000 * (RGBA & 0xFF); + } else if (VALUE.starts_with("rgb(") && VALUE.ends_with(')')) { + const auto VALUEWITHOUTFUNC = VALUE.substr(4, VALUE.length() - 5); + + if (trim(VALUEWITHOUTFUNC).length() != 6) { + Debug::log(WARN, "invalid length {} for rgb", VALUEWITHOUTFUNC.length()); + throw std::invalid_argument("rgb() expects length of 6 characters (3 bytes)"); + } + + const auto RGB = std::stol(VALUEWITHOUTFUNC, nullptr, 16); + + return RGB + 0xFF000000; // 0xFF for opaque + } else if (VALUE.starts_with("true") || VALUE.starts_with("on") || VALUE.starts_with("yes")) { + return 1; + } else if (VALUE.starts_with("false") || VALUE.starts_with("off") || VALUE.starts_with("no")) { + return 0; + } + + if (VALUE.empty() || !isNumber(VALUE)) + return 0; + + return std::stoll(VALUE); } \ No newline at end of file diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index 13aa1b0..7441a16 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -5,3 +5,4 @@ #include std::string absolutePath(const std::string&, const std::string&); +int64_t configStringToInt(const std::string& VALUE); diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index 42e3d9c..68e7529 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -150,6 +150,23 @@ CRenderer::CRenderer() { blurFinishShader.colorizeTint = glGetUniformLocation(prog, "colorizeTint"); blurFinishShader.boostA = glGetUniformLocation(prog, "boostA"); + prog = createProgram(QUADVERTSRC, FRAGBORDER); + borderShader.program = prog; + borderShader.proj = glGetUniformLocation(prog, "proj"); + borderShader.thick = glGetUniformLocation(prog, "thick"); + borderShader.posAttrib = glGetAttribLocation(prog, "pos"); + borderShader.texAttrib = glGetAttribLocation(prog, "texcoord"); + borderShader.topLeft = glGetUniformLocation(prog, "topLeft"); + borderShader.bottomRight = glGetUniformLocation(prog, "bottomRight"); + borderShader.fullSize = glGetUniformLocation(prog, "fullSize"); + borderShader.fullSizeUntransformed = glGetUniformLocation(prog, "fullSizeUntransformed"); + borderShader.radius = glGetUniformLocation(prog, "radius"); + borderShader.radiusOuter = glGetUniformLocation(prog, "radiusOuter"); + borderShader.gradient = glGetUniformLocation(prog, "gradient"); + borderShader.gradientLength = glGetUniformLocation(prog, "gradientLength"); + borderShader.angle = glGetUniformLocation(prog, "angle"); + borderShader.alpha = glGetUniformLocation(prog, "alpha"); + asyncResourceGatherer = std::make_unique(); } @@ -250,6 +267,43 @@ void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) { glDisableVertexAttribArray(rectShader.posAttrib); } +void CRenderer::renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding, float alpha) { + Mat3x3 matrix = projMatrix.projectBox(box, HYPRUTILS_TRANSFORM_NORMAL, box.rot); + Mat3x3 glMatrix = projection.copy().multiply(matrix); + + glUseProgram(borderShader.program); + + glUniformMatrix3fv(borderShader.proj, 1, GL_TRUE, glMatrix.getMatrix().data()); + + static_assert(sizeof(CColor) == 4 * sizeof(float)); // otherwise the line below this will fail + + glUniform4fv(borderShader.gradient, gradient.m_vColors.size(), (float*)gradient.m_vColors.data()); + glUniform1i(borderShader.gradientLength, gradient.m_vColors.size()); + glUniform1f(borderShader.angle, (int)(gradient.m_fAngle / (M_PI / 180.0)) % 360 * (M_PI / 180.0)); + glUniform1f(borderShader.alpha, alpha); + + const auto TOPLEFT = Vector2D(box.x, box.y); + const auto FULLSIZE = Vector2D(box.width, box.height); + + glUniform2f(borderShader.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y); + glUniform2f(borderShader.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y); + glUniform2f(borderShader.fullSizeUntransformed, (float)box.width, (float)box.height); + glUniform1f(borderShader.radius, rounding); + glUniform1f(borderShader.radiusOuter, rounding); + glUniform1f(borderShader.thick, thickness); + + glVertexAttribPointer(borderShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + glVertexAttribPointer(borderShader.texAttrib, 2, GL_FLOAT, GL_FALSE, 0, fullVerts); + + glEnableVertexAttribArray(borderShader.posAttrib); + glEnableVertexAttribArray(borderShader.texAttrib); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + glDisableVertexAttribArray(borderShader.posAttrib); + glDisableVertexAttribArray(borderShader.texAttrib); +} + void CRenderer::renderTexture(const CBox& box, const CTexture& tex, float a, int rounding, std::optional tr) { Mat3x3 matrix = projMatrix.projectBox(box, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot); Mat3x3 glMatrix = projection.copy().multiply(matrix); diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index 8418ca5..55501d7 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -6,8 +6,8 @@ #include "Shader.hpp" #include "../core/LockSurface.hpp" #include "../helpers/Color.hpp" -#include "../helpers/Math.hpp" #include "AsyncResourceGatherer.hpp" +#include "../config/ConfigDataValues.hpp" #include "widgets/IWidget.hpp" #include "Framebuffer.hpp" @@ -31,6 +31,7 @@ class CRenderer { SRenderFeedback renderLock(const CSessionLockSurface& surface); void renderRect(const CBox& box, const CColor& col, int rounding = 0); + void renderBorder(const CBox& box, const CGradientValueData& gradient, int thickness, int rounding = 0, float alpha = 1.0); void renderTexture(const CBox& box, const CTexture& tex, float a = 1.0, int rounding = 0, std::optional tr = {}); void blurFB(const CFramebuffer& outfb, SBlurParams params); @@ -53,6 +54,7 @@ class CRenderer { CShader blurShader2; CShader blurPrepareShader; CShader blurFinishShader; + CShader borderShader; Mat3x3 projMatrix = Mat3x3::identity(); Mat3x3 projection; diff --git a/src/renderer/Shaders.hpp b/src/renderer/Shaders.hpp index e5c6b35..9a2f417 100644 --- a/src/renderer/Shaders.hpp +++ b/src/renderer/Shaders.hpp @@ -1,6 +1,10 @@ #pragma once #include +#include +#include + +constexpr float SHADER_ROUNDED_SMOOTHING_FACTOR = M_PI / 5.34665792551; inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVarName) -> std::string { return R"#( @@ -12,17 +16,21 @@ inline static constexpr auto ROUNDED_SHADER_FUNC = [](const std::string colorVar pixCoord -= fullSize * 0.5 - radius; pixCoord += vec2(1.0, 1.0) / fullSize; // center the pix dont make it top-left + // smoothing constant for the edge: more = blurrier, but smoother + const float SMOOTHING_CONSTANT = )#" + + std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(; + if (pixCoord.x + pixCoord.y > radius) { float dist = length(pixCoord); - if (dist > radius + 1.0) + if (dist > radius + SMOOTHING_CONSTANT * 2.0) discard; - if (dist > radius - 1.0) { + if (dist > radius - SMOOTHING_CONSTANT * 2.0) { float dist = length(pixCoord); - float normalized = 1.0 - smoothstep(0.0, 1.0, dist - radius + 0.5); + float normalized = 1.0 - smoothstep(0.0, 1.0, (dist - radius + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); )#" + colorVarName + R"#( = )#" + colorVarName + R"#( * normalized; @@ -361,4 +369,125 @@ void main() { gl_FragColor = pixColor; } +)#"; + +// makes a stencil without corners +inline const std::string FRAGBORDER = R"#( +precision highp float; +varying vec4 v_color; +varying vec2 v_texcoord; + +uniform vec2 topLeft; +uniform vec2 fullSize; +uniform vec2 fullSizeUntransformed; +uniform float radius; +uniform float radiusOuter; +uniform float thick; + +uniform vec4 gradient[10]; +uniform int gradientLength; +uniform float angle; +uniform float alpha; + +vec4 getColorForCoord(vec2 normalizedCoord) { + if (gradientLength < 2) + return gradient[0]; + + float finalAng = 0.0; + + if (angle > 4.71 /* 270 deg */) { + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = 6.28 - angle; + } else if (angle > 3.14 /* 180 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + normalizedCoord[1] = 1.0 - normalizedCoord[1]; + finalAng = angle - 3.14; + } else if (angle > 1.57 /* 90 deg */) { + normalizedCoord[0] = 1.0 - normalizedCoord[0]; + finalAng = 3.14 - angle; + } else { + finalAng = angle; + } + + float sine = sin(finalAng); + + float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradientLength - 1); + int bottom = int(floor(progress)); + int top = bottom + 1; + + return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress); +} + +void main() { + + highp vec2 pixCoord = vec2(gl_FragCoord); + highp vec2 pixCoordOuter = pixCoord; + highp vec2 originalPixCoord = v_texcoord; + originalPixCoord *= fullSizeUntransformed; + float additionalAlpha = 1.0; + + vec4 pixColor = vec4(1.0, 1.0, 1.0, 1.0); + + bool done = false; + + pixCoord -= topLeft + fullSize * 0.5; + pixCoord *= vec2(lessThan(pixCoord, vec2(0.0))) * -2.0 + 1.0; + pixCoordOuter = pixCoord; + pixCoord -= fullSize * 0.5 - radius; + pixCoordOuter -= fullSize * 0.5 - radiusOuter; + + // center the pixes dont make it top-left + pixCoord += vec2(1.0, 1.0) / fullSize; + pixCoordOuter += vec2(1.0, 1.0) / fullSize; + + if (min(pixCoord.x, pixCoord.y) > 0.0 && radius > 0.0) { + // smoothing constant for the edge: more = blurrier, but smoother + const float SMOOTHING_CONSTANT = )#" + + std::format("{:.7f}", SHADER_ROUNDED_SMOOTHING_FACTOR) + R"#(; + + float dist = length(pixCoord); + float distOuter = length(pixCoordOuter); + float h = (thick / 2.0); + + if (dist < radius - h) { + // lower + float normalized = smoothstep(0.0, 1.0, (dist - radius + thick + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + additionalAlpha *= normalized; + done = true; + } else if (min(pixCoordOuter.x, pixCoordOuter.y) > 0.0) { + // higher + float normalized = 1.0 - smoothstep(0.0, 1.0, (distOuter - radiusOuter + SMOOTHING_CONSTANT) / (SMOOTHING_CONSTANT * 2.0)); + additionalAlpha *= normalized; + done = true; + } else if (distOuter < radiusOuter - h) { + additionalAlpha = 1.0; + done = true; + } + } + + // now check for other shit + if (!done) { + // distance to all straight bb borders + float distanceT = originalPixCoord[1]; + float distanceB = fullSizeUntransformed[1] - originalPixCoord[1]; + float distanceL = originalPixCoord[0]; + float distanceR = fullSizeUntransformed[0] - originalPixCoord[0]; + + // get the smallest + float smallest = min(min(distanceT, distanceB), min(distanceL, distanceR)); + + if (smallest > thick) + discard; + } + + if (additionalAlpha == 0.0) + discard; + + pixColor = getColorForCoord(v_texcoord); + pixColor.rgb *= pixColor[3]; + + pixColor *= alpha * additionalAlpha; + + gl_FragColor = pixColor; +} )#"; \ No newline at end of file diff --git a/src/renderer/widgets/Image.cpp b/src/renderer/widgets/Image.cpp index 99befdf..8550826 100644 --- a/src/renderer/widgets/Image.cpp +++ b/src/renderer/widgets/Image.cpp @@ -86,7 +86,7 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r size = std::any_cast(props.at("size")); rounding = std::any_cast(props.at("rounding")); border = std::any_cast(props.at("border_size")); - color = std::any_cast(props.at("border_color")); + color = *CGradientValueData::fromAnyPv(props.at("border_color")); pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_); halign = std::any_cast(props.at("halign")); valign = std::any_cast(props.at("valign")); @@ -156,7 +156,8 @@ bool CImage::draw(const SRenderData& data) { glClear(GL_COLOR_BUFFER_BIT); if (border > 0) - g_pRenderer->renderRect(borderBox, color, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : std::min(borderBox.w, borderBox.h) / 2.0); + g_pRenderer->renderBorder(borderBox, color, border, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : std::min(borderBox.w, borderBox.h) / 2.0, + data.opacity); texbox.round(); g_pRenderer->renderTexture(texbox, asset->texture, 1.0, ALLOWROUND ? rounding : std::min(texbox.w, texbox.h) / 2.0, HYPRUTILS_TRANSFORM_NORMAL); diff --git a/src/renderer/widgets/Image.hpp b/src/renderer/widgets/Image.hpp index f12e1d8..1831efb 100644 --- a/src/renderer/widgets/Image.hpp +++ b/src/renderer/widgets/Image.hpp @@ -3,6 +3,7 @@ #include "IWidget.hpp" #include "../../helpers/Color.hpp" #include "../../helpers/Math.hpp" +#include "../../config/ConfigDataValues.hpp" #include "../../core/Timer.hpp" #include "../AsyncResourceGatherer.hpp" #include "Shadowable.hpp" @@ -32,7 +33,7 @@ class CImage : public IWidget { int rounding; double border; double angle; - CColor color; + CGradientValueData color; Vector2D pos; std::string halign, valign, path; diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index 7f81ae8..948a62e 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -33,14 +33,14 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u configFailTimeoutMs = std::any_cast(props.at("fail_timeout")); fontFamily = std::any_cast(props.at("font_family")); colorConfig.transitionMs = std::any_cast(props.at("fail_transition")); - colorConfig.outer = std::any_cast(props.at("outer_color")); + colorConfig.outer = CGradientValueData::fromAnyPv(props.at("outer_color")); colorConfig.inner = std::any_cast(props.at("inner_color")); colorConfig.font = std::any_cast(props.at("font_color")); - colorConfig.fail = std::any_cast(props.at("fail_color")); - colorConfig.check = std::any_cast(props.at("check_color")); - colorConfig.both = std::any_cast(props.at("bothlock_color")); - colorConfig.caps = std::any_cast(props.at("capslock_color")); - colorConfig.num = std::any_cast(props.at("numlock_color")); + colorConfig.fail = CGradientValueData::fromAnyPv(props.at("fail_color")); + colorConfig.check = CGradientValueData::fromAnyPv(props.at("check_color")); + colorConfig.both = CGradientValueData::fromAnyPv(props.at("bothlock_color")); + colorConfig.caps = CGradientValueData::fromAnyPv(props.at("capslock_color")); + colorConfig.num = CGradientValueData::fromAnyPv(props.at("numlock_color")); colorConfig.invertNum = std::any_cast(props.at("invert_numlock")); colorConfig.swapFont = std::any_cast(props.at("swap_font_color")); } catch (const std::bad_any_cast& e) { @@ -57,13 +57,14 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u dots.spacing = std::clamp(dots.spacing, -1.f, 1.f); colorConfig.transitionMs = std::clamp(colorConfig.transitionMs, 0, 1000); - colorConfig.both = colorConfig.both == -1 ? colorConfig.outer : colorConfig.both; - colorConfig.caps = colorConfig.caps == -1 ? colorConfig.outer : colorConfig.caps; - colorConfig.num = colorConfig.num == -1 ? colorConfig.outer : colorConfig.num; + colorConfig.both = colorConfig.both->m_vColors.empty() ? colorConfig.outer : colorConfig.both; + colorConfig.caps = colorConfig.caps->m_vColors.empty() ? colorConfig.outer : colorConfig.caps; + colorConfig.num = colorConfig.num->m_vColors.empty() ? colorConfig.outer : colorConfig.num; - colorState.inner = colorConfig.inner; - colorState.outer = colorConfig.outer; - colorState.font = colorConfig.font; + colorState.inner = colorConfig.inner; + colorState.outer = *colorConfig.outer; + colorState.font = colorConfig.font; + colorState.outerSource = colorConfig.outer; if (!dots.textFormat.empty()) { dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat); @@ -218,15 +219,18 @@ bool CPasswordInputField::draw(const SRenderData& data) { shadowData.opacity *= fade.a; shadow.draw(shadowData); - CColor outerCol = colorState.outer; - outerCol.a *= fade.a * data.opacity; + CGradientValueData outerGrad = colorState.outer; + for (auto& c : outerGrad.m_vColors) + c.a *= fade.a * data.opacity; + CColor innerCol = colorState.inner; innerCol.a *= fade.a * data.opacity; CColor fontCol = colorState.font; fontCol.a *= fade.a * data.opacity; if (outThick > 0) { - g_pRenderer->renderRect(outerBox, outerCol, rounding == -1 ? outerBox.h / 2.0 : rounding); + const auto OUTERROUND = rounding == -1 ? outerBox.h / 2.0 : rounding; + g_pRenderer->renderBorder(outerBox, outerGrad, outThick, OUTERROUND, fade.a * data.opacity); if (passwordLength != 0 && hiddenInputState.enabled && !fade.animated && data.opacity == 1.0) { CBox outerBoxScaled = outerBox; @@ -238,13 +242,13 @@ bool CPasswordInputField::draw(const SRenderData& data) { outerBoxScaled.x += outerBoxScaled.w; glEnable(GL_SCISSOR_TEST); glScissor(outerBoxScaled.x, outerBoxScaled.y, outerBoxScaled.w, outerBoxScaled.h); - g_pRenderer->renderRect(outerBox, hiddenInputState.lastColor, rounding == -1 ? outerBox.h / 2.0 : rounding); + g_pRenderer->renderBorder(outerBox, hiddenInputState.lastColor, outThick, OUTERROUND, fade.a * data.opacity); glScissor(0, 0, viewport.x, viewport.y); glDisable(GL_SCISSOR_TEST); } } - g_pRenderer->renderRect(inputFieldBox, innerCol, rounding == -1 ? inputFieldBox.h / 2.0 : rounding - outThick); + g_pRenderer->renderRect(inputFieldBox, innerCol, rounding == -1 ? inputFieldBox.h / 2.0 : rounding - outThick - 1); if (!hiddenInputState.enabled && !g_pHyprlock->m_bFadeStarted) { const int RECTPASSSIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f; @@ -434,6 +438,28 @@ static void changeColor(const CColor& source, const CColor& target, CColor& subj changeChannel(source.a, target.a, subject.a, multi, animated); } +static void changeGrad(CGradientValueData* psource, CGradientValueData* ptarget, CGradientValueData& subject, const double& multi, bool& animated) { + if (!psource || !ptarget) + return; + + subject.m_vColors.resize(ptarget->m_vColors.size(), subject.m_vColors.back()); + + for (size_t i = 0; i < subject.m_vColors.size(); ++i) { + const CColor& sourceCol = (i < psource->m_vColors.size()) ? psource->m_vColors[i] : psource->m_vColors.back(); + const CColor& targetCol = (i < ptarget->m_vColors.size()) ? ptarget->m_vColors[i] : ptarget->m_vColors.back(); + changeColor(sourceCol, targetCol, subject.m_vColors[i], multi, animated); + } + + if (psource->m_fAngle != ptarget->m_fAngle) { + const float DELTA = ptarget->m_fAngle - psource->m_fAngle; + subject.m_fAngle += DELTA * multi; + animated = true; + + if ((psource->m_fAngle < ptarget->m_fAngle && subject.m_fAngle > ptarget->m_fAngle) || (psource->m_fAngle > ptarget->m_fAngle && subject.m_fAngle < ptarget->m_fAngle)) + subject.m_fAngle = ptarget->m_fAngle; + } +} + void CPasswordInputField::updateColors() { const bool BORDERLESS = outThick == 0; const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock; @@ -442,51 +468,51 @@ void CPasswordInputField::updateColors() { std::clamp(std::chrono::duration_cast(std::chrono::system_clock::now() - colorState.lastFrame).count() / (double)colorConfig.transitionMs, 0.0016, 0.5); - CColor targetColor; + // + CGradientValueData* targetGrad = nullptr; - if (checkWaiting) { - targetColor = colorConfig.check; - } else if (displayFail) { - targetColor = colorConfig.fail; - } + if (checkWaiting) + targetGrad = colorConfig.check; + else if (displayFail) + targetGrad = colorConfig.fail; - if (g_pHyprlock->m_bCapsLock && NUMLOCK) { - targetColor = colorConfig.both; - } else if (g_pHyprlock->m_bCapsLock) { - targetColor = colorConfig.caps; - } else if (NUMLOCK) { - targetColor = colorConfig.num; - } + if (g_pHyprlock->m_bCapsLock && NUMLOCK) + targetGrad = colorConfig.both; + else if (g_pHyprlock->m_bCapsLock) + targetGrad = colorConfig.caps; + else if (NUMLOCK) + targetGrad = colorConfig.num; - CColor outerTarget = colorConfig.outer; - CColor innerTarget = colorConfig.inner; - CColor fontTarget = (displayFail) ? colorConfig.fail : colorConfig.font; + CGradientValueData* outerTarget = colorConfig.outer; + CColor innerTarget = colorConfig.inner; + CColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font; if (checkWaiting || displayFail || g_pHyprlock->m_bCapsLock || NUMLOCK) { if (BORDERLESS && colorConfig.swapFont) { - fontTarget = targetColor; + fontTarget = colorConfig.fail->m_vColors.front(); } else if (BORDERLESS && !colorConfig.swapFont) { - innerTarget = targetColor; + innerTarget = colorConfig.fail->m_vColors.front(); // When changing the inner color the font cannot be fail_color fontTarget = colorConfig.font; } else { - outerTarget = targetColor; + outerTarget = targetGrad; } } - if (targetColor != colorState.currentTarget) { - colorState.outerSource = colorState.outer; + if (targetGrad != colorState.currentTarget) { + colorState.outerSource = &colorState.outer; colorState.innerSource = colorState.inner; - colorState.currentTarget = targetColor; + colorState.currentTarget = targetGrad; } colorState.animated = false; - changeColor(colorState.outerSource, outerTarget, colorState.outer, MULTI, colorState.animated); + if (!BORDERLESS) + changeGrad(colorState.outerSource, outerTarget, colorState.outer, MULTI, colorState.animated); changeColor(colorState.innerSource, innerTarget, colorState.inner, MULTI, colorState.animated); - // Font color is only chaned, when `swap_font_color` is set to true and no boarder is present. + // Font color is only chaned, when `swap_font_color` is set to true and no border is present. // It is not animated, because that does not look good and we would need to rerender the text for each frame. colorState.font = fontTarget; diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index 1de7574..d83b533 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -5,6 +5,7 @@ #include "../../helpers/Math.hpp" #include "../../core/Timer.hpp" #include "Shadowable.hpp" +#include "src/config/ConfigDataValues.hpp" #include #include #include @@ -89,31 +90,31 @@ class CPasswordInputField : public IWidget { } hiddenInputState; struct { - CColor outer; - CColor inner; - CColor font; - CColor fail; - CColor check; - CColor caps; - CColor num; - CColor both; + CGradientValueData* outer = nullptr; + CColor inner; + CColor font; + CGradientValueData* fail = nullptr; + CGradientValueData* check = nullptr; + CGradientValueData* caps = nullptr; + CGradientValueData* num = nullptr; + CGradientValueData* both = nullptr; - int transitionMs = 0; - bool invertNum = false; - bool swapFont = false; + int transitionMs = 0; + bool invertNum = false; + bool swapFont = false; } colorConfig; struct { - CColor outer; - CColor inner; - CColor font; + CGradientValueData outer; + CColor inner; + CColor font; - CColor outerSource; - CColor innerSource; + CGradientValueData* outerSource = nullptr; + CColor innerSource; - CColor currentTarget; + CGradientValueData* currentTarget = nullptr; - bool animated = false; + bool animated = false; // std::chrono::system_clock::time_point lastFrame; diff --git a/src/renderer/widgets/Shape.cpp b/src/renderer/widgets/Shape.cpp index e45b74a..c3773ea 100644 --- a/src/renderer/widgets/Shape.cpp +++ b/src/renderer/widgets/Shape.cpp @@ -7,16 +7,16 @@ CShape::CShape(const Vector2D& viewport_, const std::unordered_map& props) : shadow(this, props, viewport_) { try { - size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport_); - rounding = std::any_cast(props.at("rounding")); - border = std::any_cast(props.at("border_size")); - color = std::any_cast(props.at("color")); - borderColor = std::any_cast(props.at("border_color")); - pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_); - halign = std::any_cast(props.at("halign")); - valign = std::any_cast(props.at("valign")); - angle = std::any_cast(props.at("rotate")); - xray = std::any_cast(props.at("xray")); + size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport_); + rounding = std::any_cast(props.at("rounding")); + border = std::any_cast(props.at("border_size")); + color = std::any_cast(props.at("color")); + borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color")); + pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_); + halign = std::any_cast(props.at("halign")); + valign = std::any_cast(props.at("valign")); + angle = std::any_cast(props.at("rotate")); + xray = std::any_cast(props.at("xray")); } catch (const std::bad_any_cast& e) { RASSERT(false, "Failed to construct CShape: {}", e.what()); // } catch (const std::out_of_range& e) { @@ -54,10 +54,8 @@ bool CShape::draw(const SRenderData& data) { if (xray) { if (border > 0) { - const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI)); - CColor borderCol = borderColor; - borderCol.a *= data.opacity; - g_pRenderer->renderRect(borderBox, borderCol, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND)); + const int PIROUND = std::min(MINHALFBORDER, std::round(border * M_PI)); + g_pRenderer->renderBorder(borderBox, borderGrad, border, rounding == -1 ? PIROUND : std::clamp(rounding, 0, PIROUND), data.opacity); } glEnable(GL_SCISSOR_TEST); @@ -79,7 +77,7 @@ bool CShape::draw(const SRenderData& data) { glClear(GL_COLOR_BUFFER_BIT); if (border > 0) - g_pRenderer->renderRect(borderBox, borderColor, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : MINHALFBORDER); + g_pRenderer->renderBorder(borderBox, borderGrad, border, ALLOWROUND ? (rounding == 0 ? 0 : rounding + std::round(border / M_PI)) : MINHALFBORDER, data.opacity); g_pRenderer->renderRect(shapeBox, color, ALLOWROUND ? rounding : MINHALFSHAPE); g_pRenderer->popFb(); diff --git a/src/renderer/widgets/Shape.hpp b/src/renderer/widgets/Shape.hpp index 2431239..1131bfa 100644 --- a/src/renderer/widgets/Shape.hpp +++ b/src/renderer/widgets/Shape.hpp @@ -2,6 +2,7 @@ #include "IWidget.hpp" #include "../../helpers/Color.hpp" +#include "../../config/ConfigDataValues.hpp" #include "Shadowable.hpp" #include #include @@ -15,23 +16,23 @@ class CShape : public IWidget { virtual bool draw(const SRenderData& data); private: - CFramebuffer shapeFB; + CFramebuffer shapeFB; - int rounding; - double border; - double angle; - CColor color; - CColor borderColor; - Vector2D size; - Vector2D pos; - CBox shapeBox; - CBox borderBox; - bool xray; + int rounding; + double border; + double angle; + CColor color; + CGradientValueData borderGrad; + Vector2D size; + Vector2D pos; + CBox shapeBox; + CBox borderBox; + bool xray; - std::string halign, valign; + std::string halign, valign; - bool firstRender = true; + bool firstRender = true; - Vector2D viewport; - CShadowable shadow; + Vector2D viewport; + CShadowable shadow; };