From 00d2cbfee3355055bf5cce18d349a7f049455eeb Mon Sep 17 00:00:00 2001 From: Maximilian Seidler <78690852+PaideiaDilemma@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:34:21 +0000 Subject: [PATCH] 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,...` --- CMakeLists.txt | 2 +- flake.lock | 18 +- src/config/ConfigDataValues.hpp | 23 +- src/config/ConfigManager.cpp | 141 ++++++++- src/config/ConfigManager.hpp | 15 +- src/core/AnimationManager.cpp | 127 ++++++++ src/core/AnimationManager.hpp | 34 +++ src/core/LockSurface.cpp | 8 +- src/core/hyprlock.cpp | 36 +-- src/core/hyprlock.hpp | 14 +- src/defines.hpp | 2 + src/helpers/AnimatedVariable.hpp | 67 ++++ src/helpers/Color.cpp | 54 +++- src/helpers/Color.hpp | 49 +-- src/main.cpp | 8 +- src/renderer/AsyncResourceGatherer.cpp | 2 +- src/renderer/Renderer.cpp | 69 +++-- src/renderer/Renderer.hpp | 16 +- src/renderer/Shader.hpp | 10 +- src/renderer/Shaders.hpp | 62 +++- src/renderer/widgets/Background.cpp | 6 +- src/renderer/widgets/Background.hpp | 4 +- src/renderer/widgets/Label.cpp | 2 +- src/renderer/widgets/PasswordInputField.cpp | 323 +++++++------------- src/renderer/widgets/PasswordInputField.hpp | 107 +++---- src/renderer/widgets/Shadowable.hpp | 12 +- src/renderer/widgets/Shape.hpp | 2 +- 27 files changed, 788 insertions(+), 425 deletions(-) create mode 100644 src/core/AnimationManager.cpp create mode 100644 src/core/AnimationManager.hpp create mode 100644 src/helpers/AnimatedVariable.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 296ef8eb..fc7e879d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -76,7 +76,7 @@ pkg_check_modules( pangocairo libdrm gbm - hyprutils>=0.2.6 + hyprutils>=0.3.3 sdbus-c++>=2.0.0 hyprgraphics) diff --git a/flake.lock b/flake.lock index cb8c168c..f055071a 100644 --- a/flake.lock +++ b/flake.lock @@ -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": { diff --git a/src/config/ConfigDataValues.hpp b/src/config/ConfigDataValues.hpp index 7e7e339a..90b9fb76 100644 --- a/src/config/ConfigDataValues.hpp +++ b/src/config/ConfigDataValues.hpp @@ -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 m_vColors; + std::vector m_vColors; + + /* Vector containing pure colors for shoving into opengl */ + std::vector 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; diff --git a/src/config/ConfigManager.cpp b/src/config/ConfigManager.cpp index a15d32dc..81c82652 100644 --- a/src/config/ConfigManager.cpp +++ b/src/config/ConfigManager.cpp @@ -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 +#include #include #include #include @@ -10,6 +12,9 @@ #include #include +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{"$FAIL"}); m_config.addSpecialConfigValue("input-field", "fail_timeout", Hyprlang::INT{2000}); - m_config.addSpecialConfigValue("input-field", "fail_transition", Hyprlang::INT{300}); m_config.addSpecialConfigValue("input-field", "capslock_color", 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::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::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 CConfigManager::handleSource(const std::string& comma return {}; } + +std::optional 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 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 {}; +} diff --git a/src/config/ConfigManager.hpp b/src/config/ConfigManager.hpp index eedf85c6..c6db06dd 100644 --- a/src/config/ConfigManager.hpp +++ b/src/config/ConfigManager.hpp @@ -1,11 +1,15 @@ #pragma once +#include + #include #include #include #include #include +#include "../defines.hpp" + class CConfigManager { public: CConfigManager(std::string configPath); @@ -19,10 +23,15 @@ class CConfigManager { std::unordered_map values; }; - std::vector getWidgetConfigs(); - std::optional handleSource(const std::string&, const std::string&); + std::vector getWidgetConfigs(); + + std::optional handleSource(const std::string&, const std::string&); + std::optional handleBezier(const std::string&, const std::string&); + std::optional handleAnimation(const std::string&, const std::string&); + + std::string configCurrentPath; - std::string configCurrentPath; + Hyprutils::Animation::CAnimationConfigTree m_AnimationTree; private: Hyprlang::CConfig m_config; diff --git a/src/core/AnimationManager.cpp b/src/core/AnimationManager.cpp new file mode 100644 index 00000000..91f8cb70 --- /dev/null +++ b/src/core/AnimationManager.cpp @@ -0,0 +1,127 @@ +#include "AnimationManager.hpp" +#include "../helpers/AnimatedVariable.hpp" +#include "../config/ConfigDataValues.hpp" +#include "../config/ConfigManager.hpp" + +#include + +CHyprlockAnimationManager::CHyprlockAnimationManager() { + ; +} + +template +void updateVariable(CAnimatedVariable& 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& 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& 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*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated float"); + updateVariable(*pTypedAV, POINTY, !**PANIMATIONSENABLED); + } break; + case AVARTYPE_VECTOR: { + auto pTypedAV = dynamic_cast*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated Vector2D"); + updateVariable(*pTypedAV, POINTY, !**PANIMATIONSENABLED); + } break; + case AVARTYPE_COLOR: { + auto pTypedAV = dynamic_cast*>(PAV.get()); + RASSERT(pTypedAV, "Failed to upcast animated CHyprColor"); + updateColorVariable(*pTypedAV, POINTY, !**PANIMATIONSENABLED); + } break; + case AVARTYPE_GRADIENT: { + auto pTypedAV = dynamic_cast*>(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; +} diff --git a/src/core/AnimationManager.hpp b/src/core/AnimationManager.hpp new file mode 100644 index 00000000..553e780b --- /dev/null +++ b/src/core/AnimationManager.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#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 + void createAnimation(const VarType& v, PHLANIMVAR& pav, SP pConfig) { + constexpr const eAnimatedVarType EAVTYPE = typeToeAnimatedVarType; + const auto PAV = makeShared>(); + + PAV->create(EAVTYPE, static_cast(this), PAV, v); + PAV->setConfig(pConfig); + + pav = std::move(PAV); + } + + bool m_bTickScheduled = false; +}; + +inline std::unique_ptr g_pAnimationManager; diff --git a/src/core/LockSurface.cpp b/src/core/LockSurface.cpp index 757c63d7..29ad05c8 100644 --- a/src/core/LockSurface.cpp +++ b/src/core/LockSurface.cpp @@ -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(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() { diff --git a/src/core/hyprlock.cpp b/src/core/hyprlock.cpp index 619273ad..5bce53ce 100644 --- a/src/core/hyprlock.cpp +++ b/src/core/hyprlock.cpp @@ -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(); 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"); - - if (**PNOFADEOUT || m_sCurrentDesktop != "Hyprland") { - releaseSessionLock(); - return; - } + const bool IMMEDIATE = m_sCurrentDesktop != "Hyprland"; - 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; diff --git a/src/core/hyprlock.hpp b/src/core/hyprlock.hpp index 2655cd6e..a2a0ca32 100644 --- a/src/core/hyprlock.hpp +++ b/src/core/hyprlock.hpp @@ -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 m_pKeyRepeatTimer = nullptr; @@ -160,6 +156,8 @@ class CHyprlock { bool timerEvent = false; } m_sLoopState; + bool m_bUnlockedCalled = false; + std::vector> m_vTimers; std::vector m_vPressedKeys; diff --git a/src/defines.hpp b/src/defines.hpp index 5043864d..7ba623de 100644 --- a/src/defines.hpp +++ b/src/defines.hpp @@ -1,6 +1,8 @@ #pragma once #include +#include using namespace Hyprutils::Memory; +using namespace Hyprgraphics; #define SP CSharedPointer #define WP CWeakPointer \ No newline at end of file diff --git a/src/helpers/AnimatedVariable.hpp b/src/helpers/AnimatedVariable.hpp new file mode 100644 index 00000000..c48176cf --- /dev/null +++ b/src/helpers/AnimatedVariable.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +#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 +// NOLINTNEXTLINE(readability-identifier-naming) +struct STypeToAnimatedVarType_t { + static constexpr eAnimatedVarType value = AVARTYPE_INVALID; +}; + +template <> +struct STypeToAnimatedVarType_t { + static constexpr eAnimatedVarType value = AVARTYPE_FLOAT; +}; + +template <> +struct STypeToAnimatedVarType_t { + static constexpr eAnimatedVarType value = AVARTYPE_VECTOR; +}; + +template <> +struct STypeToAnimatedVarType_t { + static constexpr eAnimatedVarType value = AVARTYPE_COLOR; +}; + +template <> +struct STypeToAnimatedVarType_t { + static constexpr eAnimatedVarType value = AVARTYPE_GRADIENT; +}; + +template +inline constexpr eAnimatedVarType typeToeAnimatedVarType = STypeToAnimatedVarType_t::value; + +// Utility to define a concept as a list of possible type +template +concept OneOf = (... or std::same_as); + +// 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 +concept Animable = OneOf; + +struct SAnimationContext {}; + +template +using CAnimatedVariable = Hyprutils::Animation::CGenericAnimatedVariable; + +template +using PHLANIMVAR = SP>; + +template +using PHLANIMVARREF = WP>; diff --git a/src/helpers/Color.cpp b/src/helpers/Color.cpp index f9a207bb..d2b6fcd2 100644 --- a/src/helpers/Color.cpp +++ b/src/helpers/Color.cpp @@ -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}; } \ No newline at end of file diff --git a/src/helpers/Color.hpp b/src/helpers/Color.hpp index 32ed39ee..81c6bc23 100644 --- a/src/helpers/Color.hpp +++ b/src/helpers/Color.hpp @@ -1,35 +1,46 @@ #pragma once #include +#include "../helpers/Log.hpp" +#include -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; - - CColor operator-(const CColor& c2) const { - return CColor(r - c2.r, g - c2.g, b - c2.b, a - c2.a); + uint32_t getAsHex() const; + Hyprgraphics::CColor::SSRGB asRGB() const; + Hyprgraphics::CColor::SOkLab asOkLab() const; + Hyprgraphics::CColor::SHSL asHSL() const; + CHyprColor stripA() const; + + // + 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 }; diff --git a/src/main.cpp b/src/main.cpp index 577280ce..ff2bcbcb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,7 @@ #include "config/ConfigManager.hpp" #include "core/hyprlock.hpp" #include "helpers/Log.hpp" +#include "core/AnimationManager.hpp" #include #include @@ -89,6 +90,8 @@ int main(int argc, char** argv, char** envp) { } } + g_pAnimationManager = std::make_unique(); + try { g_pConfigManager = std::make_unique(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(wlDisplay, immediate, immediateRender, noFadeIn); + g_pHyprlock = std::make_unique(wlDisplay, immediate, immediateRender); g_pHyprlock->run(); } catch (const std::exception& ex) { Debug::log(CRIT, "Hyprlock threw: {}", ex.what()); diff --git a/src/renderer/AsyncResourceGatherer.cpp b/src/renderer/AsyncResourceGatherer.cpp index b255a2ae..4cd1653d 100644 --- a/src/renderer/AsyncResourceGatherer.cpp +++ b/src/renderer/AsyncResourceGatherer.cpp @@ -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(rq.props.at("font_size")) : 16; - const CColor FONTCOLOR = rq.props.contains("color") ? std::any_cast(rq.props.at("color")) : CColor(1.0, 1.0, 1.0, 1.0); + const CHyprColor FONTCOLOR = rq.props.contains("color") ? std::any_cast(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(rq.props.at("font_family")) : "Sans"; const bool ISCMD = rq.props.contains("cmd") ? std::any_cast(rq.props.at("cmd")) : false; diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index 035982a3..ffe9f8e3 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -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 #include #include -#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(); + + 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::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(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); -} \ No newline at end of file +} + +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); +} diff --git a/src/renderer/Renderer.hpp b/src/renderer/Renderer.hpp index 66bb844d..344dadeb 100644 --- a/src/renderer/Renderer.hpp +++ b/src/renderer/Renderer.hpp @@ -5,6 +5,7 @@ #include #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 colorize; - float boostA = 1.0; + int size = 0, passes = 0; + float noise = 0, contrast = 0, brightness = 0, vibrancy = 0, vibrancy_darkness = 0; + std::optional 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 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 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 opacity; + std::vector boundFBs; }; diff --git a/src/renderer/Shader.hpp b/src/renderer/Shader.hpp index 84251675..cce40505 100644 --- a/src/renderer/Shader.hpp +++ b/src/renderer/Shader.hpp @@ -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; diff --git a/src/renderer/Shaders.hpp b/src/renderer/Shaders.hpp index 15b6c23a..08b1356a 100644 --- a/src/renderer/Shaders.hpp +++ b/src/renderer/Shaders.hpp @@ -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); diff --git a/src/renderer/widgets/Background.cpp b/src/renderer/widgets/Background.cpp index 7319eed4..998d2653 100644 --- a/src/renderer/widgets/Background.cpp +++ b/src/renderer/widgets/Background.cpp @@ -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 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; diff --git a/src/renderer/widgets/Background.hpp b/src/renderer/widgets/Background.hpp index 9e11aff0..65623f55 100644 --- a/src/renderer/widgets/Background.hpp +++ b/src/renderer/widgets/Background.hpp @@ -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; diff --git a/src/renderer/widgets/Label.cpp b/src/renderer/widgets/Label.cpp index 28715ff7..dbd533a3 100644 --- a/src/renderer/widgets/Label.cpp +++ b/src/renderer/widgets/Label.cpp @@ -82,7 +82,7 @@ CLabel::CLabel(const Vector2D& viewport_, const std::unordered_map(props.at("text_align")); std::string fontFamily = std::any_cast(props.at("font_family")); - CColor labelColor = std::any_cast(props.at("color")); + CHyprColor labelColor = std::any_cast(props.at("color")); int fontSize = std::any_cast(props.at("font_size")); label = formatString(labelPreFormat); diff --git a/src/renderer/widgets/PasswordInputField.cpp b/src/renderer/widgets/PasswordInputField.cpp index b80e4252..84f18384 100644 --- a/src/renderer/widgets/PasswordInputField.cpp +++ b/src/renderer/widgets/PasswordInputField.cpp @@ -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 +#include #include #include #include @@ -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(props.at("halign")); valign = std::any_cast(props.at("valign")); outThick = std::any_cast(props.at("outline_thickness")); @@ -22,7 +27,6 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u dots.spacing = std::any_cast(props.at("dots_spacing")); dots.center = std::any_cast(props.at("dots_center")); dots.rounding = std::any_cast(props.at("dots_rounding")); - dots.fadeMs = std::any_cast(props.at("dots_fade_time")); dots.textFormat = std::any_cast(props.at("dots_text_format")); fadeOnEmpty = std::any_cast(props.at("fade_on_empty")); fadeTimeoutMs = std::any_cast(props.at("fade_timeout")); @@ -32,7 +36,6 @@ CPasswordInputField::CPasswordInputField(const Vector2D& viewport_, const std::u configFailText = std::any_cast(props.at("fail_text")); configFailTimeoutMs = std::any_cast(props.at("fail_timeout")); fontFamily = std::any_cast(props.at("font_family")); - colorConfig.transitionMs = std::any_cast(props.at("fail_transition")); colorConfig.outer = CGradientValueData::fromAnyPv(props.at("outer_color")); colorConfig.inner = std::any_cast(props.at("inner_color")); colorConfig.font = std::any_cast(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::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::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::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(); + CBox inputFieldBox = {pos, size->value()}; + CBox outerBox = {pos - Vector2D{outThick, outThick}, size->value() + Vector2D{outThick * 2, outThick * 2}}; + SRenderData shadowData = data; - shadowData.opacity *= fade.a; + shadowData.opacity *= fade.a->value(); - if (!dynamicWidth.animated || size.x > dynamicWidth.source) + 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 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(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 ((dynamicWidth.source < targetSizeX && size.x > targetSizeX) || (dynamicWidth.source > targetSizeX && size.x < targetSizeX)) { - size.x = targetSizeX; - redrawShadow = true; - dynamicWidth.animated = false; - } - } + if (size->isBeingAnimated()) + redrawShadow = true; - 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; - - ((float*)&hiddenInputState.lastColor.r)[r3] = r1 + 155 / 255.0; - ((float*)&hiddenInputState.lastColor.r)[(r3 + r4) % 3] = r2 + 155 / 255.0; - - 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)]; - } - } + const auto BASEOK = colorConfig.outer->m_vColors.front().asOkLab(); - hiddenInputState.lastColor.a = 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) { + // convert to polar coordinates + const auto OKICHCHROMA = std::sqrt(std::pow(BASEOK.a, 2) + std::pow(BASEOK.b, 2)); - const float DELTA = target - source; + // now randomly rotate the hue + const double OKICHHUE = (rand() % 10000000 / 10000000.0) * M_PI * 4; - if (subject != target) { - subject += DELTA * multi; - animated = true; + // convert back to OkLab + const Hyprgraphics::CColor newColor = Hyprgraphics::CColor::SOkLab{ + .l = BASEOK.l, + .a = OKICHCHROMA * std::cos(OKICHHUE), + .b = OKICHCHROMA * std::sin(OKICHHUE), + }; - 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; - } + hiddenInputState.lastColor = {newColor, 1.0}; + hiddenInputState.lastQuadrant = (hiddenInputState.lastQuadrant + rand() % 3 + 1) % 4; } 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::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; - - colorState.currentTarget = targetGrad; - } - - colorState.animated = false; + if (!BORDERLESS && *outerTarget != colorState.outer->goal()) + *colorState.outer = *outerTarget; - if (!BORDERLESS) - changeGrad(colorState.outerSource, outerTarget, colorState.outer, MULTI, colorState.animated); - changeColor(colorState.innerSource, innerTarget, colorState.inner, MULTI, colorState.animated); + if (innerTarget != colorState.inner->goal()) + *colorState.inner = innerTarget; - // 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(); } diff --git a/src/renderer/widgets/PasswordInputField.hpp b/src/renderer/widgets/PasswordInputField.hpp index b06beef4..9a2e3731 100644 --- a/src/renderer/widgets/PasswordInputField.hpp +++ b/src/renderer/widgets/PasswordInputField.hpp @@ -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 #include #include @@ -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 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 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 fadeOutTimer = nullptr; - bool allowFadeOut = false; + PHLANIMVAR a; + bool appearing = true; + std::shared_ptr 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 outer; + PHLANIMVAR 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; diff --git a/src/renderer/widgets/Shadowable.hpp b/src/renderer/widgets/Shadowable.hpp index 1b06fdbd..5a18a158 100644 --- a/src/renderer/widgets/Shadowable.hpp +++ b/src/renderer/widgets/Shadowable.hpp @@ -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; diff --git a/src/renderer/widgets/Shape.hpp b/src/renderer/widgets/Shape.hpp index 1131bfa1..996ddcb8 100644 --- a/src/renderer/widgets/Shape.hpp +++ b/src/renderer/widgets/Shape.hpp @@ -21,7 +21,7 @@ class CShape : public IWidget { int rounding; double border; double angle; - CColor color; + CHyprColor color; CGradientValueData borderGrad; Vector2D size; Vector2D pos;