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
This commit is contained in:
Maximilian Seidler 2024-11-09 17:54:44 +01:00 committed by GitHub
parent 4fc133c96f
commit 6c3c444136
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 499 additions and 112 deletions

View file

@ -1,12 +1,19 @@
#pragma once
#include "../helpers/Log.hpp"
#include "../helpers/Color.hpp"
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/string/VarList.hpp>
#include <any>
#include <string>
#include <vector>
#include <cmath>
using namespace Hyprutils::String;
enum eConfigValueDataTypes {
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<CColor> 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<void*>(v);
RASSERT(P, "Empty config value");
return P;
}
};

View file

@ -2,6 +2,7 @@
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigDataValues.hpp"
#include <hyprlang.hpp>
#include <hyprutils/path/Path.hpp>
#include <filesystem>
#include <glob.h>
@ -69,6 +70,65 @@ static void configHandleLayoutOptionDestroy(void** data) {
delete reinterpret_cast<CLayoutValueData*>(*data);
}
static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void** data) {
std::string V = VALUE;
if (!*data)
*data = new CGradientValueData();
const auto DATA = reinterpret_cast<CGradientValueData*>(*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<CGradientValueData*>(*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{"<i>Input Password</i>"});
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{"<i>$FAIL</i>"});
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"});

View file

@ -1,5 +1,9 @@
#include <filesystem>
#include "MiscFunctions.hpp"
#include "../helpers/Log.hpp"
#include <hyprutils/string/String.hpp>
using namespace Hyprutils::String;
std::string absolutePath(const std::string& rawpath, const std::string& currentDir) {
std::filesystem::path path(rawpath);
@ -16,3 +20,43 @@ std::string absolutePath(const std::string& rawpath, const std::string& currentD
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);
}

View file

@ -5,3 +5,4 @@
#include <hyprutils/math/Vector2D.hpp>
std::string absolutePath(const std::string&, const std::string&);
int64_t configStringToInt(const std::string& VALUE);

View file

@ -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<CAsyncResourceGatherer>();
}
@ -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<eTransform> tr) {
Mat3x3 matrix = projMatrix.projectBox(box, tr.value_or(HYPRUTILS_TRANSFORM_FLIPPED_180), box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);

View file

@ -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<eTransform> 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;

View file

@ -1,6 +1,10 @@
#pragma once
#include <string>
#include <format>
#include <cmath>
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;
@ -362,3 +370,124 @@ 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;
}
)#";

View file

@ -86,7 +86,7 @@ CImage::CImage(const Vector2D& viewport_, COutput* output_, const std::string& r
size = std::any_cast<Hyprlang::INT>(props.at("size"));
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = std::any_cast<Hyprlang::INT>(props.at("border_color"));
color = *CGradientValueData::fromAnyPv(props.at("border_color"));
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(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);

View file

@ -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;

View file

@ -33,14 +33,14 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
configFailTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fail_timeout"));
fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
colorConfig.transitionMs = std::any_cast<Hyprlang::INT>(props.at("fail_transition"));
colorConfig.outer = std::any_cast<Hyprlang::INT>(props.at("outer_color"));
colorConfig.outer = CGradientValueData::fromAnyPv(props.at("outer_color"));
colorConfig.inner = std::any_cast<Hyprlang::INT>(props.at("inner_color"));
colorConfig.font = std::any_cast<Hyprlang::INT>(props.at("font_color"));
colorConfig.fail = std::any_cast<Hyprlang::INT>(props.at("fail_color"));
colorConfig.check = std::any_cast<Hyprlang::INT>(props.at("check_color"));
colorConfig.both = std::any_cast<Hyprlang::INT>(props.at("bothlock_color"));
colorConfig.caps = std::any_cast<Hyprlang::INT>(props.at("capslock_color"));
colorConfig.num = std::any_cast<Hyprlang::INT>(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<Hyprlang::INT>(props.at("invert_numlock"));
colorConfig.swapFont = std::any_cast<Hyprlang::INT>(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.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::milliseconds>(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;
CGradientValueData* outerTarget = colorConfig.outer;
CColor innerTarget = colorConfig.inner;
CColor fontTarget = (displayFail) ? colorConfig.fail : colorConfig.font;
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;

View file

@ -5,6 +5,7 @@
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "Shadowable.hpp"
#include "src/config/ConfigDataValues.hpp"
#include <chrono>
#include <vector>
#include <any>
@ -89,14 +90,14 @@ class CPasswordInputField : public IWidget {
} hiddenInputState;
struct {
CColor outer;
CGradientValueData* outer = nullptr;
CColor inner;
CColor font;
CColor fail;
CColor check;
CColor caps;
CColor num;
CColor both;
CGradientValueData* fail = nullptr;
CGradientValueData* check = nullptr;
CGradientValueData* caps = nullptr;
CGradientValueData* num = nullptr;
CGradientValueData* both = nullptr;
int transitionMs = 0;
bool invertNum = false;
@ -104,14 +105,14 @@ class CPasswordInputField : public IWidget {
} colorConfig;
struct {
CColor outer;
CGradientValueData outer;
CColor inner;
CColor font;
CColor outerSource;
CGradientValueData* outerSource = nullptr;
CColor innerSource;
CColor currentTarget;
CGradientValueData* currentTarget = nullptr;
bool animated = false;

View file

@ -11,7 +11,7 @@ CShape::CShape(const Vector2D& viewport_, const std::unordered_map<std::string,
rounding = std::any_cast<Hyprlang::INT>(props.at("rounding"));
border = std::any_cast<Hyprlang::INT>(props.at("border_size"));
color = std::any_cast<Hyprlang::INT>(props.at("color"));
borderColor = std::any_cast<Hyprlang::INT>(props.at("border_color"));
borderGrad = *CGradientValueData::fromAnyPv(props.at("border_color"));
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
@ -55,9 +55,7 @@ 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));
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();

View file

@ -2,6 +2,7 @@
#include "IWidget.hpp"
#include "../../helpers/Color.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "Shadowable.hpp"
#include <hyprutils/math/Box.hpp>
#include <string>
@ -21,7 +22,7 @@ class CShape : public IWidget {
double border;
double angle;
CColor color;
CColor borderColor;
CGradientValueData borderGrad;
Vector2D size;
Vector2D pos;
CBox shapeBox;