This commit is contained in:
Vaxry 2025-12-19 11:24:48 -05:00 committed by GitHub
commit e00f7673a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 711 additions and 31 deletions

View file

@ -13,7 +13,7 @@ void CExpoGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {
m_firstUpdate = true;
if (!g_pOverview)
g_pOverview = std::make_unique<COverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
g_pOverview = makeShared<COverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
else {
g_pOverview->selectHoveredWorkspace();
g_pOverview->setClosing(true);

32
hyprexpo/IOverview.hpp Normal file
View file

@ -0,0 +1,32 @@
#pragma once
#include <hyprland/src/helpers/memory/Memory.hpp>
class IOverview {
public:
IOverview() = default;
virtual ~IOverview() = default;
virtual void render() = 0;
virtual void damage() = 0;
virtual void onDamageReported() = 0;
virtual void onPreRender() = 0;
virtual void setClosing(bool closing) = 0;
virtual void resetSwipe() = 0;
virtual void onSwipeUpdate(double delta) = 0;
virtual void onSwipeEnd() = 0;
virtual void close() = 0;
virtual void selectHoveredWorkspace() = 0;
virtual void fullRender() = 0;
bool blockOverviewRendering = false;
bool blockDamageReporting = false;
PHLMONITORREF pMonitor;
bool m_isSwiping = false;
};
inline SP<IOverview> g_pOverview;

View file

@ -6,6 +6,6 @@ else
endif
all:
$(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp overview.cpp ExpoGesture.cpp OverviewPassElement.cpp -o hyprexpo.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -Wno-narrowing
$(CXX) -shared -fPIC $(EXTRA_FLAGS) main.cpp overview.cpp ExpoGesture.cpp OverviewPassElement.cpp scrollOverview.cpp -o hyprexpo.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b -Wno-narrowing
clean:
rm ./hyprexpo.so

View file

@ -23,12 +23,21 @@ plugin {
| property | type | description | default |
| --- | --- | --- | --- |
columns | number | how many desktops are displayed on one line | `3`
gap_size | number | gap between desktops | `5`
bg_col | color | color in gaps (between desktops) | `rgb(000000)`
workspace_method | [center/first] [workspace] | position of the desktops | `center current`
skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false`
gesture_distance | number | how far is the max for the gesture | `300`
|columns | number | how many desktops are displayed on one line | `3`|
|gap_size | number | gap between desktops | `5`|
|bg_col | color | color in gaps (between desktops) | `rgb(000000)`|
|workspace_method | [center/first] [workspace] | position of the desktops | `center current`|
|skip_empty | boolean | whether the grid displays workspaces sequentially by id using selector "r" (`false`) or skips empty workspaces using selector "m" (`true`) | `false`|
|gesture_distance | number | how far is the max for the gesture | `300`|
#### Subcategory `scrolling`
Applies to the scrolling layout overview
| property | type | description | default |
| --- | --- | --- | --- |
| scroll_moves_up_down | bool | if enabled, scrolling will move workspaces up/down instead of zooming | true |
| default_zoom | float | default zoom out value, [0.1 - 0.9] | 0.5 |
### Keywords

View file

@ -2,4 +2,5 @@
#include <hyprland/src/plugins/PluginAPI.hpp>
inline HANDLE PHANDLE = nullptr;
inline HANDLE PHANDLE = nullptr;
inline bool IS_SCROLLING = false;

View file

@ -10,12 +10,14 @@
#include <hyprland/src/render/Renderer.hpp>
#include <hyprland/src/managers/input/trackpad/GestureTypes.hpp>
#include <hyprland/src/managers/input/trackpad/TrackpadGestures.hpp>
#include <hyprland/src/managers/LayoutManager.hpp>
#include <hyprutils/string/ConstVarList.hpp>
using namespace Hyprutils::String;
#include "globals.hpp"
#include "overview.hpp"
#include "scrollOverview.hpp"
#include "ExpoGesture.hpp"
// Methods
@ -67,6 +69,8 @@ static void hkAddDamageB(void* thisptr, const pixman_region32_t* rg) {
static SDispatchResult onExpoDispatcher(std::string arg) {
IS_SCROLLING = g_pLayoutManager->getCurrentLayout()->getLayoutName() == "scrolling";
if (g_pOverview && g_pOverview->m_isSwiping)
return {.success = false, .error = "already swiping"};
@ -82,7 +86,12 @@ static SDispatchResult onExpoDispatcher(std::string arg) {
g_pOverview->close();
else {
renderingOverview = true;
g_pOverview = std::make_unique<COverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
if (IS_SCROLLING)
g_pOverview = makeShared<CScrollOverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
else
g_pOverview = makeShared<COverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
renderingOverview = false;
}
return {};
@ -98,7 +107,10 @@ static SDispatchResult onExpoDispatcher(std::string arg) {
return {};
renderingOverview = true;
g_pOverview = std::make_unique<COverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
if (IS_SCROLLING)
g_pOverview = makeShared<CScrollOverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
else
g_pOverview = makeShared<COverview>(Desktop::focusState()->monitor()->m_activeWorkspace);
renderingOverview = false;
return {};
}
@ -239,6 +251,8 @@ APICALL EXPORT PLUGIN_DESCRIPTION_INFO PLUGIN_INIT(HANDLE handle) {
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:skip_empty", Hyprlang::INT{0});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:scrolling:scroll_moves_up_down", Hyprlang::INT{1});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:scrolling:default_zoom", Hyprlang::FLOAT{0.5});
HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance", Hyprlang::INT{200});

View file

@ -10,6 +10,7 @@
#include <hyprland/src/managers/animation/DesktopAnimationManager.hpp>
#include <hyprland/src/managers/cursor/CursorShapeOverrideController.hpp>
#include <hyprland/src/managers/input/InputManager.hpp>
#include <hyprland/src/managers/LayoutManager.hpp>
#include <hyprland/src/helpers/time/Time.hpp>
#undef private
#include "OverviewPassElement.hpp"

View file

@ -9,43 +9,40 @@
#include <hyprland/src/managers/HookSystemManager.hpp>
#include <vector>
#include "IOverview.hpp"
// 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 {
class COverview : public IOverview {
public:
COverview(PHLWORKSPACE startedOn_, bool swipe = false);
~COverview();
virtual ~COverview();
void render();
void damage();
void onDamageReported();
void onPreRender();
virtual void render();
virtual void damage();
virtual void onDamageReported();
virtual void onPreRender();
void setClosing(bool closing);
virtual void setClosing(bool closing);
void resetSwipe();
void onSwipeUpdate(double delta);
void onSwipeEnd();
virtual void resetSwipe();
virtual void onSwipeUpdate(double delta);
virtual void onSwipeEnd();
// close without a selection
void close();
void selectHoveredWorkspace();
virtual void close();
virtual void selectHoveredWorkspace();
bool blockOverviewRendering = false;
bool blockDamageReporting = false;
PHLMONITORREF pMonitor;
bool m_isSwiping = false;
virtual void fullRender();
private:
void redrawID(int id, bool forcelowres = false);
void redrawAll(bool forcelowres = false);
void onWorkspaceChange();
void fullRender();
int SIDE_LENGTH = 3;
int GAP_WIDTH = 5;
@ -84,5 +81,3 @@ class COverview {
friend class COverviewPassElement;
};
inline std::unique_ptr<COverview> g_pOverview;

533
hyprexpo/scrollOverview.cpp Normal file
View file

@ -0,0 +1,533 @@
#include "scrollOverview.hpp"
#include <any>
#define private public
#include <hyprland/src/render/Renderer.hpp>
#include <hyprland/src/Compositor.hpp>
#include <hyprland/src/config/ConfigValue.hpp>
#include <hyprland/src/config/ConfigManager.hpp>
#include <hyprland/src/managers/animation/AnimationManager.hpp>
#include <hyprland/src/managers/animation/DesktopAnimationManager.hpp>
#include <hyprland/src/managers/input/InputManager.hpp>
#include <hyprland/src/managers/LayoutManager.hpp>
#include <hyprland/src/managers/cursor/CursorShapeOverrideController.hpp>
#include <hyprland/src/desktop/state/FocusState.hpp>
#include <hyprland/src/helpers/time/Time.hpp>
#undef private
#include "OverviewPassElement.hpp"
static void damageMonitor(WP<Hyprutils::Animation::CBaseAnimatedVariable> thisptr) {
g_pOverview->damage();
}
static void removeOverview(WP<Hyprutils::Animation::CBaseAnimatedVariable> thisptr) {
g_pOverview.reset();
}
CScrollOverview::~CScrollOverview() {
g_pHyprRenderer->makeEGLCurrent();
images.clear(); // otherwise we get a vram leak
Cursor::overrideController->unsetOverride(Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
g_pHyprOpenGL->markBlurDirtyForMonitor(pMonitor.lock());
}
CScrollOverview::CScrollOverview(PHLWORKSPACE startedOn_, bool swipe_) : startedOn(startedOn_), swipe(swipe_) {
static auto* const* PDEFAULTZOOM = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:scrolling:default_zoom")->getDataStaticPtr();
const auto PMONITOR = Desktop::focusState()->monitor();
pMonitor = PMONITOR;
for (const auto& w : g_pCompositor->getWorkspaces()) {
if (w && w->m_monitor == pMonitor && !w->m_isSpecialWorkspace)
images.emplace_back(makeShared<SWorkspaceImage>(w.lock()));
}
std::sort(images.begin(), images.end(), [](const auto& a, const auto& b) { return a->pWorkspace->m_id < b->pWorkspace->m_id; });
g_pAnimationManager->createAnimation(1.F, scale, g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE);
g_pAnimationManager->createAnimation({}, viewOffset, g_pConfigManager->getAnimationPropertyConfig("windowsMove"), AVARDAMAGE_NONE);
scale->setUpdateCallback(damageMonitor);
viewOffset->setUpdateCallback(damageMonitor);
if (!swipe)
*scale = std::clamp(**PDEFAULTZOOM, 0.1F, 0.9F);
lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position;
auto onCursorMove = [this](void* self, SCallbackInfo& info, std::any param) {
if (closing)
return;
info.cancelled = true;
lastMousePosLocal = g_pInputManager->getMouseCoordsInternal() - pMonitor->m_position;
// highlightHoverDebug();
};
auto onCursorSelect = [this](void* self, SCallbackInfo& info, std::any param) {
if (closing)
return;
info.cancelled = true;
selectHoveredWorkspace();
close();
};
auto onMouseAxis = [this](void* self, SCallbackInfo& info, std::any param) {
if (closing)
return;
info.cancelled = true;
auto data = std::any_cast<std::unordered_map<std::string, std::any>>(param);
auto e = std::any_cast<IPointer::SAxisEvent>(data["event"]);
static auto* const* PZOOM = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:scrolling:scroll_moves_up_down")->getDataStaticPtr();
if (!**PZOOM) {
const auto VAL = std::clamp(sc<float>(scale->value() + e.delta / -500.F), 0.05F, 0.95F);
*scale = VAL;
} else
moveViewportWorkspace(e.delta > 0);
};
auto onWindowOpen = [this](void* self, SCallbackInfo& info, std::any param) {
if (closing)
return;
redrawAll();
};
mouseMoveHook = g_pHookSystem->hookDynamic("mouseMove", onCursorMove);
touchMoveHook = g_pHookSystem->hookDynamic("touchMove", onCursorMove);
mouseAxisHook = g_pHookSystem->hookDynamic("mouseAxis", onMouseAxis);
mouseButtonHook = g_pHookSystem->hookDynamic("mouseButton", onCursorSelect);
touchDownHook = g_pHookSystem->hookDynamic("touchDown", onCursorSelect);
windowOpenHook = g_pHookSystem->hookDynamic("openWindow", onWindowOpen);
Cursor::overrideController->setOverride("left_ptr", Cursor::CURSOR_OVERRIDE_SPECIAL_ACTION);
redrawAll();
size_t activeIdx = 0;
for (size_t i = 0; i < images.size(); ++i) {
if (images[i]->pWorkspace && images[i]->pWorkspace == startedOn) {
activeIdx = i;
break;
}
}
viewportCurrentWorkspace = activeIdx;
}
void CScrollOverview::selectHoveredWorkspace() {
size_t activeIdx = 0;
for (size_t i = 0; i < images.size(); ++i) {
if (images[i]->pWorkspace && images[i]->pWorkspace == startedOn) {
activeIdx = i;
break;
}
}
const auto VIEWPORT_CENTER = CBox{{}, pMonitor->m_size}.middle();
float yoff = -(float)activeIdx * pMonitor->m_size.y * scale->value();
bool found = false;
for (const auto& wimg : images) {
for (const auto& img : wimg->windowImages) {
CBox texbox = {img->pWindow->m_realPosition->value() - pMonitor->m_position, img->pWindow->m_realSize->value()};
// scale the box to the viewport center
texbox.translate(-VIEWPORT_CENTER).scale(scale->value()).translate(VIEWPORT_CENTER).translate(-viewOffset->value() * scale->value());
texbox.translate({0.F, yoff});
// texbox.scale(pMonitor->m_scale).round();
if (texbox.containsPoint(lastMousePosLocal)) {
closeOnWindow = img->pWindow;
// *viewOffset = CBox{img->pWindow->m_realPosition->value(), img->pWindow->m_realSize->value()}.translate({0.F, yoff / scale->value()}).middle() -
// CBox{pMonitor->m_position, pMonitor->m_size}.middle();
found = true;
break;
}
}
if (found)
break;
yoff += pMonitor->m_size.y * scale->value();
}
}
void CScrollOverview::moveViewportWorkspace(bool up) {
size_t activeIdx = 0;
for (size_t i = 0; i < images.size(); ++i) {
if (images[i]->pWorkspace && images[i]->pWorkspace == startedOn) {
activeIdx = i;
break;
}
}
if (viewportCurrentWorkspace == 0 && !up)
return;
if (viewportCurrentWorkspace == images.size() - 1 && up)
return;
if (up)
viewportCurrentWorkspace++;
else
viewportCurrentWorkspace--;
*viewOffset = {viewOffset->value().x, (sc<long>(viewportCurrentWorkspace) - sc<long>(activeIdx)) * pMonitor->m_size.y};
}
void CScrollOverview::highlightHoverDebug() {
size_t activeIdx = 0;
for (size_t i = 0; i < images.size(); ++i) {
if (images[i]->pWorkspace && images[i]->pWorkspace == startedOn) {
activeIdx = i;
break;
}
}
const auto VIEWPORT_CENTER = CBox{{}, pMonitor->m_size}.middle();
float yoff = -(float)activeIdx * pMonitor->m_size.y * scale->value();
for (const auto& wimg : images) {
for (const auto& img : wimg->windowImages) {
CBox texbox = {img->pWindow->m_realPosition->value() - pMonitor->m_position, img->pWindow->m_realSize->value()};
// scale the box to the viewport center
texbox.translate(-VIEWPORT_CENTER).scale(scale->value()).translate(VIEWPORT_CENTER).translate(-viewOffset->value() * scale->value());
texbox.translate({0.F, yoff});
// texbox.scale(pMonitor->m_scale).round();
if (texbox.containsPoint(lastMousePosLocal)) {
img->highlight = true;
continue;
}
img->highlight = false;
}
yoff += pMonitor->m_size.y * scale->value();
}
}
SP<CScrollOverview::SWorkspaceImage> CScrollOverview::imageForWorkspace(PHLWORKSPACE w) {
for (const auto& i : images) {
if (i->pWorkspace == w)
return i;
}
return nullptr;
}
void CScrollOverview::redrawWorkspace(PHLWORKSPACE workspace, bool forcelowres) {
if (pMonitor->m_activeWorkspace != startedOn && !closing) {
// likely user changed.
onWorkspaceChange();
}
blockOverviewRendering = true;
g_pHyprRenderer->makeEGLCurrent();
auto image = imageForWorkspace(workspace);
if (!image)
return;
// get all tiled windows on the workspace and max dim
// TODO: float
std::vector<PHLWINDOW> windows;
for (const auto& w : g_pCompositor->m_windows) {
if (!validMapped(w) || w->m_isFloating || w->m_workspace != workspace)
continue;
windows.emplace_back(w);
}
for (const auto& w : windows) {
auto img = image->windowImages.emplace_back(makeShared<SWindowImage>());
img->pWindow = w;
img->fb.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, pMonitor->m_output->state->state().drmFormat);
if (!w->m_isX11 && w->wlSurface()) {
img->windowCommit = makeUnique<CHyprSignalListener>(w->wlSurface()->resource()->m_events.commit.listen([wk = WP<SWindowImage>{img}] {
if (!wk || !wk->pWindow)
return;
if (wk->pWindow->wlSurface()->resource()->m_current.accumulateBufferDamage().empty())
return;
reinterpretPointerCast<CScrollOverview>(g_pOverview)->redrawWindowImage(wk.lock());
g_pOverview->damage();
}));
}
redrawWindowImage(img);
}
blockOverviewRendering = false;
}
void CScrollOverview::redrawWindowImage(SP<SWindowImage> img) {
if (!img->pWindow)
return;
CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX};
g_pHyprRenderer->beginRender(pMonitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &img->fb);
g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 0});
g_pHyprRenderer->renderWindow(img->pWindow.lock(), pMonitor.lock(), Time::steadyNow(), true, RENDER_PASS_ALL, true, true);
g_pHyprOpenGL->m_renderData.blockScreenShader = true;
g_pHyprRenderer->endRender();
img->lastWindowPosition = img->pWindow->m_realPosition->value();
img->lastWindowSize = img->pWindow->m_realSize->value();
}
void CScrollOverview::redrawAll(bool forcelowres) {
for (const auto& img : images) {
redrawWorkspace(img->pWorkspace);
}
// redraw bg
if (backgroundFb.m_size != pMonitor->m_pixelSize) {
backgroundFb.release();
backgroundFb.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, pMonitor->m_output->state->state().drmFormat);
floatingFb.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, pMonitor->m_output->state->state().drmFormat);
}
CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX};
g_pHyprRenderer->beginRender(pMonitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &backgroundFb);
g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 1.0});
g_pHyprRenderer->renderAllClientsForWorkspace(pMonitor.lock(), nullptr, Time::steadyNow());
g_pHyprOpenGL->m_renderData.blockScreenShader = true;
g_pHyprRenderer->endRender();
// render floating as well. For these, we disable decos to match tiled ones.
g_pHyprRenderer->beginRender(pMonitor.lock(), fakeDamage, RENDER_MODE_FULL_FAKE, nullptr, &floatingFb);
g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 0});
for (const auto& w : g_pCompositor->m_windows) {
if (!validMapped(w) || !w->m_isFloating || w->m_workspace != startedOn)
continue;
g_pHyprRenderer->renderWindow(w, pMonitor.lock(), Time::steadyNow(), false, RENDER_PASS_ALL);
}
g_pHyprOpenGL->m_renderData.blockScreenShader = true;
g_pHyprRenderer->endRender();
}
void CScrollOverview::damage() {
blockDamageReporting = true;
g_pHyprRenderer->damageMonitor(pMonitor.lock());
blockDamageReporting = false;
}
void CScrollOverview::onDamageReported() {
; // TODO:
}
void CScrollOverview::close() {
closing = true;
if (!closeOnWindow)
closeOnWindow = Desktop::focusState()->window();
if (closeOnWindow == Desktop::focusState()->window())
*viewOffset = Vector2D{};
else {
if (closeOnWindow->m_workspace != pMonitor->m_activeWorkspace) {
g_pDesktopAnimationManager->startAnimation(pMonitor->m_activeWorkspace, CDesktopAnimationManager::ANIMATION_TYPE_OUT, true, true);
g_pDesktopAnimationManager->startAnimation(closeOnWindow->m_workspace, CDesktopAnimationManager::ANIMATION_TYPE_IN, false, true);
pMonitor->changeWorkspace(closeOnWindow->m_workspace, true, true, true);
}
Desktop::focusState()->fullWindowFocus(closeOnWindow.lock());
size_t activeIdx = 0;
for (size_t i = 0; i < images.size(); ++i) {
if (images[i]->pWorkspace && images[i]->pWorkspace == startedOn) {
activeIdx = i;
break;
}
}
float yoff = -(float)activeIdx * pMonitor->m_size.y * scale->value();
bool found = false;
for (const auto& wimg : images) {
for (const auto& img : wimg->windowImages) {
if (img->pWindow == closeOnWindow && closeOnWindow) {
Vector2D middleOfWindow = CBox{img->pWindow->m_realPosition->value(), img->pWindow->m_realSize->value()}.translate({0.F, yoff / scale->value()}).middle() -
CBox{pMonitor->m_position, pMonitor->m_size}.middle();
// we need to do this because the window doesnt have to be centered after click
*viewOffset = middleOfWindow +
(CBox{pMonitor->m_position, pMonitor->m_size}.middle() - CBox{img->pWindow->m_realPosition->value(), img->pWindow->m_realSize->value()}.middle());
found = true;
break;
}
}
if (found)
break;
yoff += pMonitor->m_size.y * scale->value();
}
}
*scale = 1.F;
scale->setCallbackOnEnd(removeOverview);
}
void CScrollOverview::onPreRender() {
;
}
void CScrollOverview::onWorkspaceChange() {
; // TODO:
}
void CScrollOverview::render() {
bool needsDamage = false;
for (const auto& img : images) {
for (const auto& i : img->windowImages) {
if (!i->pWindow)
continue;
if (i->lastWindowSize != i->pWindow->m_realSize->value() || i->lastWindowPosition != i->pWindow->m_realPosition->value()) {
needsDamage = true;
i->lastWindowPosition = i->pWindow->m_realPosition->value();
i->lastWindowSize = i->pWindow->m_realSize->value();
}
}
}
if (needsDamage)
damage();
g_pHyprRenderer->m_renderPass.add(makeUnique<COverviewPassElement>());
}
void CScrollOverview::fullRender() {
g_pHyprOpenGL->clear(CHyprColor{0, 0, 0, 1});
CBox texbox = {{}, pMonitor->m_size};
texbox.scale(pMonitor->m_scale);
texbox.round();
CRegion damage{0, 0, INT16_MAX, INT16_MAX};
g_pHyprOpenGL->renderTextureInternal(backgroundFb.getTexture(), texbox, {.damage = &damage, .a = 1.0});
const auto VIEWPORT_CENTER = CBox{{}, pMonitor->m_size}.middle();
size_t activeIdx = 0;
for (size_t i = 0; i < images.size(); ++i) {
if (images[i]->pWorkspace && images[i]->pWorkspace == startedOn) {
activeIdx = i;
break;
}
}
// render all views
float yoff = -(float)activeIdx * pMonitor->m_size.y * scale->value();
for (const auto& wimg : images) {
bool dirty = false;
for (const auto& img : wimg->windowImages) {
if (!img->pWindow) {
dirty = true;
continue;
}
CBox texbox = CBox{img->pWindow->m_realPosition->value() - pMonitor->m_position, pMonitor->m_size};
// scale the box to the viewport center
texbox.translate(-VIEWPORT_CENTER).scale(scale->value()).translate(VIEWPORT_CENTER).translate(-viewOffset->value() * scale->value());
texbox.translate({0.F, yoff});
texbox.scale(pMonitor->m_scale).round();
CRegion damage{0, 0, INT16_MAX, INT16_MAX};
g_pHyprOpenGL->renderTextureInternal(img->fb.getTexture(), texbox, {.damage = &damage, .a = 1.0 * img->pWindow->m_alpha->value()});
if (img->highlight) {
CBox texbox2 = CBox{img->pWindow->m_realPosition->value(), img->pWindow->m_realSize->value()}
.translate(-VIEWPORT_CENTER)
.scale(scale->value())
.translate(VIEWPORT_CENTER)
.translate({0.F, yoff});
g_pHyprOpenGL->renderRect(texbox2, CHyprColor{0.5, 0.0, 0.0, 0.5}, CHyprOpenGLImpl::SRectRenderData{.round = 5});
}
}
CBox floatbox = CBox{pMonitor->m_position + Vector2D{0.F, yoff / scale->value()}, pMonitor->m_size};
floatbox.translate(-VIEWPORT_CENTER).scale(scale->value()).translate(VIEWPORT_CENTER).translate(-viewOffset->value() * scale->value());
floatbox.translate({0.F, yoff});
floatbox.scale(pMonitor->m_scale).round();
g_pHyprOpenGL->renderTextureInternal(floatingFb.getTexture(), floatbox, {.damage = &damage, .a = 1.0});
yoff += pMonitor->m_size.y * scale->value();
if (dirty)
std::erase_if(wimg->windowImages, [](const auto& e) { return !e->pWindow; });
}
}
static float hyprlerp(const float& from, const float& to, const float perc) {
return (to - from) * perc + from;
}
static Vector2D hyprlerp(const Vector2D& from, const Vector2D& to, const float perc) {
return Vector2D{hyprlerp(from.x, to.x, perc), hyprlerp(from.y, to.y, perc)};
}
void CScrollOverview::setClosing(bool closing_) {
closing = closing_;
}
void CScrollOverview::resetSwipe() {
static auto* const* PDEFAULTZOOM = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:scrolling:default_zoom")->getDataStaticPtr();
if (closing) {
close();
return;
}
(*scale) = **PDEFAULTZOOM;
m_isSwiping = false;
}
void CScrollOverview::onSwipeUpdate(double delta) {
static auto* const* PDEFAULTZOOM = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:scrolling:default_zoom")->getDataStaticPtr();
static auto* const* PDISTANCE = (Hyprlang::INT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:gesture_distance")->getDataStaticPtr();
m_isSwiping = true;
const float PERC = closing ? std::clamp(delta / (double)**PDISTANCE, 0.0, 1.0) : 1.0 - std::clamp(delta / (double)**PDISTANCE, 0.0, 1.0);
scale->setValueAndWarp(hyprlerp(1.F, **PDEFAULTZOOM, PERC));
}
void CScrollOverview::onSwipeEnd() {
static auto* const* PDEFAULTZOOM = (Hyprlang::FLOAT* const*)HyprlandAPI::getConfigValue(PHANDLE, "plugin:hyprexpo:scrolling:default_zoom")->getDataStaticPtr();
if (closing) {
close();
return;
}
(*scale) = **PDEFAULTZOOM;
m_isSwiping = false;
}

View file

@ -0,0 +1,95 @@
#pragma once
#define WLR_USE_UNSTABLE
#include "globals.hpp"
#include <hyprland/src/desktop/DesktopTypes.hpp>
#include <hyprland/src/render/Framebuffer.hpp>
#include <hyprland/src/helpers/AnimatedVariable.hpp>
#include <hyprland/src/managers/HookSystemManager.hpp>
#include <hyprland/src/helpers/signal/Signal.hpp>
#include <vector>
#include "IOverview.hpp"
class CMonitor;
class CScrollOverview : public IOverview {
public:
CScrollOverview(PHLWORKSPACE startedOn_, bool swipe = false);
virtual ~CScrollOverview();
virtual void render();
virtual void damage();
virtual void onDamageReported();
virtual void onPreRender();
virtual void setClosing(bool closing);
virtual void resetSwipe();
virtual void onSwipeUpdate(double delta);
virtual void onSwipeEnd();
// close without a selection
virtual void close();
virtual void selectHoveredWorkspace();
virtual void fullRender();
private:
void redrawWorkspace(PHLWORKSPACE w, bool forcelowres = false);
void redrawAll(bool forcelowres = false);
void onWorkspaceChange();
void highlightHoverDebug();
void moveViewportWorkspace(bool up);
bool damageDirty = false;
size_t viewportCurrentWorkspace = 0;
struct SWindowImage {
PHLWINDOWREF pWindow;
CFramebuffer fb;
bool highlight = false;
UP<CHyprSignalListener> windowCommit;
Vector2D lastWindowPosition, lastWindowSize;
};
void redrawWindowImage(SP<SWindowImage>);
struct SWorkspaceImage {
PHLWORKSPACE pWorkspace;
CBox box;
std::vector<SP<SWindowImage>> windowImages;
};
CFramebuffer backgroundFb;
CFramebuffer floatingFb;
Vector2D lastMousePosLocal = Vector2D{};
PHLWINDOWREF closeOnWindow;
std::vector<SP<SWorkspaceImage>> images;
SP<SWorkspaceImage> imageForWorkspace(PHLWORKSPACE w);
PHLWORKSPACE startedOn;
PHLANIMVAR<float> scale;
PHLANIMVAR<Vector2D> viewOffset;
bool closing = false;
SP<HOOK_CALLBACK_FN> mouseMoveHook;
SP<HOOK_CALLBACK_FN> mouseButtonHook;
SP<HOOK_CALLBACK_FN> touchMoveHook;
SP<HOOK_CALLBACK_FN> touchDownHook;
SP<HOOK_CALLBACK_FN> mouseAxisHook;
SP<HOOK_CALLBACK_FN> windowOpenHook;
bool swipe = false;
bool swipeWasCommenced = false;
friend class CScrollOverviewPassElement;
};
inline std::unique_ptr<CScrollOverview> g_pScrollOverview;