layout: rethonk layouts from the ground up (#12890)
Rewrites layouts to be much smaller, and deal with much less annoying BS. Improves the overall architecture, unifies handling of pseudotiling, and various other improvements.
This commit is contained in:
parent
51f8849e54
commit
723870337f
82 changed files with 8431 additions and 5527 deletions
772
src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp
Normal file
772
src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.cpp
Normal file
|
|
@ -0,0 +1,772 @@
|
|||
#include "DwindleAlgorithm.hpp"
|
||||
|
||||
#include "../../Algorithm.hpp"
|
||||
#include "../../../space/Space.hpp"
|
||||
#include "../../../target/WindowTarget.hpp"
|
||||
#include "../../../LayoutManager.hpp"
|
||||
|
||||
#include "../../../../config/ConfigValue.hpp"
|
||||
#include "../../../../desktop/state/FocusState.hpp"
|
||||
#include "../../../../helpers/Monitor.hpp"
|
||||
#include "../../../../Compositor.hpp"
|
||||
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
|
||||
using namespace Layout;
|
||||
using namespace Layout::Tiled;
|
||||
|
||||
struct Layout::Tiled::SDwindleNodeData {
|
||||
WP<SDwindleNodeData> pParent;
|
||||
bool isNode = false;
|
||||
WP<ITarget> pTarget;
|
||||
std::array<WP<SDwindleNodeData>, 2> children = {};
|
||||
WP<SDwindleNodeData> self;
|
||||
bool splitTop = false; // for preserve_split
|
||||
CBox box = {0};
|
||||
float splitRatio = 1.f;
|
||||
bool valid = true;
|
||||
bool ignoreFullscreenChecks = false;
|
||||
|
||||
// For list lookup
|
||||
bool operator==(const SDwindleNodeData& rhs) const {
|
||||
return pTarget.lock() == rhs.pTarget.lock() && box == rhs.box && pParent == rhs.pParent && children[0] == rhs.children[0] && children[1] == rhs.children[1];
|
||||
}
|
||||
|
||||
void recalcSizePosRecursive(bool force = false, bool horizontalOverride = false, bool verticalOverride = false) {
|
||||
if (children[0]) {
|
||||
static auto PSMARTSPLIT = CConfigValue<Hyprlang::INT>("dwindle:smart_split");
|
||||
static auto PPRESERVESPLIT = CConfigValue<Hyprlang::INT>("dwindle:preserve_split");
|
||||
static auto PFLMULT = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
|
||||
|
||||
if (*PPRESERVESPLIT == 0 && *PSMARTSPLIT == 0)
|
||||
splitTop = box.h * *PFLMULT > box.w;
|
||||
|
||||
if (verticalOverride)
|
||||
splitTop = true;
|
||||
else if (horizontalOverride)
|
||||
splitTop = false;
|
||||
|
||||
const auto SPLITSIDE = !splitTop;
|
||||
|
||||
if (SPLITSIDE) {
|
||||
// split left/right
|
||||
const float FIRSTSIZE = box.w / 2.0 * splitRatio;
|
||||
children[0]->box = CBox{box.x, box.y, FIRSTSIZE, box.h}.noNegativeSize();
|
||||
children[1]->box = CBox{box.x + FIRSTSIZE, box.y, box.w - FIRSTSIZE, box.h}.noNegativeSize();
|
||||
} else {
|
||||
// split top/bottom
|
||||
const float FIRSTSIZE = box.h / 2.0 * splitRatio;
|
||||
children[0]->box = CBox{box.x, box.y, box.w, FIRSTSIZE}.noNegativeSize();
|
||||
children[1]->box = CBox{box.x, box.y + FIRSTSIZE, box.w, box.h - FIRSTSIZE}.noNegativeSize();
|
||||
}
|
||||
|
||||
children[0]->recalcSizePosRecursive(force);
|
||||
children[1]->recalcSizePosRecursive(force);
|
||||
} else
|
||||
pTarget->setPositionGlobal(box);
|
||||
}
|
||||
};
|
||||
|
||||
void CDwindleAlgorithm::newTarget(SP<ITarget> target) {
|
||||
addTarget(target);
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::addTarget(SP<ITarget> target, bool newTarget) {
|
||||
const auto WORK_AREA = m_parent->space()->workArea();
|
||||
|
||||
const auto PNODE = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());
|
||||
PNODE->self = PNODE;
|
||||
|
||||
const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
|
||||
const auto PWORKSPACE = m_parent->space()->workspace();
|
||||
|
||||
static auto PUSEACTIVE = CConfigValue<Hyprlang::INT>("dwindle:use_active_for_splits");
|
||||
static auto PDEFAULTSPLIT = CConfigValue<Hyprlang::FLOAT>("dwindle:default_split_ratio");
|
||||
|
||||
// Populate the node with our window's data
|
||||
PNODE->pTarget = target;
|
||||
PNODE->isNode = false;
|
||||
|
||||
SP<SDwindleNodeData> OPENINGON;
|
||||
|
||||
const auto MOUSECOORDS = m_overrideFocalPoint.value_or(g_pInputManager->getMouseCoordsInternal());
|
||||
const auto ACTIVE_MON = Desktop::focusState()->monitor();
|
||||
|
||||
if ((PWORKSPACE == ACTIVE_MON->m_activeWorkspace || (PWORKSPACE->m_isSpecialWorkspace && PMONITOR->m_activeSpecialWorkspace)) && !*PUSEACTIVE) {
|
||||
OPENINGON = getNodeFromWindow(
|
||||
g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS | Desktop::View::SKIP_FULLSCREEN_PRIORITY));
|
||||
|
||||
if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON))
|
||||
OPENINGON = getClosestNode(MOUSECOORDS);
|
||||
|
||||
} else if (*PUSEACTIVE) {
|
||||
if (Desktop::focusState()->window() && !Desktop::focusState()->window()->m_isFloating && Desktop::focusState()->window() != target->window() &&
|
||||
Desktop::focusState()->window()->m_workspace == PWORKSPACE && Desktop::focusState()->window()->m_isMapped) {
|
||||
OPENINGON = getNodeFromWindow(Desktop::focusState()->window());
|
||||
} else {
|
||||
OPENINGON = getNodeFromWindow(g_pCompositor->vectorToWindowUnified(MOUSECOORDS, Desktop::View::RESERVED_EXTENTS | Desktop::View::INPUT_EXTENTS));
|
||||
}
|
||||
|
||||
if (!OPENINGON && g_pCompositor->isPointOnReservedArea(MOUSECOORDS, ACTIVE_MON))
|
||||
OPENINGON = getClosestNode(MOUSECOORDS);
|
||||
|
||||
} else
|
||||
OPENINGON = getFirstNode();
|
||||
|
||||
// first, check if OPENINGON isn't too big.
|
||||
const auto PREDSIZEMAX = OPENINGON ? Vector2D(OPENINGON->box.w, OPENINGON->box.h) : PMONITOR->m_size;
|
||||
if (const auto MAXSIZE = target->maxSize().value_or(Math::VECTOR2D_MAX); MAXSIZE.x < PREDSIZEMAX.x || MAXSIZE.y < PREDSIZEMAX.y) {
|
||||
// we can't continue. make it floating.
|
||||
std::erase(m_dwindleNodesData, PNODE);
|
||||
m_parent->setFloating(target, true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// last fail-safe to avoid duplicate fullscreens
|
||||
if ((!OPENINGON || OPENINGON->pTarget.lock() == target) && getNodes() > 1) {
|
||||
for (auto& node : m_dwindleNodesData) {
|
||||
if (node->pTarget.lock() && node->pTarget.lock() != target) {
|
||||
OPENINGON = node;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if it's the first, it's easy. Make it fullscreen.
|
||||
if (!OPENINGON || OPENINGON->pTarget.lock() == target) {
|
||||
PNODE->box = WORK_AREA;
|
||||
PNODE->pTarget->setPositionGlobal(PNODE->box);
|
||||
return;
|
||||
}
|
||||
|
||||
// get the node under our cursor
|
||||
|
||||
const auto NEWPARENT = m_dwindleNodesData.emplace_back(makeShared<SDwindleNodeData>());
|
||||
|
||||
// make the parent have the OPENINGON's stats
|
||||
NEWPARENT->box = OPENINGON->box;
|
||||
NEWPARENT->pParent = OPENINGON->pParent;
|
||||
NEWPARENT->isNode = true; // it is a node
|
||||
NEWPARENT->splitRatio = std::clamp(*PDEFAULTSPLIT, 0.1F, 1.9F);
|
||||
|
||||
static auto PWIDTHMULTIPLIER = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
|
||||
|
||||
// if cursor over first child, make it first, etc
|
||||
const auto SIDEBYSIDE = NEWPARENT->box.w > NEWPARENT->box.h * *PWIDTHMULTIPLIER;
|
||||
NEWPARENT->splitTop = !SIDEBYSIDE;
|
||||
|
||||
static auto PFORCESPLIT = CConfigValue<Hyprlang::INT>("dwindle:force_split");
|
||||
static auto PERMANENTDIRECTIONOVERRIDE = CConfigValue<Hyprlang::INT>("dwindle:permanent_direction_override");
|
||||
static auto PSMARTSPLIT = CConfigValue<Hyprlang::INT>("dwindle:smart_split");
|
||||
static auto PSPLITBIAS = CConfigValue<Hyprlang::INT>("dwindle:split_bias");
|
||||
|
||||
bool horizontalOverride = false;
|
||||
bool verticalOverride = false;
|
||||
|
||||
// let user select position -> top, right, bottom, left
|
||||
if (m_overrideDirection != Math::DIRECTION_DEFAULT) {
|
||||
|
||||
// this is horizontal
|
||||
if (m_overrideDirection % 2 == 0)
|
||||
verticalOverride = true;
|
||||
else
|
||||
horizontalOverride = true;
|
||||
|
||||
// 0 -> top and left | 1,2 -> right and bottom
|
||||
if (m_overrideDirection % 3 == 0) {
|
||||
NEWPARENT->children[1] = OPENINGON;
|
||||
NEWPARENT->children[0] = PNODE;
|
||||
} else {
|
||||
NEWPARENT->children[0] = OPENINGON;
|
||||
NEWPARENT->children[1] = PNODE;
|
||||
}
|
||||
|
||||
// whether or not the override persists after opening one window
|
||||
if (*PERMANENTDIRECTIONOVERRIDE == 0)
|
||||
m_overrideDirection = Math::DIRECTION_DEFAULT;
|
||||
} else if (*PSMARTSPLIT == 1) {
|
||||
const auto PARENT_CENTER = NEWPARENT->box.pos() + NEWPARENT->box.size() / 2;
|
||||
const auto PARENT_PROPORTIONS = NEWPARENT->box.h / NEWPARENT->box.w;
|
||||
const auto DELTA = MOUSECOORDS - PARENT_CENTER;
|
||||
const auto DELTA_SLOPE = DELTA.y / DELTA.x;
|
||||
|
||||
if (abs(DELTA_SLOPE) < PARENT_PROPORTIONS) {
|
||||
if (DELTA.x > 0) {
|
||||
// right
|
||||
NEWPARENT->splitTop = false;
|
||||
NEWPARENT->children[0] = OPENINGON;
|
||||
NEWPARENT->children[1] = PNODE;
|
||||
} else {
|
||||
// left
|
||||
NEWPARENT->splitTop = false;
|
||||
NEWPARENT->children[0] = PNODE;
|
||||
NEWPARENT->children[1] = OPENINGON;
|
||||
}
|
||||
} else {
|
||||
if (DELTA.y > 0) {
|
||||
// bottom
|
||||
NEWPARENT->splitTop = true;
|
||||
NEWPARENT->children[0] = OPENINGON;
|
||||
NEWPARENT->children[1] = PNODE;
|
||||
} else {
|
||||
// top
|
||||
NEWPARENT->splitTop = true;
|
||||
NEWPARENT->children[0] = PNODE;
|
||||
NEWPARENT->children[1] = OPENINGON;
|
||||
}
|
||||
}
|
||||
} else if (*PFORCESPLIT == 0 || !newTarget) {
|
||||
if ((SIDEBYSIDE &&
|
||||
VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y + NEWPARENT->box.h)) ||
|
||||
(!SIDEBYSIDE &&
|
||||
VECINRECT(MOUSECOORDS, NEWPARENT->box.x, NEWPARENT->box.y / *PWIDTHMULTIPLIER, NEWPARENT->box.x + NEWPARENT->box.w, NEWPARENT->box.y + NEWPARENT->box.h / 2.f))) {
|
||||
// we are hovering over the first node, make PNODE first.
|
||||
NEWPARENT->children[1] = OPENINGON;
|
||||
NEWPARENT->children[0] = PNODE;
|
||||
} else {
|
||||
// we are hovering over the second node, make PNODE second.
|
||||
NEWPARENT->children[0] = OPENINGON;
|
||||
NEWPARENT->children[1] = PNODE;
|
||||
}
|
||||
} else {
|
||||
if (*PFORCESPLIT == 1) {
|
||||
NEWPARENT->children[1] = OPENINGON;
|
||||
NEWPARENT->children[0] = PNODE;
|
||||
} else {
|
||||
NEWPARENT->children[0] = OPENINGON;
|
||||
NEWPARENT->children[1] = PNODE;
|
||||
}
|
||||
}
|
||||
|
||||
// split in favor of a specific window
|
||||
if (*PSPLITBIAS && NEWPARENT->children[0] == PNODE)
|
||||
NEWPARENT->splitRatio = 2.f - NEWPARENT->splitRatio;
|
||||
|
||||
// and update the previous parent if it exists
|
||||
if (OPENINGON->pParent) {
|
||||
if (OPENINGON->pParent->children[0] == OPENINGON) {
|
||||
OPENINGON->pParent->children[0] = NEWPARENT;
|
||||
} else {
|
||||
OPENINGON->pParent->children[1] = NEWPARENT;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the children
|
||||
if (!verticalOverride && (NEWPARENT->box.w * *PWIDTHMULTIPLIER > NEWPARENT->box.h || horizontalOverride)) {
|
||||
// split left/right -> forced
|
||||
OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};
|
||||
PNODE->box = {Vector2D(NEWPARENT->box.x + NEWPARENT->box.w / 2.f, NEWPARENT->box.y), Vector2D(NEWPARENT->box.w / 2.f, NEWPARENT->box.h)};
|
||||
} else {
|
||||
// split top/bottom
|
||||
OPENINGON->box = {NEWPARENT->box.pos(), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};
|
||||
PNODE->box = {Vector2D(NEWPARENT->box.x, NEWPARENT->box.y + NEWPARENT->box.h / 2.f), Vector2D(NEWPARENT->box.w, NEWPARENT->box.h / 2.f)};
|
||||
}
|
||||
|
||||
OPENINGON->pParent = NEWPARENT;
|
||||
PNODE->pParent = NEWPARENT;
|
||||
|
||||
NEWPARENT->recalcSizePosRecursive(false, horizontalOverride, verticalOverride);
|
||||
|
||||
calculateWorkspace();
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
|
||||
m_overrideFocalPoint = focalPoint;
|
||||
addTarget(target, false);
|
||||
m_overrideFocalPoint.reset();
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::removeTarget(SP<ITarget> target) {
|
||||
const auto PNODE = getNodeFromTarget(target);
|
||||
|
||||
if (!PNODE) {
|
||||
Log::logger->log(Log::ERR, "onWindowRemovedTiling node null?");
|
||||
return;
|
||||
}
|
||||
|
||||
if (target->fullscreenMode() != FSMODE_NONE)
|
||||
g_pCompositor->setWindowFullscreenInternal(target->window(), FSMODE_NONE);
|
||||
|
||||
const auto PPARENT = PNODE->pParent;
|
||||
|
||||
if (!PPARENT) {
|
||||
Log::logger->log(Log::DEBUG, "Removing last node (dwindle)");
|
||||
std::erase(m_dwindleNodesData, PNODE);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto PSIBLING = PPARENT->children[0] == PNODE ? PPARENT->children[1] : PPARENT->children[0];
|
||||
|
||||
PSIBLING->pParent = PPARENT->pParent;
|
||||
|
||||
if (PPARENT->pParent != nullptr) {
|
||||
if (PPARENT->pParent->children[0] == PPARENT)
|
||||
PPARENT->pParent->children[0] = PSIBLING;
|
||||
else
|
||||
PPARENT->pParent->children[1] = PSIBLING;
|
||||
}
|
||||
|
||||
PPARENT->valid = false;
|
||||
PNODE->valid = false;
|
||||
|
||||
std::erase(m_dwindleNodesData, PPARENT);
|
||||
std::erase(m_dwindleNodesData, PNODE);
|
||||
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
|
||||
if (!validMapped(target->window()))
|
||||
return;
|
||||
|
||||
const auto PNODE = getNodeFromTarget(target);
|
||||
|
||||
if (!PNODE)
|
||||
return;
|
||||
|
||||
static auto PANIMATE = CConfigValue<Hyprlang::INT>("misc:animate_manual_resizes");
|
||||
static auto PSMARTRESIZING = CConfigValue<Hyprlang::INT>("dwindle:smart_resizing");
|
||||
|
||||
// get some data about our window
|
||||
const auto PMONITOR = m_parent->space()->workspace()->m_monitor;
|
||||
const auto MONITOR_WORKAREA = PMONITOR->logicalBoxMinusReserved();
|
||||
const auto BOX = target->position();
|
||||
const bool DISPLAYLEFT = STICKS(BOX.x, MONITOR_WORKAREA.x);
|
||||
const bool DISPLAYRIGHT = STICKS(BOX.x + BOX.w, MONITOR_WORKAREA.x + MONITOR_WORKAREA.w);
|
||||
const bool DISPLAYTOP = STICKS(BOX.y, MONITOR_WORKAREA.y);
|
||||
const bool DISPLAYBOTTOM = STICKS(BOX.y + BOX.h, MONITOR_WORKAREA.y + MONITOR_WORKAREA.h);
|
||||
|
||||
// construct allowed movement
|
||||
Vector2D allowedMovement = Δ;
|
||||
if (DISPLAYLEFT && DISPLAYRIGHT)
|
||||
allowedMovement.x = 0;
|
||||
|
||||
if (DISPLAYBOTTOM && DISPLAYTOP)
|
||||
allowedMovement.y = 0;
|
||||
|
||||
if (*PSMARTRESIZING == 1) {
|
||||
// Identify inner and outer nodes for both directions
|
||||
SP<SDwindleNodeData> PVOUTER = nullptr;
|
||||
SP<SDwindleNodeData> PVINNER = nullptr;
|
||||
SP<SDwindleNodeData> PHOUTER = nullptr;
|
||||
SP<SDwindleNodeData> PHINNER = nullptr;
|
||||
|
||||
const auto LEFT = corner == CORNER_TOPLEFT || corner == CORNER_BOTTOMLEFT || DISPLAYRIGHT;
|
||||
const auto TOP = corner == CORNER_TOPLEFT || corner == CORNER_TOPRIGHT || DISPLAYBOTTOM;
|
||||
const auto RIGHT = corner == CORNER_TOPRIGHT || corner == CORNER_BOTTOMRIGHT || DISPLAYLEFT;
|
||||
const auto BOTTOM = corner == CORNER_BOTTOMLEFT || corner == CORNER_BOTTOMRIGHT || DISPLAYTOP;
|
||||
const auto NONE = corner == CORNER_NONE;
|
||||
|
||||
for (auto PCURRENT = PNODE; PCURRENT && PCURRENT->pParent; PCURRENT = PCURRENT->pParent.lock()) {
|
||||
const auto PPARENT = PCURRENT->pParent;
|
||||
|
||||
if (!PVOUTER && PPARENT->splitTop && (NONE || (TOP && PPARENT->children[1] == PCURRENT) || (BOTTOM && PPARENT->children[0] == PCURRENT)))
|
||||
PVOUTER = PCURRENT;
|
||||
else if (!PVOUTER && !PVINNER && PPARENT->splitTop)
|
||||
PVINNER = PCURRENT;
|
||||
else if (!PHOUTER && !PPARENT->splitTop && (NONE || (LEFT && PPARENT->children[1] == PCURRENT) || (RIGHT && PPARENT->children[0] == PCURRENT)))
|
||||
PHOUTER = PCURRENT;
|
||||
else if (!PHOUTER && !PHINNER && !PPARENT->splitTop)
|
||||
PHINNER = PCURRENT;
|
||||
|
||||
if (PVOUTER && PHOUTER)
|
||||
break;
|
||||
}
|
||||
|
||||
if (PHOUTER) {
|
||||
PHOUTER->pParent->splitRatio = std::clamp(PHOUTER->pParent->splitRatio + allowedMovement.x * 2.f / PHOUTER->pParent->box.w, 0.1, 1.9);
|
||||
|
||||
if (PHINNER) {
|
||||
const auto ORIGINAL = PHINNER->box.w;
|
||||
PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
if (PHINNER->pParent->children[0] == PHINNER)
|
||||
PHINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);
|
||||
else
|
||||
PHINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.x) / PHINNER->pParent->box.w * 2.f, 0.1, 1.9);
|
||||
PHINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
} else
|
||||
PHOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
}
|
||||
|
||||
if (PVOUTER) {
|
||||
PVOUTER->pParent->splitRatio = std::clamp(PVOUTER->pParent->splitRatio + allowedMovement.y * 2.f / PVOUTER->pParent->box.h, 0.1, 1.9);
|
||||
|
||||
if (PVINNER) {
|
||||
const auto ORIGINAL = PVINNER->box.h;
|
||||
PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
if (PVINNER->pParent->children[0] == PVINNER)
|
||||
PVINNER->pParent->splitRatio = std::clamp((ORIGINAL - allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);
|
||||
else
|
||||
PVINNER->pParent->splitRatio = std::clamp(2 - (ORIGINAL + allowedMovement.y) / PVINNER->pParent->box.h * 2.f, 0.1, 1.9);
|
||||
PVINNER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
} else
|
||||
PVOUTER->pParent->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
}
|
||||
} else {
|
||||
// get the correct containers to apply splitratio to
|
||||
const auto PPARENT = PNODE->pParent;
|
||||
|
||||
if (!PPARENT)
|
||||
return; // the only window on a workspace, ignore
|
||||
|
||||
const bool PARENTSIDEBYSIDE = !PPARENT->splitTop;
|
||||
|
||||
// Get the parent's parent
|
||||
auto PPARENT2 = PPARENT->pParent;
|
||||
|
||||
Hyprutils::Utils::CScopeGuard x([target, this] {
|
||||
// snap all windows, don't animate resizes if they are manual
|
||||
if (target == g_layoutManager->dragController()->target()) {
|
||||
for (const auto& w : m_dwindleNodesData) {
|
||||
if (w->isNode)
|
||||
continue;
|
||||
|
||||
w->pTarget->warpPositionSize();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// No parent means we have only 2 windows, and thus one axis of freedom
|
||||
if (!PPARENT2) {
|
||||
if (PARENTSIDEBYSIDE) {
|
||||
allowedMovement.x *= 2.f / PPARENT->box.w;
|
||||
PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);
|
||||
PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
} else {
|
||||
allowedMovement.y *= 2.f / PPARENT->box.h;
|
||||
PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);
|
||||
PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Get first parent with other split
|
||||
while (PPARENT2 && PPARENT2->splitTop == !PARENTSIDEBYSIDE)
|
||||
PPARENT2 = PPARENT2->pParent;
|
||||
|
||||
// no parent, one axis of freedom
|
||||
if (!PPARENT2) {
|
||||
if (PARENTSIDEBYSIDE) {
|
||||
allowedMovement.x *= 2.f / PPARENT->box.w;
|
||||
PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.x, 0.1, 1.9);
|
||||
PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
} else {
|
||||
allowedMovement.y *= 2.f / PPARENT->box.h;
|
||||
PPARENT->splitRatio = std::clamp(PPARENT->splitRatio + allowedMovement.y, 0.1, 1.9);
|
||||
PPARENT->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 2 axes of freedom
|
||||
const auto SIDECONTAINER = PARENTSIDEBYSIDE ? PPARENT : PPARENT2;
|
||||
const auto TOPCONTAINER = PARENTSIDEBYSIDE ? PPARENT2 : PPARENT;
|
||||
|
||||
allowedMovement.x *= 2.f / SIDECONTAINER->box.w;
|
||||
allowedMovement.y *= 2.f / TOPCONTAINER->box.h;
|
||||
|
||||
SIDECONTAINER->splitRatio = std::clamp(SIDECONTAINER->splitRatio + allowedMovement.x, 0.1, 1.9);
|
||||
TOPCONTAINER->splitRatio = std::clamp(TOPCONTAINER->splitRatio + allowedMovement.y, 0.1, 1.9);
|
||||
SIDECONTAINER->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
TOPCONTAINER->recalcSizePosRecursive(*PANIMATE == 0);
|
||||
}
|
||||
|
||||
// snap all windows, don't animate resizes if they are manual
|
||||
if (target == g_layoutManager->dragController()->target()) {
|
||||
for (const auto& w : m_dwindleNodesData) {
|
||||
if (w->isNode)
|
||||
continue;
|
||||
|
||||
w->pTarget->warpPositionSize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SP<ITarget> CDwindleAlgorithm::getNextCandidate(SP<ITarget> old) {
|
||||
const auto MIDDLE = old->position().middle();
|
||||
|
||||
if (const auto NODE = getClosestNode(MIDDLE); NODE)
|
||||
return NODE->pTarget.lock();
|
||||
|
||||
if (const auto NODE = getFirstNode(); NODE)
|
||||
return NODE->pTarget.lock();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
|
||||
auto nodeA = getNodeFromTarget(a);
|
||||
auto nodeB = getNodeFromTarget(b);
|
||||
|
||||
if (nodeA)
|
||||
nodeA->pTarget = b;
|
||||
if (nodeB)
|
||||
nodeB->pTarget = a;
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::recalculate() {
|
||||
calculateWorkspace();
|
||||
}
|
||||
|
||||
std::optional<Vector2D> CDwindleAlgorithm::predictSizeForNewTarget() {
|
||||
// get window candidate
|
||||
PHLWINDOW candidate = Desktop::focusState()->window();
|
||||
|
||||
if (!candidate || candidate->m_workspace != m_parent->space()->workspace())
|
||||
candidate = m_parent->space()->workspace()->getFirstWindow();
|
||||
|
||||
// create a fake node
|
||||
SDwindleNodeData node;
|
||||
|
||||
if (!candidate)
|
||||
return Desktop::focusState()->monitor()->m_size;
|
||||
else {
|
||||
const auto PNODE = getNodeFromWindow(candidate);
|
||||
|
||||
if (!PNODE)
|
||||
return {};
|
||||
|
||||
node = *PNODE;
|
||||
node.pTarget.reset();
|
||||
|
||||
CBox box = PNODE->box;
|
||||
|
||||
static auto PFLMULT = CConfigValue<Hyprlang::FLOAT>("dwindle:split_width_multiplier");
|
||||
|
||||
bool splitTop = box.h * *PFLMULT > box.w;
|
||||
|
||||
const auto SPLITSIDE = !splitTop;
|
||||
|
||||
if (SPLITSIDE)
|
||||
node.box = {{}, {box.w / 2.0, box.h}};
|
||||
else
|
||||
node.box = {{}, {box.w, box.h / 2.0}};
|
||||
|
||||
// TODO: make this better and more accurate
|
||||
|
||||
return node.box.size();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
|
||||
const auto PNODE = getNodeFromTarget(t);
|
||||
const Vector2D originalPos = t->position().middle();
|
||||
|
||||
if (!PNODE || !t->window())
|
||||
return;
|
||||
|
||||
Vector2D focalPoint;
|
||||
|
||||
const auto WINDOWIDEALBB =
|
||||
t->fullscreenMode() != FSMODE_NONE ? m_parent->space()->workspace()->m_monitor->logicalBox() : t->window()->getWindowIdealBoundingBoxIgnoreReserved();
|
||||
|
||||
switch (dir) {
|
||||
case Math::DIRECTION_UP: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, -1.0}; break;
|
||||
case Math::DIRECTION_DOWN: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x / 2.0, WINDOWIDEALBB.size().y + 1.0}; break;
|
||||
case Math::DIRECTION_LEFT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{-1.0, WINDOWIDEALBB.size().y / 2.0}; break;
|
||||
case Math::DIRECTION_RIGHT: focalPoint = WINDOWIDEALBB.pos() + Vector2D{WINDOWIDEALBB.size().x + 1.0, WINDOWIDEALBB.size().y / 2.0}; break;
|
||||
default: return;
|
||||
}
|
||||
|
||||
t->window()->setAnimationsToMove();
|
||||
|
||||
removeTarget(t);
|
||||
|
||||
const auto PMONITORFOCAL = g_pCompositor->getMonitorFromVector(focalPoint);
|
||||
|
||||
if (PMONITORFOCAL != m_parent->space()->workspace()->m_monitor) {
|
||||
// move with a focal point
|
||||
|
||||
if (PMONITORFOCAL->m_activeWorkspace)
|
||||
t->assignToSpace(PMONITORFOCAL->m_activeWorkspace->m_space);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
movedTarget(t, focalPoint);
|
||||
|
||||
// restore focus to the previous position
|
||||
if (silent) {
|
||||
const auto PNODETOFOCUS = getClosestNode(originalPos);
|
||||
if (PNODETOFOCUS && PNODETOFOCUS->pTarget)
|
||||
Desktop::focusState()->fullWindowFocus(PNODETOFOCUS->pTarget->window(), Desktop::FOCUS_REASON_KEYBIND);
|
||||
}
|
||||
}
|
||||
|
||||
// --------- internal --------- //
|
||||
|
||||
void CDwindleAlgorithm::calculateWorkspace() {
|
||||
const auto PWORKSPACE = m_parent->space()->workspace();
|
||||
const auto PMONITOR = PWORKSPACE->m_monitor;
|
||||
|
||||
if (!PMONITOR || PWORKSPACE->m_hasFullscreenWindow)
|
||||
return;
|
||||
|
||||
const auto TOPNODE = getMasterNode();
|
||||
|
||||
if (TOPNODE) {
|
||||
TOPNODE->box = m_parent->space()->workArea();
|
||||
TOPNODE->recalcSizePosRecursive();
|
||||
}
|
||||
}
|
||||
|
||||
SP<SDwindleNodeData> CDwindleAlgorithm::getNodeFromTarget(SP<ITarget> t) {
|
||||
for (const auto& n : m_dwindleNodesData) {
|
||||
if (n->pTarget == t)
|
||||
return n;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SP<SDwindleNodeData> CDwindleAlgorithm::getNodeFromWindow(PHLWINDOW w) {
|
||||
return w ? getNodeFromTarget(w->layoutTarget()) : nullptr;
|
||||
}
|
||||
|
||||
int CDwindleAlgorithm::getNodes() {
|
||||
return m_dwindleNodesData.size();
|
||||
}
|
||||
|
||||
SP<SDwindleNodeData> CDwindleAlgorithm::getFirstNode() {
|
||||
return m_dwindleNodesData.empty() ? nullptr : m_dwindleNodesData.at(0);
|
||||
}
|
||||
|
||||
SP<SDwindleNodeData> CDwindleAlgorithm::getClosestNode(const Vector2D& point) {
|
||||
SP<SDwindleNodeData> res = nullptr;
|
||||
double distClosest = -1;
|
||||
for (auto& n : m_dwindleNodesData) {
|
||||
if (n->pTarget && Desktop::View::validMapped(n->pTarget->window())) {
|
||||
auto distAnother = vecToRectDistanceSquared(point, n->box.pos(), n->box.pos() + n->box.size());
|
||||
if (!res || distAnother < distClosest) {
|
||||
res = n;
|
||||
distClosest = distAnother;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
SP<SDwindleNodeData> CDwindleAlgorithm::getMasterNode() {
|
||||
for (auto& n : m_dwindleNodesData) {
|
||||
if (!n->pParent)
|
||||
return n;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::expected<void, std::string> CDwindleAlgorithm::layoutMsg(const std::string_view& sv) {
|
||||
const auto ARGS = CVarList2(std::string{sv}, 0, ' ');
|
||||
|
||||
const auto CURRENT_NODE = getNodeFromWindow(Desktop::focusState()->window());
|
||||
|
||||
if (ARGS[0] == "togglesplit") {
|
||||
if (CURRENT_NODE)
|
||||
toggleSplit(CURRENT_NODE);
|
||||
} else if (ARGS[0] == "swapsplit") {
|
||||
if (CURRENT_NODE)
|
||||
swapSplit(CURRENT_NODE);
|
||||
} else if (ARGS[0] == "movetoroot") {
|
||||
auto node = CURRENT_NODE;
|
||||
if (!ARGS[1].empty()) {
|
||||
auto w = g_pCompositor->getWindowByRegex(std::string{ARGS[1]});
|
||||
if (w)
|
||||
node = getNodeFromWindow(w);
|
||||
}
|
||||
|
||||
const auto STABLE = ARGS[2].empty() || ARGS[2] != "unstable";
|
||||
moveToRoot(node, STABLE);
|
||||
} else if (ARGS[0] == "preselect") {
|
||||
auto direction = ARGS[1];
|
||||
|
||||
if (direction.empty()) {
|
||||
Log::logger->log(Log::ERR, "Expected direction for preselect");
|
||||
return std::unexpected("No direction for preselect");
|
||||
}
|
||||
|
||||
switch (direction.front()) {
|
||||
case 'u':
|
||||
case 't': {
|
||||
m_overrideDirection = Math::DIRECTION_UP;
|
||||
break;
|
||||
}
|
||||
case 'd':
|
||||
case 'b': {
|
||||
m_overrideDirection = Math::DIRECTION_DOWN;
|
||||
break;
|
||||
}
|
||||
case 'r': {
|
||||
m_overrideDirection = Math::DIRECTION_RIGHT;
|
||||
break;
|
||||
}
|
||||
case 'l': {
|
||||
m_overrideDirection = Math::DIRECTION_LEFT;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// any other character resets the focus direction
|
||||
// needed for the persistent mode
|
||||
m_overrideDirection = Math::DIRECTION_DEFAULT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::toggleSplit(SP<SDwindleNodeData> x) {
|
||||
if (!x || !x->pParent)
|
||||
return;
|
||||
|
||||
if (x->pTarget->fullscreenMode() != FSMODE_NONE)
|
||||
return;
|
||||
|
||||
x->pParent->splitTop = !x->pParent->splitTop;
|
||||
|
||||
x->pParent->recalcSizePosRecursive();
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::swapSplit(SP<SDwindleNodeData> x) {
|
||||
if (x->pTarget->fullscreenMode() != FSMODE_NONE)
|
||||
return;
|
||||
|
||||
std::swap(x->pParent->children[0], x->pParent->children[1]);
|
||||
|
||||
x->pParent->recalcSizePosRecursive();
|
||||
}
|
||||
|
||||
void CDwindleAlgorithm::moveToRoot(SP<SDwindleNodeData> x, bool stable) {
|
||||
if (!x || !x->pParent)
|
||||
return;
|
||||
|
||||
if (x->pTarget->fullscreenMode() != FSMODE_NONE)
|
||||
return;
|
||||
|
||||
// already at root
|
||||
if (!x->pParent->pParent)
|
||||
return;
|
||||
|
||||
auto& pNode = x->pParent->children[0] == x ? x->pParent->children[0] : x->pParent->children[1];
|
||||
|
||||
// instead of [getMasterNodeOnWorkspace], we walk back to root since we need
|
||||
// to know which children of root is our ancestor
|
||||
auto pAncestor = x, pRoot = x->pParent.lock();
|
||||
while (pRoot->pParent) {
|
||||
pAncestor = pRoot;
|
||||
pRoot = pRoot->pParent.lock();
|
||||
}
|
||||
|
||||
auto& pSwap = pRoot->children[0] == pAncestor ? pRoot->children[1] : pRoot->children[0];
|
||||
std::swap(pNode, pSwap);
|
||||
std::swap(pNode->pParent, pSwap->pParent);
|
||||
|
||||
// [stable] in that the focused window occupies same side of screen
|
||||
if (stable)
|
||||
std::swap(pRoot->children[0], pRoot->children[1]);
|
||||
|
||||
pRoot->recalcSizePosRecursive();
|
||||
}
|
||||
57
src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp
Normal file
57
src/layout/algorithm/tiled/dwindle/DwindleAlgorithm.hpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
#include "../../TiledAlgorithm.hpp"
|
||||
|
||||
namespace Layout {
|
||||
class CAlgorithm;
|
||||
}
|
||||
|
||||
namespace Layout::Tiled {
|
||||
struct SDwindleNodeData;
|
||||
|
||||
class CDwindleAlgorithm : public ITiledAlgorithm {
|
||||
public:
|
||||
CDwindleAlgorithm() = default;
|
||||
virtual ~CDwindleAlgorithm() = default;
|
||||
|
||||
virtual void newTarget(SP<ITarget> target);
|
||||
virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
|
||||
virtual void removeTarget(SP<ITarget> target);
|
||||
|
||||
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
virtual void recalculate();
|
||||
|
||||
virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
|
||||
|
||||
virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
|
||||
virtual std::optional<Vector2D> predictSizeForNewTarget();
|
||||
|
||||
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
|
||||
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
|
||||
|
||||
private:
|
||||
std::vector<SP<SDwindleNodeData>> m_dwindleNodesData;
|
||||
|
||||
struct {
|
||||
bool started = false;
|
||||
bool pseudo = false;
|
||||
bool xExtent = false;
|
||||
bool yExtent = false;
|
||||
} m_pseudoDragFlags;
|
||||
|
||||
std::optional<Vector2D> m_overrideFocalPoint; // for onWindowCreatedTiling.
|
||||
|
||||
void addTarget(SP<ITarget> target, bool newTarget = true);
|
||||
void calculateWorkspace();
|
||||
SP<SDwindleNodeData> getNodeFromTarget(SP<ITarget>);
|
||||
SP<SDwindleNodeData> getNodeFromWindow(PHLWINDOW w);
|
||||
int getNodes();
|
||||
SP<SDwindleNodeData> getFirstNode();
|
||||
SP<SDwindleNodeData> getClosestNode(const Vector2D&);
|
||||
SP<SDwindleNodeData> getMasterNode();
|
||||
|
||||
void toggleSplit(SP<SDwindleNodeData>);
|
||||
void swapSplit(SP<SDwindleNodeData>);
|
||||
void moveToRoot(SP<SDwindleNodeData>, bool stable = true);
|
||||
|
||||
Math::eDirection m_overrideDirection = Math::DIRECTION_DEFAULT;
|
||||
};
|
||||
};
|
||||
1292
src/layout/algorithm/tiled/master/MasterAlgorithm.cpp
Normal file
1292
src/layout/algorithm/tiled/master/MasterAlgorithm.cpp
Normal file
File diff suppressed because it is too large
Load diff
75
src/layout/algorithm/tiled/master/MasterAlgorithm.hpp
Normal file
75
src/layout/algorithm/tiled/master/MasterAlgorithm.hpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
#include "../../TiledAlgorithm.hpp"
|
||||
|
||||
#include <hyprutils/string/VarList2.hpp>
|
||||
|
||||
namespace Layout {
|
||||
class CAlgorithm;
|
||||
}
|
||||
|
||||
namespace Layout::Tiled {
|
||||
struct SMasterNodeData;
|
||||
|
||||
//orientation determines which side of the screen the master area resides
|
||||
enum eOrientation : uint8_t {
|
||||
ORIENTATION_LEFT = 0,
|
||||
ORIENTATION_TOP,
|
||||
ORIENTATION_RIGHT,
|
||||
ORIENTATION_BOTTOM,
|
||||
ORIENTATION_CENTER
|
||||
};
|
||||
|
||||
struct SMasterWorkspaceData {
|
||||
WORKSPACEID workspaceID = WORKSPACE_INVALID;
|
||||
eOrientation orientation = ORIENTATION_LEFT;
|
||||
// Previously focused non-master window when `focusmaster previous` command was issued
|
||||
WP<ITarget> focusMasterPrev;
|
||||
|
||||
//
|
||||
bool operator==(const SMasterWorkspaceData& rhs) const {
|
||||
return workspaceID == rhs.workspaceID;
|
||||
}
|
||||
};
|
||||
|
||||
class CMasterAlgorithm : public ITiledAlgorithm {
|
||||
public:
|
||||
CMasterAlgorithm() = default;
|
||||
virtual ~CMasterAlgorithm() = default;
|
||||
|
||||
virtual void newTarget(SP<ITarget> target);
|
||||
virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
|
||||
virtual void removeTarget(SP<ITarget> target);
|
||||
|
||||
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
virtual void recalculate();
|
||||
|
||||
virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
|
||||
|
||||
virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
|
||||
virtual std::optional<Vector2D> predictSizeForNewTarget();
|
||||
|
||||
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
|
||||
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
|
||||
|
||||
private:
|
||||
std::vector<SP<SMasterNodeData>> m_masterNodesData;
|
||||
SMasterWorkspaceData m_workspaceData;
|
||||
|
||||
void addTarget(SP<ITarget> target, bool firstMap);
|
||||
|
||||
bool m_forceWarps = false;
|
||||
|
||||
void buildOrientationCycleVectorFromVars(std::vector<eOrientation>& cycle, Hyprutils::String::CVarList2* vars);
|
||||
void buildOrientationCycleVectorFromEOperation(std::vector<eOrientation>& cycle);
|
||||
void runOrientationCycle(Hyprutils::String::CVarList2* vars, int next);
|
||||
eOrientation getDynamicOrientation();
|
||||
int getNodesNo();
|
||||
SP<SMasterNodeData> getNodeFromWindow(PHLWINDOW);
|
||||
SP<SMasterNodeData> getNodeFromTarget(SP<ITarget>);
|
||||
SP<SMasterNodeData> getMasterNode();
|
||||
SP<SMasterNodeData> getClosestNode(const Vector2D&);
|
||||
void calculateWorkspace();
|
||||
SP<ITarget> getNextTarget(SP<ITarget>, bool, bool);
|
||||
int getMastersNo();
|
||||
bool isWindowTiled(PHLWINDOW);
|
||||
};
|
||||
};
|
||||
274
src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp
Normal file
274
src/layout/algorithm/tiled/monocle/MonocleAlgorithm.cpp
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
#include "MonocleAlgorithm.hpp"
|
||||
|
||||
#include "../../Algorithm.hpp"
|
||||
#include "../../../space/Space.hpp"
|
||||
#include "../../../target/WindowTarget.hpp"
|
||||
#include "../../../LayoutManager.hpp"
|
||||
|
||||
#include "../../../../config/ConfigValue.hpp"
|
||||
#include "../../../../desktop/state/FocusState.hpp"
|
||||
#include "../../../../desktop/history/WindowHistoryTracker.hpp"
|
||||
#include "../../../../helpers/Monitor.hpp"
|
||||
#include "../../../../Compositor.hpp"
|
||||
|
||||
#include <hyprutils/string/VarList2.hpp>
|
||||
#include <hyprutils/string/ConstVarList.hpp>
|
||||
#include <hyprutils/utils/ScopeGuard.hpp>
|
||||
|
||||
using namespace Hyprutils::String;
|
||||
using namespace Hyprutils::Utils;
|
||||
using namespace Layout;
|
||||
using namespace Layout::Tiled;
|
||||
|
||||
CMonocleAlgorithm::CMonocleAlgorithm() {
|
||||
// hook into focus changes to bring focused window to front
|
||||
m_focusCallback = g_pHookSystem->hookDynamic("activeWindow", [this](void* hk, SCallbackInfo& info, std::any param) {
|
||||
const auto PWINDOW = std::any_cast<Desktop::View::SWindowActiveEvent>(param).window;
|
||||
|
||||
if (!PWINDOW)
|
||||
return;
|
||||
|
||||
if (!PWINDOW->m_workspace->isVisible())
|
||||
return;
|
||||
|
||||
const auto TARGET = PWINDOW->layoutTarget();
|
||||
if (!TARGET)
|
||||
return;
|
||||
|
||||
focusTargetUpdate(TARGET);
|
||||
});
|
||||
}
|
||||
|
||||
CMonocleAlgorithm::~CMonocleAlgorithm() {
|
||||
// unhide all windows before destruction
|
||||
for (const auto& data : m_targetDatas) {
|
||||
const auto TARGET = data->target.lock();
|
||||
if (!TARGET)
|
||||
continue;
|
||||
|
||||
const auto WINDOW = TARGET->window();
|
||||
if (WINDOW)
|
||||
WINDOW->setHidden(false);
|
||||
}
|
||||
|
||||
m_focusCallback.reset();
|
||||
}
|
||||
|
||||
SP<SMonocleTargetData> CMonocleAlgorithm::dataFor(SP<ITarget> t) {
|
||||
for (auto& data : m_targetDatas) {
|
||||
if (data->target.lock() == t)
|
||||
return data;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::newTarget(SP<ITarget> target) {
|
||||
const auto DATA = m_targetDatas.emplace_back(makeShared<SMonocleTargetData>(target));
|
||||
|
||||
m_currentVisibleIndex = m_targetDatas.size() - 1;
|
||||
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint) {
|
||||
newTarget(target);
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::removeTarget(SP<ITarget> target) {
|
||||
auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; });
|
||||
|
||||
if (it == m_targetDatas.end())
|
||||
return;
|
||||
|
||||
// unhide window when removing from monocle layout
|
||||
const auto WINDOW = target->window();
|
||||
if (WINDOW)
|
||||
WINDOW->setHidden(false);
|
||||
|
||||
const auto INDEX = std::distance(m_targetDatas.begin(), it);
|
||||
m_targetDatas.erase(it);
|
||||
|
||||
if (m_targetDatas.empty()) {
|
||||
m_currentVisibleIndex = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// try to use the last window in history if we can
|
||||
for (const auto& historyWindow : Desktop::History::windowTracker()->historyForWorkspace(m_parent->space()->workspace()) | std::views::reverse) {
|
||||
auto it = std::ranges::find_if(m_targetDatas, [&historyWindow](const auto& d) { return d->target == historyWindow->layoutTarget(); });
|
||||
|
||||
if (it == m_targetDatas.end())
|
||||
continue;
|
||||
|
||||
// we found a historical target, use that first
|
||||
m_currentVisibleIndex = std::distance(m_targetDatas.begin(), it);
|
||||
|
||||
recalculate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if we didn't find history, fall back to last
|
||||
|
||||
if (m_currentVisibleIndex >= (int)m_targetDatas.size())
|
||||
m_currentVisibleIndex = m_targetDatas.size() - 1;
|
||||
else if (INDEX <= m_currentVisibleIndex && m_currentVisibleIndex > 0)
|
||||
m_currentVisibleIndex--;
|
||||
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner) {
|
||||
// monocle layout doesn't support manual resizing, all windows are fullscreen
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::recalculate() {
|
||||
if (m_targetDatas.empty())
|
||||
return;
|
||||
|
||||
const auto WORK_AREA = m_parent->space()->workArea();
|
||||
|
||||
for (size_t i = 0; i < m_targetDatas.size(); ++i) {
|
||||
const auto& DATA = m_targetDatas[i];
|
||||
const auto TARGET = DATA->target.lock();
|
||||
|
||||
if (!TARGET)
|
||||
continue;
|
||||
|
||||
const auto WINDOW = TARGET->window();
|
||||
if (!WINDOW)
|
||||
continue;
|
||||
|
||||
DATA->layoutBox = WORK_AREA;
|
||||
TARGET->setPositionGlobal(WORK_AREA);
|
||||
|
||||
const bool SHOULD_BE_VISIBLE = ((int)i == m_currentVisibleIndex);
|
||||
WINDOW->setHidden(!SHOULD_BE_VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
SP<ITarget> CMonocleAlgorithm::getNextCandidate(SP<ITarget> old) {
|
||||
if (m_targetDatas.empty())
|
||||
return nullptr;
|
||||
|
||||
auto it = std::ranges::find_if(m_targetDatas, [old](const auto& data) { return data->target.lock() == old; });
|
||||
|
||||
if (it == m_targetDatas.end()) {
|
||||
if (m_currentVisibleIndex >= 0 && m_currentVisibleIndex < (int)m_targetDatas.size())
|
||||
return m_targetDatas[m_currentVisibleIndex]->target.lock();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto next = std::next(it);
|
||||
if (next == m_targetDatas.end())
|
||||
next = m_targetDatas.begin();
|
||||
|
||||
return next->get()->target.lock();
|
||||
}
|
||||
|
||||
std::expected<void, std::string> CMonocleAlgorithm::layoutMsg(const std::string_view& sv) {
|
||||
CVarList2 vars(std::string{sv}, 0, 's');
|
||||
|
||||
if (vars.size() < 1)
|
||||
return std::unexpected("layoutmsg requires at least 1 argument");
|
||||
|
||||
const auto COMMAND = vars[0];
|
||||
|
||||
if (COMMAND == "cyclenext") {
|
||||
cycleNext();
|
||||
return {};
|
||||
} else if (COMMAND == "cycleprev") {
|
||||
cyclePrev();
|
||||
return {};
|
||||
}
|
||||
|
||||
return std::unexpected(std::format("Unknown monocle layoutmsg: {}", COMMAND));
|
||||
}
|
||||
|
||||
std::optional<Vector2D> CMonocleAlgorithm::predictSizeForNewTarget() {
|
||||
const auto WORK_AREA = m_parent->space()->workArea();
|
||||
return WORK_AREA.size();
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::swapTargets(SP<ITarget> a, SP<ITarget> b) {
|
||||
auto nodeA = dataFor(a);
|
||||
auto nodeB = dataFor(b);
|
||||
|
||||
if (nodeA)
|
||||
nodeA->target = b;
|
||||
if (nodeB)
|
||||
nodeB->target = a;
|
||||
|
||||
recalculate();
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent) {
|
||||
// try to find a monitor in the specified direction, thats the logical thing
|
||||
if (!t || !t->space() || !t->space()->workspace())
|
||||
return;
|
||||
|
||||
const auto PMONINDIR = g_pCompositor->getMonitorInDirection(t->space()->workspace()->m_monitor.lock(), dir);
|
||||
|
||||
// if we found a monitor, move the window there
|
||||
if (PMONINDIR && PMONINDIR != t->space()->workspace()->m_monitor.lock()) {
|
||||
const auto TARGETWS = PMONINDIR->m_activeWorkspace;
|
||||
|
||||
if (t->window())
|
||||
t->window()->setAnimationsToMove();
|
||||
|
||||
t->assignToSpace(TARGETWS->m_space);
|
||||
}
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::cycleNext() {
|
||||
if (m_targetDatas.empty())
|
||||
return;
|
||||
|
||||
m_currentVisibleIndex = (m_currentVisibleIndex + 1) % m_targetDatas.size();
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::cyclePrev() {
|
||||
if (m_targetDatas.empty())
|
||||
return;
|
||||
|
||||
m_currentVisibleIndex--;
|
||||
if (m_currentVisibleIndex < 0)
|
||||
m_currentVisibleIndex = m_targetDatas.size() - 1;
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::focusTargetUpdate(SP<ITarget> target) {
|
||||
auto it = std::ranges::find_if(m_targetDatas, [target](const auto& data) { return data->target.lock() == target; });
|
||||
|
||||
if (it == m_targetDatas.end())
|
||||
return;
|
||||
|
||||
const auto NEW_INDEX = std::distance(m_targetDatas.begin(), it);
|
||||
|
||||
if (m_currentVisibleIndex != NEW_INDEX) {
|
||||
m_currentVisibleIndex = NEW_INDEX;
|
||||
updateVisible();
|
||||
}
|
||||
}
|
||||
|
||||
void CMonocleAlgorithm::updateVisible() {
|
||||
recalculate();
|
||||
|
||||
const auto VISIBLE_TARGET = getVisibleTarget();
|
||||
if (!VISIBLE_TARGET)
|
||||
return;
|
||||
|
||||
const auto WINDOW = VISIBLE_TARGET->window();
|
||||
if (!WINDOW)
|
||||
return;
|
||||
|
||||
Desktop::focusState()->fullWindowFocus(WINDOW, Desktop::FOCUS_REASON_DESKTOP_STATE_CHANGE);
|
||||
}
|
||||
|
||||
SP<ITarget> CMonocleAlgorithm::getVisibleTarget() {
|
||||
if (m_currentVisibleIndex < 0 || m_currentVisibleIndex >= (int)m_targetDatas.size())
|
||||
return nullptr;
|
||||
|
||||
return m_targetDatas[m_currentVisibleIndex]->target.lock();
|
||||
}
|
||||
52
src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp
Normal file
52
src/layout/algorithm/tiled/monocle/MonocleAlgorithm.hpp
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../TiledAlgorithm.hpp"
|
||||
#include "../../../../managers/HookSystemManager.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Layout::Tiled {
|
||||
|
||||
struct SMonocleTargetData {
|
||||
SMonocleTargetData(SP<ITarget> t) : target(t) {
|
||||
;
|
||||
}
|
||||
|
||||
WP<ITarget> target;
|
||||
CBox layoutBox;
|
||||
};
|
||||
|
||||
class CMonocleAlgorithm : public ITiledAlgorithm {
|
||||
public:
|
||||
CMonocleAlgorithm();
|
||||
virtual ~CMonocleAlgorithm();
|
||||
|
||||
virtual void newTarget(SP<ITarget> target);
|
||||
virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
|
||||
virtual void removeTarget(SP<ITarget> target);
|
||||
|
||||
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
virtual void recalculate();
|
||||
|
||||
virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
|
||||
|
||||
virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
|
||||
virtual std::optional<Vector2D> predictSizeForNewTarget();
|
||||
|
||||
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
|
||||
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
|
||||
|
||||
private:
|
||||
std::vector<SP<SMonocleTargetData>> m_targetDatas;
|
||||
SP<HOOK_CALLBACK_FN> m_focusCallback;
|
||||
|
||||
int m_currentVisibleIndex = 0;
|
||||
|
||||
SP<SMonocleTargetData> dataFor(SP<ITarget> t);
|
||||
void cycleNext();
|
||||
void cyclePrev();
|
||||
void focusTargetUpdate(SP<ITarget> target);
|
||||
void updateVisible();
|
||||
SP<ITarget> getVisibleTarget();
|
||||
};
|
||||
};
|
||||
293
src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp
Normal file
293
src/layout/algorithm/tiled/scrolling/ScrollTapeController.cpp
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
#include "ScrollTapeController.hpp"
|
||||
#include "ScrollingAlgorithm.hpp"
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
using namespace Layout::Tiled;
|
||||
|
||||
CScrollTapeController::CScrollTapeController(eScrollDirection direction) : m_direction(direction) {
|
||||
;
|
||||
}
|
||||
|
||||
void CScrollTapeController::setDirection(eScrollDirection dir) {
|
||||
m_direction = dir;
|
||||
}
|
||||
|
||||
eScrollDirection CScrollTapeController::getDirection() const {
|
||||
return m_direction;
|
||||
}
|
||||
|
||||
bool CScrollTapeController::isPrimaryHorizontal() const {
|
||||
return m_direction == SCROLL_DIR_RIGHT || m_direction == SCROLL_DIR_LEFT;
|
||||
}
|
||||
|
||||
bool CScrollTapeController::isReversed() const {
|
||||
return m_direction == SCROLL_DIR_LEFT || m_direction == SCROLL_DIR_UP;
|
||||
}
|
||||
|
||||
size_t CScrollTapeController::stripCount() const {
|
||||
return m_strips.size();
|
||||
}
|
||||
|
||||
SStripData& CScrollTapeController::getStrip(size_t index) {
|
||||
return m_strips[index];
|
||||
}
|
||||
|
||||
const SStripData& CScrollTapeController::getStrip(size_t index) const {
|
||||
return m_strips[index];
|
||||
}
|
||||
|
||||
void CScrollTapeController::setOffset(double offset) {
|
||||
m_offset = offset;
|
||||
}
|
||||
|
||||
double CScrollTapeController::getOffset() const {
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
void CScrollTapeController::adjustOffset(double delta) {
|
||||
m_offset += delta;
|
||||
}
|
||||
|
||||
size_t CScrollTapeController::addStrip(float size) {
|
||||
m_strips.emplace_back();
|
||||
m_strips.back().size = size;
|
||||
return m_strips.size() - 1;
|
||||
}
|
||||
|
||||
void CScrollTapeController::insertStrip(size_t afterIndex, float size) {
|
||||
if (afterIndex >= m_strips.size()) {
|
||||
addStrip(size);
|
||||
return;
|
||||
}
|
||||
|
||||
SStripData newStrip;
|
||||
newStrip.size = size;
|
||||
m_strips.insert(m_strips.begin() + afterIndex + 1, newStrip);
|
||||
}
|
||||
|
||||
void CScrollTapeController::removeStrip(size_t index) {
|
||||
if (index < m_strips.size())
|
||||
m_strips.erase(m_strips.begin() + index);
|
||||
}
|
||||
|
||||
double CScrollTapeController::getPrimary(const Vector2D& v) const {
|
||||
return isPrimaryHorizontal() ? v.x : v.y;
|
||||
}
|
||||
|
||||
double CScrollTapeController::getSecondary(const Vector2D& v) const {
|
||||
return isPrimaryHorizontal() ? v.y : v.x;
|
||||
}
|
||||
|
||||
void CScrollTapeController::setPrimary(Vector2D& v, double val) const {
|
||||
if (isPrimaryHorizontal())
|
||||
v.x = val;
|
||||
else
|
||||
v.y = val;
|
||||
}
|
||||
|
||||
void CScrollTapeController::setSecondary(Vector2D& v, double val) const {
|
||||
if (isPrimaryHorizontal())
|
||||
v.y = val;
|
||||
else
|
||||
v.x = val;
|
||||
}
|
||||
|
||||
Vector2D CScrollTapeController::makeVector(double primary, double secondary) const {
|
||||
if (isPrimaryHorizontal())
|
||||
return {primary, secondary};
|
||||
else
|
||||
return {secondary, primary};
|
||||
}
|
||||
|
||||
double CScrollTapeController::calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne) const {
|
||||
if (m_strips.empty())
|
||||
return 0.0;
|
||||
|
||||
if (fullscreenOnOne && m_strips.size() == 1)
|
||||
return getPrimary(usableArea.size());
|
||||
|
||||
double total = 0.0;
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
|
||||
for (const auto& strip : m_strips) {
|
||||
total += usablePrimary * strip.size;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
double CScrollTapeController::calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
|
||||
if (stripIndex >= m_strips.size())
|
||||
return 0.0;
|
||||
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
double current = 0.0;
|
||||
|
||||
for (size_t i = 0; i < stripIndex; ++i) {
|
||||
const double stripSize = (fullscreenOnOne && m_strips.size() == 1) ? usablePrimary : usablePrimary * m_strips[i].size;
|
||||
current += stripSize;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
double CScrollTapeController::calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
|
||||
if (stripIndex >= m_strips.size())
|
||||
return 0.0;
|
||||
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
|
||||
if (fullscreenOnOne && m_strips.size() == 1)
|
||||
return usablePrimary;
|
||||
|
||||
return usablePrimary * m_strips[stripIndex].size;
|
||||
}
|
||||
|
||||
CBox CScrollTapeController::calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne) {
|
||||
if (stripIndex >= m_strips.size())
|
||||
return {};
|
||||
|
||||
const auto& strip = m_strips[stripIndex];
|
||||
if (targetIndex >= strip.targetSizes.size())
|
||||
return {};
|
||||
|
||||
const double usableSecondary = getSecondary(usableArea.size());
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
const double cameraOffset = calculateCameraOffset(usableArea, fullscreenOnOne);
|
||||
|
||||
// calculate position along primary axis (strip position)
|
||||
double primaryPos = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
|
||||
double primarySize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
|
||||
|
||||
// calculate position along secondary axis (within strip)
|
||||
double secondaryPos = 0.0;
|
||||
for (size_t i = 0; i < targetIndex; ++i) {
|
||||
secondaryPos += strip.targetSizes[i] * usableSecondary;
|
||||
}
|
||||
double secondarySize = strip.targetSizes[targetIndex] * usableSecondary;
|
||||
|
||||
// apply camera offset based on direction
|
||||
// for RIGHT/DOWN: scroll offset moves content left/up (subtract)
|
||||
// for LEFT/UP: scroll offset moves content right/down (different coordinate system)
|
||||
if (m_direction == SCROLL_DIR_LEFT) {
|
||||
// LEFT: flip the entire primary axis, then apply offset
|
||||
primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
|
||||
} else if (m_direction == SCROLL_DIR_UP) {
|
||||
// UP: flip the entire primary axis, then apply offset
|
||||
primaryPos = usablePrimary - primaryPos - primarySize + cameraOffset;
|
||||
} else {
|
||||
// RIGHT/DOWN: normal offset
|
||||
primaryPos -= cameraOffset;
|
||||
}
|
||||
|
||||
// create the box in primary/secondary coordinates
|
||||
Vector2D pos = makeVector(primaryPos, secondaryPos);
|
||||
Vector2D size = makeVector(primarySize, secondarySize);
|
||||
|
||||
// translate to workspace position
|
||||
pos = pos + workspaceOffset;
|
||||
|
||||
return CBox{pos, size};
|
||||
}
|
||||
|
||||
double CScrollTapeController::calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne) {
|
||||
const double maxExtent = calculateMaxExtent(usableArea, fullscreenOnOne);
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
|
||||
// don't adjust the offset if we are dragging
|
||||
if (isBeingDragged())
|
||||
return m_offset;
|
||||
|
||||
// if the content fits in viewport, center it
|
||||
if (maxExtent < usablePrimary)
|
||||
m_offset = std::round((maxExtent - usablePrimary) / 2.0);
|
||||
|
||||
// if the offset is negative but we already extended, reset offset to 0
|
||||
if (maxExtent > usablePrimary && m_offset < 0.0)
|
||||
m_offset = 0.0;
|
||||
|
||||
return m_offset;
|
||||
}
|
||||
|
||||
Vector2D CScrollTapeController::getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne) {
|
||||
const double offset = calculateCameraOffset(usableArea, fullscreenOnOne);
|
||||
|
||||
if (isReversed())
|
||||
return makeVector(offset, 0.0);
|
||||
else
|
||||
return makeVector(-offset, 0.0);
|
||||
}
|
||||
|
||||
void CScrollTapeController::centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
|
||||
if (stripIndex >= m_strips.size())
|
||||
return;
|
||||
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
|
||||
const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
|
||||
|
||||
m_offset = stripStart - (usablePrimary - stripSize) / 2.0;
|
||||
}
|
||||
|
||||
void CScrollTapeController::fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) {
|
||||
if (stripIndex >= m_strips.size())
|
||||
return;
|
||||
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
|
||||
const double stripSize = calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
|
||||
|
||||
m_offset = std::clamp(m_offset, stripStart - usablePrimary + stripSize, stripStart);
|
||||
}
|
||||
|
||||
bool CScrollTapeController::isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne) const {
|
||||
if (stripIndex >= m_strips.size())
|
||||
return false;
|
||||
|
||||
const double stripStart = calculateStripStart(stripIndex, usableArea, fullscreenOnOne);
|
||||
const double stripEnd = stripStart + calculateStripSize(stripIndex, usableArea, fullscreenOnOne);
|
||||
const double viewStart = m_offset;
|
||||
const double viewEnd = m_offset + getPrimary(usableArea.size());
|
||||
|
||||
return stripStart < viewEnd && viewStart < stripEnd;
|
||||
}
|
||||
|
||||
size_t CScrollTapeController::getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne) const {
|
||||
if (m_strips.empty())
|
||||
return 0;
|
||||
|
||||
const double usablePrimary = getPrimary(usableArea.size());
|
||||
double currentPos = m_offset;
|
||||
|
||||
for (size_t i = 0; i < m_strips.size(); ++i) {
|
||||
const double stripSize = calculateStripSize(i, usableArea, fullscreenOnOne);
|
||||
currentPos += stripSize;
|
||||
|
||||
if (currentPos >= usablePrimary / 2.0 - 2.0)
|
||||
return i;
|
||||
}
|
||||
|
||||
return m_strips.empty() ? 0 : m_strips.size() - 1;
|
||||
}
|
||||
|
||||
void CScrollTapeController::swapStrips(size_t a, size_t b) {
|
||||
if (a >= m_strips.size() || b >= m_strips.size())
|
||||
return;
|
||||
|
||||
std::swap(m_strips.at(a), m_strips.at(b));
|
||||
}
|
||||
|
||||
bool CScrollTapeController::isBeingDragged() const {
|
||||
for (const auto& s : m_strips) {
|
||||
if (!s.userData)
|
||||
continue;
|
||||
|
||||
for (const auto& d : s.userData->targetDatas) {
|
||||
if (d->target == g_layoutManager->dragController()->target())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../../../helpers/math/Math.hpp"
|
||||
#include "../../../../helpers/memory/Memory.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace Layout::Tiled {
|
||||
|
||||
struct SColumnData;
|
||||
|
||||
enum eScrollDirection : uint8_t {
|
||||
SCROLL_DIR_RIGHT = 0,
|
||||
SCROLL_DIR_LEFT,
|
||||
SCROLL_DIR_DOWN,
|
||||
SCROLL_DIR_UP,
|
||||
};
|
||||
|
||||
struct SStripData {
|
||||
float size = 1.F; // size along primary axis
|
||||
std::vector<float> targetSizes; // sizes along secondary axis for each target in this strip
|
||||
WP<SColumnData> userData;
|
||||
|
||||
SStripData() = default;
|
||||
};
|
||||
|
||||
struct STapeLayoutResult {
|
||||
CBox box;
|
||||
size_t stripIndex = 0;
|
||||
size_t targetIndex = 0;
|
||||
};
|
||||
|
||||
class CScrollTapeController {
|
||||
public:
|
||||
CScrollTapeController(eScrollDirection direction = SCROLL_DIR_RIGHT);
|
||||
~CScrollTapeController() = default;
|
||||
|
||||
void setDirection(eScrollDirection dir);
|
||||
eScrollDirection getDirection() const;
|
||||
bool isPrimaryHorizontal() const;
|
||||
bool isReversed() const;
|
||||
|
||||
size_t addStrip(float size = 1.0F);
|
||||
void insertStrip(size_t afterIndex, float size = 1.0F);
|
||||
void removeStrip(size_t index);
|
||||
size_t stripCount() const;
|
||||
SStripData& getStrip(size_t index);
|
||||
const SStripData& getStrip(size_t index) const;
|
||||
void swapStrips(size_t a, size_t b);
|
||||
|
||||
void setOffset(double offset);
|
||||
double getOffset() const;
|
||||
void adjustOffset(double delta);
|
||||
|
||||
double calculateMaxExtent(const CBox& usableArea, bool fullscreenOnOne = false) const;
|
||||
double calculateStripStart(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
|
||||
double calculateStripSize(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
|
||||
|
||||
CBox calculateTargetBox(size_t stripIndex, size_t targetIndex, const CBox& usableArea, const Vector2D& workspaceOffset, bool fullscreenOnOne = false);
|
||||
|
||||
double calculateCameraOffset(const CBox& usableArea, bool fullscreenOnOne = false);
|
||||
Vector2D getCameraTranslation(const CBox& usableArea, bool fullscreenOnOne = false);
|
||||
|
||||
void centerStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);
|
||||
void fitStrip(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false);
|
||||
|
||||
bool isStripVisible(size_t stripIndex, const CBox& usableArea, bool fullscreenOnOne = false) const;
|
||||
|
||||
size_t getStripAtCenter(const CBox& usableArea, bool fullscreenOnOne = false) const;
|
||||
|
||||
private:
|
||||
eScrollDirection m_direction = SCROLL_DIR_RIGHT;
|
||||
std::vector<SStripData> m_strips;
|
||||
double m_offset = 0.0;
|
||||
|
||||
double getPrimary(const Vector2D& v) const;
|
||||
double getSecondary(const Vector2D& v) const;
|
||||
void setPrimary(Vector2D& v, double val) const;
|
||||
void setSecondary(Vector2D& v, double val) const;
|
||||
bool isBeingDragged() const;
|
||||
|
||||
Vector2D makeVector(double primary, double secondary) const;
|
||||
};
|
||||
};
|
||||
1412
src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp
Normal file
1412
src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.cpp
Normal file
File diff suppressed because it is too large
Load diff
137
src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp
Normal file
137
src/layout/algorithm/tiled/scrolling/ScrollingAlgorithm.hpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
#pragma once
|
||||
|
||||
#include "../../TiledAlgorithm.hpp"
|
||||
#include "../../../../managers/HookSystemManager.hpp"
|
||||
#include "../../../../helpers/math/Direction.hpp"
|
||||
#include "ScrollTapeController.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace Layout::Tiled {
|
||||
class CScrollingAlgorithm;
|
||||
struct SColumnData;
|
||||
struct SScrollingData;
|
||||
|
||||
struct SScrollingTargetData {
|
||||
SScrollingTargetData(SP<ITarget> t, SP<SColumnData> col) : target(t), column(col) {
|
||||
;
|
||||
}
|
||||
|
||||
WP<ITarget> target;
|
||||
WP<SColumnData> column;
|
||||
bool ignoreFullscreenChecks = false;
|
||||
|
||||
CBox layoutBox;
|
||||
};
|
||||
|
||||
struct SColumnData {
|
||||
SColumnData(SP<SScrollingData> data) : scrollingData(data) {
|
||||
;
|
||||
}
|
||||
|
||||
void add(SP<ITarget> t);
|
||||
void add(SP<ITarget> t, int after);
|
||||
void add(SP<SScrollingTargetData> w);
|
||||
void add(SP<SScrollingTargetData> w, int after);
|
||||
void remove(SP<ITarget> t);
|
||||
bool has(SP<ITarget> t);
|
||||
size_t idx(SP<ITarget> t);
|
||||
|
||||
// index of lowest target that is above y.
|
||||
size_t idxForHeight(float y);
|
||||
|
||||
void up(SP<SScrollingTargetData> w);
|
||||
void down(SP<SScrollingTargetData> w);
|
||||
|
||||
SP<SScrollingTargetData> next(SP<SScrollingTargetData> w);
|
||||
SP<SScrollingTargetData> prev(SP<SScrollingTargetData> w);
|
||||
|
||||
std::vector<SP<SScrollingTargetData>> targetDatas;
|
||||
WP<SScrollingData> scrollingData;
|
||||
WP<SScrollingTargetData> lastFocusedTarget;
|
||||
|
||||
WP<SColumnData> self;
|
||||
|
||||
// Helper methods to access controller-managed data
|
||||
float getColumnWidth() const;
|
||||
void setColumnWidth(float width);
|
||||
float getTargetSize(size_t idx) const;
|
||||
void setTargetSize(size_t idx, float size);
|
||||
float getTargetSize(SP<SScrollingTargetData> target) const;
|
||||
void setTargetSize(SP<SScrollingTargetData> target, float size);
|
||||
};
|
||||
|
||||
struct SScrollingData {
|
||||
SScrollingData(CScrollingAlgorithm* algo);
|
||||
|
||||
std::vector<SP<SColumnData>> columns;
|
||||
|
||||
UP<CScrollTapeController> controller;
|
||||
|
||||
SP<SColumnData> add();
|
||||
SP<SColumnData> add(int after);
|
||||
int64_t idx(SP<SColumnData> c);
|
||||
void remove(SP<SColumnData> c);
|
||||
double maxWidth();
|
||||
SP<SColumnData> next(SP<SColumnData> c);
|
||||
SP<SColumnData> prev(SP<SColumnData> c);
|
||||
SP<SColumnData> atCenter();
|
||||
|
||||
bool visible(SP<SColumnData> c);
|
||||
void centerCol(SP<SColumnData> c);
|
||||
void fitCol(SP<SColumnData> c);
|
||||
void centerOrFitCol(SP<SColumnData> c);
|
||||
|
||||
void recalculate(bool forceInstant = false);
|
||||
|
||||
CScrollingAlgorithm* algorithm = nullptr;
|
||||
WP<SScrollingData> self;
|
||||
std::optional<double> lockedCameraOffset;
|
||||
};
|
||||
|
||||
class CScrollingAlgorithm : public ITiledAlgorithm {
|
||||
public:
|
||||
CScrollingAlgorithm();
|
||||
virtual ~CScrollingAlgorithm();
|
||||
|
||||
virtual void newTarget(SP<ITarget> target);
|
||||
virtual void movedTarget(SP<ITarget> target, std::optional<Vector2D> focalPoint = std::nullopt);
|
||||
virtual void removeTarget(SP<ITarget> target);
|
||||
|
||||
virtual void resizeTarget(const Vector2D& Δ, SP<ITarget> target, eRectCorner corner = CORNER_NONE);
|
||||
virtual void recalculate();
|
||||
|
||||
virtual SP<ITarget> getNextCandidate(SP<ITarget> old);
|
||||
|
||||
virtual std::expected<void, std::string> layoutMsg(const std::string_view& sv);
|
||||
virtual std::optional<Vector2D> predictSizeForNewTarget();
|
||||
|
||||
virtual void swapTargets(SP<ITarget> a, SP<ITarget> b);
|
||||
virtual void moveTargetInDirection(SP<ITarget> t, Math::eDirection dir, bool silent);
|
||||
|
||||
CBox usableArea();
|
||||
|
||||
private:
|
||||
SP<SScrollingData> m_scrollingData;
|
||||
|
||||
SP<HOOK_CALLBACK_FN> m_configCallback;
|
||||
SP<HOOK_CALLBACK_FN> m_focusCallback;
|
||||
SP<HOOK_CALLBACK_FN> m_mouseButtonCallback;
|
||||
|
||||
struct {
|
||||
std::vector<float> configuredWidths;
|
||||
} m_config;
|
||||
|
||||
eScrollDirection getDynamicDirection();
|
||||
|
||||
SP<SScrollingTargetData> findBestNeighbor(SP<SScrollingTargetData> pCurrent, SP<SColumnData> pTargetCol);
|
||||
SP<SScrollingTargetData> dataFor(SP<ITarget> t);
|
||||
SP<SScrollingTargetData> closestNode(const Vector2D& posGlobglobgabgalab);
|
||||
|
||||
void focusTargetUpdate(SP<ITarget> target);
|
||||
void moveTargetTo(SP<ITarget> t, Math::eDirection dir, bool silent);
|
||||
void focusOnInput(SP<ITarget> target, bool hardInput);
|
||||
|
||||
friend struct SScrollingData;
|
||||
};
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue