From 87955bceb1a5629e865abf00cc7076878370a18a Mon Sep 17 00:00:00 2001 From: Vaxry Date: Sat, 4 Nov 2023 00:39:14 +0000 Subject: [PATCH] hyprtrails: add plugin --- hyprload.toml | 11 ++ hyprtrails/Makefile | 4 + hyprtrails/README.md | 18 +++ hyprtrails/globals.hpp | 12 ++ hyprtrails/main.cpp | 134 +++++++++++++++++ hyprtrails/shaders.hpp | 54 +++++++ hyprtrails/trail.cpp | 316 +++++++++++++++++++++++++++++++++++++++++ hyprtrails/trail.hpp | 62 ++++++++ 8 files changed, 611 insertions(+) create mode 100644 hyprtrails/Makefile create mode 100644 hyprtrails/README.md create mode 100644 hyprtrails/globals.hpp create mode 100644 hyprtrails/main.cpp create mode 100644 hyprtrails/shaders.hpp create mode 100644 hyprtrails/trail.cpp create mode 100644 hyprtrails/trail.hpp diff --git a/hyprload.toml b/hyprload.toml index 968eaa3..84f0ac1 100644 --- a/hyprload.toml +++ b/hyprload.toml @@ -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", +] diff --git a/hyprtrails/Makefile b/hyprtrails/Makefile new file mode 100644 index 0000000..9415eb7 --- /dev/null +++ b/hyprtrails/Makefile @@ -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 diff --git a/hyprtrails/README.md b/hyprtrails/README.md new file mode 100644 index 0000000..3f168c1 --- /dev/null +++ b/hyprtrails/README.md @@ -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) + } +} + +``` diff --git a/hyprtrails/globals.hpp b/hyprtrails/globals.hpp new file mode 100644 index 0000000..bce0691 --- /dev/null +++ b/hyprtrails/globals.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; + +struct SGlobalState { + CShader trailShader; + wl_event_source* tick = nullptr; +}; + +inline std::unique_ptr g_pGlobalState; \ No newline at end of file diff --git a/hyprtrails/main.cpp b/hyprtrails/main.cpp new file mode 100644 index 0000000..01dc350 --- /dev/null +++ b/hyprtrails/main.cpp @@ -0,0 +1,134 @@ +#define WLR_USE_UNSTABLE + +#include + +#include +#include +#include +#include +#include + +#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(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(); + 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); } \ No newline at end of file diff --git a/hyprtrails/shaders.hpp b/hyprtrails/shaders.hpp new file mode 100644 index 0000000..8c299fe --- /dev/null +++ b/hyprtrails/shaders.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include + +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; +})#"; diff --git a/hyprtrails/trail.cpp b/hyprtrails/trail.cpp new file mode 100644 index 0000000..39ee75d --- /dev/null +++ b/hyprtrails/trail.cpp @@ -0,0 +1,316 @@ +#include "trail.hpp" + +#include +#include + +#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& verts) { + std::vector 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 points; + std::vector bezierPts; + std::vector pointsForBezier; + std::vector agesForBezier; + + float originalCoeff = 50; + + auto msFrom = [](std::chrono::system_clock::time_point t) -> float { + return std::chrono::duration_cast(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(ageFloor)] + + (agesForBezier[static_cast(ageCeil)] - agesForBezier[static_cast(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); +} \ No newline at end of file diff --git a/hyprtrails/trail.hpp b/hyprtrails/trail.hpp new file mode 100644 index 0000000..84b29e0 --- /dev/null +++ b/hyprtrails/trail.hpp @@ -0,0 +1,62 @@ +#pragma once + +#define WLR_USE_UNSTABLE + +#include +#include +#include + +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> 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; +}; \ No newline at end of file