core: introduce animation manager and animation config (#631)

BREAKING:
- Removed `input-field:dots_fade_time`. Now configured via
`animation=inputFieldDots,...`
- Removed `input-field:fail_transition`. Now configured via
`animation=inputFieldColors,...`
- Removed `general:no_fade_in` and `general:no_fade_out`. Now configured
globally via `animations:enabled` or via `animation=fadeIn,...` and
`animation=fadeOut,...`
This commit is contained in:
Maximilian Seidler 2025-01-06 12:34:21 +00:00 committed by GitHub
parent 8f68fad50a
commit 00d2cbfee3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 787 additions and 424 deletions

View file

@ -76,7 +76,7 @@ pkg_check_modules(
pangocairo
libdrm
gbm
hyprutils>=0.2.6
hyprutils>=0.3.3
sdbus-c++>=2.0.0
hyprgraphics)

View file

@ -13,11 +13,11 @@
]
},
"locked": {
"lastModified": 1734906236,
"narHash": "sha256-vH/ysV2ONGQgYZPtcJKwc8jJivzyVxru2aaOxC20ZOE=",
"lastModified": 1736115290,
"narHash": "sha256-Jcn6yAzfUMcxy3tN/iZRbi/QgrYm7XLyVRl9g/nbUl4=",
"owner": "hyprwm",
"repo": "hyprgraphics",
"rev": "6dea3fba08fd704dd624b6d4b261638fb4003c9c",
"rev": "52202272d89da32a9f866c0d10305a5e3d954c50",
"type": "github"
},
"original": {
@ -62,11 +62,11 @@
]
},
"locked": {
"lastModified": 1735316583,
"narHash": "sha256-AiiUwHWHfEdpFzXy7l1x3zInCUa1xcRMrbZ1XRSkzwU=",
"lastModified": 1736164519,
"narHash": "sha256-1LimBKvDpBbeX+qW7T240WEyw+DBVpDotZB4JYm8Aps=",
"owner": "hyprwm",
"repo": "hyprutils",
"rev": "8f15d45b120b33712f6db477fe5ffb18034d0ea8",
"rev": "3c895da64b0eb19870142196fa48c07090b441c4",
"type": "github"
},
"original": {
@ -100,11 +100,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1735291276,
"narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
"lastModified": 1736012469,
"narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "634fd46801442d760e09493a794c4f15db2d0cbb",
"rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
"type": "github"
},
"original": {

View file

@ -62,8 +62,9 @@ class CLayoutValueData : public ICustomConfigValueData {
class CGradientValueData : public ICustomConfigValueData {
public:
CGradientValueData() {};
CGradientValueData(CColor col) {
CGradientValueData(CHyprColor col) {
m_vColors.push_back(col);
updateColorsOk();
};
virtual ~CGradientValueData() {};
@ -71,14 +72,29 @@ class CGradientValueData : public ICustomConfigValueData {
return CVD_TYPE_GRADIENT;
}
void reset(CColor col) {
void reset(CHyprColor col) {
m_vColors.clear();
m_vColors.emplace_back(col);
m_fAngle = 0;
updateColorsOk();
}
void updateColorsOk() {
m_vColorsOkLabA.clear();
for (auto& c : m_vColors) {
const auto OKLAB = c.asOkLab();
m_vColorsOkLabA.emplace_back(OKLAB.l);
m_vColorsOkLabA.emplace_back(OKLAB.a);
m_vColorsOkLabA.emplace_back(OKLAB.b);
m_vColorsOkLabA.emplace_back(c.a);
}
}
/* Vector containing the colors */
std::vector<CColor> m_vColors;
std::vector<CHyprColor> m_vColors;
/* Vector containing pure colors for shoving into opengl */
std::vector<float> m_vColorsOkLabA;
/* Float corresponding to the angle (rad) */
float m_fAngle = 0;
@ -86,6 +102,7 @@ class CGradientValueData : public ICustomConfigValueData {
/* Whether this gradient stores a fallback value (not exlicitly set) */
bool m_bIsFallback = false;
//
bool operator==(const CGradientValueData& other) const {
if (other.m_vColors.size() != m_vColors.size() || m_fAngle != other.m_fAngle)
return false;

View file

@ -1,8 +1,10 @@
#include "ConfigManager.hpp"
#include "ConfigDataValues.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigDataValues.hpp"
#include "../core/AnimationManager.hpp"
#include <hyprlang.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/path/Path.hpp>
#include <hyprutils/string/String.hpp>
#include <filesystem>
@ -10,6 +12,9 @@
#include <cstring>
#include <mutex>
using namespace Hyprutils::String;
using namespace Hyprutils::Animation;
ICustomConfigValueData::~ICustomConfigValueData() {
; // empty
}
@ -26,6 +31,30 @@ static Hyprlang::CParseResult handleSource(const char* c, const char* v) {
return result;
}
static Hyprlang::CParseResult handleBezier(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleBezier(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult handleAnimation(const char* c, const char* v) {
const std::string VALUE = v;
const std::string COMMAND = c;
const auto RESULT = g_pConfigManager->handleAnimation(COMMAND, VALUE);
Hyprlang::CParseResult result;
if (RESULT.has_value())
result.setError(RESULT.value().c_str());
return result;
}
static Hyprlang::CParseResult configHandleLayoutOption(const char* v, void** data) {
const std::string VALUE = v;
@ -120,7 +149,7 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void**
continue;
try {
DATA->m_vColors.push_back(CColor(configStringToInt(var)));
DATA->m_vColors.push_back(CHyprColor(configStringToInt(var)));
} catch (std::exception& e) {
Debug::log(WARN, "Error parsing gradient {}", V);
parseError = "Error parsing gradient " + V + ": " + e.what();
@ -139,6 +168,8 @@ static Hyprlang::CParseResult configHandleGradientSet(const char* VALUE, void**
DATA->m_vColors.push_back(0); // transparent
}
DATA->updateColorsOk();
Hyprlang::CParseResult result;
if (!parseError.empty())
result.setError(parseError.c_str());
@ -183,8 +214,6 @@ void CConfigManager::init() {
m_config.addConfigValue("general:text_trim", Hyprlang::INT{1});
m_config.addConfigValue("general:hide_cursor", Hyprlang::INT{0});
m_config.addConfigValue("general:grace", Hyprlang::INT{0});
m_config.addConfigValue("general:no_fade_in", Hyprlang::INT{0});
m_config.addConfigValue("general:no_fade_out", Hyprlang::INT{0});
m_config.addConfigValue("general:ignore_empty_input", Hyprlang::INT{0});
m_config.addConfigValue("general:immediate_render", Hyprlang::INT{0});
m_config.addConfigValue("general:fractional_scaling", Hyprlang::INT{2});
@ -196,6 +225,8 @@ void CConfigManager::init() {
m_config.addConfigValue("auth:fingerprint:present_message", Hyprlang::STRING{"Scanning fingerprint"});
m_config.addConfigValue("auth:fingerprint:retry_delay", Hyprlang::INT{250});
m_config.addConfigValue("animations:enabled", Hyprlang::INT{1});
m_config.addSpecialCategory("background", Hyprlang::SSpecialCategoryOptions{.key = nullptr, .anonymousKeyBased = true});
m_config.addSpecialConfigValue("background", "monitor", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("background", "path", Hyprlang::STRING{""});
@ -253,7 +284,6 @@ void CConfigManager::init() {
m_config.addSpecialConfigValue("input-field", "dots_center", Hyprlang::INT{1});
m_config.addSpecialConfigValue("input-field", "dots_spacing", Hyprlang::FLOAT{0.2});
m_config.addSpecialConfigValue("input-field", "dots_rounding", Hyprlang::INT{-1});
m_config.addSpecialConfigValue("input-field", "dots_fade_time", Hyprlang::INT{200});
m_config.addSpecialConfigValue("input-field", "dots_text_format", Hyprlang::STRING{""});
m_config.addSpecialConfigValue("input-field", "fade_on_empty", Hyprlang::INT{1});
m_config.addSpecialConfigValue("input-field", "fade_timeout", Hyprlang::INT{2000});
@ -269,7 +299,6 @@ void CConfigManager::init() {
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", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "numlock_color", GRADIENTCONFIG(""));
m_config.addSpecialConfigValue("input-field", "bothlock_color", GRADIENTCONFIG(""));
@ -293,6 +322,30 @@ void CConfigManager::init() {
SHADOWABLE("label");
m_config.registerHandler(&::handleSource, "source", {false});
m_config.registerHandler(&::handleBezier, "bezier", {false});
m_config.registerHandler(&::handleAnimation, "animation", {false});
//
// Init Animations
//
m_AnimationTree.createNode("global");
// toplevel
m_AnimationTree.createNode("fade", "global");
m_AnimationTree.createNode("inputField", "global");
// inputField
m_AnimationTree.createNode("inputFieldColors", "inputField");
m_AnimationTree.createNode("inputFieldFade", "inputField");
m_AnimationTree.createNode("inputFieldWidth", "inputField");
m_AnimationTree.createNode("inputFieldDots", "inputField");
// fade
m_AnimationTree.createNode("fadeIn", "fade");
m_AnimationTree.createNode("fadeOut", "fade");
// set config for root node
m_AnimationTree.setConfigForNode("global", 1, 8.f, "default");
m_config.commence();
@ -412,7 +465,6 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"dots_spacing", m_config.getSpecialConfigValue("input-field", "dots_spacing", k.c_str())},
{"dots_center", m_config.getSpecialConfigValue("input-field", "dots_center", k.c_str())},
{"dots_rounding", m_config.getSpecialConfigValue("input-field", "dots_rounding", k.c_str())},
{"dots_fade_time", m_config.getSpecialConfigValue("input-field", "dots_fade_time", k.c_str())},
{"dots_text_format", m_config.getSpecialConfigValue("input-field", "dots_text_format", k.c_str())},
{"fade_on_empty", m_config.getSpecialConfigValue("input-field", "fade_on_empty", k.c_str())},
{"fade_timeout", m_config.getSpecialConfigValue("input-field", "fade_timeout", k.c_str())},
@ -428,7 +480,6 @@ std::vector<CConfigManager::SWidgetConfig> CConfigManager::getWidgetConfigs() {
{"fail_color", m_config.getSpecialConfigValue("input-field", "fail_color", k.c_str())},
{"fail_text", m_config.getSpecialConfigValue("input-field", "fail_text", k.c_str())},
{"fail_timeout", m_config.getSpecialConfigValue("input-field", "fail_timeout", k.c_str())},
{"fail_transition", m_config.getSpecialConfigValue("input-field", "fail_transition", k.c_str())},
{"capslock_color", m_config.getSpecialConfigValue("input-field", "capslock_color", k.c_str())},
{"numlock_color", m_config.getSpecialConfigValue("input-field", "numlock_color", k.c_str())},
{"bothlock_color", m_config.getSpecialConfigValue("input-field", "bothlock_color", k.c_str())},
@ -512,3 +563,77 @@ std::optional<std::string> CConfigManager::handleSource(const std::string& comma
return {};
}
std::optional<std::string> CConfigManager::handleBezier(const std::string& command, const std::string& args) {
const auto ARGS = CVarList(args);
std::string bezierName = ARGS[0];
if (ARGS[1] == "")
return "too few arguments";
float p1x = std::stof(ARGS[1]);
if (ARGS[2] == "")
return "too few arguments";
float p1y = std::stof(ARGS[2]);
if (ARGS[3] == "")
return "too few arguments";
float p2x = std::stof(ARGS[3]);
if (ARGS[4] == "")
return "too few arguments";
float p2y = std::stof(ARGS[4]);
if (ARGS[5] != "")
return "too many arguments";
g_pAnimationManager->addBezierWithName(bezierName, Vector2D(p1x, p1y), Vector2D(p2x, p2y));
return {};
}
std::optional<std::string> CConfigManager::handleAnimation(const std::string& command, const std::string& args) {
const auto ARGS = CVarList(args);
const auto ANIMNAME = ARGS[0];
if (!m_AnimationTree.nodeExists(ANIMNAME))
return "no such animation";
// This helper casts strings like "1", "true", "off", "yes"... to int.
int64_t enabledInt = configStringToInt(ARGS[1]);
// Checking that the int is 1 or 0 because the helper can return integers out of range.
if (enabledInt != 0 && enabledInt != 1)
return "invalid animation on/off state";
if (enabledInt) {
int64_t speed = -1;
// speed
if (isNumber(ARGS[2], true)) {
speed = std::stof(ARGS[2]);
if (speed <= 0) {
speed = 1.f;
return "invalid speed";
}
} else {
speed = 10.f;
return "invalid speed";
}
std::string bezierName = ARGS[3];
// ARGS[4] (style) currently usused by hyprlock
m_AnimationTree.setConfigForNode(ANIMNAME, enabledInt, speed, ARGS[3], "");
if (!g_pAnimationManager->bezierExists(bezierName)) {
const auto PANIMNODE = m_AnimationTree.getConfig(ANIMNAME);
PANIMNODE->internalBezier = "default";
return "no such bezier";
}
}
return {};
}

View file

@ -1,11 +1,15 @@
#pragma once
#include <hyprutils/animation/AnimationConfig.hpp>
#include <hyprlang.hpp>
#include <optional>
#include <vector>
#include <memory>
#include <unordered_map>
#include "../defines.hpp"
class CConfigManager {
public:
CConfigManager(std::string configPath);
@ -19,10 +23,15 @@ class CConfigManager {
std::unordered_map<std::string, std::any> values;
};
std::vector<SWidgetConfig> getWidgetConfigs();
std::optional<std::string> handleSource(const std::string&, const std::string&);
std::vector<SWidgetConfig> getWidgetConfigs();
std::string configCurrentPath;
std::optional<std::string> handleSource(const std::string&, const std::string&);
std::optional<std::string> handleBezier(const std::string&, const std::string&);
std::optional<std::string> handleAnimation(const std::string&, const std::string&);
std::string configCurrentPath;
Hyprutils::Animation::CAnimationConfigTree m_AnimationTree;
private:
Hyprlang::CConfig m_config;

View file

@ -0,0 +1,127 @@
#include "AnimationManager.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../config/ConfigDataValues.hpp"
#include "../config/ConfigManager.hpp"
#include <utility>
CHyprlockAnimationManager::CHyprlockAnimationManager() {
;
}
template <Animable VarType>
void updateVariable(CAnimatedVariable<VarType>& av, const float POINTY, bool warp = false) {
if (POINTY >= 1.f || warp || !av.enabled() || av.value() == av.goal()) {
av.warp();
return;
}
const auto DELTA = av.goal() - av.begun();
av.value() = av.begun() + DELTA * POINTY;
}
void updateColorVariable(CAnimatedVariable<CHyprColor>& av, const float POINTY, bool warp = false) {
if (POINTY >= 1.f || warp || !av.enabled() || av.value() == av.goal()) {
av.warp();
return;
}
// convert both to OkLab, then lerp that, and convert back.
// This is not as fast as just lerping rgb, but it's WAY more precise...
// Use the CHyprColor cache for OkLab
const auto& L1 = av.begun().asOkLab();
const auto& L2 = av.goal().asOkLab();
static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + (two - one) * progress; };
const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{
.l = lerp(L1.l, L2.l, POINTY),
.a = lerp(L1.a, L2.a, POINTY),
.b = lerp(L1.b, L2.b, POINTY),
};
av.value() = {lerped, lerp(av.begun().a, av.goal().a, POINTY)};
}
void updateGradientVariable(CAnimatedVariable<CGradientValueData>& av, const float POINTY, bool warp = false) {
if (POINTY >= 1.f || warp || av.value() == av.goal()) {
av.warp();
return;
}
av.value().m_vColors.resize(av.goal().m_vColors.size(), av.goal().m_vColors.back());
for (size_t i = 0; i < av.value().m_vColors.size(); ++i) {
const CHyprColor& sourceCol = (i < av.begun().m_vColors.size()) ? av.begun().m_vColors[i] : av.begun().m_vColors.back();
const CHyprColor& targetCol = (i < av.goal().m_vColors.size()) ? av.goal().m_vColors[i] : av.goal().m_vColors.back();
const auto& L1 = sourceCol.asOkLab();
const auto& L2 = targetCol.asOkLab();
static const auto lerp = [](const float one, const float two, const float progress) -> float { return one + (two - one) * progress; };
const Hyprgraphics::CColor lerped = Hyprgraphics::CColor::SOkLab{
.l = lerp(L1.l, L2.l, POINTY),
.a = lerp(L1.a, L2.a, POINTY),
.b = lerp(L1.b, L2.b, POINTY),
};
av.value().m_vColors[i] = {lerped, lerp(sourceCol.a, targetCol.a, POINTY)};
av.value().updateColorsOk();
}
if (av.begun().m_fAngle != av.goal().m_fAngle) {
const float DELTA = av.goal().m_fAngle - av.begun().m_fAngle;
av.value().m_fAngle = av.begun().m_fAngle + DELTA * POINTY;
}
}
void CHyprlockAnimationManager::tick() {
static auto* const PANIMATIONSENABLED = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("animations:enabled");
for (auto const& av : m_vActiveAnimatedVariables) {
const auto PAV = av.lock();
if (!PAV || !PAV->ok())
continue;
const auto SPENT = PAV->getPercent();
const auto PBEZIER = getBezier(PAV->getBezierName());
const auto POINTY = PBEZIER->getYForPoint(SPENT);
switch (PAV->m_Type) {
case AVARTYPE_FLOAT: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<float>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated float");
updateVariable(*pTypedAV, POINTY, !**PANIMATIONSENABLED);
} break;
case AVARTYPE_VECTOR: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<Vector2D>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated Vector2D");
updateVariable(*pTypedAV, POINTY, !**PANIMATIONSENABLED);
} break;
case AVARTYPE_COLOR: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<CHyprColor>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated CHyprColor");
updateColorVariable(*pTypedAV, POINTY, !**PANIMATIONSENABLED);
} break;
case AVARTYPE_GRADIENT: {
auto pTypedAV = dynamic_cast<CAnimatedVariable<CGradientValueData>*>(PAV.get());
RASSERT(pTypedAV, "Failed to upcast animated CGradientValueData");
updateGradientVariable(*pTypedAV, POINTY, !**PANIMATIONSENABLED);
} break;
default: continue;
}
av->onUpdate();
}
tickDone();
}
void CHyprlockAnimationManager::scheduleTick() {
m_bTickScheduled = true;
}
void CHyprlockAnimationManager::onTicked() {
m_bTickScheduled = false;
}

View file

@ -0,0 +1,34 @@
#pragma once
#include <hyprutils/animation/AnimationManager.hpp>
#include <hyprutils/animation/AnimatedVariable.hpp>
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/Math.hpp"
#include "../defines.hpp"
class CHyprlockAnimationManager : public Hyprutils::Animation::CAnimationManager {
public:
CHyprlockAnimationManager();
void tick();
virtual void scheduleTick();
virtual void onTicked();
using SAnimationPropertyConfig = Hyprutils::Animation::SAnimationPropertyConfig;
template <Animable VarType>
void createAnimation(const VarType& v, PHLANIMVAR<VarType>& pav, SP<SAnimationPropertyConfig> pConfig) {
constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType<VarType>;
const auto PAV = makeShared<CAnimatedVariable<VarType>>();
PAV->create(EAVTYPE, static_cast<Hyprutils::Animation::CAnimationManager*>(this), PAV, v);
PAV->setConfig(pConfig);
pav = std::move(PAV);
}
bool m_bTickScheduled = false;
};
inline std::unique_ptr<CHyprlockAnimationManager> g_pAnimationManager;

View file

@ -1,9 +1,10 @@
#include "LockSurface.hpp"
#include "hyprlock.hpp"
#include "../helpers/Log.hpp"
#include "Egl.hpp"
#include "../config/ConfigManager.hpp"
#include "../core/AnimationManager.hpp"
#include "../helpers/Log.hpp"
#include "../renderer/Renderer.hpp"
#include "src/config/ConfigManager.hpp"
CSessionLockSurface::~CSessionLockSurface() {
if (eglWindow)
@ -123,6 +124,7 @@ void CSessionLockSurface::render() {
return;
}
g_pAnimationManager->tick();
const auto FEEDBACK = g_pRenderer->renderLock(*this);
frameCallback = makeShared<CCWlCallback>(surface->sendFrame());
frameCallback->setDone([this](CCWlCallback* r, uint32_t data) {
@ -134,7 +136,7 @@ void CSessionLockSurface::render() {
eglSwapBuffers(g_pEGL->eglDisplay, eglSurface);
needsFrame = FEEDBACK.needsFrame;
needsFrame = FEEDBACK.needsFrame || g_pAnimationManager->shouldTickForNext();
}
void CSessionLockSurface::onCallback() {

View file

@ -1,4 +1,5 @@
#include "hyprlock.hpp"
#include "AnimationManager.hpp"
#include "../helpers/Log.hpp"
#include "../config/ConfigManager.hpp"
#include "../renderer/Renderer.hpp"
@ -22,7 +23,7 @@
using namespace Hyprutils::OS;
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender, const bool noFadeIn) {
CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender) {
m_sWaylandState.display = wl_display_connect(wlDisplay.empty() ? nullptr : wlDisplay.c_str());
if (!m_sWaylandState.display) {
Debug::log(CRIT, "Couldn't connect to a wayland compositor");
@ -40,9 +41,6 @@ CHyprlock::CHyprlock(const std::string& wlDisplay, const bool immediate, const b
const auto PIMMEDIATERENDER = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:immediate_render");
m_bImmediateRender = immediateRender || **PIMMEDIATERENDER;
const auto* const PNOFADEIN = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_in");
m_bNoFadeIn = noFadeIn || **PNOFADEIN;
const auto CURRENTDESKTOP = getenv("XDG_CURRENT_DESKTOP");
const auto SZCURRENTD = std::string{CURRENTDESKTOP ? CURRENTDESKTOP : ""};
m_sCurrentDesktop = SZCURRENTD;
@ -316,9 +314,6 @@ void CHyprlock::run() {
g_pAuth = std::make_unique<CAuth>();
g_pAuth->start();
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
const bool NOFADEOUT = **PNOFADEOUT;
Debug::log(LOG, "Running on {}", m_sCurrentDesktop);
// Hyprland violates the protocol a bit to allow for this.
@ -430,17 +425,13 @@ void CHyprlock::run() {
});
m_sLoopState.event = true; // let it process once
g_pRenderer->startFadeIn();
while (!m_bTerminate) {
std::unique_lock lk(m_sLoopState.eventRequestMutex);
if (m_sLoopState.event == false)
m_sLoopState.loopCV.wait_for(lk, std::chrono::milliseconds(5000), [this] { return m_sLoopState.event; });
if (!NOFADEOUT && m_bFadeStarted && std::chrono::system_clock::now() > m_tFadeEnds) {
releaseSessionLock();
break;
}
if (m_bTerminate)
break;
@ -494,11 +485,6 @@ void CHyprlock::run() {
m_sLoopState.timersMutex.unlock();
passed.clear();
if (!NOFADEOUT && m_bFadeStarted && std::chrono::system_clock::now() > m_tFadeEnds) {
releaseSessionLock();
break;
}
}
const auto DPY = m_sWaylandState.display;
@ -529,21 +515,16 @@ void CHyprlock::run() {
}
void CHyprlock::unlock() {
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
const bool IMMEDIATE = m_sCurrentDesktop != "Hyprland";
if (**PNOFADEOUT || m_sCurrentDesktop != "Hyprland") {
releaseSessionLock();
return;
}
m_tFadeEnds = std::chrono::system_clock::now() + std::chrono::milliseconds(500);
m_bFadeStarted = true;
g_pRenderer->startFadeOut(true, IMMEDIATE);
m_bUnlockedCalled = true;
renderAllOutputs();
}
bool CHyprlock::isUnlocked() {
return m_bFadeStarted || m_bTerminate;
return m_bUnlockedCalled || m_bTerminate;
}
void CHyprlock::clearPasswordBuffer() {
@ -607,7 +588,7 @@ void CHyprlock::repeatKey(xkb_keysym_t sym) {
}
void CHyprlock::onKey(uint32_t key, bool down) {
if (m_bFadeStarted || m_bTerminate)
if (isUnlocked())
return;
if (down && std::chrono::system_clock::now() < m_tGraceEnds) {
@ -715,6 +696,7 @@ void CHyprlock::acquireSessionLock() {
void CHyprlock::releaseSessionLock() {
Debug::log(LOG, "Unlocking session");
if (m_bTerminate) {
Debug::log(ERR, "Unlock already happend?");
return;

View file

@ -29,7 +29,7 @@ struct SDMABUFModifier {
class CHyprlock {
public:
CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender, const bool noFadeIn);
CHyprlock(const std::string& wlDisplay, const bool immediate, const bool immediateRender);
~CHyprlock();
void run();
@ -89,20 +89,16 @@ class CHyprlock {
bool m_bLocked = false;
bool m_bCapsLock = false;
bool m_bNumLock = false;
bool m_bCtrl = false;
bool m_bFadeStarted = false;
bool m_bCapsLock = false;
bool m_bNumLock = false;
bool m_bCtrl = false;
bool m_bImmediateRender = false;
bool m_bNoFadeIn = false;
std::string m_sCurrentDesktop = "";
//
std::chrono::system_clock::time_point m_tGraceEnds;
std::chrono::system_clock::time_point m_tFadeEnds;
Vector2D m_vLastEnterCoords = {};
std::shared_ptr<CTimer> m_pKeyRepeatTimer = nullptr;
@ -160,6 +156,8 @@ class CHyprlock {
bool timerEvent = false;
} m_sLoopState;
bool m_bUnlockedCalled = false;
std::vector<std::shared_ptr<CTimer>> m_vTimers;
std::vector<uint32_t> m_vPressedKeys;

View file

@ -1,6 +1,8 @@
#pragma once
#include <hyprutils/memory/WeakPtr.hpp>
#include <hyprgraphics/color/Color.hpp>
using namespace Hyprutils::Memory;
using namespace Hyprgraphics;
#define SP CSharedPointer
#define WP CWeakPointer

View file

@ -0,0 +1,67 @@
#pragma once
#include <hyprutils/animation/AnimatedVariable.hpp>
#include "Color.hpp"
#include "Math.hpp"
#include "../defines.hpp"
#include "../config/ConfigDataValues.hpp"
enum eAnimatedVarType {
AVARTYPE_INVALID = -1,
AVARTYPE_FLOAT,
AVARTYPE_VECTOR,
AVARTYPE_COLOR,
AVARTYPE_GRADIENT
};
// Utility to bind a type with its corresponding eAnimatedVarType
template <class T>
// NOLINTNEXTLINE(readability-identifier-naming)
struct STypeToAnimatedVarType_t {
static constexpr eAnimatedVarType value = AVARTYPE_INVALID;
};
template <>
struct STypeToAnimatedVarType_t<float> {
static constexpr eAnimatedVarType value = AVARTYPE_FLOAT;
};
template <>
struct STypeToAnimatedVarType_t<Vector2D> {
static constexpr eAnimatedVarType value = AVARTYPE_VECTOR;
};
template <>
struct STypeToAnimatedVarType_t<CHyprColor> {
static constexpr eAnimatedVarType value = AVARTYPE_COLOR;
};
template <>
struct STypeToAnimatedVarType_t<CGradientValueData> {
static constexpr eAnimatedVarType value = AVARTYPE_GRADIENT;
};
template <class T>
inline constexpr eAnimatedVarType typeToeAnimatedVarType = STypeToAnimatedVarType_t<T>::value;
// Utility to define a concept as a list of possible type
template <class T, class... U>
concept OneOf = (... or std::same_as<T, U>);
// Concept to describe which type can be placed into CAnimatedVariable
// This is mainly to get better errors if we put a type that's not supported
// Otherwise template errors are ugly
template <class T>
concept Animable = OneOf<T, Vector2D, float, CHyprColor, CGradientValueData>;
struct SAnimationContext {};
template <Animable VarType>
using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable<VarType, SAnimationContext>;
template <Animable VarType>
using PHLANIMVAR = SP<CAnimatedVariable<VarType>>;
template <Animable VarType>
using PHLANIMVARREF = WP<CAnimatedVariable<VarType>>;

View file

@ -5,22 +5,52 @@
#define GREEN(c) ((double)(((c) >> 8) & 0xff) / 255.0)
#define BLUE(c) ((double)(((c)) & 0xff) / 255.0)
CColor::CColor() {}
CHyprColor::CHyprColor() {}
CColor::CColor(float r, float g, float b, float a) {
this->r = r;
this->g = g;
this->b = b;
this->a = a;
CHyprColor::CHyprColor(float r_, float g_, float b_, float a_) {
r = r_;
g = g_;
b = b_;
a = a_;
okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{r, g, b}).asOkLab();
}
CColor::CColor(uint64_t hex) {
this->r = RED(hex);
this->g = GREEN(hex);
this->b = BLUE(hex);
this->a = ALPHA(hex);
CHyprColor::CHyprColor(uint64_t hex) {
r = RED(hex);
g = GREEN(hex);
b = BLUE(hex);
a = ALPHA(hex);
okLab = Hyprgraphics::CColor(Hyprgraphics::CColor::SSRGB{r, g, b}).asOkLab();
}
uint32_t CColor::getAsHex() const {
CHyprColor::CHyprColor(const Hyprgraphics::CColor& color, float a_) {
const auto SRGB = color.asRgb();
r = SRGB.r;
g = SRGB.g;
b = SRGB.b;
a = a_;
okLab = color.asOkLab();
}
uint32_t CHyprColor::getAsHex() const {
return (uint32_t)(a * 255.f) * 0x1000000 + (uint32_t)(r * 255.f) * 0x10000 + (uint32_t)(g * 255.f) * 0x100 + (uint32_t)(b * 255.f) * 0x1;
}
Hyprgraphics::CColor::SSRGB CHyprColor::asRGB() const {
return {r, g, b};
}
Hyprgraphics::CColor::SOkLab CHyprColor::asOkLab() const {
return okLab;
}
Hyprgraphics::CColor::SHSL CHyprColor::asHSL() const {
return Hyprgraphics::CColor(okLab).asHSL();
}
CHyprColor CHyprColor::stripA() const {
return {r, g, b, 1.F};
}

View file

@ -1,35 +1,46 @@
#pragma once
#include <cstdint>
#include "../helpers/Log.hpp"
#include <hyprgraphics/color/Color.hpp>
class CColor {
class CHyprColor {
public:
CColor();
CColor(float r, float g, float b, float a);
CColor(uint64_t);
float r = 0, g = 0, b = 0, a = 1.f;
CHyprColor();
CHyprColor(float r, float g, float b, float a);
CHyprColor(const Hyprgraphics::CColor& col, float a);
CHyprColor(uint64_t);
// AR32
uint32_t getAsHex() const;
uint32_t getAsHex() const;
Hyprgraphics::CColor::SSRGB asRGB() const;
Hyprgraphics::CColor::SOkLab asOkLab() const;
Hyprgraphics::CColor::SHSL asHSL() const;
CHyprColor stripA() const;
CColor operator-(const CColor& c2) const {
return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a);
//
bool operator==(const CHyprColor& c2) const {
return c2.r == r && c2.g == g && c2.b == b && c2.a == a;
}
CColor operator+(const CColor& c2) const {
return CColor(r + c2.r, g + c2.g, b + c2.b, a + c2.a);
// stubs for the AnimationMgr
CHyprColor operator-(const CHyprColor& c2) const {
RASSERT(false, "CHyprColor: - is a STUB");
return {};
}
CColor operator*(const float& v) const {
return CColor(r * v, g * v, b * v, a * v);
CHyprColor operator+(const CHyprColor& c2) const {
RASSERT(false, "CHyprColor: + is a STUB");
return {};
}
bool operator==(const CColor& c2) const {
return r == c2.r && g == c2.g && b == c2.b && a == c2.a;
CHyprColor operator*(const float& c2) const {
RASSERT(false, "CHyprColor: * is a STUB");
return {};
}
CColor stripA() const {
return {r, g, b, 1};
}
double r = 0, g = 0, b = 0, a = 0;
private:
Hyprgraphics::CColor::SOkLab okLab; // cache for the OkLab representation
};

View file

@ -2,6 +2,7 @@
#include "config/ConfigManager.hpp"
#include "core/hyprlock.hpp"
#include "helpers/Log.hpp"
#include "core/AnimationManager.hpp"
#include <cstddef>
#include <string_view>
@ -89,6 +90,8 @@ int main(int argc, char** argv, char** envp) {
}
}
g_pAnimationManager = std::make_unique<CHyprlockAnimationManager>();
try {
g_pConfigManager = std::make_unique<CConfigManager>(configPath);
g_pConfigManager->init();
@ -100,8 +103,11 @@ int main(int argc, char** argv, char** envp) {
return 1;
}
if (noFadeIn)
g_pConfigManager->m_AnimationTree.setConfigForNode("fadeIn", false, 0.f, "default");
try {
g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate, immediateRender, noFadeIn);
g_pHyprlock = std::make_unique<CHyprlock>(wlDisplay, immediate, immediateRender);
g_pHyprlock->run();
} catch (const std::exception& ex) {
Debug::log(CRIT, "Hyprlock threw: {}", ex.what());

View file

@ -216,7 +216,7 @@ void CAsyncResourceGatherer::renderText(const SPreloadRequest& rq) {
target.id = rq.id;
const int FONTSIZE = rq.props.contains("font_size") ? std::any_cast<int>(rq.props.at("font_size")) : 16;
const CColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CColor>(rq.props.at("color")) : CColor(1.0, 1.0, 1.0, 1.0);
const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast<CHyprColor>(rq.props.at("color")) : CHyprColor(1.0, 1.0, 1.0, 1.0);
const std::string FONTFAMILY = rq.props.contains("font_family") ? std::any_cast<std::string>(rq.props.at("font_family")) : "Sans";
const bool ISCMD = rq.props.contains("cmd") ? std::any_cast<bool>(rq.props.at("cmd")) : false;

View file

@ -1,15 +1,16 @@
#include "Renderer.hpp"
#include "../core/Egl.hpp"
#include "Shaders.hpp"
#include "../config/ConfigManager.hpp"
#include "../helpers/Color.hpp"
#include "../core/AnimationManager.hpp"
#include "../core/Egl.hpp"
#include "../core/Output.hpp"
#include "../core/hyprlock.hpp"
#include "../helpers/Color.hpp"
#include "../helpers/Log.hpp"
#include "../renderer/DMAFrame.hpp"
#include <GLES3/gl32.h>
#include <GLES3/gl3ext.h>
#include <algorithm>
#include "Shaders.hpp"
#include "src/helpers/Log.hpp"
#include "widgets/PasswordInputField.hpp"
#include "widgets/Background.hpp"
#include "widgets/Label.hpp"
@ -186,18 +187,22 @@ CRenderer::CRenderer() {
borderShader.gradient = glGetUniformLocation(prog, "gradient");
borderShader.gradientLength = glGetUniformLocation(prog, "gradientLength");
borderShader.angle = glGetUniformLocation(prog, "angle");
borderShader.gradient2 = glGetUniformLocation(prog, "gradient2");
borderShader.gradient2Length = glGetUniformLocation(prog, "gradient2Length");
borderShader.angle2 = glGetUniformLocation(prog, "angle2");
borderShader.gradientLerp = glGetUniformLocation(prog, "gradientLerp");
borderShader.alpha = glGetUniformLocation(prog, "alpha");
asyncResourceGatherer = std::make_unique<CAsyncResourceGatherer>();
g_pAnimationManager->createAnimation(0.f, opacity, g_pConfigManager->m_AnimationTree.getConfig("fadeIn"));
}
static int frames = 0;
static bool firstFullFrame = false;
static int frames = 0;
//
CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf) {
static auto* const PDISABLEBAR = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:disable_loading_bar");
static auto* const PNOFADEOUT = (Hyprlang::INT* const*)g_pConfigManager->getValuePtr("general:no_fade_out");
projection = Mat3x3::outputProjection(surf.size, HYPRUTILS_TRANSFORM_NORMAL);
@ -215,7 +220,6 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
SRenderFeedback feedback;
float bga = 0.0;
const bool WAITFORASSETS = !g_pHyprlock->m_bImmediateRender && !asyncResourceGatherer->gathered;
if (WAITFORASSETS) {
@ -223,29 +227,14 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
// render status
if (!**PDISABLEBAR) {
CBox progress = {0, 0, asyncResourceGatherer->progress * surf.size.x, 2};
renderRect(progress, CColor{0.2f, 0.1f, 0.1f, 1.f}, 0);
renderRect(progress, CHyprColor{0.2f, 0.1f, 0.1f, 1.f}, 0);
}
} else {
if (!firstFullFrame) {
firstFullFrameTime = std::chrono::system_clock::now();
firstFullFrame = true;
}
bga = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - firstFullFrameTime).count() / 500000.0, 0.0, 1.0);
if (g_pHyprlock->m_bNoFadeIn)
bga = 1.0;
if (g_pHyprlock->m_bFadeStarted && !**PNOFADEOUT) {
bga =
std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(g_pHyprlock->m_tFadeEnds - std::chrono::system_clock::now()).count() / 500000.0 - 0.02, 0.0, 1.0);
// - 0.02 so that the fade ends a little earlier than the final second
}
// render widgets
const auto WIDGETS = getOrCreateWidgetsFor(&surf);
for (auto& w : *WIDGETS) {
feedback.needsFrame = w->draw({bga}) || feedback.needsFrame;
feedback.needsFrame = w->draw({opacity->value()}) || feedback.needsFrame;
}
}
@ -253,14 +242,14 @@ CRenderer::SRenderFeedback CRenderer::renderLock(const CSessionLockSurface& surf
Debug::log(TRACE, "frame {}", frames);
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered || bga < 1.0;
feedback.needsFrame = feedback.needsFrame || !asyncResourceGatherer->gathered;
glDisable(GL_BLEND);
return feedback;
}
void CRenderer::renderRect(const CBox& box, const CColor& col, int rounding) {
void CRenderer::renderRect(const CBox& box, const CHyprColor& col, int rounding) {
const auto ROUNDEDBOX = box.copy().round();
Mat3x3 matrix = projMatrix.projectBox(ROUNDEDBOX, HYPRUTILS_TRANSFORM_NORMAL, box.rot);
Mat3x3 glMatrix = projection.copy().multiply(matrix);
@ -298,12 +287,11 @@ void CRenderer::renderBorder(const CBox& box, const CGradientValueData& gradient
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());
glUniform4fv(borderShader.gradient, gradient.m_vColorsOkLabA.size(), (float*)gradient.m_vColorsOkLabA.data());
glUniform1i(borderShader.gradientLength, gradient.m_vColorsOkLabA.size() / 4);
glUniform1f(borderShader.angle, (int)(gradient.m_fAngle / (M_PI / 180.0)) % 360 * (M_PI / 180.0));
glUniform1f(borderShader.alpha, alpha);
glUniform1i(borderShader.gradient2Length, 0);
const auto TOPLEFT = Vector2D(ROUNDEDBOX.x, ROUNDEDBOX.y);
const auto FULLSIZE = Vector2D(ROUNDEDBOX.width, ROUNDEDBOX.height);
@ -637,4 +625,21 @@ void CRenderer::popFb() {
void CRenderer::removeWidgetsFor(const CSessionLockSurface* surf) {
widgets.erase(surf);
}
}
void CRenderer::startFadeIn() {
Debug::log(LOG, "Starting fade in");
*opacity = 1.f;
opacity->setCallbackOnEnd([this](auto) { opacity->setConfig(g_pConfigManager->m_AnimationTree.getConfig("fadeOut")); }, true);
}
void CRenderer::startFadeOut(bool unlock, bool immediate) {
if (immediate)
opacity->setValueAndWarp(0.f);
else
*opacity = 0.f;
if (unlock)
opacity->setCallbackOnEnd([](auto) { g_pHyprlock->releaseSessionLock(); }, true);
}

View file

@ -5,6 +5,7 @@
#include <optional>
#include "Shader.hpp"
#include "../core/LockSurface.hpp"
#include "../helpers/AnimatedVariable.hpp"
#include "../helpers/Color.hpp"
#include "AsyncResourceGatherer.hpp"
#include "../config/ConfigDataValues.hpp"
@ -22,15 +23,15 @@ class CRenderer {
};
struct SBlurParams {
int size = 0, passes = 0;
float noise = 0, contrast = 0, brightness = 0, vibrancy = 0, vibrancy_darkness = 0;
std::optional<CColor> colorize;
float boostA = 1.0;
int size = 0, passes = 0;
float noise = 0, contrast = 0, brightness = 0, vibrancy = 0, vibrancy_darkness = 0;
std::optional<CHyprColor> colorize;
float boostA = 1.0;
};
SRenderFeedback renderLock(const CSessionLockSurface& surface);
void renderRect(const CBox& box, const CColor& col, int rounding = 0);
void renderRect(const CBox& box, const CHyprColor& 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 renderTextureMix(const CBox& box, const CTexture& tex, const CTexture& tex2, float a = 1.0, float mixFactor = 0.0, int rounding = 0, std::optional<eTransform> tr = {});
@ -44,6 +45,9 @@ class CRenderer {
void removeWidgetsFor(const CSessionLockSurface* surf);
void startFadeIn();
void startFadeOut(bool unlock = false, bool immediate = true);
private:
widgetMap_t widgets;
@ -61,6 +65,8 @@ class CRenderer {
Mat3x3 projMatrix = Mat3x3::identity();
Mat3x3 projection;
PHLANIMVAR<float> opacity;
std::vector<GLint> boundFBs;
};

View file

@ -41,9 +41,13 @@ class CShader {
GLint applyTint = -1;
GLint tint = -1;
GLint gradient = -1;
GLint gradientLength = -1;
GLint angle = -1;
GLint gradient = -1;
GLint gradientLength = -1;
GLint gradient2 = -1;
GLint gradient2Length = -1;
GLint gradientLerp = -1;
GLint angle = -1;
GLint angle2 = -1;
GLint time = -1;
GLint distort = -1;

View file

@ -427,12 +427,32 @@ uniform float radius;
uniform float radiusOuter;
uniform float thick;
// Gradients are in OkLabA!!!! {l, a, b, alpha}
uniform vec4 gradient[10];
uniform vec4 gradient2[10];
uniform int gradientLength;
uniform int gradient2Length;
uniform float angle;
uniform float angle2;
uniform float gradientLerp;
uniform float alpha;
vec4 getColorForCoord(vec2 normalizedCoord) {
float linearToGamma(float x) {
return x >= 0.0031308 ? 1.055 * pow(x, 0.416666666) - 0.055 : 12.92 * x;
}
vec4 okLabAToSrgb(vec4 lab) {
float l = pow(lab[0] + lab[1] * 0.3963377774 + lab[2] * 0.2158037573, 3.0);
float m = pow(lab[0] + lab[1] * (-0.1055613458) + lab[2] * (-0.0638541728), 3.0);
float s = pow(lab[0] + lab[1] * (-0.0894841775) + lab[2] * (-1.2914855480), 3.0);
return vec4(linearToGamma(l * 4.0767416621 + m * -3.3077115913 + s * 0.2309699292),
linearToGamma(l * (-1.2684380046) + m * 2.6097574011 + s * (-0.3413193965)),
linearToGamma(l * (-0.0041960863) + m * (-0.7034186147) + s * 1.7076147010),
lab[3]);
}
vec4 getOkColorForCoordArray1(vec2 normalizedCoord) {
if (gradientLength < 2)
return gradient[0];
@ -461,6 +481,46 @@ vec4 getColorForCoord(vec2 normalizedCoord) {
return gradient[top] * (progress - float(bottom)) + gradient[bottom] * (float(top) - progress);
}
vec4 getOkColorForCoordArray2(vec2 normalizedCoord) {
if (gradient2Length < 2)
return gradient2[0];
float finalAng = 0.0;
if (angle2 > 4.71 /* 270 deg */) {
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = 6.28 - angle;
} else if (angle2 > 3.14 /* 180 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
normalizedCoord[1] = 1.0 - normalizedCoord[1];
finalAng = angle - 3.14;
} else if (angle2 > 1.57 /* 90 deg */) {
normalizedCoord[0] = 1.0 - normalizedCoord[0];
finalAng = 3.14 - angle2;
} else {
finalAng = angle2;
}
float sine = sin(finalAng);
float progress = (normalizedCoord[1] * sine + normalizedCoord[0] * (1.0 - sine)) * float(gradient2Length - 1);
int bottom = int(floor(progress));
int top = bottom + 1;
return gradient2[top] * (progress - float(bottom)) + gradient2[bottom] * (float(top) - progress);
}
vec4 getColorForCoord(vec2 normalizedCoord) {
vec4 result1 = getOkColorForCoordArray1(normalizedCoord);
if (gradient2Length <= 0)
return okLabAToSrgb(result1);
vec4 result2 = getOkColorForCoordArray2(normalizedCoord);
return okLabAToSrgb(mix(result1, result2, gradientLerp));
}
void main() {
highp vec2 pixCoord = vec2(gl_FragCoord);

View file

@ -57,7 +57,7 @@ CBackground::CBackground(const Vector2D& viewport_, COutput* output_, const std:
}
}
void CBackground::renderRect(CColor color) {
void CBackground::renderRect(CHyprColor color) {
CBox monbox = {0, 0, viewport.x, viewport.y};
g_pRenderer->renderRect(monbox, color, 0);
}
@ -86,7 +86,7 @@ static void onAssetCallbackTimer(std::shared_ptr<CTimer> self, void* data) {
bool CBackground::draw(const SRenderData& data) {
if (resourceID.empty()) {
CColor col = color;
CHyprColor col = color;
col.a *= data.opacity;
renderRect(col);
return data.opacity < 1.0;
@ -96,7 +96,7 @@ bool CBackground::draw(const SRenderData& data) {
asset = g_pRenderer->asyncResourceGatherer->getAssetByID(resourceID);
if (!asset) {
CColor col = color;
CHyprColor col = color;
col.a *= data.opacity;
renderRect(col);
return true;

View file

@ -27,7 +27,7 @@ class CBackground : public IWidget {
~CBackground();
virtual bool draw(const SRenderData& data);
void renderRect(CColor color);
void renderRect(CHyprColor color);
void onReloadTimerUpdate();
void onCrossFadeTimerUpdate();
@ -53,7 +53,7 @@ class CBackground : public IWidget {
float crossFadeTime = -1.0;
CColor color;
CHyprColor color;
SPreloadedAsset* asset = nullptr;
COutput* output = nullptr;
bool isScreenshot = false;

View file

@ -82,7 +82,7 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map<std::string,
std::string textAlign = std::any_cast<Hyprlang::STRING>(props.at("text_align"));
std::string fontFamily = std::any_cast<Hyprlang::STRING>(props.at("font_family"));
CColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
CHyprColor labelColor = std::any_cast<Hyprlang::INT>(props.at("color"));
int fontSize = std::any_cast<Hyprlang::INT>(props.at("font_size"));
label = formatString(labelPreFormat);

View file

@ -3,7 +3,12 @@
#include "../../core/hyprlock.hpp"
#include "../../auth/Auth.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../config/ConfigManager.hpp"
#include "../../helpers/Log.hpp"
#include "../../core/AnimationManager.hpp"
#include "../../helpers/Color.hpp"
#include <cmath>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/string/String.hpp>
#include <algorithm>
#include <hyprlang.hpp>
@ -14,7 +19,7 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
viewport(viewport_), outputStringPort(output), shadow(this, props, viewport_) {
try {
pos = CLayoutValueData::fromAnyPv(props.at("position"))->getAbsolute(viewport_);
size = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport_);
configSize = CLayoutValueData::fromAnyPv(props.at("size"))->getAbsolute(viewport_);
halign = std::any_cast<Hyprlang::STRING>(props.at("halign"));
valign = std::any_cast<Hyprlang::STRING>(props.at("valign"));
outThick = std::any_cast<Hyprlang::INT>(props.at("outline_thickness"));
@ -22,7 +27,6 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
dots.spacing = std::any_cast<Hyprlang::FLOAT>(props.at("dots_spacing"));
dots.center = std::any_cast<Hyprlang::INT>(props.at("dots_center"));
dots.rounding = std::any_cast<Hyprlang::INT>(props.at("dots_rounding"));
dots.fadeMs = std::any_cast<Hyprlang::INT>(props.at("dots_fade_time"));
dots.textFormat = std::any_cast<Hyprlang::STRING>(props.at("dots_text_format"));
fadeOnEmpty = std::any_cast<Hyprlang::INT>(props.at("fade_on_empty"));
fadeTimeoutMs = std::any_cast<Hyprlang::INT>(props.at("fade_timeout"));
@ -32,7 +36,6 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
configFailText = std::any_cast<Hyprlang::STRING>(props.at("fail_text"));
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 = 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"));
@ -49,19 +52,14 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
RASSERT(false, "Missing property for CPasswordInputField: {}", e.what()); //
}
configPos = pos;
configSize = size;
configPos = pos;
colorState.font = colorConfig.font;
pos = posFromHVAlign(viewport, size, pos, halign, valign);
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
dots.spacing = std::clamp(dots.spacing, -1.f, 1.f);
colorConfig.transitionMs = std::clamp(colorConfig.transitionMs, 0, 1000);
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
pos = posFromHVAlign(viewport, configSize, pos, halign, valign);
dots.size = std::clamp(dots.size, 0.2f, 0.8f);
dots.spacing = std::clamp(dots.spacing, -1.f, 1.f);
colorState.inner = colorConfig.inner;
colorState.outer = *colorConfig.outer;
colorState.font = colorConfig.font;
colorState.outerSource = colorConfig.outer;
colorConfig.caps = colorConfig.caps->m_bIsFallback ? colorConfig.fail : colorConfig.caps;
if (!dots.textFormat.empty()) {
dots.textResourceID = std::format("input:{}-{}", (uintptr_t)this, dots.textFormat);
@ -70,12 +68,21 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u
request.asset = dots.textFormat;
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = colorState.font;
request.props["font_size"] = (int)(std::nearbyint(size.y * dots.size * 0.5f) * 2.f);
request.props["color"] = colorConfig.font;
request.props["font_size"] = (int)(std::nearbyint(configSize.y * dots.size * 0.5f) * 2.f);
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
g_pAnimationManager->createAnimation(0.f, fade.a, g_pConfigManager->m_AnimationTree.getConfig("inputFieldFade"));
g_pAnimationManager->createAnimation(0.f, dots.currentAmount, g_pConfigManager->m_AnimationTree.getConfig("inputFieldDots"));
g_pAnimationManager->createAnimation(configSize, size, g_pConfigManager->m_AnimationTree.getConfig("inputFieldWidth"));
g_pAnimationManager->createAnimation(colorConfig.inner, colorState.inner, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
g_pAnimationManager->createAnimation(*colorConfig.outer, colorState.outer, g_pConfigManager->m_AnimationTree.getConfig("inputFieldColors"));
srand(std::chrono::system_clock::now().time_since_epoch().count());
// request the inital placeholder asset
updatePlaceholder();
}
@ -95,7 +102,7 @@ void CPasswordInputField::onFadeOutTimer() {
void CPasswordInputField::updateFade() {
if (!fadeOnEmpty) {
fade.a = 1.0;
fade.a->setValueAndWarp(1.0);
return;
}
@ -109,73 +116,30 @@ void CPasswordInputField::updateFade() {
fade.fadeOutTimer.reset();
}
if (!INPUTUSED && fade.a != 0.0 && (!fade.animated || fade.appearing)) {
if (!INPUTUSED && fade.a->goal() != 0.0) {
if (fade.allowFadeOut || fadeTimeoutMs == 0) {
fade.a = 1.0;
fade.animated = true;
fade.appearing = false;
fade.start = std::chrono::system_clock::now();
*fade.a = 0.0;
fade.allowFadeOut = false;
} else if (!fade.fadeOutTimer.get())
fade.fadeOutTimer = g_pHyprlock->addTimer(std::chrono::milliseconds(fadeTimeoutMs), fadeOutCallback, this);
}
if (INPUTUSED && fade.a != 1.0 && (!fade.animated || !fade.appearing)) {
fade.a = 0.0;
fade.animated = true;
fade.appearing = true;
fade.start = std::chrono::system_clock::now();
}
if (fade.animated) {
if (fade.appearing)
fade.a = std::clamp(std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
else
fade.a = std::clamp(1.0 - std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - fade.start).count() / 100000.0, 0.0, 1.0);
if ((fade.appearing && fade.a == 1.0) || (!fade.appearing && fade.a == 0.0))
fade.animated = false;
} else if (INPUTUSED && fade.a->goal() != 1.0)
*fade.a = 1.0;
if (fade.a->isBeingAnimated())
redrawShadow = true;
}
}
void CPasswordInputField::updateDots() {
if (passwordLength == dots.currentAmount)
if (dots.currentAmount->goal() == passwordLength)
return;
// Fully fading the dots to 0 currently does not look good
if (passwordLength == 0 && dots.currentAmount > 2) {
dots.currentAmount = 0;
return;
}
if (std::abs(passwordLength - dots.currentAmount) > 1) {
dots.currentAmount = std::clamp(dots.currentAmount, passwordLength - 1.f, passwordLength + 1.f);
dots.lastFrame = std::chrono::system_clock::now();
}
const auto DELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - dots.lastFrame).count(), 0, 20000);
const float TOADD = dots.fadeMs > 0 ? ((double)DELTA / 1000000.0) * (1000.0 / (double)dots.fadeMs) : 1;
if (passwordLength > dots.currentAmount) {
dots.currentAmount += TOADD;
if (dots.currentAmount > passwordLength)
dots.currentAmount = passwordLength;
} else if (passwordLength < dots.currentAmount) {
dots.currentAmount -= TOADD;
if (dots.currentAmount < passwordLength)
dots.currentAmount = passwordLength;
}
dots.lastFrame = std::chrono::system_clock::now();
if (passwordLength == 0)
dots.currentAmount->setValueAndWarp(passwordLength);
else
*dots.currentAmount = passwordLength;
}
bool CPasswordInputField::draw(const SRenderData& data) {
CBox inputFieldBox = {pos, size};
CBox outerBox = {pos - Vector2D{outThick, outThick}, size + Vector2D{outThick * 2, outThick * 2}};
if (firstRender || redrawShadow) {
firstRender = false;
redrawShadow = false;
@ -195,26 +159,29 @@ bool CPasswordInputField::draw(const SRenderData& data) {
updateWidth();
updateHiddenInputState();
SRenderData shadowData = data;
shadowData.opacity *= fade.a;
CBox inputFieldBox = {pos, size->value()};
CBox outerBox = {pos - Vector2D{outThick, outThick}, size->value() + Vector2D{outThick * 2, outThick * 2}};
if (!dynamicWidth.animated || size.x > dynamicWidth.source)
SRenderData shadowData = data;
shadowData.opacity *= fade.a->value();
if (!size->isBeingAnimated())
shadow.draw(shadowData);
CGradientValueData outerGrad = colorState.outer;
for (auto& c : outerGrad.m_vColors)
c.a *= fade.a * data.opacity;
//CGradientValueData outerGrad = colorState.outer->value();
//for (auto& c : outerGrad.m_vColors)
// c.a *= fade.a->value() * data.opacity;
CColor innerCol = colorState.inner;
innerCol.a *= fade.a * data.opacity;
CColor fontCol = colorState.font;
fontCol.a *= fade.a * data.opacity;
CHyprColor innerCol = colorState.inner->value();
innerCol.a *= fade.a->value() * data.opacity;
CHyprColor fontCol = colorState.font;
fontCol.a *= fade.a->value() * data.opacity;
if (outThick > 0) {
const int BORDERROUND = roundingForBorderBox(outerBox, rounding, outThick);
g_pRenderer->renderBorder(outerBox, outerGrad, outThick, BORDERROUND, fade.a * data.opacity);
const auto OUTERROUND = roundingForBorderBox(outerBox, rounding, outThick);
g_pRenderer->renderBorder(outerBox, colorState.outer->value(), outThick, OUTERROUND, fade.a->value() * data.opacity);
if (passwordLength != 0 && hiddenInputState.enabled && !fade.animated && data.opacity == 1.0) {
if (passwordLength != 0 && !checkWaiting && hiddenInputState.enabled) {
CBox outerBoxScaled = outerBox;
Vector2D p = outerBox.pos();
outerBoxScaled.translate(-p).scale(0.5).translate(p);
@ -224,7 +191,7 @@ 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->renderBorder(outerBox, hiddenInputState.lastColor, outThick, BORDERROUND, fade.a * data.opacity);
g_pRenderer->renderBorder(outerBox, hiddenInputState.lastColor, outThick, OUTERROUND, fade.a->value() * data.opacity);
glScissor(0, 0, viewport.x, viewport.y);
glDisable(GL_SCISSOR_TEST);
}
@ -233,7 +200,7 @@ bool CPasswordInputField::draw(const SRenderData& data) {
const int ROUND = roundingForBox(inputFieldBox, rounding);
g_pRenderer->renderRect(inputFieldBox, innerCol, ROUND);
if (!hiddenInputState.enabled && !g_pHyprlock->m_bFadeStarted) {
if (!hiddenInputState.enabled) {
const int RECTPASSSIZE = std::nearbyint(inputFieldBox.h * dots.size * 0.5f) * 2.f;
Vector2D passSize{RECTPASSSIZE, RECTPASSSIZE};
int passSpacing = std::floor(passSize.x * dots.spacing);
@ -250,49 +217,53 @@ bool CPasswordInputField::draw(const SRenderData& data) {
}
}
const int DOT_PAD = (inputFieldBox.h - passSize.y) / 2;
const int DOT_AREA_WIDTH = inputFieldBox.w - DOT_PAD * 2; // avail width for dots
const int MAX_DOTS = std::round(DOT_AREA_WIDTH * 1.0 / (passSize.x + passSpacing)); // max amount of dots that can fit in the area
const int DOT_FLOORED = std::floor(dots.currentAmount);
const float DOT_ALPHA = fontCol.a;
const auto CURRDOTS = dots.currentAmount->value();
const int DOTPAD = (inputFieldBox.h - passSize.y) / 2;
const int DOTAREAWIDTH = inputFieldBox.w - DOTPAD * 2;
const int MAXDOTS = std::round(DOTAREAWIDTH * 1.0 / (passSize.x + passSpacing));
const int DOTFLOORED = std::floor(CURRDOTS);
const auto DOTALPHA = fontCol.a;
// Calculate the total width required for all dots including spaces between them
const int TOTAL_DOTS_WIDTH = (passSize.x + passSpacing) * dots.currentAmount - passSpacing;
const int CURRWIDTH = (passSize.x + passSpacing) * CURRDOTS - passSpacing;
// Calculate starting x-position to ensure dots stay centered within the input field
int xstart = dots.center ? (DOT_AREA_WIDTH - TOTAL_DOTS_WIDTH) / 2 + DOT_PAD : DOT_PAD;
int xstart = dots.center ? (DOTAREAWIDTH - CURRWIDTH) / 2 + DOTPAD : DOTPAD;
if (dots.currentAmount > MAX_DOTS)
xstart = (inputFieldBox.w + MAX_DOTS * (passSize.x + passSpacing) - passSpacing - 2 * TOTAL_DOTS_WIDTH) / 2;
if (CURRDOTS > MAXDOTS)
xstart = (inputFieldBox.w + MAXDOTS * (passSize.x + passSpacing) - passSpacing - 2 * CURRWIDTH) / 2;
if (dots.rounding == -1)
dots.rounding = passSize.x / 2.0;
else if (dots.rounding == -2)
dots.rounding = rounding == -1 ? passSize.x / 2.0 : rounding * dots.size;
for (int i = 0; i < dots.currentAmount; ++i) {
if (i < DOT_FLOORED - MAX_DOTS)
for (int i = 0; i < CURRDOTS; ++i) {
if (i < DOTFLOORED - MAXDOTS)
continue;
if (dots.currentAmount != DOT_FLOORED) {
if (i == DOT_FLOORED)
fontCol.a *= (dots.currentAmount - DOT_FLOORED) * data.opacity;
else if (i == DOT_FLOORED - MAX_DOTS)
fontCol.a *= (1 - dots.currentAmount + DOT_FLOORED) * data.opacity;
if (CURRDOTS != DOTFLOORED) {
if (i == DOTFLOORED)
fontCol.a *= (CURRDOTS - DOTFLOORED) * data.opacity;
else if (i == DOTFLOORED - MAXDOTS)
fontCol.a *= (1 - CURRDOTS + DOTFLOORED) * data.opacity;
}
Vector2D dotPosition =
inputFieldBox.pos() + Vector2D{xstart + (int)inputFieldBox.w % 2 / 2.f + i * (passSize.x + passSpacing), inputFieldBox.h / 2.f - passSize.y / 2.f};
inputFieldBox.pos() + Vector2D{xstart + (int)inputFieldBox.w % 2 / 2.0 + i * (passSize.x + passSpacing), inputFieldBox.h / 2.0 - passSize.y / 2.0};
CBox box{dotPosition, passSize};
if (!dots.textFormat.empty()) {
if (!dots.textAsset)
if (!dots.textAsset) {
forceReload = true;
fontCol.a = DOTALPHA;
break;
}
g_pRenderer->renderTexture(box, dots.textAsset->texture, fontCol.a, dots.rounding);
} else {
g_pRenderer->renderRect(box, fontCol, dots.rounding);
}
fontCol.a = DOT_ALPHA;
fontCol.a = DOTALPHA;
}
}
@ -304,16 +275,16 @@ bool CPasswordInputField::draw(const SRenderData& data) {
currAsset = placeholder.asset;
if (currAsset && currAsset->texture.m_vSize.x + size.y <= size.x) {
if (currAsset && currAsset->texture.m_vSize.x + size->value().y <= size->value().x) {
Vector2D pos = outerBox.pos() + outerBox.size() / 2.f;
pos = pos - currAsset->texture.m_vSize / 2.f;
CBox textbox{pos, currAsset->texture.m_vSize};
g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a, 0);
g_pRenderer->renderTexture(textbox, currAsset->texture, data.opacity * fade.a->value(), 0);
} else
forceReload = true;
}
return dots.currentAmount != passwordLength || fade.animated || colorState.animated || redrawShadow || data.opacity < 1.0 || dynamicWidth.animated || forceReload;
return redrawShadow || forceReload;
}
static void failTimeoutCallback(std::shared_ptr<CTimer> self, void* data) {
@ -368,42 +339,26 @@ void CPasswordInputField::updatePlaceholder() {
request.type = CAsyncResourceGatherer::eTargetType::TARGET_TEXT;
request.props["font_family"] = fontFamily;
request.props["color"] = colorState.font;
request.props["font_size"] = (int)size.y / 4;
request.props["font_size"] = (int)size->value().y / 4;
g_pRenderer->asyncResourceGatherer->requestAsyncAssetPreload(request);
}
void CPasswordInputField::updateWidth() {
const auto NOW = std::chrono::system_clock::now();
double targetSizeX = configSize.x;
double targetSizeX = configSize.x;
if (placeholder.asset)
targetSizeX = placeholder.asset->texture.m_vSize.x + size.y;
targetSizeX = placeholder.asset->texture.m_vSize.x + size->goal().y;
if (targetSizeX < configSize.x)
targetSizeX = configSize.x;
if (size.x != targetSizeX) {
if (!dynamicWidth.animated) {
dynamicWidth.source = size.x;
dynamicWidth.start = NOW;
dynamicWidth.animated = true;
}
if (size->goal().x != targetSizeX)
*size = Vector2D{targetSizeX, configSize.y};
const auto TIMEDELTA = std::clamp((int)std::chrono::duration_cast<std::chrono::microseconds>(NOW - dynamicWidth.start).count(), 1000, 100000);
const auto INCR = std::clamp(std::abs(targetSizeX - dynamicWidth.source) * TIMEDELTA / 1000000.0, 1.0, 1000.0);
if (size.x > targetSizeX)
size.x -= INCR;
else
size.x += INCR;
if (size->isBeingAnimated())
redrawShadow = true;
if ((dynamicWidth.source < targetSizeX && size.x > targetSizeX) || (dynamicWidth.source > targetSizeX && size.x < targetSizeX)) {
size.x = targetSizeX;
redrawShadow = true;
dynamicWidth.animated = false;
}
}
pos = posFromHVAlign(viewport, size, configPos, halign, valign);
pos = posFromHVAlign(viewport, size->value(), configPos, halign, valign);
}
void CPasswordInputField::updateHiddenInputState() {
@ -413,78 +368,29 @@ void CPasswordInputField::updateHiddenInputState() {
// randomize new thang
hiddenInputState.lastPasswordLength = passwordLength;
srand(std::chrono::system_clock::now().time_since_epoch().count());
float r1 = (rand() % 100) / 255.0;
float r2 = (rand() % 100) / 255.0;
int r3 = rand() % 3;
int r4 = rand() % 2;
int r5 = rand() % 2;
const auto BASEOK = colorConfig.outer->m_vColors.front().asOkLab();
((float*)&hiddenInputState.lastColor.r)[r3] = r1 + 155 / 255.0;
((float*)&hiddenInputState.lastColor.r)[(r3 + r4) % 3] = r2 + 155 / 255.0;
// convert to polar coordinates
const auto OKICHCHROMA = std::sqrt(std::pow(BASEOK.a, 2) + std::pow(BASEOK.b, 2));
for (int i = 0; i < 3; ++i) {
if (i != r3 && i != ((r3 + r4) % 3)) {
((float*)&hiddenInputState.lastColor.r)[i] = 1.0 - ((float*)&hiddenInputState.lastColor.r)[r5 ? r3 : ((r3 + r4) % 3)];
}
}
// now randomly rotate the hue
const double OKICHHUE = (rand() % 10000000 / 10000000.0) * M_PI * 4;
hiddenInputState.lastColor.a = 1.0;
// convert back to OkLab
const Hyprgraphics::CColor newColor = Hyprgraphics::CColor::SOkLab{
.l = BASEOK.l,
.a = OKICHCHROMA * std::cos(OKICHHUE),
.b = OKICHCHROMA * std::sin(OKICHHUE),
};
hiddenInputState.lastColor = {newColor, 1.0};
hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4;
}
static void changeChannel(const float& source, const float& target, float& subject, const double& multi, bool& animated) {
const float DELTA = target - source;
if (subject != target) {
subject += DELTA * multi;
animated = true;
if ((source < target && subject > target) || (source > target && subject < target))
subject = target;
}
}
static void changeColor(const CColor& source, const CColor& target, CColor& subject, const double& multi, bool& animated) {
changeChannel(source.r, target.r, subject.r, multi, animated);
changeChannel(source.g, target.g, subject.g, multi, animated);
changeChannel(source.b, target.b, subject.b, multi, animated);
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;
const auto MULTI = colorConfig.transitionMs == 0 ?
1.0 :
std::clamp(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now() - colorState.lastFrame).count() / (double)colorConfig.transitionMs,
0.0016, 0.5);
const bool BORDERLESS = outThick == 0;
const bool NUMLOCK = (colorConfig.invertNum) ? !g_pHyprlock->m_bNumLock : g_pHyprlock->m_bNumLock;
//
CGradientValueData* targetGrad = nullptr;
if (g_pHyprlock->m_bCapsLock && NUMLOCK && !colorConfig.both->m_bIsFallback)
@ -500,37 +406,26 @@ void CPasswordInputField::updateColors() {
targetGrad = colorConfig.fail;
CGradientValueData* outerTarget = colorConfig.outer;
CColor innerTarget = colorConfig.inner;
CColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font;
CHyprColor innerTarget = colorConfig.inner;
CHyprColor fontTarget = (displayFail) ? colorConfig.fail->m_vColors.front() : colorConfig.font;
if (checkWaiting || displayFail || g_pHyprlock->m_bCapsLock || NUMLOCK) {
if (BORDERLESS && colorConfig.swapFont) {
fontTarget = colorConfig.fail->m_vColors.front();
} else if (BORDERLESS && !colorConfig.swapFont) {
innerTarget = colorConfig.fail->m_vColors.front();
// When changing the inner color the font cannot be fail_color
// When changing the inner color, the font cannot be fail_color
fontTarget = colorConfig.font;
} else {
} else if (targetGrad) {
outerTarget = targetGrad;
}
}
if (targetGrad != colorState.currentTarget) {
colorState.outerSource = &colorState.outer;
colorState.innerSource = colorState.inner;
if (!BORDERLESS && *outerTarget != colorState.outer->goal())
*colorState.outer = *outerTarget;
colorState.currentTarget = targetGrad;
}
if (innerTarget != colorState.inner->goal())
*colorState.inner = innerTarget;
colorState.animated = false;
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 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;
colorState.lastFrame = std::chrono::system_clock::now();
}

View file

@ -5,7 +5,8 @@
#include "../../helpers/Math.hpp"
#include "../../core/Timer.hpp"
#include "Shadowable.hpp"
#include "src/config/ConfigDataValues.hpp"
#include "../../config/ConfigDataValues.hpp"
#include "../../helpers/AnimatedVariable.hpp"
#include <chrono>
#include <vector>
#include <any>
@ -21,58 +22,48 @@ class CPasswordInputField : public IWidget {
void onFadeOutTimer();
private:
void updateDots();
void updateFade();
void updatePlaceholder();
void updateWidth();
void updateHiddenInputState();
void updateInputState();
void updateColors();
void updateDots();
void updateFade();
void updatePlaceholder();
void updateWidth();
void updateHiddenInputState();
void updateInputState();
void updateColors();
bool firstRender = true;
bool redrawShadow = false;
bool checkWaiting = false;
bool displayFail = false;
bool firstRender = true;
bool redrawShadow = false;
bool checkWaiting = false;
bool displayFail = false;
size_t passwordLength = 0;
size_t passwordLength = 0;
Vector2D size;
Vector2D pos;
Vector2D viewport;
Vector2D configPos;
Vector2D configSize;
PHLANIMVAR<Vector2D> size;
Vector2D pos;
Vector2D viewport;
Vector2D configPos;
Vector2D configSize;
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
uint64_t configFailTimeoutMs = 2000;
std::string halign, valign, configFailText, outputStringPort, configPlaceholderText, fontFamily;
uint64_t configFailTimeoutMs = 2000;
int outThick, rounding;
int outThick, rounding;
struct {
std::chrono::system_clock::time_point start;
bool animated = false;
double source = 0;
} dynamicWidth;
struct {
float currentAmount = 0;
int fadeMs = 0;
std::chrono::system_clock::time_point lastFrame;
bool center = false;
float size = 0;
float spacing = 0;
int rounding = 0;
std::string textFormat = "";
SPreloadedAsset* textAsset = nullptr;
std::string textResourceID;
PHLANIMVAR<float> currentAmount;
bool center = false;
float size = 0;
float spacing = 0;
int rounding = 0;
std::string textFormat = "";
std::string textResourceID;
SPreloadedAsset* textAsset = nullptr;
} dots;
struct {
std::chrono::system_clock::time_point start;
float a = 0;
bool appearing = true;
bool animated = false;
std::shared_ptr<CTimer> fadeOutTimer = nullptr;
bool allowFadeOut = false;
PHLANIMVAR<float> a;
bool appearing = true;
std::shared_ptr<CTimer> fadeOutTimer = nullptr;
bool allowFadeOut = false;
} fade;
struct {
@ -90,16 +81,16 @@ class CPasswordInputField : public IWidget {
} placeholder;
struct {
CColor lastColor;
int lastQuadrant = 0;
int lastPasswordLength = 0;
bool enabled = false;
CHyprColor lastColor;
int lastQuadrant = 0;
int lastPasswordLength = 0;
bool enabled = false;
} hiddenInputState;
struct {
CGradientValueData* outer = nullptr;
CColor inner;
CColor font;
CHyprColor inner;
CHyprColor font;
CGradientValueData* fail = nullptr;
CGradientValueData* check = nullptr;
CGradientValueData* caps = nullptr;
@ -112,19 +103,11 @@ class CPasswordInputField : public IWidget {
} colorConfig;
struct {
CGradientValueData outer;
CColor inner;
CColor font;
CGradientValueData* outerSource = nullptr;
CColor innerSource;
CGradientValueData* currentTarget = nullptr;
bool animated = false;
//
std::chrono::system_clock::time_point lastFrame;
PHLANIMVAR<CGradientValueData> outer;
PHLANIMVAR<CHyprColor> inner;
// 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.
CHyprColor font;
} colorState;
bool fadeOnEmpty;

View file

@ -18,12 +18,12 @@ class CShadowable {
virtual bool draw(const IWidget::SRenderData& data);
private:
IWidget* widget = nullptr;
int size = 10;
int passes = 4;
float boostA = 1.0;
CColor color{0, 0, 0, 1.0};
Vector2D viewport;
IWidget* widget = nullptr;
int size = 10;
int passes = 4;
float boostA = 1.0;
CHyprColor color{0, 0, 0, 1.0};
Vector2D viewport;
// to avoid recursive shadows
bool ignoreDraw = false;

View file

@ -21,7 +21,7 @@ class CShape : public IWidget {
int rounding;
double border;
double angle;
CColor color;
CHyprColor color;
CGradientValueData borderGrad;
Vector2D size;
Vector2D pos;