mirror of
https://github.com/virtcode/hypr-dynamic-cursors
synced 2025-09-19 16:13:21 +02:00
feat: stretch mode
This commit is contained in:
parent
91c03dea2e
commit
5fee8c5545
18 changed files with 234 additions and 79 deletions
24
README.md
24
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. <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.
|
||||
|
|
|
@ -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<CMonitor> 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<CMonitor> 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, SP<CPoint
|
|||
static auto* const* PNEAREST = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, CONFIG_SHAKE_NEAREST)->getDataStaticPtr();
|
||||
|
||||
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, SP<CPoint
|
|||
|
||||
// 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()};
|
||||
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, SP<CPointerManager:
|
|||
if (!P_MONITOR->output->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;
|
||||
|
|
|
@ -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<CEventLoopTimer> 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();
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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});
|
||||
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/math/Vector2D.hpp>
|
||||
#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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
40
src/mode/ModeStretch.cpp
Normal file
40
src/mode/ModeStretch.cpp
Normal 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
16
src/mode/ModeStretch.hpp
Normal 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;
|
||||
|
||||
};
|
|
@ -1,43 +1,15 @@
|
|||
#include "ModeTilt.hpp"
|
||||
#include "utils.hpp"
|
||||
#include "../globals.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() {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
class CModeTilt : public IMode {
|
||||
public:
|
||||
virtual EModeUpdate strategy();
|
||||
virtual double update(Vector2D pos);
|
||||
virtual SModeResult update(Vector2D pos);
|
||||
|
||||
private:
|
||||
|
||||
|
|
49
src/mode/utils.cpp
Normal file
49
src/mode/utils.cpp
Normal 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
31
src/mode/utils.hpp
Normal 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);
|
|
@ -1,4 +1,5 @@
|
|||
#include "globals.hpp"
|
||||
#include "src/debug/Log.hpp"
|
||||
#include <GLES2/gl2.h>
|
||||
|
||||
#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<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");
|
||||
|
||||
alpha = std::clamp(alpha, 0.f, 1.f);
|
||||
|
@ -58,7 +72,7 @@ void renderCursorTextureInternalWithDamage(SP<CTexture> 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);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#include <hyprland/src/render/OpenGL.hpp>
|
||||
#include <hyprland/src/helpers/math/Math.hpp>
|
||||
|
||||
void renderCursorTextureInternalWithDamage(SP<CTexture> 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<CTexture> tex, CBox* pBox, CRegion* damage, float alpha, Vector2D hotspot, bool nearest, float stretchAngle, Vector2D stretch);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue