diff --git a/README.md b/README.md index 7bd1b3a..a77f6b8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This repo houses official plugins for Hyprland. - borders-plus-plus -> adds one or two additional borders to windows - csgo-vulkan-fix -> fixes custom resolutions on CS:GO with `-vulkan` - hyprbars -> adds title bars to windows + - hyprexpo -> adds an expo-like workspace overview - hyprtrails -> adds smooth trails behind moving windows - hyprwinwrap -> clone of xwinwrap, allows you to put any app as a wallpaper diff --git a/hyprexpo/Makefile b/hyprexpo/Makefile new file mode 100644 index 0000000..deadaf3 --- /dev/null +++ b/hyprexpo/Makefile @@ -0,0 +1,4 @@ +all: + $(CXX) -shared -fPIC --no-gnu-unique main.cpp overview.cpp -o hyprexpo.so -g `pkg-config --cflags pixman-1 libdrm hyprland` -std=c++2b -Wno-narrowing +clean: + rm ./hyprexpo.so diff --git a/hyprexpo/README.md b/hyprexpo/README.md new file mode 100644 index 0000000..12a3929 --- /dev/null +++ b/hyprexpo/README.md @@ -0,0 +1,19 @@ +# hyprexpo + +Overview plugin like gnome kde or wf. + +## Config + +```ini + +plugin { + hyprexpo { + columns = 3 + gap_size = 5 + bg_col = rgb(111111) + workspace_method = center current # [center/first] [workspace] e.g. first 1 or center m+1 + enable_gesture = true # laptop touchpad, 4 fingers + } +} + +``` \ No newline at end of file diff --git a/hyprexpo/globals.hpp b/hyprexpo/globals.hpp new file mode 100644 index 0000000..2257475 --- /dev/null +++ b/hyprexpo/globals.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; \ No newline at end of file diff --git a/hyprexpo/main.cpp b/hyprexpo/main.cpp new file mode 100644 index 0000000..096eba2 --- /dev/null +++ b/hyprexpo/main.cpp @@ -0,0 +1,211 @@ +#define WLR_USE_UNSTABLE + +#include + +#include +#include +#include +#include + +#include "globals.hpp" +#include "overview.hpp" + +// Methods +inline CFunctionHook* g_pRenderWorkspaceHook = nullptr; +inline CFunctionHook* g_pAddDamageHookA = nullptr; +inline CFunctionHook* g_pAddDamageHookB = nullptr; +inline CFunctionHook* g_pSwipeBeginHook = nullptr; +inline CFunctionHook* g_pSwipeEndHook = nullptr; +inline CFunctionHook* g_pSwipeUpdateHook = nullptr; +typedef void (*origRenderWorkspace)(void*, CMonitor*, PHLWORKSPACE, timespec*, const CBox&); +typedef void (*origAddDamageA)(void*, const CBox*); +typedef void (*origAddDamageB)(void*, const pixman_region32_t*); +typedef void (*origSwipeBegin)(void*, wlr_pointer_swipe_begin_event*); +typedef void (*origSwipeEnd)(void*, wlr_pointer_swipe_end_event*); +typedef void (*origSwipeUpdate)(void*, wlr_pointer_swipe_update_event*); + +// Do NOT change this function. +APICALL EXPORT std::string PLUGIN_API_VERSION() { + return HYPRLAND_API_VERSION; +} + +static bool renderingOverview = false; + +// +static void hkRenderWorkspace(void* thisptr, CMonitor* pMonitor, PHLWORKSPACE pWorkspace, timespec* now, const CBox& geometry) { + if (!g_pOverview || renderingOverview || g_pOverview->blockOverviewRendering) + ((origRenderWorkspace)(g_pRenderWorkspaceHook->m_pOriginal))(thisptr, pMonitor, pWorkspace, now, geometry); + else + g_pOverview->render(); +} + +static void hkAddDamageA(void* thisptr, const CBox* box) { + const auto PMONITOR = (CMonitor*)thisptr; + + if (!g_pOverview || g_pOverview->pMonitor != PMONITOR || g_pOverview->blockDamageReporting) { + ((origAddDamageA)g_pAddDamageHookA->m_pOriginal)(thisptr, box); + return; + } + + g_pOverview->onDamageReported(); +} + +static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) { + const auto PMONITOR = (CMonitor*)thisptr; + + if (!g_pOverview || g_pOverview->pMonitor != PMONITOR || g_pOverview->blockDamageReporting) { + ((origAddDamageB)g_pAddDamageHookB->m_pOriginal)(thisptr, rg); + return; + } + + g_pOverview->onDamageReported(); +} + +static float gestured = 0; + +static void hkSwipeBegin(void* thisptr, wlr_pointer_swipe_begin_event* e) { + static auto* const* PENABLE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:enable_gesture")->getDataStaticPtr(); + + if (g_pOverview) + return; + + if (!**PENABLE || e->fingers != 4) { + ((origSwipeBegin)g_pSwipeBeginHook->m_pOriginal)(thisptr, e); + return; + } + + renderingOverview = true; + g_pOverview = std::make_unique(g_pCompositor->m_pLastMonitor->activeWorkspace, true); + renderingOverview = false; + + gestured = 0; +} + +static void hkSwipeUpdate(void* thisptr, wlr_pointer_swipe_update_event* e) { + if (!g_pOverview) { + ((origSwipeUpdate)g_pSwipeUpdateHook->m_pOriginal)(thisptr, e); + return; + } + + gestured += e->dy; + + g_pOverview->onSwipeUpdate(gestured); +} + +static void hkSwipeEnd(void* thisptr, wlr_pointer_swipe_end_event* e) { + if (!g_pOverview) { + ((origSwipeEnd)g_pSwipeEndHook->m_pOriginal)(thisptr, e); + return; + } + + g_pOverview->onSwipeEnd(); +} + +static void onExpoDispatcher(std::string arg) { + + if (arg == "toggle") { + if (g_pOverview) + g_pOverview->close(); + else { + renderingOverview = true; + g_pOverview = std::make_unique(g_pCompositor->m_pLastMonitor->activeWorkspace); + renderingOverview = false; + } + return; + } + + if (arg == "off" || arg == "close" || arg == "disable") { + if (g_pOverview) + g_pOverview->close(); + return; + } + + if (g_pOverview) + return; + + renderingOverview = true; + g_pOverview = std::make_unique(g_pCompositor->m_pLastMonitor->activeWorkspace); + renderingOverview = false; +} + +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, "[hyprexpo] 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("[he] Version mismatch"); + } + + auto FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "renderWorkspace"); + if (FNS.empty()) + throw std::runtime_error("[he] No fns for hook renderWorkspace"); + + g_pRenderWorkspaceHook = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkRenderWorkspace); + + FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "addDamageEPK15pixman_region32"); + if (FNS.empty()) + throw std::runtime_error("[he] No fns for hook addDamageEPK15pixman_region32"); + + g_pAddDamageHookB = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageB); + + FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "addDamageEPK4CBox"); + if (FNS.empty()) + throw std::runtime_error("[he] No fns for hook addDamageEPK4CBox"); + + g_pAddDamageHookA = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkAddDamageA); + + FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "onSwipeBegin"); + if (FNS.empty()) + throw std::runtime_error("[he] No fns for hook onSwipeBegin"); + + g_pSwipeBeginHook = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkSwipeBegin); + + FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "onSwipeEnd"); + if (FNS.empty()) + throw std::runtime_error("[he] No fns for hook onSwipeEnd"); + + g_pSwipeEndHook = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkSwipeEnd); + + FNS = HyprlandAPI::findFunctionsByName(PHANDLE, "onSwipeUpdate"); + if (FNS.empty()) + throw std::runtime_error("[he] No fns for hook onSwipeUpdate"); + + g_pSwipeUpdateHook = HyprlandAPI::createFunctionHook(PHANDLE, FNS[0].address, (void*)hkSwipeUpdate); + + bool success = g_pRenderWorkspaceHook->hook(); + success = success && g_pAddDamageHookA->hook(); + success = success && g_pAddDamageHookB->hook(); + // mega buggy, I'll have to fix it one day. + // success = success && g_pSwipeBeginHook->hook(); + // success = success && g_pSwipeEndHook->hook(); + // success = success && g_pSwipeUpdateHook->hook(); + + if (!success) + throw std::runtime_error("[he] Failed initializing hooks"); + + HyprlandAPI::registerCallbackDynamic(PHANDLE, "preRender", [](void* self, SCallbackInfo& info, std::any param) { + if (!g_pOverview) + return; + g_pOverview->onPreRender(); + }); + + HyprlandAPI::addDispatcher(PHANDLE, "hyprexpo:expo", onExpoDispatcher); + + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:columns", Hyprlang::INT{3}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gap_size", Hyprlang::INT{5}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:bg_col", Hyprlang::INT{0xFF111111}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method", Hyprlang::STRING{"center current"}); + + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:enable_gesture", Hyprlang::INT{1}); + + HyprlandAPI::reloadConfig(); + + return {"hyprexpo", "A plugin for an overview", "Vaxry", "1.0"}; +} + +APICALL EXPORT void PLUGIN_EXIT() { + ; +} diff --git a/hyprexpo/overview.cpp b/hyprexpo/overview.cpp new file mode 100644 index 0000000..fa9e1c2 --- /dev/null +++ b/hyprexpo/overview.cpp @@ -0,0 +1,411 @@ +#include "overview.hpp" +#include +#define private public +#include +#include +#include +#undef private + +static void damageMonitor(void*) { + g_pOverview->damage(); +} + +static void removeOverview(void*) { + g_pOverview.reset(); +} + +COverview::~COverview() { + g_pHookSystem->unhook(mouseButtonHook); + g_pHookSystem->unhook(mouseMoveHook); + g_pInputManager->unsetCursorImage(); + g_pHyprOpenGL->markBlurDirtyForMonitor(pMonitor); +} + +COverview::COverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn_), swipe(swipe_) { + const auto PMONITOR = g_pCompositor->m_pLastMonitor; + pMonitor = PMONITOR; + + static auto* const* PCOLUMNS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:columns")->getDataStaticPtr(); + static auto* const* PGAPS = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gap_size")->getDataStaticPtr(); + static auto* const* PCOL = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:bg_col")->getDataStaticPtr(); + static auto const* PMETHOD = (Hyprlang::STRING const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:workspace_method")->getDataStaticPtr(); + + SIDE_LENGTH = **PCOLUMNS; + GAP_WIDTH = **PGAPS; + BG_COLOR = **PCOL; + + // process the method + bool methodCenter = true; + int methodStartID = pMonitor->activeWorkspaceID(); + CVarList method{*PMETHOD, 0, 's', true}; + if (method.size() < 2) + Debug::log(ERR, "[he] invalid workspace_method"); + else { + methodCenter = method[0] == "center"; + std::string s; + methodStartID = getWorkspaceIDFromString(method[1], s); + if (methodStartID == WORKSPACE_INVALID) + methodStartID = pMonitor->activeWorkspaceID(); + } + + images.resize(SIDE_LENGTH * SIDE_LENGTH); + + if (methodCenter) { + int currentID = methodStartID; + int firstID = currentID; + + int backtracked = 0; + + for (size_t i = 1; i < images.size() / 2; ++i) { + std::string s; + currentID = getWorkspaceIDFromString("r-" + std::to_string(i), s); + if (currentID >= firstID) + break; + + backtracked++; + firstID = currentID; + } + + for (size_t i = 0; i < SIDE_LENGTH * SIDE_LENGTH; ++i) { + auto& image = images[i]; + std::string s; + currentID = + getWorkspaceIDFromString("r" + ((int64_t)i - backtracked < 0 ? std::to_string((int64_t)i - backtracked) : "+" + std::to_string((int64_t)i - backtracked)), s); + image.workspaceID = currentID; + } + } else { + int currentID = methodStartID; + images[0].workspaceID = currentID; + for (size_t i = 1; i < SIDE_LENGTH * SIDE_LENGTH; ++i) { + auto& image = images[i]; + std::string s; + currentID = getWorkspaceIDFromString("r+" + std::to_string(i), s); + image.workspaceID = currentID; + } + } + + g_pHyprRenderer->makeEGLCurrent(); + + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + Vector2D tileSize = pMonitor->vecSize / SIDE_LENGTH; + Vector2D tileRenderSize = (pMonitor->vecSize - Vector2D{GAP_WIDTH * pMonitor->scale, GAP_WIDTH * pMonitor->scale} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; + CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2}; + + int currentid = 0; + + PHLWORKSPACE openSpecial = PMONITOR->activeSpecialWorkspace; + if (openSpecial) + PMONITOR->activeSpecialWorkspace.reset(); + + g_pHyprRenderer->m_bBlockSurfaceFeedback = true; + + startedOn->m_bVisible = false; + + for (size_t i = 0; i < SIDE_LENGTH * SIDE_LENGTH; ++i) { + COverview::SWorkspaceImage& image = images[i]; + image.fb.alloc(monbox.w, monbox.h, PMONITOR->drmFormat); + + CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; + g_pHyprRenderer->beginRender(PMONITOR, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &image.fb); + + g_pHyprOpenGL->clear(CColor{0, 0, 0, 1.0}); + + const auto PWORKSPACE = g_pCompositor->getWorkspaceByID(image.workspaceID); + + if (PWORKSPACE == startedOn) + currentid = i; + + if (PWORKSPACE) { + image.pWorkspace = PWORKSPACE; + PMONITOR->activeWorkspace = PWORKSPACE; + PWORKSPACE->startAnim(true, true, true); + PWORKSPACE->m_bVisible = true; + + if (PWORKSPACE == startedOn) + PMONITOR->activeSpecialWorkspace = openSpecial; + + g_pHyprRenderer->renderWorkspace(PMONITOR, PWORKSPACE, &now, monbox); + + PWORKSPACE->m_bVisible = false; + PWORKSPACE->startAnim(false, false, true); + + if (PWORKSPACE == startedOn) + PMONITOR->activeSpecialWorkspace.reset(); + } else + g_pHyprRenderer->renderWorkspace(PMONITOR, PWORKSPACE, &now, monbox); + + image.box = {(i % SIDE_LENGTH) * tileRenderSize.x + (i % SIDE_LENGTH) * GAP_WIDTH, (i / SIDE_LENGTH) * tileRenderSize.y + (i / SIDE_LENGTH) * GAP_WIDTH, tileRenderSize.x, + tileRenderSize.y}; + + g_pHyprRenderer->endRender(); + } + + g_pHyprRenderer->m_bBlockSurfaceFeedback = false; + + PMONITOR->activeSpecialWorkspace = openSpecial; + PMONITOR->activeWorkspace = startedOn; + startedOn->m_bVisible = true; + startedOn->startAnim(true, true, true); + + // zoom on the current workspace. + const auto& TILE = images[std::clamp(currentid, 0, SIDE_LENGTH * SIDE_LENGTH)]; + + size.create(pMonitor->vecSize * pMonitor->vecSize / tileSize, g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE); + pos.create((-((pMonitor->vecSize / (double)SIDE_LENGTH) * Vector2D{currentid % SIDE_LENGTH, currentid / SIDE_LENGTH}) * pMonitor->scale) * (pMonitor->vecSize / tileSize), + g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE); + + size.setUpdateCallback(damageMonitor); + pos.setUpdateCallback(damageMonitor); + + if (!swipe) { + size = pMonitor->vecSize; + pos = {0, 0}; + + size.setCallbackOnEnd([this](void*) { redrawAll(true); }); + } + + openedID = currentid; + + g_pInputManager->setCursorImageUntilUnset("left_ptr"); + + lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->vecPosition; + + mouseMoveHook = g_pHookSystem->hookDynamic("mouseMove", [this](void* self, SCallbackInfo& info, std::any param) { + if (closing) + return; + + info.cancelled = true; + lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->vecPosition; + }); + + mouseButtonHook = g_pHookSystem->hookDynamic("mouseButton", [this](void* self, SCallbackInfo& info, std::any param) { + if (closing) + return; + + info.cancelled = true; + + // get tile x,y + int x = lastMousePosLocal.x / pMonitor->vecSize.x * SIDE_LENGTH; + int y = lastMousePosLocal.y / pMonitor->vecSize.y * SIDE_LENGTH; + + closeOnID = x + y * SIDE_LENGTH; + + close(); + }); +} + +void COverview::redrawID(int id, bool forcelowres) { + blockOverviewRendering = true; + + g_pHyprRenderer->makeEGLCurrent(); + + id = std::clamp(id, 0, SIDE_LENGTH * SIDE_LENGTH); + + Vector2D tileSize = pMonitor->vecSize / SIDE_LENGTH; + Vector2D tileRenderSize = (pMonitor->vecSize - Vector2D{GAP_WIDTH, GAP_WIDTH} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; + CBox monbox{0, 0, tileSize.x * 2, tileSize.y * 2}; + + if (!forcelowres && (size.value() != pMonitor->vecSize || closing)) + monbox = {{0, 0}, pMonitor->vecPixelSize}; + + if (!ENABLE_LOWRES) + monbox = {{0, 0}, pMonitor->vecPixelSize}; + + timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + auto& image = images[id]; + + if (image.fb.m_vSize != monbox.size()) { + image.fb.release(); + image.fb.m_pStencilTex = nullptr; + image.fb.alloc(monbox.w, monbox.h, pMonitor->drmFormat); + } + + CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX}; + g_pHyprRenderer->beginRender(pMonitor, fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &image.fb); + + g_pHyprOpenGL->clear(CColor{0, 0, 0, 1.0}); + + const auto PWORKSPACE = image.pWorkspace; + + PHLWORKSPACE openSpecial = pMonitor->activeSpecialWorkspace; + if (openSpecial) + pMonitor->activeSpecialWorkspace.reset(); + + startedOn->m_bVisible = false; + + if (PWORKSPACE) { + pMonitor->activeWorkspace = PWORKSPACE; + PWORKSPACE->startAnim(true, true, true); + PWORKSPACE->m_bVisible = true; + + if (PWORKSPACE == startedOn) + pMonitor->activeSpecialWorkspace = openSpecial; + + g_pHyprRenderer->renderWorkspace(pMonitor, PWORKSPACE, &now, monbox); + + PWORKSPACE->m_bVisible = false; + PWORKSPACE->startAnim(false, false, true); + + if (PWORKSPACE == startedOn) + pMonitor->activeSpecialWorkspace.reset(); + } else + g_pHyprRenderer->renderWorkspace(pMonitor, PWORKSPACE, &now, monbox); + + g_pHyprRenderer->endRender(); + + pMonitor->activeSpecialWorkspace = openSpecial; + pMonitor->activeWorkspace = startedOn; + startedOn->m_bVisible = true; + startedOn->startAnim(true, true, true); + + blockOverviewRendering = false; +} + +void COverview::redrawAll(bool forcelowres) { + for (size_t i = 0; i < SIDE_LENGTH * SIDE_LENGTH; ++i) { + redrawID(i, forcelowres); + } +} + +void COverview::damage() { + blockDamageReporting = true; + g_pHyprRenderer->damageMonitor(pMonitor); + blockDamageReporting = false; +} + +void COverview::onDamageReported() { + damageDirty = true; + + Vector2D SIZE = size.value(); + + Vector2D tileSize = (SIZE / SIDE_LENGTH); + Vector2D tileRenderSize = (SIZE - Vector2D{GAP_WIDTH, GAP_WIDTH} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; + const auto& TILE = images[std::clamp(openedID, 0, SIDE_LENGTH * SIDE_LENGTH)]; + CBox texbox = CBox{(openedID % SIDE_LENGTH) * tileRenderSize.x + (openedID % SIDE_LENGTH) * GAP_WIDTH, + (openedID / SIDE_LENGTH) * tileRenderSize.y + (openedID / SIDE_LENGTH) * GAP_WIDTH, tileRenderSize.x, tileRenderSize.y} + .translate(pMonitor->vecPosition); + + damage(); + + blockDamageReporting = true; + g_pHyprRenderer->damageBox(&texbox); + blockDamageReporting = false; + g_pCompositor->scheduleFrameForMonitor(pMonitor); +} + +void COverview::close() { + if (closing) + return; + + const int ID = closeOnID == -1 ? openedID : closeOnID; + + const auto& TILE = images[std::clamp(ID, 0, SIDE_LENGTH * SIDE_LENGTH)]; + + Vector2D tileSize = (pMonitor->vecSize / SIDE_LENGTH); + + size = pMonitor->vecSize * pMonitor->vecSize / tileSize; + pos = (-((pMonitor->vecSize / (double)SIDE_LENGTH) * Vector2D{ID % SIDE_LENGTH, ID / SIDE_LENGTH}) * pMonitor->scale) * (pMonitor->vecSize / tileSize); + + size.setCallbackOnEnd(removeOverview); + + closing = true; + + redrawAll(); + + if (TILE.workspaceID != pMonitor->activeWorkspaceID()) { + pMonitor->setSpecialWorkspace(0); + + const auto NEWIDWS = g_pCompositor->getWorkspaceByID(TILE.workspaceID); + + if (!NEWIDWS) + g_pKeybindManager->changeworkspace(std::to_string(TILE.workspaceID)); + else + g_pKeybindManager->changeworkspace(NEWIDWS->getConfigName()); + + pMonitor->activeWorkspace->startAnim(true, true, true); + + startedOn = pMonitor->activeWorkspace; + } +} + +void COverview::onPreRender() { + if (damageDirty) { + damageDirty = false; + redrawID(openedID); + } +} + +void COverview::onWorkspaceChange() { + startedOn->startAnim(false, false, true); + startedOn = pMonitor->activeWorkspace; + for (size_t i = 0; i < SIDE_LENGTH * SIDE_LENGTH; ++i) { + if (images[i].workspaceID != pMonitor->activeWorkspaceID()) + continue; + + openedID = i; + break; + } + startedOn->startAnim(true, true, true); +} + +void COverview::render() { + + const auto GAPSIZE = (closing ? (1.0 - size.getPercent()) : size.getPercent()) * GAP_WIDTH; + + if (pMonitor->activeWorkspace != startedOn) { + // likely user changed. + onWorkspaceChange(); + } + + Vector2D SIZE = size.value(); + + Vector2D tileSize = (SIZE / SIDE_LENGTH); + Vector2D tileRenderSize = (SIZE - Vector2D{GAPSIZE, GAPSIZE} * (SIDE_LENGTH - 1)) / SIDE_LENGTH; + + g_pHyprOpenGL->clear(CColor{0.1, 0.1, 0.1, 1.0}); + + for (size_t y = 0; y < SIDE_LENGTH; ++y) { + for (size_t x = 0; x < SIDE_LENGTH; ++x) { + CBox texbox = {x * tileRenderSize.x + x * GAPSIZE, y * tileRenderSize.y + y * GAPSIZE, tileRenderSize.x, tileRenderSize.y}; + texbox.scale(pMonitor->scale).translate(pos.value()); + texbox.round(); + CRegion damage{0, 0, INT16_MAX, INT16_MAX}; + g_pHyprOpenGL->renderTextureInternalWithDamage(images[x + y * SIDE_LENGTH].fb.m_cTex, &texbox, 1.0, &damage); + } + } +} + +static float lerp(const float& from, const float& to, const float perc) { + return (to - from) * perc + from; +} + +static Vector2D lerp(const Vector2D& from, const Vector2D& to, const float perc) { + return Vector2D{lerp(from.x, to.x, perc), lerp(from.y, to.y, perc)}; +} + +void COverview::onSwipeUpdate(double delta) { + const float PERC = std::clamp(delta / 300.0, 0.0, 1.0); + + Vector2D tileSize = (pMonitor->vecSize / SIDE_LENGTH); + + const auto SIZEMAX = pMonitor->vecSize * pMonitor->vecSize / tileSize; + const auto POSMAX = + (-((pMonitor->vecSize / (double)SIDE_LENGTH) * Vector2D{openedID % SIDE_LENGTH, openedID / SIDE_LENGTH}) * pMonitor->scale) * (pMonitor->vecSize / tileSize); + + const auto SIZEMIN = pMonitor->vecSize; + const auto POSMIN = Vector2D{0, 0}; + + size.setValueAndWarp(lerp(SIZEMIN, SIZEMAX, PERC)); + pos.setValueAndWarp(lerp(POSMIN, POSMAX, PERC)); +} + +void COverview::onSwipeEnd() { + size = pMonitor->vecSize; + pos = {0, 0}; + + size.setCallbackOnEnd([this](void*) { redrawAll(true); }); +} \ No newline at end of file diff --git a/hyprexpo/overview.hpp b/hyprexpo/overview.hpp new file mode 100644 index 0000000..dda3d36 --- /dev/null +++ b/hyprexpo/overview.hpp @@ -0,0 +1,77 @@ +#pragma once + +#define WLR_USE_UNSTABLE + +#include "globals.hpp" +#include +#include +#include +#include +#include + +// saves on resources, but is a bit broken rn with blur. +// hyprland's fault, but cba to fix. +constexpr bool ENABLE_LOWRES = false; + +class CMonitor; + +class COverview { + public: + COverview(PHLWORKSPACE startedOn_, bool swipe = false); + ~COverview(); + + void render(); + void damage(); + void onDamageReported(); + void onPreRender(); + + void onSwipeUpdate(double delta); + void onSwipeEnd(); + + // close without a selection + void close(); + + bool blockOverviewRendering = false; + bool blockDamageReporting = false; + + CMonitor* pMonitor = nullptr; + + private: + void redrawID(int id, bool forcelowres = false); + void redrawAll(bool forcelowres = false); + void onWorkspaceChange(); + + int SIDE_LENGTH = 3; + int GAP_WIDTH = 5; + CColor BG_COLOR = CColor{0.1, 0.1, 0.1, 1.0}; + + bool damageDirty = false; + + struct SWorkspaceImage { + CFramebuffer fb; + int64_t workspaceID = -1; + PHLWORKSPACE pWorkspace; + CBox box; + }; + + Vector2D lastMousePosLocal = Vector2D{}; + + int openedID = -1; + int closeOnID = -1; + + std::vector images; + + PHLWORKSPACE startedOn; + + CAnimatedVariable size; + CAnimatedVariable pos; + + bool closing = false; + + HOOK_CALLBACK_FN* mouseMoveHook = nullptr; + HOOK_CALLBACK_FN* mouseButtonHook = nullptr; + + bool swipe = false; +}; + +inline std::unique_ptr g_pOverview; \ No newline at end of file