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.
### 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.

View file

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

View file

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

View file

@ -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"

View file

@ -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,

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

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

View file

@ -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;
};

View file

@ -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;
}

View file

@ -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
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 "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;
}

View file

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

View file

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