feat: stretch mode

This commit is contained in:
Virt 2024-07-02 15:35:37 +02:00
commit 5fee8c5545
18 changed files with 234 additions and 79 deletions

View file

@ -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. 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 ### 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` #### `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. 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 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 ### 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`. 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: # sets the cursor behaviour, supports these values:
# tilt - tilt the cursor based on x-velocity # tilt - tilt the cursor based on x-velocity
# rotate - rotate the cursor based on movement direction # 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 # none - do not change the cursors behaviour
mode = tilt mode = tilt
@ -132,6 +138,20 @@ plugin:dynamic-cursors {
function = negative_quadratic 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 # enable shake to find
# magnifies the cursor if its is being shaken # magnifies the cursor if its is being shaken
shake = true shake = true
@ -163,7 +183,7 @@ plugin:dynamic-cursors {
## performance ## 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. > **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. <br> **Software Cursors**: No (additional) performance impact. <br>
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. 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.

View file

@ -1,4 +1,5 @@
#include "globals.hpp" #include "globals.hpp"
#include "mode/Mode.hpp"
#include "src/debug/Log.hpp" #include "src/debug/Log.hpp"
#include "src/managers/eventLoop/EventLoopManager.hpp" #include "src/managers/eventLoop/EventLoopManager.hpp"
@ -61,6 +62,7 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
return; return;
auto state = pointers->stateFor(pMonitor); auto state = pointers->stateFor(pMonitor);
auto zoom = resultShown.scale;
if ((!state->hardwareFailed && state->softwareLocks == 0)) { if ((!state->hardwareFailed && state->softwareLocks == 0)) {
return; return;
@ -88,10 +90,10 @@ void CDynamicCursors::renderSoftware(CPointerManager* pointers, SP<CMonitor> pMo
box.h *= zoom; box.h *= zoom;
// we rotate the cursor by our calculated amount // we rotate the cursor by our calculated amount
box.rot = this->angle; box.rot = resultShown.rotation;
// now pass the hotspot to rotate around // 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) { void CDynamicCursors::damageSoftware(CPointerManager* pointers) {
// we damage a 3x3 area around the cursor, to accomodate for all possible hotspots and rotations // 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; Vector2D size = pointers->currentCursorImage.size / pointers->currentCursorImage.scale * zoom;
CBox b = CBox{pointers->pointerPos, size * 3}.translate(-(pointers->currentCursorImage.hotspot * zoom + size)); CBox b = CBox{pointers->pointerPos, size * 3}.translate(-(pointers->currentCursorImage.hotspot * zoom + size));
@ -126,6 +129,7 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPoint
static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr(); static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr();
auto output = state->monitor->output; auto output = state->monitor->output;
auto zoom = resultShown.scale;
auto size = pointers->currentCursorImage.size * zoom; auto size = pointers->currentCursorImage.size * zoom;
// we try to allocate a buffer that is thrice as big, see software rendering // we try to allocate a buffer that is thrice as big, see software rendering
@ -189,10 +193,10 @@ wlr_buffer* CDynamicCursors::renderHardware(CPointerManager* pointers, SP<CPoint
// the box should start in the middle portion, rotate by our calculated amount // the box should start in the middle portion, rotate by our calculated amount
CBox xbox = {size, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()}; CBox xbox = {size, Vector2D{pointers->currentCursorImage.size / pointers->currentCursorImage.scale * state->monitor->scale * zoom}.round()};
xbox.rot = this->angle; xbox.rot = resultShown.rotation;
// use our custom draw function // 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(); g_pHyprOpenGL->end();
glFlush(); glFlush();
@ -215,7 +219,7 @@ bool CDynamicCursors::setHardware(CPointerManager* pointers, SP<CPointerManager:
if (!P_MONITOR->output->cursor_swapchain) return false; if (!P_MONITOR->output->cursor_swapchain) return false;
// we need to transform the hotspot manually as we need to indent it by the size // 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) .transform(wlTransformToHyprutils(wlr_output_transform_invert(P_MONITOR->transform)), P_MONITOR->output->cursor_swapchain->width, P_MONITOR->output->cursor_swapchain->height)
.pos(); .pos();
@ -269,6 +273,7 @@ IMode* CDynamicCursors::currentMode() {
if (!strcmp(*PMODE, "rotate")) return &rotate; if (!strcmp(*PMODE, "rotate")) return &rotate;
else if (!strcmp(*PMODE, "tilt")) return &tilt; else if (!strcmp(*PMODE, "tilt")) return &tilt;
else if (!strcmp(*PMODE, "stretch")) return &stretch;
else return nullptr; else return nullptr;
} }
@ -280,33 +285,26 @@ void CDynamicCursors::calculate(EModeUpdate type) {
IMode* mode = currentMode(); IMode* mode = currentMode();
// calculate angle and zoom // calculate angle and zoom
double angle = 0;
if (mode) { if (mode) {
if (mode->strategy() == type) angle = mode->update(g_pPointerManager->pointerPos); if (mode->strategy() == type) resultMode = mode->update(g_pPointerManager->pointerPos);
else angle = this->angle; } else resultMode = SModeResult();
}
double zoom = 1;
if (**PSHAKE) { if (**PSHAKE) {
if (type == TICK) zoom = shake.update(g_pPointerManager->pointerPos); if (type == TICK) resultShake = shake.update(g_pPointerManager->pointerPos);
else zoom = this->zoom;
}
if (zoom > 1 && !**PSHAKE_EFFECTS) angle = 0;
if ( // reset mode results if shaking
std::abs(this->angle - angle) > ((PI / 180) * **PTHRESHOLD) || if (resultShake > 1 && !**PSHAKE_EFFECTS) resultMode = SModeResult();
this->zoom - zoom != 0 // we don't have a threshold here as this will not happen that often } else resultShake = 1;
) {
this->zoom = zoom;
this->angle = angle;
// clamp to zero at low angles, so that the normal position in tilt looks fine (and actually is 0) auto result = resultMode;
if (std::abs(this->angle) < ((PI / 180) * **PTHRESHOLD)) result.scale *= resultShake;
this->angle = 0;
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 // lock software cursors if zooming
if (zoom > 1) { if (resultShown.scale > 1) {
if (!zoomSoftware) { if (!zoomSoftware) {
g_pPointerManager->lockSoftwareAll(); g_pPointerManager->lockSoftwareAll();
zoomSoftware = true; zoomSoftware = true;

View file

@ -8,6 +8,7 @@
#include "mode/ModeRotate.hpp" #include "mode/ModeRotate.hpp"
#include "mode/ModeTilt.hpp" #include "mode/ModeTilt.hpp"
#include "mode/ModeStretch.hpp"
#include "other/Shake.hpp" #include "other/Shake.hpp"
class CDynamicCursors { class CDynamicCursors {
@ -32,10 +33,11 @@ class CDynamicCursors {
private: private:
SP<CEventLoopTimer> tick; SP<CEventLoopTimer> tick;
// current angle of the cursor in radiants // current state of the cursor
double angle; SModeResult resultMode;
// current zoom value of the cursor double resultShake;
double zoom = 1;
SModeResult resultShown;
// whether we have already locked software for cursor zoom // whether we have already locked software for cursor zoom
bool zoomSoftware = false; bool zoomSoftware = false;
@ -43,6 +45,7 @@ class CDynamicCursors {
// modes // modes
CModeRotate rotate; CModeRotate rotate;
CModeTilt tilt; CModeTilt tilt;
CModeStretch stretch;
/* returns the current mode, nullptr if none is selected */ /* returns the current mode, nullptr if none is selected */
IMode* currentMode(); IMode* currentMode();

View file

@ -15,6 +15,8 @@
#define CONFIG_ROTATE_OFFSET "plugin:dynamic-cursors:rotate:offset" #define CONFIG_ROTATE_OFFSET "plugin:dynamic-cursors:rotate:offset"
#define CONFIG_MASS "plugin:dynamic-cursors:tilt:limit" #define CONFIG_MASS "plugin:dynamic-cursors:tilt:limit"
#define CONFIG_FUNCTION "plugin:dynamic-cursors:tilt:function" #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" #define CONFIG_HW_DEBUG "plugin:dynamic-cursors:hw_debug"

View file

@ -37,6 +37,13 @@ void matrixTranslate(float mat[9], float x, float y) {
wlr_matrix_multiply(mat, mat, translate); 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) { void matrixRotate(float mat[9], float rad) {
float rotate[9] = { float rotate[9] = {
cos(rad), -sin(rad), 0.0f, sin(rad), cos(rad), 0.0f, 0.0f, 0.0f, 1.0f, cos(rad), -sin(rad), 0.0f, sin(rad), cos(rad), 0.0f, 0.0f, 0.0f, 1.0f,

View file

@ -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 matrixTransform(float mat[9], eTransform transform);
void matrixTranslate(float mat[9], float x, float y); 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 matrixMultiply(float mat[9], const float a[9], const float b[9]);
void matrixIdentity(float mat[9]); void matrixIdentity(float mat[9]);
void matrixRotate(float mat[9], float rad); void matrixRotate(float mat[9], float rad);

View file

@ -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_FUNCTION, Hyprlang::STRING{"negative_quadratic"});
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_MASS, Hyprlang::INT{5000}); 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_LENGTH, Hyprlang::INT{20});
HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ROTATE_OFFSET, Hyprlang::FLOAT{0}); HyprlandAPI::addConfigValue(PHANDLE, CONFIG_ROTATE_OFFSET, Hyprlang::FLOAT{0});

View file

@ -1,19 +1,14 @@
#pragma once #pragma once
#include <hyprutils/math/Vector2D.hpp> #include <hyprutils/math/Vector2D.hpp>
#include "utils.hpp"
using namespace Hyprutils::Math; 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 { class IMode {
public: public:
/* returns the desired updating strategy for the given mode */ /* returns the desired updating strategy for the given mode */
virtual EModeUpdate strategy() = 0; virtual EModeUpdate strategy() = 0;
/* updates the calculations and returns the new angle */ /* updates the calculations and returns the new angle */
virtual double update(Vector2D pos) = 0; virtual SModeResult update(Vector2D pos) = 0;
}; };

View file

@ -5,7 +5,7 @@ EModeUpdate CModeRotate::strategy() {
return MOVE; 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* PLENGTH = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_LENGTH)->getDataStaticPtr();
static auto* const* POFFSET = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_ROTATE_OFFSET)->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.x += pos.x;
end.y += pos.y; end.y += pos.y;
return angle; auto result = SModeResult();
result.rotation = angle;
return result;
} }

View file

@ -8,7 +8,7 @@ this results in a rotating mouse cursor
class CModeRotate : public IMode { class CModeRotate : public IMode {
public: public:
virtual EModeUpdate strategy(); virtual EModeUpdate strategy();
virtual double update(Vector2D pos); virtual SModeResult update(Vector2D pos);
private: private:

40
src/mode/ModeStretch.cpp Normal file
View file

@ -0,0 +1,40 @@
#include "ModeStretch.hpp"
#include "utils.hpp"
#include "../globals.hpp"
#include <hyprland/src/Compositor.hpp>
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;
}

16
src/mode/ModeStretch.hpp Normal file
View file

@ -0,0 +1,16 @@
#include "Mode.hpp"
#include <hyprutils/math/Vector2D.hpp>
#include <vector>
class CModeStretch : public IMode {
public:
virtual EModeUpdate strategy();
virtual SModeResult update(Vector2D pos);
private:
// ring buffer of last position samples
std::vector<Vector2D> samples;
int samples_index = 0;
};

View file

@ -1,43 +1,15 @@
#include "ModeTilt.hpp" #include "ModeTilt.hpp"
#include "utils.hpp"
#include "../globals.hpp" #include "../globals.hpp"
#include <hyprland/src/Compositor.hpp> #include <hyprland/src/Compositor.hpp>
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() { EModeUpdate CModeTilt::strategy() {
return TICK; 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 // create samples array
int max = g_pHyprRenderer->m_pMostHzMonitor->refreshRate / 10; // 100ms worth of history 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 // calculate speed and tilt
double speed = (samples[current].x - samples[first].x) / 0.1; 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;
} }

View file

@ -5,7 +5,7 @@
class CModeTilt : public IMode { class CModeTilt : public IMode {
public: public:
virtual EModeUpdate strategy(); virtual EModeUpdate strategy();
virtual double update(Vector2D pos); virtual SModeResult update(Vector2D pos);
private: private:

49
src/mode/utils.cpp Normal file
View file

@ -0,0 +1,49 @@
#include "utils.hpp"
#include <string>
#include <hyprland/src/debug/Log.hpp>
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;
}

31
src/mode/utils.hpp Normal file
View file

@ -0,0 +1,31 @@
#pragma once
#include <hyprutils/math/Vector2D.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)
};
/* 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);

View file

@ -1,4 +1,5 @@
#include "globals.hpp" #include "globals.hpp"
#include "src/debug/Log.hpp"
#include <GLES2/gl2.h> #include <GLES2/gl2.h>
#define private public #define private public
@ -15,7 +16,7 @@
/* /*
This is the projectBox method from hyprland, but with support for rotation around a point, the hotspot. 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 x = box.x;
double y = box.y; double y = box.y;
double width = box.width; double width = box.width;
@ -24,6 +25,19 @@ void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotat
matrixIdentity(mat); matrixIdentity(mat);
matrixTranslate(mat, x, y); 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) { if (rotation != 0) {
matrixTranslate(mat, hotspot.x, hotspot.y); matrixTranslate(mat, hotspot.x, hotspot.y);
matrixRotate(mat, rotation); 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. This renders a texture with damage but rotates the texture around a given hotspot.
*/ */
void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest) { void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch) {
TRACY_GPU_ZONE("RenderDynamicCursor"); TRACY_GPU_ZONE("RenderDynamicCursor");
alpha = std::clamp(alpha, 0.f, 1.f); alpha = std::clamp(alpha, 0.f, 1.f);
@ -58,7 +72,7 @@ void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion
// get transform // get transform
const auto TRANSFORM = wlTransformToHyprutils(wlr_output_transform_invert(!g_pHyprOpenGL->m_bEndFrame ? WL_OUTPUT_TRANSFORM_NORMAL : g_pHyprOpenGL->m_RenderData.pMonitor->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]; 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]; float glMatrix[9];
wlr_matrix_multiply(glMatrix, g_pHyprOpenGL->m_RenderData.projection, matrix); wlr_matrix_multiply(glMatrix, g_pHyprOpenGL->m_RenderData.projection, matrix);

View file

@ -1,5 +1,4 @@
#include <hyprland/src/render/OpenGL.hpp> #include <hyprland/src/render/OpenGL.hpp>
#include <hyprland/src/helpers/math/Math.hpp> #include <hyprland/src/helpers/math/Math.hpp>
void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest); void renderCursorTextureInternalWithDamage(SP<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch);
void projectCursorBox(float mat[9], CBox& box, eTransform transform, float rotation, const float projection[9], Vector2D hotspot);