mirror of
https://github.com/hyprwm/hyprland-plugins.git
synced 2024-12-22 06:59:48 +01:00
hyprtrails: add plugin
This commit is contained in:
parent
e15ac98dc6
commit
87955bceb1
8 changed files with 611 additions and 0 deletions
|
@ -30,3 +30,14 @@ output = "hyprbars/hyprbars.so"
|
|||
steps = [
|
||||
"make -C hyprbars all",
|
||||
]
|
||||
|
||||
[hyprtrails]
|
||||
description = "A plugin to add trails behind moving windows"
|
||||
version = "1.0"
|
||||
authors = ["Vaxry"]
|
||||
|
||||
[hyprtrails.build]
|
||||
output = "hyprtrails/hyprtrails.so"
|
||||
steps = [
|
||||
"make -C hyprtrails all",
|
||||
]
|
||||
|
|
4
hyprtrails/Makefile
Normal file
4
hyprtrails/Makefile
Normal file
|
@ -0,0 +1,4 @@
|
|||
all:
|
||||
$(CXX) -shared -fPIC --no-gnu-unique main.cpp trail.cpp -o hyprtrails.so -g `pkg-config --cflags pixman-1 libdrm hyprland` -std=c++2b -O2
|
||||
clean:
|
||||
rm ./hyprtrails.so
|
18
hyprtrails/README.md
Normal file
18
hyprtrails/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
# hyprtrails
|
||||
|
||||
A neat, but useless plugin to add trails behind windows:
|
||||
|
||||
https://github.com/hyprwm/hyprland-plugins/assets/43317083/6c31b839-92cd-4510-bb12-110d77dd5b44
|
||||
|
||||
Maybe isn't the most efficient. The curve-related settings are only for advanced users.
|
||||
Be warned they _incredibly_ impact performance.
|
||||
|
||||
Only setting you may want to change:
|
||||
```ini
|
||||
plugin {
|
||||
hyprtrails {
|
||||
color = rgba(ffaa00ff)
|
||||
}
|
||||
}
|
||||
|
||||
```
|
12
hyprtrails/globals.hpp
Normal file
12
hyprtrails/globals.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprland/src/plugins/PluginAPI.hpp>
|
||||
|
||||
inline HANDLE PHANDLE = nullptr;
|
||||
|
||||
struct SGlobalState {
|
||||
CShader trailShader;
|
||||
wl_event_source* tick = nullptr;
|
||||
};
|
||||
|
||||
inline std::unique_ptr<SGlobalState> g_pGlobalState;
|
134
hyprtrails/main.cpp
Normal file
134
hyprtrails/main.cpp
Normal file
|
@ -0,0 +1,134 @@
|
|||
#define WLR_USE_UNSTABLE
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <any>
|
||||
#include <hyprland/src/Compositor.hpp>
|
||||
#include <hyprland/src/Window.hpp>
|
||||
#include <hyprland/src/config/ConfigManager.hpp>
|
||||
#include <hyprland/src/render/Shaders.hpp>
|
||||
|
||||
#include "globals.hpp"
|
||||
#include "shaders.hpp"
|
||||
#include "trail.hpp"
|
||||
|
||||
// Do NOT change this function.
|
||||
APICALL EXPORT std::string PLUGIN_API_VERSION() { return HYPRLAND_API_VERSION; }
|
||||
|
||||
void onNewWindow(void* self, std::any data) {
|
||||
// data is guaranteed
|
||||
auto* const PWINDOW = std::any_cast<CWindow*>(data);
|
||||
|
||||
HyprlandAPI::addWindowDecoration(PHANDLE, PWINDOW, new CTrail(PWINDOW));
|
||||
}
|
||||
|
||||
GLuint CompileShader(const GLuint& type, std::string src) {
|
||||
auto shader = glCreateShader(type);
|
||||
|
||||
auto shaderSource = src.c_str();
|
||||
|
||||
glShaderSource(shader, 1, (const GLchar**)&shaderSource, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint ok;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &ok);
|
||||
|
||||
if (ok == GL_FALSE) throw std::runtime_error("compileShader() failed!");
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
GLuint CreateProgram(const std::string& vert, const std::string& frag) {
|
||||
auto vertCompiled = CompileShader(GL_VERTEX_SHADER, vert);
|
||||
|
||||
if (!vertCompiled) throw std::runtime_error("Compiling vshader failed.");
|
||||
|
||||
auto fragCompiled = CompileShader(GL_FRAGMENT_SHADER, frag);
|
||||
|
||||
if (!fragCompiled) throw std::runtime_error("Compiling fshader failed.");
|
||||
|
||||
auto prog = glCreateProgram();
|
||||
glAttachShader(prog, vertCompiled);
|
||||
glAttachShader(prog, fragCompiled);
|
||||
glLinkProgram(prog);
|
||||
|
||||
glDetachShader(prog, vertCompiled);
|
||||
glDetachShader(prog, fragCompiled);
|
||||
glDeleteShader(vertCompiled);
|
||||
glDeleteShader(fragCompiled);
|
||||
|
||||
GLint ok;
|
||||
glGetProgramiv(prog, GL_LINK_STATUS, &ok);
|
||||
|
||||
if (ok == GL_FALSE) throw std::runtime_error("createProgram() failed! GL_LINK_STATUS not OK!");
|
||||
|
||||
return prog;
|
||||
}
|
||||
|
||||
int onTick(void* data) {
|
||||
EMIT_HOOK_EVENT("trailTick", nullptr);
|
||||
|
||||
const int TIMEOUT = g_pHyprRenderer->m_pMostHzMonitor ? 1000.0 / g_pHyprRenderer->m_pMostHzMonitor->refreshRate : 16;
|
||||
wl_event_source_timer_update(g_pGlobalState->tick, TIMEOUT);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void initGlobal() {
|
||||
RASSERT(
|
||||
eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, wlr_egl_get_context(g_pCompositor->m_sWLREGL)),
|
||||
"Couldn't set current EGL!");
|
||||
|
||||
GLuint prog = CreateProgram(QUADTRAIL, FRAGTRAIL);
|
||||
g_pGlobalState->trailShader.program = prog;
|
||||
g_pGlobalState->trailShader.proj = glGetUniformLocation(prog, "proj");
|
||||
g_pGlobalState->trailShader.tex = glGetUniformLocation(prog, "tex");
|
||||
g_pGlobalState->trailShader.color = glGetUniformLocation(prog, "color");
|
||||
g_pGlobalState->trailShader.texAttrib = glGetAttribLocation(prog, "colors");
|
||||
g_pGlobalState->trailShader.posAttrib = glGetAttribLocation(prog, "pos");
|
||||
g_pGlobalState->trailShader.gradient = glGetUniformLocation(prog, "snapshots");
|
||||
|
||||
RASSERT(eglMakeCurrent(wlr_egl_get_display(g_pCompositor->m_sWLREGL), EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT),
|
||||
"Couldn't unset current EGL!");
|
||||
|
||||
g_pGlobalState->tick = wl_event_loop_add_timer(g_pCompositor->m_sWLEventLoop, &onTick, nullptr);
|
||||
wl_event_source_timer_update(g_pGlobalState->tick, 1);
|
||||
}
|
||||
|
||||
APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
|
||||
PHANDLE = handle;
|
||||
|
||||
const std::string HASH = __hyprland_api_get_hash();
|
||||
|
||||
if (HASH != GIT_COMMIT_HASH) {
|
||||
HyprlandAPI::addNotification(PHANDLE, "[ht] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)",
|
||||
CColor{1.0, 0.2, 0.2, 1.0}, 5000);
|
||||
throw std::runtime_error("[ht] Version mismatch");
|
||||
}
|
||||
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:bezier_step", SConfigValue{.floatValue = 0.025});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:points_per_step", SConfigValue{.intValue = 2});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:history_points", SConfigValue{.intValue = 20});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:history_step", SConfigValue{.intValue = 2});
|
||||
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprtrails:color", SConfigValue{.intValue = configStringToInt("rgba(ffaa00ff)")});
|
||||
|
||||
HyprlandAPI::registerCallbackDynamic(PHANDLE, "openWindow", [&](void* self, SCallbackInfo& info, std::any data) { onNewWindow(self, data); });
|
||||
|
||||
g_pGlobalState = std::make_unique<SGlobalState>();
|
||||
initGlobal();
|
||||
|
||||
// add deco to existing windows
|
||||
for (auto& w : g_pCompositor->m_vWindows) {
|
||||
if (w->isHidden() || !w->m_bIsMapped) continue;
|
||||
|
||||
HyprlandAPI::addWindowDecoration(PHANDLE, w.get(), new CTrail(w.get()));
|
||||
}
|
||||
|
||||
HyprlandAPI::reloadConfig();
|
||||
|
||||
HyprlandAPI::addNotification(PHANDLE, "[hyprtrails] Initialized successfully!", CColor{0.2, 1.0, 0.2, 1.0}, 5000);
|
||||
|
||||
return {"hyprtrails", "A plugin to add trails behind moving windows", "Vaxry", "1.0"};
|
||||
}
|
||||
|
||||
APICALL EXPORT void PLUGIN_EXIT() { wl_event_source_remove(g_pGlobalState->tick); }
|
54
hyprtrails/shaders.hpp
Normal file
54
hyprtrails/shaders.hpp
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
inline const std::string QUADTRAIL = R"#(
|
||||
uniform mat3 proj;
|
||||
uniform vec4 color;
|
||||
attribute vec2 pos;
|
||||
attribute vec2 texcoord;
|
||||
attribute vec4 colors;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texcoord;
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(proj * vec3(pos, 1.0), 1.0);
|
||||
v_color = color;
|
||||
v_texcoord = texcoord;
|
||||
})#";
|
||||
|
||||
inline const std::string FRAGTRAIL = R"#(
|
||||
precision mediump float;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texcoord;
|
||||
|
||||
uniform vec4 window;
|
||||
|
||||
float distToRect(vec4 rect) {
|
||||
float dx = max(rect[0] - v_texcoord[0], max(0.0, v_texcoord[0] - rect[2]));
|
||||
float dy = max(rect[1] - v_texcoord[1], max(0.0, v_texcoord[1] - rect[3]));
|
||||
return sqrt(dx*dx + dy*dy);
|
||||
}
|
||||
|
||||
float alphaForShot(vec4 shot, float threshold) {
|
||||
|
||||
float dist = distToRect(shot);
|
||||
|
||||
if (dist > threshold)
|
||||
return 0.0;
|
||||
|
||||
if (dist <= 0.0)
|
||||
return 0.0;
|
||||
|
||||
return 1.0 - (dist * (1.0 / threshold));
|
||||
}
|
||||
|
||||
void main() {
|
||||
|
||||
vec4 pixColor = v_color;
|
||||
float a = v_color[3]; // clamp(alphaForShot(window, 0.5), 0.0, 1.0); // todo
|
||||
|
||||
pixColor.rgb *= a;
|
||||
|
||||
gl_FragColor = pixColor;
|
||||
})#";
|
316
hyprtrails/trail.cpp
Normal file
316
hyprtrails/trail.cpp
Normal file
|
@ -0,0 +1,316 @@
|
|||
#include "trail.hpp"
|
||||
|
||||
#include <hyprland/src/Compositor.hpp>
|
||||
#include <hyprland/src/Window.hpp>
|
||||
|
||||
#include "globals.hpp"
|
||||
|
||||
void CTrail::onTick() {
|
||||
static auto* const PHISTORYSTEP = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:history_step")->intValue;
|
||||
static auto* const PHISTORYPOINTS = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:history_points")->intValue;
|
||||
|
||||
m_iTimer++;
|
||||
|
||||
if (m_iTimer > *PHISTORYSTEP) {
|
||||
m_dLastGeoms.push_front({box{(float)m_pWindow->m_vRealPosition.vec().x, (float)m_pWindow->m_vRealPosition.vec().y,
|
||||
(float)m_pWindow->m_vRealSize.vec().x, (float)m_pWindow->m_vRealSize.vec().y},
|
||||
std::chrono::system_clock::now()});
|
||||
while (m_dLastGeoms.size() > *PHISTORYPOINTS) m_dLastGeoms.pop_back();
|
||||
|
||||
m_iTimer = 0;
|
||||
}
|
||||
|
||||
if (m_bNeedsDamage) {
|
||||
g_pHyprRenderer->damageBox(&m_bLastBox);
|
||||
m_bNeedsDamage = false;
|
||||
}
|
||||
}
|
||||
|
||||
CTrail::CTrail(CWindow* pWindow) : IHyprWindowDecoration(pWindow), m_pWindow(pWindow) {
|
||||
m_vLastWindowPos = pWindow->m_vRealPosition.vec();
|
||||
m_vLastWindowSize = pWindow->m_vRealSize.vec();
|
||||
|
||||
pTickCb = g_pHookSystem->hookDynamic("trailTick", [this](void* self, SCallbackInfo& info, std::any data) { this->onTick(); });
|
||||
}
|
||||
|
||||
CTrail::~CTrail() {
|
||||
damageEntire();
|
||||
g_pHookSystem->unhook(pTickCb);
|
||||
}
|
||||
|
||||
SWindowDecorationExtents CTrail::getWindowDecorationExtents() { return m_seExtents; }
|
||||
|
||||
SWindowDecorationExtents CTrail::getWindowDecorationReservedArea() { return m_seExtents; }
|
||||
|
||||
void scaleBox2(box& box, float coeff) {
|
||||
float hwl = (box.w - (box.w * coeff)) / 2.0;
|
||||
float hhl = (box.h - (box.h * coeff)) / 2.0;
|
||||
|
||||
box.w *= coeff;
|
||||
box.h *= coeff;
|
||||
box.x += hwl;
|
||||
box.y += hhl;
|
||||
}
|
||||
|
||||
Vector2D vecForT(const Vector2D& a, const Vector2D& b, const float& t) {
|
||||
const Vector2D vec_PQ = b - a;
|
||||
return Vector2D{a + vec_PQ * t};
|
||||
}
|
||||
|
||||
Vector2D vecForBezierT(const float& t, const std::vector<Vector2D>& verts) {
|
||||
std::vector<Vector2D> pts;
|
||||
|
||||
for (size_t vertexIndex = 0; vertexIndex < verts.size() - 1; vertexIndex++) {
|
||||
Vector2D p = verts[vertexIndex];
|
||||
pts.push_back(vecForT(p, verts[vertexIndex + 1], t));
|
||||
}
|
||||
|
||||
if (pts.size() > 1)
|
||||
return vecForBezierT(t, pts);
|
||||
else
|
||||
return pts[0];
|
||||
}
|
||||
|
||||
void CTrail::draw(CMonitor* pMonitor, float a, const Vector2D& offset) {
|
||||
if (!g_pCompositor->windowValidMapped(m_pWindow)) return;
|
||||
|
||||
if (!m_pWindow->m_sSpecialRenderData.decorate) return;
|
||||
|
||||
static auto* const PBEZIERSTEP = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:bezier_step")->floatValue;
|
||||
static auto* const PPOINTSPERSTEP = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:points_per_step")->intValue;
|
||||
static auto* const PCOLOR = &HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprtrails:color")->intValue;
|
||||
|
||||
const CColor COLOR = *PCOLOR;
|
||||
|
||||
if (m_dLastGeoms.size() < 2) return;
|
||||
|
||||
box thisbox = box{(float)m_pWindow->m_vRealPosition.vec().x, (float)m_pWindow->m_vRealPosition.vec().y, (float)m_pWindow->m_vRealSize.vec().x,
|
||||
(float)m_pWindow->m_vRealSize.vec().y};
|
||||
wlr_box wlrbox = {thisbox.x - pMonitor->vecPosition.x, thisbox.y - pMonitor->vecPosition.y, thisbox.w, thisbox.h};
|
||||
|
||||
g_pHyprOpenGL->scissor((wlr_box*)nullptr); // allow the entire window and stencil to render
|
||||
glClearStencil(0);
|
||||
glClear(GL_STENCIL_BUFFER_BIT);
|
||||
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
|
||||
glStencilFunc(GL_ALWAYS, 1, -1);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||
|
||||
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
|
||||
g_pHyprOpenGL->renderRect(&wlrbox, CColor(0, 0, 0, 0), m_pWindow->rounding() * pMonitor->scale);
|
||||
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
|
||||
|
||||
glStencilFunc(GL_NOTEQUAL, 1, -1);
|
||||
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
|
||||
|
||||
wlr_box monbox = {0, 0, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecPixelSize.y};
|
||||
|
||||
float matrix[9];
|
||||
wlr_matrix_project_box(matrix, &monbox, wlr_output_transform_invert(WL_OUTPUT_TRANSFORM_NORMAL), 0,
|
||||
g_pHyprOpenGL->m_RenderData.pMonitor->output->transform_matrix); // TODO: write own, don't use WLR here
|
||||
|
||||
float glMatrix[9];
|
||||
wlr_matrix_multiply(glMatrix, g_pHyprOpenGL->m_RenderData.projection, matrix);
|
||||
|
||||
g_pHyprOpenGL->blend(true);
|
||||
|
||||
glUseProgram(g_pGlobalState->trailShader.program);
|
||||
|
||||
#ifndef GLES2
|
||||
glUniformMatrix3fv(g_pGlobalState->trailShader.proj, 1, GL_TRUE, glMatrix);
|
||||
#else
|
||||
wlr_matrix_transpose(glMatrix, glMatrix);
|
||||
glUniformMatrix3fv(g_pGlobalState->trailShader.proj, 1, GL_FALSE, glMatrix);
|
||||
#endif
|
||||
|
||||
std::vector<point2> points;
|
||||
std::vector<Vector2D> bezierPts;
|
||||
std::vector<Vector2D> pointsForBezier;
|
||||
std::vector<float> agesForBezier;
|
||||
|
||||
float originalCoeff = 50;
|
||||
|
||||
auto msFrom = [](std::chrono::system_clock::time_point t) -> float {
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now() - t).count() / 1000.0;
|
||||
};
|
||||
|
||||
auto dist = [&](const point2& a, const point2& b) -> float {
|
||||
Vector2D diff = Vector2D{a.x - b.x, a.y - b.y} * pMonitor->vecPixelSize;
|
||||
return std::sqrt(diff.x * diff.x + diff.y * diff.y);
|
||||
};
|
||||
|
||||
float msMaxToLast = msFrom(m_dLastGeoms.back().second);
|
||||
|
||||
float dists[2] = {0, 0};
|
||||
|
||||
Vector2D mainVec = {originalCoeff / pMonitor->vecSize.x, originalCoeff / pMonitor->vecSize.y};
|
||||
Vector2D windowMiddle = m_pWindow->middle() - pMonitor->vecPosition;
|
||||
|
||||
points.push_back(Vector2D{cos(0) * mainVec.x - sin(0) * mainVec.y + windowMiddle.x / pMonitor->vecPixelSize.x,
|
||||
sin(0) * mainVec.x + cos(0) * mainVec.y + windowMiddle.y / pMonitor->vecPixelSize.y});
|
||||
points.push_back(Vector2D{cos(-M_PI_2) * mainVec.x - sin(-M_PI_2) * mainVec.y + windowMiddle.x / pMonitor->vecPixelSize.x,
|
||||
sin(-M_PI_2) * mainVec.x + cos(-M_PI_2) * mainVec.y + windowMiddle.y / pMonitor->vecPixelSize.y});
|
||||
points.push_back(Vector2D{cos(M_PI_2) * mainVec.x - sin(M_PI_2) * mainVec.y + windowMiddle.x / pMonitor->vecPixelSize.x,
|
||||
sin(M_PI_2) * mainVec.x + cos(M_PI_2) * mainVec.y + windowMiddle.y / pMonitor->vecPixelSize.y});
|
||||
points.push_back(Vector2D{cos(M_PI) * mainVec.x - sin(M_PI) * mainVec.y + windowMiddle.x / pMonitor->vecPixelSize.x,
|
||||
sin(M_PI) * mainVec.x + cos(M_PI) * mainVec.y + windowMiddle.y / pMonitor->vecPixelSize.y});
|
||||
|
||||
pointsForBezier.push_back(windowMiddle);
|
||||
agesForBezier.push_back(0);
|
||||
|
||||
for (size_t i = 0; i < m_dLastGeoms.size(); i += 1) {
|
||||
box box = m_dLastGeoms[i].first;
|
||||
box.x -= pMonitor->vecPosition.x;
|
||||
box.y -= pMonitor->vecPosition.y;
|
||||
Vector2D middle = {box.x + box.w / 2.0, box.y + box.h / 2.0};
|
||||
|
||||
if (middle == pointsForBezier[pointsForBezier.size() - 1]) continue;
|
||||
|
||||
pointsForBezier.push_back(middle);
|
||||
agesForBezier.push_back(msFrom(m_dLastGeoms[i].second));
|
||||
}
|
||||
|
||||
if (pointsForBezier.size() < 3) {
|
||||
glClearStencil(0);
|
||||
glClear(GL_STENCIL_BUFFER_BIT);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
glStencilMask(-1);
|
||||
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
||||
g_pHyprOpenGL->scissor((wlr_box*)nullptr);
|
||||
return;
|
||||
}
|
||||
|
||||
float maxAge = agesForBezier.back();
|
||||
float tCoeff = *PBEZIERSTEP;
|
||||
int pointsPerBezier = *PPOINTSPERSTEP;
|
||||
bezierPts.push_back(vecForBezierT(0, pointsForBezier));
|
||||
for (float t = tCoeff; t <= 1.0; t += tCoeff) {
|
||||
bezierPts.push_back(vecForBezierT(t, pointsForBezier));
|
||||
|
||||
const Vector2D& lastbezier = bezierPts.back();
|
||||
const Vector2D& lastprevbezier = bezierPts[bezierPts.size() - 2];
|
||||
|
||||
for (size_t i = 1; i < pointsPerBezier + 1; ++i) {
|
||||
const float bezierPointStep = (1.0 / (pointsPerBezier + 2));
|
||||
const Vector2D& middle = vecForT(lastprevbezier, lastbezier, bezierPointStep * (i + 1));
|
||||
const Vector2D& lastmiddle = vecForT(lastprevbezier, lastbezier, bezierPointStep * i);
|
||||
|
||||
Vector2D vecNormal = {middle.x - lastmiddle.x, middle.y - lastmiddle.y};
|
||||
|
||||
// normalize vec
|
||||
float invlen = 1.0 / std::sqrt(vecNormal.x * vecNormal.x + vecNormal.y * vecNormal.y);
|
||||
vecNormal.x *= invlen;
|
||||
vecNormal.y *= invlen;
|
||||
|
||||
// make sure it points up
|
||||
// vecNormal.y = std::abs(vecNormal.y);
|
||||
|
||||
// extend by coeff
|
||||
float ageCoeff = t * (agesForBezier.size() - 1);
|
||||
float ageFloor = std::floor(ageCoeff);
|
||||
float ageCeil = std::ceil(ageCoeff);
|
||||
float approxAge = agesForBezier[static_cast<int>(ageFloor)] +
|
||||
(agesForBezier[static_cast<int>(ageCeil)] - agesForBezier[static_cast<int>(ageFloor)]) * (ageCoeff - ageFloor);
|
||||
float coeff = originalCoeff * (1.0 - (approxAge / maxAge));
|
||||
Vector2D newVec = {vecNormal.x * coeff / pMonitor->vecSize.x, vecNormal.y * coeff / pMonitor->vecSize.y};
|
||||
|
||||
if ((newVec.x == 0 && newVec.y == 0) || std::isnan(newVec.x) || std::isnan(newVec.y)) continue;
|
||||
|
||||
// rotate by 90 and -90 and add middle
|
||||
points.push_back(Vector2D{cos(M_PI_2) * newVec.x - sin(M_PI_2) * newVec.y + middle.x / pMonitor->vecPixelSize.x,
|
||||
sin(M_PI_2) * newVec.x + cos(M_PI_2) * newVec.y + middle.y / pMonitor->vecPixelSize.y});
|
||||
points.push_back(Vector2D{cos(-M_PI_2) * newVec.x - sin(-M_PI_2) * newVec.y + middle.x / pMonitor->vecPixelSize.x,
|
||||
sin(-M_PI_2) * newVec.x + cos(-M_PI_2) * newVec.y + middle.y / pMonitor->vecPixelSize.y});
|
||||
}
|
||||
}
|
||||
|
||||
box thisboxopengl = box{(m_pWindow->m_vRealPosition.vec().x - pMonitor->vecPosition.x) / pMonitor->vecPixelSize.x,
|
||||
(m_pWindow->m_vRealPosition.vec().y - pMonitor->vecPosition.y) / pMonitor->vecPixelSize.y,
|
||||
(m_pWindow->m_vRealPosition.vec().x + m_pWindow->m_vRealSize.vec().x) / pMonitor->vecPixelSize.x,
|
||||
(m_pWindow->m_vRealPosition.vec().y + m_pWindow->m_vRealSize.vec().y) / pMonitor->vecPixelSize.y};
|
||||
glUniform4f(g_pGlobalState->trailShader.gradient, thisboxopengl.x, thisboxopengl.y, thisboxopengl.w, thisboxopengl.h);
|
||||
glUniform4f(g_pGlobalState->trailShader.color, COLOR.r, COLOR.g, COLOR.b, COLOR.a);
|
||||
|
||||
wlr_box transformedBox;
|
||||
wlr_box_transform(&transformedBox, &monbox, wlr_output_transform_invert(g_pHyprOpenGL->m_RenderData.pMonitor->transform),
|
||||
g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.x, g_pHyprOpenGL->m_RenderData.pMonitor->vecTransformedSize.y);
|
||||
|
||||
glVertexAttribPointer(g_pGlobalState->trailShader.posAttrib, 2, GL_FLOAT, GL_FALSE, 0, (float*)points.data());
|
||||
|
||||
glEnableVertexAttribArray(g_pGlobalState->trailShader.posAttrib);
|
||||
|
||||
if (g_pHyprOpenGL->m_RenderData.clipBox.width != 0 && g_pHyprOpenGL->m_RenderData.clipBox.height != 0) {
|
||||
CRegion damageClip{g_pHyprOpenGL->m_RenderData.clipBox.x, g_pHyprOpenGL->m_RenderData.clipBox.y, g_pHyprOpenGL->m_RenderData.clipBox.width,
|
||||
g_pHyprOpenGL->m_RenderData.clipBox.height};
|
||||
damageClip.intersect(g_pHyprOpenGL->m_RenderData.damage);
|
||||
|
||||
if (!damageClip.empty()) {
|
||||
for (auto& RECT : damageClip.getRects()) {
|
||||
g_pHyprOpenGL->scissor(&RECT);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (auto& RECT : g_pHyprOpenGL->m_RenderData.damage.getRects()) {
|
||||
g_pHyprOpenGL->scissor(&RECT);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, points.size());
|
||||
}
|
||||
}
|
||||
|
||||
glDisableVertexAttribArray(g_pGlobalState->trailShader.posAttrib);
|
||||
|
||||
glClearStencil(0);
|
||||
glClear(GL_STENCIL_BUFFER_BIT);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
|
||||
glStencilMask(-1);
|
||||
glStencilFunc(GL_ALWAYS, 1, 0xFF);
|
||||
g_pHyprOpenGL->scissor((wlr_box*)nullptr);
|
||||
|
||||
// calculate damage
|
||||
float minX = 9999999;
|
||||
float minY = 9999999;
|
||||
float maxX = -9999999;
|
||||
float maxY = -9999999;
|
||||
|
||||
for (auto& p : points) {
|
||||
if (p.x < minX) minX = p.x;
|
||||
if (p.y < minY) minY = p.y;
|
||||
if (p.x > maxX) maxX = p.x;
|
||||
if (p.y > maxY) maxY = p.y;
|
||||
}
|
||||
|
||||
// bring back to global coords
|
||||
minX *= pMonitor->vecPixelSize.x;
|
||||
minY *= pMonitor->vecPixelSize.y;
|
||||
maxX *= pMonitor->vecPixelSize.x;
|
||||
maxY *= pMonitor->vecPixelSize.y;
|
||||
|
||||
m_bLastBox.x = minX + pMonitor->vecPosition.x;
|
||||
m_bLastBox.y = minY + pMonitor->vecPosition.y;
|
||||
m_bLastBox.width = maxX - minX;
|
||||
m_bLastBox.height = maxY - minY;
|
||||
|
||||
m_bNeedsDamage = true;
|
||||
}
|
||||
|
||||
eDecorationType CTrail::getDecorationType() { return DECORATION_CUSTOM; }
|
||||
|
||||
void CTrail::updateWindow(CWindow* pWindow) {
|
||||
const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(pWindow->m_iWorkspaceID);
|
||||
|
||||
const auto WORKSPACEOFFSET = PWORKSPACE && !pWindow->m_bPinned ? PWORKSPACE->m_vRenderOffset.vec() : Vector2D();
|
||||
|
||||
m_vLastWindowPos = pWindow->m_vRealPosition.vec() + WORKSPACEOFFSET;
|
||||
m_vLastWindowSize = pWindow->m_vRealSize.vec();
|
||||
|
||||
damageEntire();
|
||||
}
|
||||
|
||||
void CTrail::damageEntire() {
|
||||
wlr_box dm = {(int)(m_vLastWindowPos.x - m_seExtents.topLeft.x), (int)(m_vLastWindowPos.y - m_seExtents.topLeft.y),
|
||||
(int)(m_vLastWindowSize.x + m_seExtents.topLeft.x + m_seExtents.bottomRight.x), (int)m_seExtents.topLeft.y};
|
||||
g_pHyprRenderer->damageBox(&dm);
|
||||
}
|
62
hyprtrails/trail.hpp
Normal file
62
hyprtrails/trail.hpp
Normal file
|
@ -0,0 +1,62 @@
|
|||
#pragma once
|
||||
|
||||
#define WLR_USE_UNSTABLE
|
||||
|
||||
#include <deque>
|
||||
#include <hyprland/src/plugins/PluginAPI.hpp>
|
||||
#include <hyprland/src/render/decorations/IHyprWindowDecoration.hpp>
|
||||
|
||||
struct box {
|
||||
float x = 0, y = 0, w = 0, h = 0;
|
||||
|
||||
Vector2D middle() { return Vector2D{x + w / 2.0, y + h / 2.0}; }
|
||||
};
|
||||
struct point2 {
|
||||
point2(const Vector2D& v) {
|
||||
x = v.x;
|
||||
y = v.y;
|
||||
}
|
||||
|
||||
point2() {
|
||||
x = 0;
|
||||
y = 0;
|
||||
}
|
||||
|
||||
float x = 0, y = 0;
|
||||
};
|
||||
|
||||
class CTrail : public IHyprWindowDecoration {
|
||||
public:
|
||||
CTrail(CWindow*);
|
||||
virtual ~CTrail();
|
||||
|
||||
virtual SWindowDecorationExtents getWindowDecorationExtents();
|
||||
|
||||
virtual void draw(CMonitor*, float a, const Vector2D& offset);
|
||||
|
||||
virtual eDecorationType getDecorationType();
|
||||
|
||||
virtual void updateWindow(CWindow*);
|
||||
|
||||
virtual void damageEntire();
|
||||
|
||||
virtual SWindowDecorationExtents getWindowDecorationReservedArea();
|
||||
|
||||
private:
|
||||
HOOK_CALLBACK_FN* pTickCb = nullptr;
|
||||
void onTick();
|
||||
|
||||
std::deque<std::pair<box, std::chrono::system_clock::time_point>> m_dLastGeoms;
|
||||
|
||||
int m_iTimer = 0;
|
||||
|
||||
SWindowDecorationExtents m_seExtents;
|
||||
|
||||
CWindow* m_pWindow = nullptr;
|
||||
|
||||
Vector2D m_vLastWindowPos;
|
||||
Vector2D m_vLastWindowSize;
|
||||
|
||||
wlr_box m_bLastBox = {0};
|
||||
bool m_bNeedsDamage = false;
|
||||
};
|
Loading…
Reference in a new issue