From 5fee8c5545c4c4a68f446408fa4ee1ae84d42c17 Mon Sep 17 00:00:00 2001 From: Virt <41426325+VirtCode@users.noreply.github.com> Date: Tue, 2 Jul 2024 15:35:37 +0200 Subject: [PATCH] feat: stretch mode --- README.md | 24 ++++++++++++++++++-- src/cursor.cpp | 46 ++++++++++++++++++------------------- src/cursor.hpp | 11 +++++---- src/globals.hpp | 2 ++ src/hyprland/math.cpp | 7 ++++++ src/hyprland/math.hpp | 1 + src/main.cpp | 3 +++ src/mode/Mode.hpp | 9 ++------ src/mode/ModeRotate.cpp | 7 ++++-- src/mode/ModeRotate.hpp | 2 +- src/mode/ModeStretch.cpp | 40 ++++++++++++++++++++++++++++++++ src/mode/ModeStretch.hpp | 16 +++++++++++++ src/mode/ModeTilt.cpp | 40 ++++++-------------------------- src/mode/ModeTilt.hpp | 2 +- src/mode/utils.cpp | 49 ++++++++++++++++++++++++++++++++++++++++ src/mode/utils.hpp | 31 +++++++++++++++++++++++++ src/renderer.cpp | 20 +++++++++++++--- src/renderer.hpp | 3 +-- 18 files changed, 234 insertions(+), 79 deletions(-) create mode 100644 src/mode/ModeStretch.cpp create mode 100644 src/mode/ModeStretch.hpp create mode 100644 src/mode/utils.cpp create mode 100644 src/mode/utils.hpp diff --git a/README.md b/README.md index 7920a5c..bb8074b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Why did I implement this again? Inspired by KDE, it also supports shake to find, to enlarge the cursor when it is shaken so it is easier to find it. It can be enabled separately or together with one simulation mode. ### simulation modes -The plugin supports two different modes, `rotate` and `tilt`. They both are customizable and have a different base behaviour. +The plugin supports a few different modes. They can all be customized induvidually. #### `rotate` In this mode, the cursor is simulated as a stick which is dragged across the screen on one end. This means it will rotate towards the movement direction, and feels really realistic. @@ -18,6 +18,11 @@ In this mode, the cursor is tilted based on the `x` direction and speed it is mo https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/ae25415c-e77f-4c85-864c-2eedbfe432e3 +#### `stretch` +This mode tries recreating the stretching and squishing that is done to moving object in comic animations. It stretches your cursor in the direction you are moving based on the speed. Yes, this is not at all realistic. + +https://github.com/VirtCode/hypr-dynamic-cursors/assets/41426325/7b8289e7-9dd2-4b57-b406-4fa28779a260 + ### shake to find The plugin supports shake to find, akin to how KDE Plasma, MacOS, etc. do it. It can also be extensively configured and is enabled by default. If you only want shake to find, and no weird cursor behaviour, you can disable the above modes with the mode `none`. @@ -99,6 +104,7 @@ plugin:dynamic-cursors { # sets the cursor behaviour, supports these values: # tilt - tilt the cursor based on x-velocity # rotate - rotate the cursor based on movement direction + # stretch - stretch the cursor shape based on direction and velocity # none - do not change the cursors behaviour mode = tilt @@ -132,6 +138,20 @@ plugin:dynamic-cursors { function = negative_quadratic } + # for mode = stretch + stretch { + + # controls how much the cursor is stretched + # this value controls at which speed (px/s) the full stretch + limit = 3000 + + # relationship between speed and tilt, supports these vaules: + # linear - a linear function is used + # quadratic - a quadratic function is used + # negative_quadratic - negative version of the quadratic one, feels more aggressive + function = quadratic + } + # enable shake to find # magnifies the cursor if its is being shaken shake = true @@ -163,7 +183,7 @@ plugin:dynamic-cursors { ## performance > **TL;DR:** Hardware cursor performance is about the same as if an animated cursor shape was shown whenever you move your mouse. Sofware cursor performance is not impacted. When the cursor is magnified during a shake, the compositor will temporarily switch to software cursors. -Depending on your hyprland configuration, this plugin can have a different performance impact. Different behaviours have a different impact, but it mainly depends on whether you are using software or hardware cursors: +Depending on your hyprland configuration, this plugin can have a different performance impact, mainly depending on whether you are using software or hardware cursors: **Software Cursors**: No (additional) performance impact.
Rotating the cursor can be done in the same draw call that is used to draw the cursor anyways, so there is no additional performance impact. Note however that software cursors in of themselves are not really efficient. diff --git a/src/cursor.cpp b/src/cursor.cpp index 8539502..fa3f9f2 100644 --- a/src/cursor.cpp +++ b/src/cursor.cpp @@ -1,4 +1,5 @@ #include "globals.hpp" +#include "mode/Mode.hpp" #include "src/debug/Log.hpp" #include "src/managers/eventLoop/EventLoopManager.hpp" @@ -61,6 +62,7 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMo return; auto state = pointers->stateFor(pMonitor); + auto zoom = resultShown.scale; if ((!state->hardwareFailed && state->softwareLocks == 0)) { return; @@ -88,10 +90,10 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP pMo box.h *= zoom; // we rotate the cursor by our calculated amount - box.rot = this->angle; + box.rot = resultShown.rotation; // now pass the hotspot to rotate around - renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + renderCursorTextureInternalWithDamage(texture, &box, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST, resultShown.stretch.angle, resultShown.stretch.magnitude); } /* @@ -101,6 +103,7 @@ It is largely identical to hyprlands implementation, but expands the damage reag void CDynamicCursors::damageSoftware(CPointerManager* pointers) { // we damage a 3x3 area around the cursor, to accomodate for all possible hotspots and rotations + auto zoom = resultShown.scale; Vector2D size = pointers->currentCursorImage.size / pointers->currentCursorImage.scale * zoom; CBox b = CBox{pointers->pointerPos, size * 3}.translate(-(pointers->currentCursorImage.hotspot * zoom + size)); @@ -126,6 +129,7 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SPgetDataStaticPtr(); auto output = state->monitor->output; + auto zoom = resultShown.scale; auto size = pointers->currentCursorImage.size * zoom; // we try to allocate a buffer that is thrice as big, see software rendering @@ -189,10 +193,10 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SPcurrentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()}; - xbox.rot = this->angle; + xbox.rot = resultShown.rotation; // use our custom draw function - renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST); + renderCursorTextureInternalWithDamage(texture, &xbox, &damage, 1.F, pointers->currentCursorImage.hotspot * state->monitor->scale * zoom, zoom > 1 && **PNEAREST, resultShown.stretch.angle, resultShown.stretch.magnitude); g_pHyprOpenGL->end(); glFlush(); @@ -215,7 +219,7 @@ bool CDynamicCursors::setHardware(CPointerManager* pointers, SPoutput->cursor_swapchain) return false; // we need to transform the hotspot manually as we need to indent it by the size - const auto HOTSPOT = CBox{((pointers->currentCursorImage.hotspot * P_MONITOR->scale) + pointers->currentCursorImage.size) * zoom, {0, 0}} + const auto HOTSPOT = CBox{((pointers->currentCursorImage.hotspot * P_MONITOR->scale) + pointers->currentCursorImage.size) * resultShown.scale, {0, 0}} .transform(wlTransformToHyprutils(wlr_output_transform_invert(P_MONITOR->transform)), P_MONITOR->output->cursor_swapchain->width, P_MONITOR->output->cursor_swapchain->height) .pos(); @@ -269,6 +273,7 @@ IMode* CDynamicCursors::currentMode() { if (!strcmp(*PMODE, "rotate")) return &rotate; else if (!strcmp(*PMODE, "tilt")) return &tilt; + else if (!strcmp(*PMODE, "stretch")) return &stretch; else return nullptr; } @@ -280,33 +285,26 @@ void CDynamicCursors::calculate(EModeUpdate type) { IMode* mode = currentMode(); // calculate angle and zoom - double angle = 0; if (mode) { - if (mode->strategy() == type) angle = mode->update(g_pPointerManager->pointerPos); - else angle = this->angle; - } + if (mode->strategy() == type) resultMode = mode->update(g_pPointerManager->pointerPos); + } else resultMode = SModeResult(); - double zoom = 1; if (**PSHAKE) { - if (type == TICK) zoom = shake.update(g_pPointerManager->pointerPos); - else zoom = this->zoom; - } - if (zoom > 1 && !**PSHAKE_EFFECTS) angle = 0; + if (type == TICK) resultShake = shake.update(g_pPointerManager->pointerPos); - if ( - std::abs(this->angle - angle) > ((PI / 180) * **PTHRESHOLD) || - this->zoom - zoom != 0 // we don't have a threshold here as this will not happen that often - ) { - this->zoom = zoom; - this->angle = angle; + // reset mode results if shaking + if (resultShake > 1 && !**PSHAKE_EFFECTS) resultMode = SModeResult(); + } else resultShake = 1; - // clamp to zero at low angles, so that the normal position in tilt looks fine (and actually is 0) - if (std::abs(this->angle) < ((PI / 180) * **PTHRESHOLD)) - this->angle = 0; + auto result = resultMode; + result.scale *= resultShake; + if (resultShown.hasDifference(&result, **PTHRESHOLD * (PI / 180.0), 0.01, 0.01)) { + resultShown = result; + resultShown.clamp(**PTHRESHOLD * (PI / 180.0), 0.01, 0.01); // clamp low values so it is rendered pixel-perfectly when no effect // lock software cursors if zooming - if (zoom > 1) { + if (resultShown.scale > 1) { if (!zoomSoftware) { g_pPointerManager->lockSoftwareAll(); zoomSoftware = true; diff --git a/src/cursor.hpp b/src/cursor.hpp index ed377f3..ad6a6bb 100644 --- a/src/cursor.hpp +++ b/src/cursor.hpp @@ -8,6 +8,7 @@ #include "mode/ModeRotate.hpp" #include "mode/ModeTilt.hpp" +#include "mode/ModeStretch.hpp" #include "other/Shake.hpp" class CDynamicCursors { @@ -32,10 +33,11 @@ class CDynamicCursors { private: SP tick; - // current angle of the cursor in radiants - double angle; - // current zoom value of the cursor - double zoom = 1; + // current state of the cursor + SModeResult resultMode; + double resultShake; + + SModeResult resultShown; // whether we have already locked software for cursor zoom bool zoomSoftware = false; @@ -43,6 +45,7 @@ class CDynamicCursors { // modes CModeRotate rotate; CModeTilt tilt; + CModeStretch stretch; /* returns the current mode, nullptr if none is selected */ IMode* currentMode(); diff --git a/src/globals.hpp b/src/globals.hpp index 592478e..4a91cda 100644 --- a/src/globals.hpp +++ b/src/globals.hpp @@ -15,6 +15,8 @@ #define CONFIG_ROTATE_OFFSET "plugin:dynamic-cursors:rotate:offset" #define CONFIG_MASS "plugin:dynamic-cursors:tilt:limit" #define CONFIG_FUNCTION "plugin:dynamic-cursors:tilt:function" +#define CONFIG_STRETCH_LIMIT "plugin:dynamic-cursors:stretch:limit" +#define CONFIG_STRETCH_FUNCTION "plugin:dynamic-cursors:stretch:function" #define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug" diff --git a/src/hyprland/math.cpp b/src/hyprland/math.cpp index 33ad4cc..98d8ebd 100644 --- a/src/hyprland/math.cpp +++ b/src/hyprland/math.cpp @@ -37,6 +37,13 @@ void matrixTranslate(float mat[9], float x, float y) { wlr_matrix_multiply(mat, mat, translate); } +void matrixScale(float mat[9], float x, float y) { + float scale[9] = { + x, 0.0f, 0.0f, 0.0f, y, 0.0f, 0.0f, 0.0f, 1.0f, + }; + matrixMultiply(mat, mat, scale); +} + void matrixRotate(float mat[9], float rad) { float rotate[9] = { cos(rad), -sin(rad), 0.0f, sin(rad), cos(rad), 0.0f, 0.0f, 0.0f, 1.0f, diff --git a/src/hyprland/math.hpp b/src/hyprland/math.hpp index 5bf8c50..9d32447 100644 --- a/src/hyprland/math.hpp +++ b/src/hyprland/math.hpp @@ -8,6 +8,7 @@ This is nessecary because we cannot use functions which are not declared in any void matrixTransform(float mat[9], eTransform transform); void matrixTranslate(float mat[9], float x, float y); +void matrixScale(float mat[9], float x, float y); void matrixMultiply(float mat[9], const float a[9], const float b[9]); void matrixIdentity(float mat[9]); void matrixRotate(float mat[9], float rad); diff --git a/src/main.cpp b/src/main.cpp index 79f12d5..a3ed0d7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -94,6 +94,9 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) { HyprlandAPI::addConfigValue(PHANDLE, CONFIG_FUNCTION, Hyprlang::STRING{"negative_quadratic"}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MASS, Hyprlang::INT{5000}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_STRETCH_FUNCTION, Hyprlang::STRING{"negative_quadratic"}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_STRETCH_LIMIT, Hyprlang::INT{3000}); + HyprlandAPI::addConfigValue(PHANDLE, CONFIG_LENGTH, Hyprlang::INT{20}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ROTATE_OFFSET, Hyprlang::FLOAT{0}); diff --git a/src/mode/Mode.hpp b/src/mode/Mode.hpp index c37f1dd..6935f4c 100644 --- a/src/mode/Mode.hpp +++ b/src/mode/Mode.hpp @@ -1,19 +1,14 @@ #pragma once #include +#include "utils.hpp" using namespace Hyprutils::Math; -/* specifies when a mode wants to be updated */ -enum EModeUpdate { - MOVE, // on mouse move - TICK // on tick (i.e. every frame) -}; - class IMode { public: /* returns the desired updating strategy for the given mode */ virtual EModeUpdate strategy() = 0; /* updates the calculations and returns the new angle */ - virtual double update(Vector2D pos) = 0; + virtual SModeResult update(Vector2D pos) = 0; }; diff --git a/src/mode/ModeRotate.cpp b/src/mode/ModeRotate.cpp index f4ee81a..d36b72e 100644 --- a/src/mode/ModeRotate.cpp +++ b/src/mode/ModeRotate.cpp @@ -5,7 +5,7 @@ EModeUpdate CModeRotate::strategy() { return MOVE; } -double CModeRotate::update(Vector2D pos) { +SModeResult CModeRotate::update(Vector2D pos) { static auto* const* PLENGTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_LENGTH)->getDataStaticPtr(); static auto* const* POFFSET = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ROTATE_OFFSET)->getDataStaticPtr(); @@ -32,5 +32,8 @@ double CModeRotate::update(Vector2D pos) { end.x += pos.x; end.y += pos.y; - return angle; + auto result = SModeResult(); + result.rotation = angle; + + return result; } diff --git a/src/mode/ModeRotate.hpp b/src/mode/ModeRotate.hpp index 385ac39..dbb60e4 100644 --- a/src/mode/ModeRotate.hpp +++ b/src/mode/ModeRotate.hpp @@ -8,7 +8,7 @@ this results in a rotating mouse cursor class CModeRotate : public IMode { public: virtual EModeUpdate strategy(); - virtual double update(Vector2D pos); + virtual SModeResult update(Vector2D pos); private: diff --git a/src/mode/ModeStretch.cpp b/src/mode/ModeStretch.cpp new file mode 100644 index 0000000..a4a1067 --- /dev/null +++ b/src/mode/ModeStretch.cpp @@ -0,0 +1,40 @@ +#include "ModeStretch.hpp" +#include "utils.hpp" +#include "../globals.hpp" +#include + +EModeUpdate CModeStretch::strategy() { + return TICK; +} + +SModeResult CModeStretch::update(Vector2D pos) { + static auto const* PFUNCTION = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_STRETCH_FUNCTION)->getDataStaticPtr(); + static auto* const* PLIMIT = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_STRETCH_LIMIT)->getDataStaticPtr(); + + // create samples array + int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate / 10; // 100ms worth of history + samples.resize(max); + + // capture current sample + samples[samples_index] = Vector2D{pos}; + int current = samples_index; + samples_index = (samples_index + 1) % max; // increase for next sample + int first = samples_index; + + // calculate speed and tilt + Vector2D speed = (samples[current] - samples[first]) / 0.1; + double mag = speed.size(); + + double angle = -std::atan(speed.x / speed.y) + PI; + if (speed.y > 0) angle += PI; + if (mag == 0) angle = 0; + + double scale = activation(*PFUNCTION, **PLIMIT, mag); + + auto result = SModeResult(); + result.stretch.angle = angle; + // we can't do more scaling than that because of how large our buffer around the cursor shape is + result.stretch.magnitude = Vector2D{1.0 - scale * 0.5, 1.0 + scale * 1.0}; + + return result; +} diff --git a/src/mode/ModeStretch.hpp b/src/mode/ModeStretch.hpp new file mode 100644 index 0000000..a0bbc38 --- /dev/null +++ b/src/mode/ModeStretch.hpp @@ -0,0 +1,16 @@ +#include "Mode.hpp" +#include +#include + +class CModeStretch : public IMode { + public: + virtual EModeUpdate strategy(); + virtual SModeResult update(Vector2D pos); + + private: + + // ring buffer of last position samples + std::vector samples; + int samples_index = 0; + +}; diff --git a/src/mode/ModeTilt.cpp b/src/mode/ModeTilt.cpp index 40029d8..3019af3 100644 --- a/src/mode/ModeTilt.cpp +++ b/src/mode/ModeTilt.cpp @@ -1,43 +1,15 @@ #include "ModeTilt.hpp" +#include "utils.hpp" #include "../globals.hpp" #include -double function(double speed) { - static auto const* PFUNCTION = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_FUNCTION)->getDataStaticPtr(); - static auto* const* PMASS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MASS)->getDataStaticPtr(); - double mass = **PMASS; - - double result = 0; - if (!strcmp(*PFUNCTION, "linear")) { - - result = speed / **PMASS; - - } else if (!strcmp(*PFUNCTION, "quadratic")) { - - - // (1 / m²) * x², is a quadratic function which will reach 1 at m - result = (1.0 / (mass * mass)) * (speed * speed); - result *= (speed > 0 ? 1 : -1); - - } else if (!strcmp(*PFUNCTION, "negative_quadratic")) { - - float x = std::abs(speed); - // (-1 / m²) * (x - m)² + 1, is a quadratic function with the inverse curvature which will reach 1 at m - result = (-1.0 / (mass * mass)) * ((x - mass) * (x - mass)) + 1; - if (x > mass) result = 1; // need to clamp manually, as the function would decrease again - - result *= (speed > 0 ? 1 : -1); - } else - Debug::log(WARN, "[dynamic-cursors] unknown air function specified"); - - return std::clamp(result, -1.0, 1.0); -} - EModeUpdate CModeTilt::strategy() { return TICK; } -double CModeTilt::update(Vector2D pos) { +SModeResult CModeTilt::update(Vector2D pos) { + static auto const* PFUNCTION = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_FUNCTION)->getDataStaticPtr(); + static auto* const* PMASS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_MASS)->getDataStaticPtr(); // create samples array int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate / 10; // 100ms worth of history @@ -52,5 +24,7 @@ double CModeTilt::update(Vector2D pos) { // calculate speed and tilt double speed = (samples[current].x - samples[first].x) / 0.1; - return function(speed) * (PI / 3); // 120° in both directions + auto result = SModeResult(); + result.rotation = activation(*PFUNCTION, **PMASS, speed) * (PI / 3); // 120° in both directions + return result; } diff --git a/src/mode/ModeTilt.hpp b/src/mode/ModeTilt.hpp index 2c69b96..a83b9e8 100644 --- a/src/mode/ModeTilt.hpp +++ b/src/mode/ModeTilt.hpp @@ -5,7 +5,7 @@ class CModeTilt : public IMode { public: virtual EModeUpdate strategy(); - virtual double update(Vector2D pos); + virtual SModeResult update(Vector2D pos); private: diff --git a/src/mode/utils.cpp b/src/mode/utils.cpp new file mode 100644 index 0000000..0460227 --- /dev/null +++ b/src/mode/utils.cpp @@ -0,0 +1,49 @@ +#include "utils.hpp" +#include +#include + +double activation(std::string function, double max, double value) { + double result = 0; + if (function == "linear") { + + result = value / max; + + } else if (function == "quadratic") { + + // (1 / m²) * x², is a quadratic function which will reach 1 at m + result = (1.0 / (max * max)) * (value * value); + result *= (value > 0 ? 1 : -1); + + } else if (function == "negative_quadratic") { + + float x = std::abs(value); + // (-1 / m²) * (x - m)² + 1, is a quadratic function with the inverse curvature which will reach 1 at m + result = (-1.0 / (max * max)) * ((x - max) * (x - max)) + 1; + if (x > max) result = 1; // need to clamp manually, as the function would decrease again + + result *= (value > 0 ? 1 : -1); + } else + Debug::log(WARN, "[dynamic-cursors] unknown air function specified"); + + return std::clamp(result, -1.0, 1.0); +} + +void SModeResult::clamp(double angle, double scale, double stretch) { + if (std::abs(this->rotation) < angle) + this->rotation = 0; + + if (std::abs(1 - this->scale) < scale) + this->scale = 1; + + if (std::abs(1 - this->stretch.magnitude.x) < stretch && std::abs(1 - this->stretch.magnitude.x) < stretch) + this->stretch.magnitude = Vector2D{1,1}; +} + +bool SModeResult::hasDifference(SModeResult* other, double angle, double scale, double stretch) { + return + std::abs(other->rotation - this->rotation) > angle || + std::abs(other->scale - this->scale) > scale || + std::abs(other->stretch.angle - this->stretch.angle) > angle || + std::abs(other->stretch.magnitude.x - this->stretch.magnitude.x) > stretch || + std::abs(other->stretch.magnitude.y - this->stretch.magnitude.y) > stretch; +} diff --git a/src/mode/utils.hpp b/src/mode/utils.hpp new file mode 100644 index 0000000..21af7db --- /dev/null +++ b/src/mode/utils.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include + +using namespace Hyprutils::Math; + +/* specifies when a mode wants to be updated */ +enum EModeUpdate { + MOVE, // on mouse move + TICK // on tick (i.e. every frame) +}; + +/* result determined by the mode */ +struct SModeResult { + // rotation of the shape around hotspot + double rotation = 0; + + // uniform scaling of the shape around hotspot + double scale = 1; + + // stretch along axis with angle, going through hotspot + struct { + double angle = 0; + Vector2D magnitude = Vector2D{1,1}; + } stretch; + + void clamp(double angle, double scale, double stretch); + bool hasDifference(SModeResult* other, double angle, double scale, double stretch); +}; + +double activation(std::string function, double max, double value); diff --git a/src/renderer.cpp b/src/renderer.cpp index 7dbb304..2663376 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -1,4 +1,5 @@ #include "globals.hpp" +#include "src/debug/Log.hpp" #include #define private public @@ -15,7 +16,7 @@ /* This is the projectBox method from hyprland, but with support for rotation around a point, the hotspot. */ -void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot) { +void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot, float stretchAngle, Vector2D stretch) { double x = box.x; double y = box.y; double width = box.width; @@ -24,6 +25,19 @@ void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotat matrixIdentity(mat); matrixTranslate(mat, x, y); + if (stretch != Vector2D{1,1}) { + // center to origin, rotate, shift up, scale, undo + // we do the shifting up so the stretch is "only to one side" + + matrixTranslate(mat, box.w / 2, box.h / 2); + matrixRotate(mat, stretchAngle); + matrixTranslate(mat, 0, box.h / 2); + matrixScale(mat, stretch.x, stretch.y); + matrixTranslate(mat, 0, box.h / -2); + matrixRotate(mat, -stretchAngle); + matrixTranslate(mat, box.w / -2, box.h / -2); + } + if (rotation != 0) { matrixTranslate(mat, hotspot.x, hotspot.y); matrixRotate(mat, rotation); @@ -44,7 +58,7 @@ void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotat /* This renders a texture with damage but rotates the texture around a given hotspot. */ -void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest) { +void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch) { TRACY_GPU_ZONE("RenderDynamicCursor"); alpha = std::clamp(alpha, 0.f, 1.f); @@ -58,7 +72,7 @@ void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion // get transform const auto TRANSFORM = wlTransformToHyprutils(wlr_output_transform_invert(!g_pHyprOpenGL->m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : g_pHyprOpenGL->m_RenderData.pMonitor->transform)); float matrix[9]; - projectCursorBox(matrix, newBox, TRANSFORM, newBox.rot, g_pHyprOpenGL->m_RenderData.monitorProjection.data(), hotspot); + projectCursorBox(matrix, newBox, TRANSFORM, newBox.rot, g_pHyprOpenGL->m_RenderData.monitorProjection.data(), hotspot, stretchAngle, stretch); float glMatrix[9]; wlr_matrix_multiply(glMatrix, g_pHyprOpenGL->m_RenderData.projection, matrix); diff --git a/src/renderer.hpp b/src/renderer.hpp index 05f26a4..c392acf 100644 --- a/src/renderer.hpp +++ b/src/renderer.hpp @@ -1,5 +1,4 @@ #include #include -void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); -void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot); +void renderCursorTextureInternalWithDamage(SP tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch);