diff --git a/CMakeLists.txt b/CMakeLists.txt index d32bc5455..c1ffc6b3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -131,7 +131,7 @@ find_package(glslang CONFIG REQUIRED) set(AQUAMARINE_MINIMUM_VERSION 0.9.3) set(HYPRLANG_MINIMUM_VERSION 0.6.7) set(HYPRCURSOR_MINIMUM_VERSION 0.1.7) -set(HYPRUTILS_MINIMUM_VERSION 0.11.1) +set(HYPRUTILS_MINIMUM_VERSION 0.13.0) set(HYPRGRAPHICS_MINIMUM_VERSION 0.5.1) pkg_check_modules(aquamarine_dep REQUIRED IMPORTED_TARGET aquamarine>=${AQUAMARINE_MINIMUM_VERSION}) diff --git a/example/hyprland.lua b/example/hyprland.lua index 16306bb22..dcc153428 100644 --- a/example/hyprland.lua +++ b/example/hyprland.lua @@ -139,10 +139,13 @@ hl.curve("linear", { type = "bezier", points = { {0, 0}, {1, 1} hl.curve("almostLinear", { type = "bezier", points = { {0.5, 0.5}, {0.75, 1} } }) hl.curve("quick", { type = "bezier", points = { {0.15, 0}, {0.1, 1} } }) +-- Default springs +hl.curve("easy", { type = "spring", mass = 1, stiffness = 71.2633, dampening = 15.8273644 }) + hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" }) hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" }) -hl.animation({ leaf = "windows", enabled = true, speed = 4.79, bezier = "easeOutQuint" }) -hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" }) +hl.animation({ leaf = "windows", enabled = true, speed = 4.79, spring = "easy" }) +hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, spring = "easy", style = "popin 87%" }) hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" }) hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" }) hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" }) diff --git a/flake.lock b/flake.lock index 45c80cd38..8ebb11f87 100644 --- a/flake.lock +++ b/flake.lock @@ -261,11 +261,11 @@ ] }, "locked": { - "lastModified": 1777148223, - "narHash": "sha256-PTf7kRFFzCW6rIYxLH2fWfVJmj86FSYe3k6L8B+IM9o=", + "lastModified": 1777492286, + "narHash": "sha256-PwuoEJQcjSKJNP5T55qhfDwIP0tw5zxEhfu8GDfKfeg=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "fa3992be2dfebe4ab06d753c6ca59bea298e798f", + "rev": "ec5c0c709706bad5b82f667fd8758eae442577ce", "type": "github" }, "original": { diff --git a/src/config/lua/bindings/LuaBindingsConfigRules.cpp b/src/config/lua/bindings/LuaBindingsConfigRules.cpp index 52ecaf698..14e172649 100644 --- a/src/config/lua/bindings/LuaBindingsConfigRules.cpp +++ b/src/config/lua/bindings/LuaBindingsConfigRules.cpp @@ -40,9 +40,12 @@ #include "../../../managers/input/trackpad/gestures/WorkspaceSwipeGesture.hpp" #include "../../../managers/permissions/DynamicPermissionManager.hpp" +#include + using namespace Config; using namespace Config::Lua; using namespace Config::Lua::Bindings; +using namespace Hyprutils::Utils; namespace { struct SFieldDesc { @@ -286,53 +289,101 @@ static int hlCurve(lua_State* L) { const auto& curveType = typeParser.parsed(); - if (curveType != "bezier") - return Internal::configError(L, std::format("hl.curve(\"{}\"): unknown curve type \"{}\", expected \"bezier\"", name, curveType)); - - lua_getfield(L, 2, "points"); - if (!lua_istable(L, -1)) { - lua_pop(L, 1); - return Internal::configError(L, std::format("hl.curve(\"{}\"): missing or invalid \"points\" field, expected a table of two points", name)); - } - int pointsIdx = lua_gettop(L); - - if (luaL_len(L, pointsIdx) != 2) { - lua_pop(L, 1); - return Internal::configError(L, std::format("hl.curve(\"{}\"): \"points\" must contain exactly 2 points, e.g. {{ {{0, 0}}, {{1, 1}} }}", name)); - } - - float coords[4] = {}; - for (int pt = 1; pt <= 2; pt++) { - lua_rawgeti(L, pointsIdx, pt); - if (!lua_istable(L, -1) || luaL_len(L, -1) != 2) { - lua_pop(L, 2); - return Internal::configError(L, std::format("hl.curve(\"{}\"): point {} must be a table of 2 numbers, e.g. {{0.25, 0.1}}", name, pt)); - } - int ptIdx = lua_gettop(L); - - for (int comp = 0; comp < 2; comp++) { - lua_rawgeti(L, ptIdx, comp + 1); - CLuaConfigFloat coordParser(0.F, -1.F, 2.F); - auto coordErr = coordParser.parse(L); + if (curveType == "bezier") { + lua_getfield(L, 2, "points"); + if (!lua_istable(L, -1)) { lua_pop(L, 1); - if (coordErr.errorCode != PARSE_ERROR_OK) { - lua_pop(L, 2); - return Internal::configError(L, std::format("hl.curve(\"{}\"): point {}[{}]: {}", name, pt, comp + 1, coordErr.message)); - } - coords[((pt - 1) * 2) + comp] = coordParser.parsed(); + return Internal::configError(L, std::format("hl.curve(\"{}\"): missing or invalid \"points\" field, expected a table of two points", name)); + } + int pointsIdx = lua_gettop(L); + + if (luaL_len(L, pointsIdx) != 2) { + lua_pop(L, 1); + return Internal::configError(L, std::format("hl.curve(\"{}\"): \"points\" must contain exactly 2 points, e.g. {{ {{0, 0}}, {{1, 1}} }}", name)); } - lua_pop(L, 1); - } - lua_pop(L, 1); + float coords[4] = {}; + for (int pt = 1; pt <= 2; pt++) { + lua_rawgeti(L, pointsIdx, pt); + if (!lua_istable(L, -1) || luaL_len(L, -1) != 2) { + lua_pop(L, 2); + return Internal::configError(L, std::format("hl.curve(\"{}\"): point {} must be a table of 2 numbers, e.g. {{0.25, 0.1}}", name, pt)); + } + int ptIdx = lua_gettop(L); + + for (int comp = 0; comp < 2; comp++) { + lua_rawgeti(L, ptIdx, comp + 1); + CLuaConfigFloat coordParser(0.F, -1.F, 2.F); + auto coordErr = coordParser.parse(L); + lua_pop(L, 1); + if (coordErr.errorCode != PARSE_ERROR_OK) { + lua_pop(L, 2); + return Internal::configError(L, std::format("hl.curve(\"{}\"): point {}[{}]: {}", name, pt, comp + 1, coordErr.message)); + } + coords[((pt - 1) * 2) + comp] = coordParser.parsed(); + } + + lua_pop(L, 1); + } + lua_pop(L, 1); + + g_pAnimationManager->addBezierWithName(name, Vector2D(coords[0], coords[1]), Vector2D(coords[2], coords[3])); + } else if (curveType == "spring") { + + Hyprutils::Animation::SSpringCurve curve; + + { + CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 2, "stiffness"); + + if (!lua_isnumber(L, -1)) + return Internal::configError(L, std::format("hl.curve(\"{}\"): stiffness expects a number", name)); + + curve.stiffness = lua_tonumber(L, -1); + + if (curve.stiffness <= 0.5F) + return Internal::configError(L, std::format("hl.curve(\"{}\"): stiffness expects a number >= 0.5", name)); + } + + { + CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 2, "dampening"); + + if (!lua_isnumber(L, -1)) + return Internal::configError(L, std::format("hl.curve(\"{}\"): dampening expects a number", name)); + + curve.damping = lua_tonumber(L, -1); + + if (curve.damping <= 0.5F) + return Internal::configError(L, std::format("hl.curve(\"{}\"): dampening expects a number >= 0.5", name)); + } + + { + CScopeGuard x([L] { lua_pop(L, 1); }); + + lua_getfield(L, 2, "mass"); + + if (!lua_isnumber(L, -1)) + return Internal::configError(L, std::format("hl.curve(\"{}\"): mass expects a number", name)); + + curve.mass = lua_tonumber(L, -1); + + if (curve.mass <= 0.5F) + return Internal::configError(L, std::format("hl.curve(\"{}\"): mass expects a number >= 0.5", name)); + } + + g_pAnimationManager->addSpringWithName(name, curve); + } else + return Internal::configError(L, std::format(R"(hl.curve("{}"): unknown curve type "{}", expected "bezier" or "spring")", name, curveType)); - g_pAnimationManager->addBezierWithName(name, Vector2D(coords[0], coords[1]), Vector2D(coords[2], coords[3])); return 0; } static int hlAnimation(lua_State* L) { if (!lua_istable(L, 1)) - return Internal::configError(L, "hl.animation: expected a table, e.g. { leaf = \"global\", enabled = true, speed = 5, bezier = \"default\" }"); + return Internal::configError(L, R"(hl.animation: expected a table, e.g. { leaf = "global", enabled = true, speed = 5, bezier = "default" })"); CLuaConfigString leafParser(""); auto leafErr = Internal::parseTableField(L, 1, "leaf", leafParser); @@ -366,15 +417,34 @@ static int hlAnimation(lua_State* L) { if (speed <= 0) return Internal::configError(L, std::format("hl.animation(\"{}\"): speed must be greater than 0", leaf)); - CLuaConfigString bezierParser(""); - auto bezierErr = Internal::parseTableField(L, 1, "bezier", bezierParser); - if (bezierErr.errorCode != PARSE_ERROR_OK) - return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, bezierErr.message)); + std::string curveName; - const auto& bezierName = bezierParser.parsed(); + if (Internal::hasTableField(L, 1, "bezier")) { + CLuaConfigString bezierParser(""); + auto bezierErr = Internal::parseTableField(L, 1, "bezier", bezierParser); + if (bezierErr.errorCode != PARSE_ERROR_OK) + return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, bezierErr.message)); - if (!g_pAnimationManager->bezierExists(bezierName)) - return Internal::configError(L, std::format("hl.animation(\"{}\"): no such bezier \"{}\"", leaf, bezierName)); + const auto& bezierName = bezierParser.parsed(); + + if (!g_pAnimationManager->bezierExists(bezierName)) + return Internal::configError(L, std::format(R"(hl.animation("{}"): no such bezier "{}")", leaf, bezierName)); + + curveName = bezierName; + } else if (Internal::hasTableField(L, 1, "spring")) { + CLuaConfigString springParser(""); + auto springErr = Internal::parseTableField(L, 1, "spring", springParser); + if (springErr.errorCode != PARSE_ERROR_OK) + return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, springErr.message)); + + const auto& springName = springParser.parsed(); + + if (!g_pAnimationManager->springExists(springName)) + return Internal::configError(L, std::format(R"(hl.animation("{}"): no such spring "{}")", leaf, springName)); + + curveName = "spring:" + springName; + } else + return Internal::configError(L, std::format(R"(hl.animation("{}"): bezier or spring is required)", leaf)); std::string style; lua_getfield(L, 1, "style"); @@ -383,7 +453,7 @@ static int hlAnimation(lua_State* L) { auto styleErr = styleParser.parse(L); if (styleErr.errorCode != PARSE_ERROR_OK) { lua_pop(L, 1); - return Internal::configError(L, std::format("hl.animation(\"{}\"): field \"style\": {}", leaf, styleErr.message)); + return Internal::configError(L, std::format(R"(hl.animation("{}"): field "style": {})", leaf, styleErr.message)); } style = styleParser.parsed(); } @@ -395,7 +465,7 @@ static int hlAnimation(lua_State* L) { return Internal::configError(L, std::format("hl.animation(\"{}\"): {}", leaf, err)); } - Config::animationTree()->setConfigForNode(leaf, true, speed, bezierName, style); + Config::animationTree()->setConfigForNode(leaf, true, speed, curveName, style); return 0; } diff --git a/src/config/lua/bindings/LuaBindingsInternal.cpp b/src/config/lua/bindings/LuaBindingsInternal.cpp index a6af42f89..e0cef1464 100644 --- a/src/config/lua/bindings/LuaBindingsInternal.cpp +++ b/src/config/lua/bindings/LuaBindingsInternal.cpp @@ -574,3 +574,14 @@ std::expected, int> Internal::buildRuleFromTable( return rule; } + +bool Internal::hasTableField(lua_State* L, int tableIdx, const char* field) { + lua_getfield(L, tableIdx, field); + if (lua_isnoneornil(L, -1)) { + lua_pop(L, 1); + return false; + } + + lua_pop(L, 1); + return true; +} diff --git a/src/config/lua/bindings/LuaBindingsInternal.hpp b/src/config/lua/bindings/LuaBindingsInternal.hpp index 5d4a8d87f..0cced6b47 100644 --- a/src/config/lua/bindings/LuaBindingsInternal.hpp +++ b/src/config/lua/bindings/LuaBindingsInternal.hpp @@ -196,6 +196,7 @@ namespace Config::Lua::Bindings::Internal { return err; } + bool hasTableField(lua_State* L, int tableIdx, const char* field); void registerToplevelBindings(lua_State* L, CConfigManager* mgr); void registerQueryBindings(lua_State* L); void registerNotificationBindings(lua_State* L); diff --git a/src/managers/animation/AnimationManager.cpp b/src/managers/animation/AnimationManager.cpp index 687f5f2c8..c5c3ca542 100644 --- a/src/managers/animation/AnimationManager.cpp +++ b/src/managers/animation/AnimationManager.cpp @@ -42,7 +42,7 @@ static void updateVariable(CAnimatedVariable& av, const float POINTY, b av.value() = av.begun() + DELTA * POINTY; } -static void updateColorVariable(CAnimatedVariable& av, const float POINTY, bool warp) { +static void updateColorVariable(CAnimatedVariable& av, const float POINTY, bool warp = false) { if (warp || av.value() == av.goal()) { av.warp(true, false); return; @@ -139,15 +139,12 @@ static void handleUpdate(CAnimatedVariable& av, bool warp) { animationsDisabled = animationsDisabled || ls->m_ruleApplicator->noanim().valueOrDefault(); } - const auto SPENT = av.getPercent(); - const auto PBEZIER = g_pAnimationManager->getBezier(av.getBezierName()); - const auto POINTY = PBEZIER->getYForPoint(SPENT); - const bool WARP = animationsDisabled || SPENT >= 1.f; + const auto STEP = av.getCurveStep(); if constexpr (std::same_as) - updateColorVariable(av, POINTY, WARP); + updateColorVariable(av, STEP.value, STEP.finished); else - updateVariable(av, POINTY, WARP); + updateVariable(av, STEP.value, STEP.finished); av.onUpdate(); }