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