From 6e215e4f9de6bf058b844788347bb093703907fe Mon Sep 17 00:00:00 2001 From: Vaxry Date: Fri, 2 May 2025 19:48:37 +0100 Subject: [PATCH] hyprscrolling: init plugin --- README.md | 1 + hyprscrolling/CMakeLists.txt | 27 ++ hyprscrolling/Makefile | 4 + hyprscrolling/README.md | 23 ++ hyprscrolling/Scrolling.cpp | 575 +++++++++++++++++++++++++++++++++++ hyprscrolling/Scrolling.hpp | 98 ++++++ hyprscrolling/default.nix | 19 ++ hyprscrolling/globals.hpp | 5 + hyprscrolling/main.cpp | 62 ++++ hyprscrolling/meson.build | 30 ++ 10 files changed, 844 insertions(+) create mode 100644 hyprscrolling/CMakeLists.txt create mode 100644 hyprscrolling/Makefile create mode 100644 hyprscrolling/README.md create mode 100644 hyprscrolling/Scrolling.cpp create mode 100644 hyprscrolling/Scrolling.hpp create mode 100644 hyprscrolling/default.nix create mode 100644 hyprscrolling/globals.hpp create mode 100644 hyprscrolling/main.cpp create mode 100644 hyprscrolling/meson.build diff --git a/README.md b/README.md index 3ca02aa..5e49a85 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ This repo houses official plugins for Hyprland. - csgo-vulkan-fix -> fixes custom resolutions on CS:GO with `-vulkan` - hyprbars -> adds title bars to windows - hyprexpo -> adds an expo-like workspace overview + - hyprscrolling -> adds a scrolling layout to hyprland - hyprtrails -> adds smooth trails behind moving windows - hyprwinwrap -> clone of xwinwrap, allows you to put any app as a wallpaper - xtra-dispatchers -> adds some new dispatchers diff --git a/hyprscrolling/CMakeLists.txt b/hyprscrolling/CMakeLists.txt new file mode 100644 index 0000000..2a05e9f --- /dev/null +++ b/hyprscrolling/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.27) + +project(hyprscrolling + DESCRIPTION "hyprscrolling plugin for Hyprland" + VERSION 0.1 +) + +set(CMAKE_CXX_STANDARD 23) + +file(GLOB_RECURSE SRC "*.cpp") + +add_library(hyprscrolling SHARED ${SRC}) + +find_package(PkgConfig REQUIRED) +pkg_check_modules(deps REQUIRED IMPORTED_TARGET + hyprland + libdrm + libinput + libudev + pangocairo + pixman-1 + wayland-server + xkbcommon +) +target_link_libraries(hyprscrolling PRIVATE rt PkgConfig::deps) + +install(TARGETS hyprscrolling) diff --git a/hyprscrolling/Makefile b/hyprscrolling/Makefile new file mode 100644 index 0000000..4285dba --- /dev/null +++ b/hyprscrolling/Makefile @@ -0,0 +1,4 @@ +all: + $(CXX) -shared -fPIC --no-gnu-unique main.cpp Scrolling.cpp -o hyprscrolling.so -g `pkg-config --cflags pixman-1 libdrm hyprland pangocairo libinput libudev wayland-server xkbcommon` -std=c++2b +clean: + rm ./hyprscrolling.so diff --git a/hyprscrolling/README.md b/hyprscrolling/README.md new file mode 100644 index 0000000..f6a5cf1 --- /dev/null +++ b/hyprscrolling/README.md @@ -0,0 +1,23 @@ +# hyprscrolling + +Adds a scrolling layout to Hyprland. + +**This plugin is a work in progress!** + +## Config + +*All config values are in `plugin:hyprscrolling`.* + +| name | description | type | default | +| -- | -- | -- | -- | +| fullscreen_on_one_column | if there's only one column, should it be fullscreen | bool | false | +| column_width | default column width as a fraction of the monitor width | float [0 - 1] | 0.5 | + + +## Layout messages + +| name | description | params | +| --- | --- | --- | +| move | move the layout horizontally, by either a relative logical px (`-200`, `+200`) or columns (`+col`, `-col`) | move data | +| colresize | resize the current column, to either a value or by a relative value e.g. `0.5`, `+0.2`, `-0.2` | relative float | +| movewindowto | same as the movewindow dispatcher but supports promotion to the right at the end | direction | \ No newline at end of file diff --git a/hyprscrolling/Scrolling.cpp b/hyprscrolling/Scrolling.cpp new file mode 100644 index 0000000..bb89ffe --- /dev/null +++ b/hyprscrolling/Scrolling.cpp @@ -0,0 +1,575 @@ +#include "Scrolling.hpp" + +#include +#include +#include +#include + +void SColumnData::add(PHLWINDOW w) { + windowDatas.emplace_back(makeShared(w, self.lock())); +} + +void SColumnData::add(SP w) { + windowDatas.emplace_back(w); + w->column = self; +} + +void SColumnData::remove(PHLWINDOW w) { + std::erase_if(windowDatas, [&w](const auto& e) { return e->window == w; }); + if (windowDatas.empty() && workspace) + workspace->remove(self.lock()); +} + +void SColumnData::up(SP w) { + for (size_t i = 1; i < windowDatas.size(); ++i) { + if (windowDatas[i] != w) + continue; + + std::swap(windowDatas[i], windowDatas[i - 1]); + } +} + +void SColumnData::down(SP w) { + for (size_t i = 0; i < windowDatas.size() - 1; ++i) { + if (windowDatas[i] != w) + continue; + + std::swap(windowDatas[i], windowDatas[i + 1]); + } +} + +SP SWorkspaceData::add() { + static const auto PCOLWIDTH = CConfigValue("plugin:hyprscrolling:column_width"); + auto col = columns.emplace_back(makeShared(self.lock())); + col->self = col; + col->columnWidth = *PCOLWIDTH; + return col; +} + +void SWorkspaceData::remove(SP c) { + std::erase(columns, c); +} + +SP SWorkspaceData::next(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] != c) + continue; + + if (i == columns.size() - 1) + return nullptr; + + return columns[i + 1]; + } + + return nullptr; +} + +SP SWorkspaceData::prev(SP c) { + for (size_t i = 0; i < columns.size(); ++i) { + if (columns[i] != c) + continue; + + if (i == 0) + return nullptr; + + return columns[i - 1]; + } + + return nullptr; +} + +void SWorkspaceData::centerCol(SP c) { + if (!c) + return; + + static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); + + PHLMONITOR PMONITOR = workspace->m_monitor.lock(); + double currentLeft = 0; + + for (const auto& COL : columns) { + const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? PMONITOR->m_size.x : PMONITOR->m_size.x * COL->columnWidth; + + if (COL != c) + currentLeft += ITEM_WIDTH; + else { + leftOffset = -currentLeft + ITEM_WIDTH / 2.0; + return; + } + } +} + +SP SWorkspaceData::atCenter() { + static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); + + PHLMONITOR PMONITOR = workspace->m_monitor.lock(); + double currentLeft = leftOffset; + + for (const auto& COL : columns) { + const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? PMONITOR->m_size.x : PMONITOR->m_size.x * COL->columnWidth; + + currentLeft += ITEM_WIDTH; + + if (currentLeft >= PMONITOR->m_size.x / 2.0 - 2) + return COL; + } + + return nullptr; +} + +void SWorkspaceData::recalculate() { + static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); + + if (!workspace || !workspace) { + Debug::log(ERR, "[scroller] broken internal state on workspace data"); + return; + } + + leftOffset = std::clamp((double)leftOffset, -maxWidth(), 0.0); + + const auto MAX_WIDTH = maxWidth(); + + PHLMONITOR PMONITOR = workspace->m_monitor.lock(); + double currentLeft = MAX_WIDTH < PMONITOR->m_size.x ? std::round((PMONITOR->m_size.x - MAX_WIDTH) / 2.0) : leftOffset; // layout pixels + + for (const auto& COL : columns) { + double currentTop = 0.0; + const double ITEM_HEIGHT = PMONITOR->m_size.y / COL->windowDatas.size(); + const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? PMONITOR->m_size.x : PMONITOR->m_size.x * COL->columnWidth; + + for (const auto& WINDOW : COL->windowDatas) { + WINDOW->layoutBox = CBox{currentLeft, currentTop, ITEM_WIDTH, ITEM_HEIGHT}.translate(PMONITOR->m_position); + + currentTop += ITEM_HEIGHT; + + layout->applyNodeDataToWindow(WINDOW, false); + } + + currentLeft += ITEM_WIDTH; + } +} + +double SWorkspaceData::maxWidth() { + static const auto PFSONONE = CConfigValue("plugin:hyprscrolling:fullscreen_on_one_column"); + + PHLMONITOR PMONITOR = workspace->m_monitor.lock(); + double currentLeft = 0; + + for (const auto& COL : columns) { + const double ITEM_WIDTH = *PFSONONE && columns.size() == 1 ? PMONITOR->m_size.x : PMONITOR->m_size.x * COL->columnWidth; + + currentLeft += ITEM_WIDTH; + } + + return currentLeft; +} + +void CScrollingLayout::applyNodeDataToWindow(SP data, bool force) { + if (!data || !data->column || !data->column->workspace) { + Debug::log(ERR, "[scroller] broken internal state on node"); + return; + } + + PHLMONITOR PMONITOR = data->column->workspace->workspace ? data->column->workspace->workspace->m_monitor.lock() : nullptr; + + if (!PMONITOR) { + Debug::log(ERR, "[scroller] broken internal state on workspace"); + return; + } + + // for gaps outer + const bool DISPLAYLEFT = STICKS(data->layoutBox.x, PMONITOR->m_position.x + PMONITOR->m_reservedTopLeft.x); + const bool DISPLAYRIGHT = STICKS(data->layoutBox.x + data->layoutBox.w, PMONITOR->m_position.x + PMONITOR->m_size.x - PMONITOR->m_reservedBottomRight.x); + const bool DISPLAYTOP = STICKS(data->layoutBox.y, PMONITOR->m_position.y + PMONITOR->m_reservedTopLeft.y); + const bool DISPLAYBOTTOM = STICKS(data->layoutBox.y + data->layoutBox.h, PMONITOR->m_position.y + PMONITOR->m_size.y - PMONITOR->m_reservedBottomRight.y); + + const auto PWINDOW = data->window.lock(); + // get specific gaps and rules for this workspace, + // if user specified them in config + const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(g_pCompositor->getWorkspaceByID(data->column->workspace->workspace->m_id)); + + if (!validMapped(PWINDOW)) { + Debug::log(ERR, "Node {} holding invalid {}!!", (uintptr_t)data.get(), PWINDOW); + onWindowRemovedTiling(PWINDOW); + return; + } + + if (PWINDOW->isFullscreen()) + return; + + PWINDOW->unsetWindowData(PRIORITY_LAYOUT); + PWINDOW->updateWindowData(); + + static auto PGAPSINDATA = CConfigValue("general:gaps_in"); + static auto PGAPSOUTDATA = CConfigValue("general:gaps_out"); + auto* const PGAPSIN = (CCssGapData*)(PGAPSINDATA.ptr())->getData(); + auto* const PGAPSOUT = (CCssGapData*)(PGAPSOUTDATA.ptr())->getData(); + + auto gapsIn = WORKSPACERULE.gapsIn.value_or(*PGAPSIN); + auto gapsOut = WORKSPACERULE.gapsOut.value_or(*PGAPSOUT); + CBox nodeBox = data->layoutBox; + nodeBox.round(); + + PWINDOW->m_size = nodeBox.size(); + PWINDOW->m_position = nodeBox.pos(); + + PWINDOW->updateWindowDecos(); + + auto calcPos = PWINDOW->m_position; + auto calcSize = PWINDOW->m_size; + + const auto OFFSETTOPLEFT = Vector2D((double)(DISPLAYLEFT ? gapsOut.m_left : gapsIn.m_left), (double)(DISPLAYTOP ? gapsOut.m_top : gapsIn.m_top)); + + const auto OFFSETBOTTOMRIGHT = Vector2D((double)(DISPLAYRIGHT ? gapsOut.m_right : gapsIn.m_right), (double)(DISPLAYBOTTOM ? gapsOut.m_bottom : gapsIn.m_bottom)); + + calcPos = calcPos + OFFSETTOPLEFT; + calcSize = calcSize - OFFSETTOPLEFT - OFFSETBOTTOMRIGHT; + + if (PWINDOW->m_isPseudotiled) { + // Calculate pseudo + float scale = 1; + + // adjust if doesnt fit + if (PWINDOW->m_pseudoSize.x > calcSize.x || PWINDOW->m_pseudoSize.y > calcSize.y) { + if (PWINDOW->m_pseudoSize.x > calcSize.x) { + scale = calcSize.x / PWINDOW->m_pseudoSize.x; + } + + if (PWINDOW->m_pseudoSize.y * scale > calcSize.y) { + scale = calcSize.y / PWINDOW->m_pseudoSize.y; + } + + auto DELTA = calcSize - PWINDOW->m_pseudoSize * scale; + calcSize = PWINDOW->m_pseudoSize * scale; + calcPos = calcPos + DELTA / 2.f; // center + } else { + auto DELTA = calcSize - PWINDOW->m_pseudoSize; + calcPos = calcPos + DELTA / 2.f; // center + calcSize = PWINDOW->m_pseudoSize; + } + } + + const auto RESERVED = PWINDOW->getFullWindowReservedArea(); + calcPos = calcPos + RESERVED.topLeft; + calcSize = calcSize - (RESERVED.topLeft + RESERVED.bottomRight); + + if (PWINDOW->onSpecialWorkspace() && !PWINDOW->isFullscreen()) { + // if special, we adjust the coords a bit + static auto PSCALEFACTOR = CConfigValue("dwindle:special_scale_factor"); + + CBox wb = {calcPos + (calcSize - calcSize * *PSCALEFACTOR) / 2.f, calcSize * *PSCALEFACTOR}; + wb.round(); // avoid rounding mess + + *PWINDOW->m_realPosition = wb.pos(); + *PWINDOW->m_realSize = wb.size(); + } else { + CBox wb = {calcPos, calcSize}; + wb.round(); // avoid rounding mess + + *PWINDOW->m_realSize = wb.size(); + *PWINDOW->m_realPosition = wb.pos(); + } + + if (force) { + g_pHyprRenderer->damageWindow(PWINDOW); + + PWINDOW->m_realPosition->warp(); + PWINDOW->m_realSize->warp(); + + g_pHyprRenderer->damageWindow(PWINDOW); + } + + PWINDOW->updateWindowDecos(); +} + +void CScrollingLayout::onEnable() { + for (auto const& w : g_pCompositor->m_windows) { + if (w->m_isFloating || !w->m_isMapped || w->isHidden()) + continue; + + onWindowCreatedTiling(w); + } +} + +void CScrollingLayout::onDisable() { + m_workspaceDatas.clear(); +} + +void CScrollingLayout::onWindowCreatedTiling(PHLWINDOW window, eDirection direction) { + auto workspaceData = dataFor(window->m_workspace); + + if (!workspaceData) { + Debug::log(LOG, "[scrolling] No workspace data yet, creating"); + workspaceData = m_workspaceDatas.emplace_back(makeShared(window->m_workspace, this)); + workspaceData->self = workspaceData; + } + + const auto PLASTFOCUS = g_pCompositor->m_lastWindow.lock(); + const SP LAST_FOCUS_DATA = PLASTFOCUS ? dataFor(PLASTFOCUS) : nullptr; + const auto PMONITOR = window->m_monitor; + + bool addNewColumn = !PLASTFOCUS || !LAST_FOCUS_DATA || PLASTFOCUS->m_workspace != window->m_workspace || workspaceData->columns.size() <= 1; + + if (!addNewColumn && PMONITOR) { + if (workspaceData->atCenter() == nullptr) + addNewColumn = true; + } + + Debug::log(LOG, "[scrolling] new window {:x}, addNewColumn: {}, columns before: {}", (uintptr_t)window.get(), addNewColumn, workspaceData->columns.size()); + + if (addNewColumn) { + auto col = workspaceData->add(); + col->add(window); + } else { + // LAST_FOCUS_DATA has to be valid + auto col = LAST_FOCUS_DATA->column; + col->add(window); + } + + workspaceData->recalculate(); +} + +void CScrollingLayout::onWindowRemovedTiling(PHLWINDOW window) { + const auto DATA = dataFor(window); + + if (!DATA) + return; + + DATA->column->remove(window); +} + +bool CScrollingLayout::isWindowTiled(PHLWINDOW window) { + const auto DATA = dataFor(window); + + return DATA; +} + +void CScrollingLayout::recalculateMonitor(const MONITORID& id) { + const auto PMONITOR = g_pCompositor->getMonitorFromID(id); + if (!PMONITOR || !PMONITOR->m_activeWorkspace) + return; + + const auto DATA = dataFor(PMONITOR->m_activeWorkspace); + + if (!DATA) + return; + + DATA->recalculate(); +} + +void CScrollingLayout::recalculateWindow(PHLWINDOW window) { + if (!window->m_workspace) + return; + + const auto DATA = dataFor(window->m_workspace); + + if (!DATA) + return; + + DATA->recalculate(); +} + +void CScrollingLayout::onBeginDragWindow() { + IHyprLayout::onBeginDragWindow(); +} + +void CScrollingLayout::resizeActiveWindow(const Vector2D&, eRectCorner corner, PHLWINDOW pWindow) { + ; +} + +void CScrollingLayout::fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE) { + ; +} + +std::any CScrollingLayout::layoutMessage(SLayoutMessageHeader header, std::string message) { + const auto ARGS = CVarList(message, 0, ' '); + if (ARGS[0] == "move") { + const auto DATA = currentWorkspaceData(); + if (!DATA) + return {}; + + if (ARGS[1] == "+col" || ARGS[1] == "col") { + const auto WDATA = dataFor(g_pCompositor->m_lastWindow.lock()); + if (!WDATA) + return {}; + + const auto COL = DATA->next(WDATA->column.lock()); + if (!COL) { + // move to max + DATA->leftOffset = -DATA->maxWidth(); + DATA->recalculate(); + g_pCompositor->focusWindow(nullptr); + return {}; + } + + DATA->centerCol(COL); + DATA->recalculate(); + + g_pCompositor->focusWindow(COL->windowDatas.front()->window.lock()); + + return {}; + } else if (ARGS[1] == "-col") { + const auto WDATA = dataFor(g_pCompositor->m_lastWindow.lock()); + if (!WDATA) { + if (DATA->leftOffset <= DATA->maxWidth() && DATA->columns.size() > 0) { + DATA->centerCol(DATA->columns.back()); + DATA->recalculate(); + g_pCompositor->focusWindow((DATA->columns.back()->windowDatas.back())->window.lock()); + } + + return {}; + } + + const auto COL = DATA->prev(WDATA->column.lock()); + if (!COL) + return {}; + DATA->centerCol(COL); + DATA->recalculate(); + + g_pCompositor->focusWindow(COL->windowDatas.back()->window.lock()); + + return {}; + } + + const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); + + if (!PLUSMINUS.has_value()) + return {}; + + DATA->leftOffset -= *PLUSMINUS; + DATA->recalculate(); + + const auto ATCENTER = DATA->atCenter(); + + g_pCompositor->focusWindow(ATCENTER ? (*ATCENTER->windowDatas.begin())->window.lock() : nullptr); + } else if (ARGS[0] == "colresize") { + const auto WDATA = dataFor(g_pCompositor->m_lastWindow.lock()); + + if (!WDATA) + return {}; + + if (ARGS[1][0] == '+' || ARGS[1][0] == '-') { + const auto PLUSMINUS = getPlusMinusKeywordResult(ARGS[1], 0); + + if (!PLUSMINUS.has_value()) + return {}; + + WDATA->column->columnWidth += *PLUSMINUS; + } else { + float abs = 0; + try { + abs = std::stof(ARGS[1]); + } catch (...) { return {}; } + + WDATA->column->columnWidth = abs; + } + + WDATA->column->columnWidth = std::clamp(WDATA->column->columnWidth, 0.05F, 1.F); + + WDATA->column->workspace->recalculate(); + } else if (ARGS[0] == "movewindowto") { + moveWindowTo(g_pCompositor->m_lastWindow.lock(), ARGS[1], false); + } + + return {}; +} + +SWindowRenderLayoutHints CScrollingLayout::requestRenderHints(PHLWINDOW a) { + return {}; +} + +void CScrollingLayout::switchWindows(PHLWINDOW a, PHLWINDOW b) { + ; +} + +void CScrollingLayout::moveWindowTo(PHLWINDOW w, const std::string& dir, bool silent) { + const auto DATA = dataFor(w); + + if (!DATA) + return; + + const auto WS = DATA->column->workspace.lock(); + + if (dir == "l") { + const auto COL = WS->prev(DATA->column.lock()); + if (!COL) + return; + + DATA->column->remove(w); + COL->add(DATA); + } else if (dir == "r") { + const auto COL = WS->next(DATA->column.lock()); + + DATA->column->remove(w); + + if (!COL) { + // make a new one + const auto NEWCOL = WS->add(); + NEWCOL->add(DATA); + } else + COL->add(DATA); + + } else if (dir == "t" || dir == "u") + DATA->column->up(DATA); + else if (dir == "b" || dir == "d") + DATA->column->down(DATA); + + WS->recalculate(); +} + +void CScrollingLayout::alterSplitRatio(PHLWINDOW, float, bool) { + ; +} + +std::string CScrollingLayout::getLayoutName() { + return "scrolling"; +} + +void CScrollingLayout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) { + ; +} + +Vector2D CScrollingLayout::predictSizeForNewWindowTiled() { + return Vector2D{}; +} + +SP CScrollingLayout::dataFor(PHLWORKSPACE ws) { + for (const auto& e : m_workspaceDatas) { + if (e->workspace != ws) + continue; + + return e; + } + + return nullptr; +} + +SP CScrollingLayout::dataFor(PHLWINDOW w) { + if (!w) + return nullptr; + + for (const auto& e : m_workspaceDatas) { + if (e->workspace != w->m_workspace) + continue; + + for (const auto& c : e->columns) { + for (const auto& d : c->windowDatas) { + if (d->window != w) + continue; + + return d; + } + } + } + + return nullptr; +} + +SP CScrollingLayout::currentWorkspaceData() { + if (!g_pCompositor->m_lastMonitor || !g_pCompositor->m_lastMonitor->m_activeWorkspace) + return nullptr; + + // FIXME: special + + return dataFor(g_pCompositor->m_lastMonitor->m_activeWorkspace); +} diff --git a/hyprscrolling/Scrolling.hpp b/hyprscrolling/Scrolling.hpp new file mode 100644 index 0000000..94ffda8 --- /dev/null +++ b/hyprscrolling/Scrolling.hpp @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include + +class CScrollingLayout; +struct SColumnData; +struct SWorkspaceData; + +struct SScrollingWindowData { + SScrollingWindowData(PHLWINDOW w, SP col) : window(w), column(col) { + ; + } + + PHLWINDOWREF window; + WP column; + + CBox layoutBox; +}; + +struct SColumnData { + SColumnData(SP ws) : workspace(ws) { + ; + } + + void add(PHLWINDOW w); + void add(SP w); + void remove(PHLWINDOW w); + + void up(SP w); + void down(SP w); + + std::vector> windowDatas; + float columnSize = 1.F; + float columnWidth = 1.F; + WP workspace; + + WP self; +}; + +struct SWorkspaceData { + SWorkspaceData(PHLWORKSPACE w, CScrollingLayout* l) : workspace(w), layout(l) { + ; + } + + PHLWORKSPACEREF workspace; + std::vector> columns; + int leftOffset = 0; + + SP add(); + void remove(SP c); + double maxWidth(); + SP next(SP c); + SP prev(SP c); + SP atCenter(); + + void centerCol(SP c); + + void recalculate(); + + CScrollingLayout* layout = nullptr; + WP self; +}; + +class CScrollingLayout : public IHyprLayout { + public: + virtual void onWindowCreatedTiling(PHLWINDOW, eDirection direction = DIRECTION_DEFAULT); + virtual void onWindowRemovedTiling(PHLWINDOW); + virtual bool isWindowTiled(PHLWINDOW); + virtual void recalculateMonitor(const MONITORID&); + virtual void recalculateWindow(PHLWINDOW); + virtual void onBeginDragWindow(); + virtual void resizeActiveWindow(const Vector2D&, eRectCorner corner = CORNER_NONE, PHLWINDOW pWindow = nullptr); + virtual void fullscreenRequestForWindow(PHLWINDOW pWindow, const eFullscreenMode CURRENT_EFFECTIVE_MODE, const eFullscreenMode EFFECTIVE_MODE); + virtual std::any layoutMessage(SLayoutMessageHeader, std::string); + virtual SWindowRenderLayoutHints requestRenderHints(PHLWINDOW); + virtual void switchWindows(PHLWINDOW, PHLWINDOW); + virtual void moveWindowTo(PHLWINDOW, const std::string& dir, bool silent); + virtual void alterSplitRatio(PHLWINDOW, float, bool); + virtual std::string getLayoutName(); + virtual void replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to); + virtual Vector2D predictSizeForNewWindowTiled(); + + virtual void onEnable(); + virtual void onDisable(); + + private: + std::vector> m_workspaceDatas; + + SP dataFor(PHLWORKSPACE ws); + SP dataFor(PHLWINDOW w); + SP currentWorkspaceData(); + + void applyNodeDataToWindow(SP node, bool force); + + friend struct SWorkspaceData; +}; \ No newline at end of file diff --git a/hyprscrolling/default.nix b/hyprscrolling/default.nix new file mode 100644 index 0000000..87a323e --- /dev/null +++ b/hyprscrolling/default.nix @@ -0,0 +1,19 @@ +{ + lib, + hyprland, + hyprlandPlugins, +}: +hyprlandPlugins.mkHyprlandPlugin hyprland { + pluginName = "xtra-dispatchers"; + version = "0.1"; + src = ./.; + + inherit (hyprland) nativeBuildInputs; + + meta = with lib; { + homepage = "https://github.com/hyprwm/hyprland-plugins"; + description = "Hyprland extra dispatchers plugin"; + license = licenses.bsd3; + platforms = platforms.linux; + }; +} diff --git a/hyprscrolling/globals.hpp b/hyprscrolling/globals.hpp new file mode 100644 index 0000000..2257475 --- /dev/null +++ b/hyprscrolling/globals.hpp @@ -0,0 +1,5 @@ +#pragma once + +#include + +inline HANDLE PHANDLE = nullptr; \ No newline at end of file diff --git a/hyprscrolling/main.cpp b/hyprscrolling/main.cpp new file mode 100644 index 0000000..6ef6f41 --- /dev/null +++ b/hyprscrolling/main.cpp @@ -0,0 +1,62 @@ +#define WLR_USE_UNSTABLE + +#include + +#include +#include + +#define private public +#include +#include +#include +#include +#include +#undef private + +#include +using namespace Hyprutils::String; + +#include "globals.hpp" +#include "Scrolling.hpp" + +// Do NOT change this function. +APICALL EXPORT std::string PLUGIN_API_VERSION() { + return HYPRLAND_API_VERSION; +} + +UP g_pScrollingLayout; + +// + +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, "[hyprscrolling] Failure in initialization: Version mismatch (headers ver is not equal to running hyprland ver)", + CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); + throw std::runtime_error("[hs] Version mismatch"); + } + + bool success = true; + + g_pScrollingLayout = makeUnique(); + + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:fullscreen_on_one_column", Hyprlang::INT{0}); + HyprlandAPI::addConfigValue(PHANDLE, "plugin:hyprscrolling:column_width", Hyprlang::FLOAT{0.5F}); + HyprlandAPI::addLayout(PHANDLE, "scrolling", g_pScrollingLayout.get()); + + if (success) HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Initialized successfully!", CHyprColor{0.2, 1.0, 0.2, 1.0}, 5000); + else { + HyprlandAPI::addNotification(PHANDLE, "[hyprscrolling] Failure in initialization: failed to register dispatchers", CHyprColor{1.0, 0.2, 0.2, 1.0}, 5000); + throw std::runtime_error("[hs] Dispatchers failed"); + } + + return {"hyprscrolling", "A plugin to add a scrolling layout to hyprland", "Vaxry", "1.0"}; +} + +APICALL EXPORT void PLUGIN_EXIT() { + HyprlandAPI::removeLayout(PHANDLE, g_pScrollingLayout.get()); + g_pScrollingLayout.reset(); +} diff --git a/hyprscrolling/meson.build b/hyprscrolling/meson.build new file mode 100644 index 0000000..e3debe0 --- /dev/null +++ b/hyprscrolling/meson.build @@ -0,0 +1,30 @@ +project('hyprscrolling', 'cpp', + version: '0.1', + default_options: ['buildtype=release'], +) + +cpp_compiler = meson.get_compiler('cpp') +if cpp_compiler.has_argument('-std=c++23') + add_global_arguments('-std=c++23', language: 'cpp') +elif cpp_compiler.has_argument('-std=c++2b') + add_global_arguments('-std=c++2b', language: 'cpp') +else + error('Could not configure current C++ compiler (' + cpp_compiler.get_id() + ' ' + cpp_compiler.version() + ') with required C++ standard (C++23)') +endif + +globber = run_command('find', '.', '-name', '*.cpp', check: true) +src = globber.stdout().strip().split('\n') + +shared_module(meson.project_name(), src, + dependencies: [ + dependency('hyprland'), + dependency('pixman-1'), + dependency('libdrm'), + dependency('pangocairo'), + dependency('libinput'), + dependency('libudev'), + dependency('wayland-server'), + dependency('xkbcommon'), + ], + install: true, +)